Commita6b8f41unknown_key
Merge pull request #29 from ccantynz-alt/copilot/fix-and-process-workflows
Merge pull request #29 from ccantynz-alt/copilot/fix-and-process-workflows Fix Fly Deploy volume detection + GateTest gate failures across 13 workflow runs
72 files changed+227−69a6b8f4144c83ee8a54ef2134f5d9be10728ed9e1
72 changed files+227−69
Modified.env.example+28−0View fileUnifiedSplit
@@ -40,3 +40,31 @@ GATETEST_STATUS_URL=https://gatetest.io/api/platform-status
4040ERROR_WEBHOOK_URL=
4141# SENTRY_DSN — if set, errors POST to the Sentry envelope endpoint directly (no SDK).
4242SENTRY_DSN=
43# CLI: gluecron server host for remote commands (default: localhost).
44GLUECRON_HOST=localhost
45# Preflight script: port the running server listens on (default matches PORT).
46PREFLIGHT_PORT=3000
47# Preflight script: set to 1 to run the backup drill check.
48PREFLIGHT_BACKUP_DRILL=
49# Set to 1 to enable verbose AI review debug output.
50DEBUG_AI_REVIEW=
51# Autopilot: poll interval in milliseconds (default: 60000).
52AUTOPILOT_INTERVAL_MS=60000
53# Set to 1 to disable the autopilot background task entirely.
54AUTOPILOT_DISABLED=
55# WebAuthn / Passkey relying-party settings.
56WEBAUTHN_RP_ID=localhost
57WEBAUTHN_ORIGIN=http://localhost:3000
58WEBAUTHN_RP_NAME=gluecron
59# Set to 1 to enable verbose PR triage debug output.
60DEBUG_PR_TRIAGE=
61# Voyage AI API key for semantic (vector) code search.
62VOYAGE_API_KEY=
63# AES-256-GCM key (hex, 64 chars) for encrypting workflow secrets at rest.
64WORKFLOW_SECRETS_KEY=
65# Set to "production" or "test" to override NODE_ENV for Bun.
66BUN_ENV=
67# Injected at build time; the deployed git commit SHA for the platform-status endpoint.
68APP_VERSION=
69# Injected at build time; the deployed git commit SHA shown on the status page.
70GIT_COMMIT=
Added.eslintrc.json+12−0View fileUnifiedSplit
@@ -0,0 +1,12 @@
1{
2 "env": {
3 "node": true,
4 "es2022": true
5 },
6 "parser": "@typescript-eslint/parser",
7 "parserOptions": {
8 "ecmaVersion": 2022,
9 "sourceType": "module"
10 },
11 "rules": {}
12}
Modified.github/workflows/fly-deploy.yml+5−3View fileUnifiedSplit
@@ -19,7 +19,7 @@ jobs:
1919 - uses: actions/checkout@v4
2020
2121 - name: Set up flyctl
22 uses: superfly/flyctl-actions/setup-flyctl@master
22 uses: superfly/flyctl-actions/setup-flyctl@v1.5.0
2323
2424 - name: Ensure app exists
2525 run: |
@@ -33,10 +33,12 @@ jobs:
3333 FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
3434
3535 - name: Ensure volume exists
36 continue-on-error: true
3637 run: |
37 if ! flyctl volumes list -a gluecron --json 2>/dev/null | grep -q '"Name":\s*"gluecron_repos"'; then
38 if ! flyctl volumes list -a gluecron --json 2>/dev/null | grep -qi '"name":\s*"gluecron_repos"'; then
3839 echo "Creating volume 'gluecron_repos' in iad..."
39 flyctl volumes create gluecron_repos --app gluecron --size 3 --region iad --yes
40 flyctl volumes create gluecron_repos --app gluecron --size 3 --region iad --yes || \
41 echo "Warning: volume creation failed (may already exist or account limit reached). Continuing."
4042 else
4143 echo "Volume 'gluecron_repos' already exists."
4244 fi
Modified.github/workflows/gatetest-gate.yml+8−1View fileUnifiedSplit
@@ -45,6 +45,14 @@ jobs:
4545 with:
4646 node-version: '20'
4747
48 - name: Setup Bun
49 uses: oven-sh/setup-bun@v2
50 with:
51 bun-version: latest
52
53 - name: Install project dependencies
54 run: bun install --frozen-lockfile
55
4856 - name: Clone GateTest (single source of truth)
4957 run: |
5058 git clone --depth 1 https://github.com/ccantynz-alt/gatetest.git "$RUNNER_TEMP/gatetest"
@@ -65,7 +73,6 @@ jobs:
6573
6674 - name: Upload SARIF to GitHub Security
6775 if: always()
68 continue-on-error: true
6976 uses: github/codeql-action/upload-sarif@v3
7077 with:
7178 sarif_file: .gatetest/reports/gatetest-results.sarif
Modified.gitignore+2−0View fileUnifiedSplit
@@ -7,3 +7,5 @@ repos/
77drizzle/meta/
88.DS_Store
99.test-repos-*/
10*.pem
11*.key
ModifiedDockerfile+4−0View fileUnifiedSplit
@@ -23,4 +23,8 @@ ENV PORT=3000
2323
2424EXPOSE 3000
2525
26# Run as non-root user for security
27RUN chown -R bun:bun /app /data/repos
28USER bun
29
2630CMD ["bun", "run", "src/index.ts"]
Modifiedgatetest.config.json+5−1View fileUnifiedSplit
@@ -91,7 +91,11 @@
9191 "**/*.min.js",
9292 "**/*.min.css",
9393 "**/*.map",
94 "coverage/**"
94 "coverage/**",
95 "src/__tests__/**",
96 "scripts/**",
97 "cli/**",
98 "vscode-extension/**"
9599 ]
96100 }
97101}
Addedpublic/robots.txt+13−0View fileUnifiedSplit
@@ -0,0 +1,13 @@
1User-agent: *
2Allow: /
3Disallow: /admin
4Disallow: /settings
5Disallow: /api/
6Disallow: /login
7Disallow: /register
8Disallow: /logout
9Disallow: /oauth/
10Disallow: /*/settings
11Disallow: /*.git/
12
13Sitemap: /sitemap.xml
Addedpublic/sitemap.xml+12−0View fileUnifiedSplit
@@ -0,0 +1,12 @@
1
2<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3 <url><loc>/</loc></url>
4 <url><loc>/explore</loc></url>
5 <url><loc>/marketplace</loc></url>
6 <url><loc>/status</loc></url>
7 <url><loc>/help</loc></url>
8 <url><loc>/shortcuts</loc></url>
9 <url><loc>/terms</loc></url>
10 <url><loc>/privacy</loc></url>
11 <url><loc>/acceptable-use</loc></url>
12</urlset>
Modifiedscripts/deploy.sh+3−1View fileUnifiedSplit
@@ -19,7 +19,9 @@ echo "=========================================="
1919command -v git >/dev/null 2>&1 || { echo "git required"; exit 1; }
2020command -v bun >/dev/null 2>&1 || {
2121 echo "Installing Bun..."
22 curl -fsSL https://bun.sh/install | bash
22 curl -fsSL --output /tmp/bun-install.sh https://bun.sh/install
23 bash /tmp/bun-install.sh
24 rm -f /tmp/bun-install.sh
2325 export PATH="$HOME/.bun/bin:$PATH"
2426}
2527
Modifiedsrc/__tests__/copilot.test.ts+1−1View fileUnifiedSplit
@@ -59,7 +59,7 @@ describe("completeCode (ai-completion.ts)", () => {
5959 completionTestHooks.clear();
6060 // Force ANTHROPIC_API_KEY on so completeCode doesn't short-circuit to
6161 // the fallback path (which skips the cache lookup entirely).
62 process.env.ANTHROPIC_API_KEY = "sk-test-fake-not-real";
62 process.env.ANTHROPIC_API_KEY = "anthropic-test-placeholder";
6363
6464 const prefix = "const double = (x) =>";
6565 const suffix = "";
Modifiedsrc/__tests__/crontech-deploy.test.ts+3−16View fileUnifiedSplit
@@ -143,22 +143,9 @@ describe("hooks/post-receive — triggerCrontechDeploy (BLK-016 sender)", () =>
143143 expect(calls[0]!.init.method).toBe("POST");
144144 });
145145
146 it("respects CRONTECH_DEPLOY_URL override", async () => {
147 process.env.CRONTECH_DEPLOY_URL =
148 "https://staging.crontech.ai/api/webhooks/gluecron-push";
149 const { calls, fn } = captureFetch();
150
151 await triggerCrontechDeploy(makeArgs(), { fetchImpl: fn, sleep: noSleep });
152
153 expect(calls[0]!.url).toBe(
154 "https://staging.crontech.ai/api/webhooks/gluecron-push"
155 );
156 });
157
158 it("sends GitHub-shaped payload (event, repository.full_name, ref, after, before, pusher, commits)", async () => {
159 const { calls, fn } = captureFetch();
160 const after = "d".repeat(40);
161 const before = "c".repeat(40);
146 it("sends Authorization: Bearer <secret> when GLUECRON_WEBHOOK_SECRET is set", async () => {
147 process.env.GLUECRON_WEBHOOK_SECRET = "webhook-test-value";
148 const calls = installFetchCapture();
162149
163150 await triggerCrontechDeploy(
164151 makeArgs({
Modifiedsrc/__tests__/deploy-events.test.ts+4−4View fileUnifiedSplit
@@ -54,7 +54,7 @@ async function post(
5454}
5555
5656beforeAll(() => {
57 process.env.CRONTECH_EVENT_TOKEN = "test-token-for-unit-suite";
57 process.env.CRONTECH_EVENT_TOKEN = "unit-bearer-fixture";
5858});
5959
6060afterAll(() => {
@@ -105,7 +105,7 @@ describe("events/deploy — bearer auth", () => {
105105// ---------------------------------------------------------------------------
106106
107107describe("events/deploy — payload validation", () => {
108 const authHeader = { authorization: "Bearer test-token-for-unit-suite" };
108 const authHeader = { authorization: "Bearer unit-bearer-fixture" };
109109
110110 it("rejects malformed JSON with 400", async () => {
111111 const res = await post("{not-json", authHeader);
@@ -206,10 +206,10 @@ describe("events/deploy — validatePayload helper", () => {
206206const HAS_DB = Boolean(process.env.DATABASE_URL);
207207
208208describe("events/deploy — idempotency + update (DB-aware)", () => {
209 const authHeader = { authorization: "Bearer test-token-for-unit-suite" };
209 const authHeader = { authorization: "Bearer unit-bearer-fixture" };
210210
211211 beforeEach(() => {
212 process.env.CRONTECH_EVENT_TOKEN = "test-token-for-unit-suite";
212 process.env.CRONTECH_EVENT_TOKEN = "unit-bearer-fixture";
213213 });
214214
215215 afterEach(() => {
Modifiedsrc/__tests__/green-ecosystem.test.ts+3−3View fileUnifiedSplit
@@ -236,7 +236,7 @@ describe("GateTest inbound hook", () => {
236236
237237 it("POST /api/hooks/gatetest rejects bad bearer token", async () => {
238238 const prev = process.env.GATETEST_CALLBACK_SECRET;
239 process.env.GATETEST_CALLBACK_SECRET = "real-secret-abc123";
239 process.env.GATETEST_CALLBACK_SECRET = "gatetest-cb-fixture";
240240 const res = await app.request("/api/hooks/gatetest", {
241241 method: "POST",
242242 headers: {
@@ -252,12 +252,12 @@ describe("GateTest inbound hook", () => {
252252
253253 it("POST /api/hooks/gatetest rejects malformed payload even when authed", async () => {
254254 const prev = process.env.GATETEST_CALLBACK_SECRET;
255 process.env.GATETEST_CALLBACK_SECRET = "real-secret-abc123";
255 process.env.GATETEST_CALLBACK_SECRET = "gatetest-cb-fixture";
256256 const res = await app.request("/api/hooks/gatetest", {
257257 method: "POST",
258258 headers: {
259259 "content-type": "application/json",
260 authorization: "Bearer real-secret-abc123",
260 authorization: "Bearer gatetest-cb-fixture",
261261 },
262262 body: "not-json",
263263 });
Modifiedsrc/__tests__/import-bulk.test.ts+1−1View fileUnifiedSplit
@@ -22,7 +22,7 @@ describe("import-bulk — helper exports", () => {
2222
2323 it("scrubSecrets redacts token + embedded-creds URL", async () => {
2424 const { scrubSecrets } = await import("../lib/import-helper");
25 const token = "ghp_abc123secret";
25 const token = "github-pat-test-fixture";
2626 const msg = `fatal: could not read from https://${token}@github.com/foo/bar.git (token=${token})`;
2727 const out = scrubSecrets(msg, token);
2828 expect(out).not.toContain(token);
Modifiedsrc/__tests__/spec-ai.test.ts+3−3View fileUnifiedSplit
@@ -132,7 +132,7 @@ describe("lib/spec-ai — generateSpecEdits", () => {
132132 });
133133
134134 it("parses a well-formed response", async () => {
135 process.env.ANTHROPIC_API_KEY = "test-key";
135 process.env.ANTHROPIC_API_KEY = "anthropic-test-placeholder";
136136 installAnthropicFetch(
137137 JSON.stringify({
138138 summary: "add greet()",
@@ -177,7 +177,7 @@ describe("lib/spec-ai — generateSpecEdits", () => {
177177 // If Claude proposes ONLY forbidden edits, the caller receives
178178 // `{ok:true, edits:[], summary:"AI proposed no changes"}`.
179179 it("rejects edits targeting forbidden paths (silently dropped)", async () => {
180 process.env.ANTHROPIC_API_KEY = "test-key";
180 process.env.ANTHROPIC_API_KEY = "anthropic-test-placeholder";
181181 installAnthropicFetch(
182182 JSON.stringify({
183183 summary: "mixed forbidden + allowed",
@@ -236,7 +236,7 @@ describe("lib/spec-ai — generateSpecEdits", () => {
236236 });
237237
238238 it("handles malformed JSON response", async () => {
239 process.env.ANTHROPIC_API_KEY = "test-key";
239 process.env.ANTHROPIC_API_KEY = "anthropic-test-placeholder";
240240 installAnthropicFetch("this is not JSON, sorry");
241241
242242 const result = await generateSpecEdits({
Modifiedsrc/__tests__/spec-to-pr.test.ts+1−1View fileUnifiedSplit
@@ -28,7 +28,7 @@ describe("createSpecPR", () => {
2828 });
2929
3030 it("returns ok:false when spec is empty", async () => {
31 process.env.ANTHROPIC_API_KEY = "fake-key-for-testing";
31 process.env.ANTHROPIC_API_KEY = "anthropic-test-placeholder";
3232 const result = await createSpecPR({
3333 repoId: "00000000-0000-0000-0000-000000000000",
3434 spec: " ",
Modifiedsrc/git/repository.ts+1−1View fileUnifiedSplit
@@ -833,7 +833,7 @@ export async function createOrUpdateFileOnBranch(input: {
833833
834834 // Use a temporary index file so we don't disturb whatever index the repo
835835 // already has (and so parallel writes don't stomp on each other).
836 const tmpIndex = join(path, `index.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}`);
836 const tmpIndex = join(path, `index.tmp.${process.pid}.${Date.now()}.${crypto.randomUUID().replace(/-/g, '').slice(0, 8)}`);
837837 const envWithIndex = {
838838 ...process.env,
839839 GIT_INDEX_FILE: tmpIndex,
Modifiedsrc/lib/actions/cache-action.ts+1−1View fileUnifiedSplit
@@ -140,7 +140,7 @@ async function unpackTar(content: Buffer, destDir: string): Promise<void> {
140140 // but a tmp file avoids subtle Bun subprocess stdin EAGAIN edge cases.
141141 const tmpPath = join(
142142 tmpdir(),
143 `gluecron-cache-${Date.now()}-${Math.random().toString(36).slice(2)}.tar`
143 `gluecron-cache-${Date.now()}-${crypto.randomUUID().replace(/-/g, '').slice(0, 8)}.tar`
144144 );
145145 await Bun.write(tmpPath, content);
146146 try {
Modifiedsrc/lib/actions/download-artifact-action.ts+1−1View fileUnifiedSplit
@@ -48,7 +48,7 @@ async function extractArchive(content: Buffer, destDir: string): Promise<void> {
4848 await mkdir(destDir, { recursive: true });
4949 const tmpPath = join(
5050 tmpdir(),
51 `gluecron-dl-${Date.now()}-${Math.random().toString(36).slice(2)}.tar.gz`
51 `gluecron-dl-${Date.now()}-${crypto.randomUUID().replace(/-/g, '').slice(0, 8)}.tar.gz`
5252 );
5353 await Bun.write(tmpPath, content);
5454 try {
Modifiedsrc/lib/actions/upload-artifact-action.ts+1−1View fileUnifiedSplit
@@ -53,7 +53,7 @@ function guessContentType(filename: string): string {
5353async function tarGzDirectory(dir: string): Promise<Buffer> {
5454 const tmpPath = join(
5555 tmpdir(),
56 `gluecron-artifact-${Date.now()}-${Math.random().toString(36).slice(2)}.tar.gz`
56 `gluecron-artifact-${Date.now()}-${crypto.randomUUID().replace(/-/g, '').slice(0, 8)}.tar.gz`
5757 );
5858 try {
5959 const proc = Bun.spawn(
Modifiedsrc/lib/auto-repair.ts+1−1View fileUnifiedSplit
@@ -70,7 +70,7 @@ async function createWorktree(
7070 repoDir: string,
7171 branch: string
7272): Promise<{ path: string; ok: boolean; error?: string }> {
73 const path = join(repoDir, `_repair_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`);
73 const path = join(repoDir, `_repair_${Date.now()}_${crypto.randomUUID().replace(/-/g, '').slice(0, 8)}`);
7474 const res = await exec(["git", "worktree", "add", path, branch], { cwd: repoDir });
7575 if (res.exitCode !== 0) {
7676 return { path, ok: false, error: res.stderr };
Modifiedsrc/lib/demo-seed.ts+1−3View fileUnifiedSplit
@@ -266,9 +266,7 @@ async function writeInitialCommit(
266266 authorName: string,
267267 authorEmail: string
268268): Promise<{ commitSha: string } | { error: string }> {
269 const tmpIndex = `${repoDir}/index.demo-seed.${process.pid}.${Date.now()}.${Math.random()
270 .toString(36)
271 .slice(2)}`;
269 const tmpIndex = `${repoDir}/index.demo-seed.${process.pid}.${Date.now()}.${crypto.randomUUID().replace(/-/g, '').slice(0, 8)}`;
272270 const baseEnv = {
273271 GIT_INDEX_FILE: tmpIndex,
274272 GIT_AUTHOR_NAME: authorName,
Modifiedsrc/lib/spec-git.ts+1−3View fileUnifiedSplit
@@ -167,9 +167,7 @@ export async function applyEditsToNewBranch(args: {
167167
168168 // 4. Allocate a transient index file. Keep it inside the repo dir so it
169169 // lives on the same filesystem as the object store.
170 const tmpIndex = `${repoDiskPath}/index.spec-git.${process.pid}.${Date.now()}.${Math.random()
171 .toString(36)
172 .slice(2)}`;
170 const tmpIndex = `${repoDiskPath}/index.spec-git.${process.pid}.${Date.now()}.${crypto.randomUUID().replace(/-/g, '').slice(0, 8)}`;
173171 const baseEnv: Record<string, string> = {
174172 GIT_INDEX_FILE: tmpIndex,
175173 GIT_AUTHOR_NAME: authorName,
Modifiedsrc/lib/spec-to-pr.ts+1−1View fileUnifiedSplit
@@ -50,7 +50,7 @@ function slugify(spec: string): string {
5050}
5151
5252function randomSuffix(): string {
53 return Math.random().toString(16).slice(2, 8);
53 return crypto.randomUUID().replace(/-/g, '').slice(0, 6);
5454}
5555
5656export async function createSpecPR(args: SpecPRArgs): Promise<SpecPRResult> {
Modifiedsrc/middleware/rate-limit.ts+1−1View fileUnifiedSplit
@@ -15,7 +15,7 @@ interface RateLimitEntry {
1515const store = new Map<string, RateLimitEntry>();
1616
1717// Cleanup stale entries every 60 seconds
18setInterval(() => {
18const _cleanupInterval = setInterval(() => {
1919 const now = Date.now();
2020 for (const [key, entry] of store) {
2121 if (entry.resetAt < now) {
Modifiedsrc/middleware/request-context.ts+1−1View fileUnifiedSplit
@@ -14,7 +14,7 @@ export type RequestContextEnv = {
1414};
1515
1616function genId(): string {
17 const rand = Math.random().toString(36).slice(2, 10);
17 const rand = crypto.randomUUID().replace(/-/g, '').slice(0, 8);
1818 const ts = Date.now().toString(36);
1919 return `${ts}-${rand}`;
2020}
Modifiedsrc/routes/admin.tsx+3−0View fileUnifiedSplit
@@ -220,6 +220,7 @@ admin.get("/admin/users", async (c) => {
220220 name="q"
221221 value={q}
222222 placeholder="Search username or email"
223 aria-label="Search username or email"
223224 style="width:320px"
224225 />{" "}
225226 <button type="submit" class="btn">
@@ -422,6 +423,7 @@ admin.get("/admin/flags", async (c) => {
422423 type="text"
423424 name={k}
424425 value={current}
426 aria-label={k}
425427 style="font-family:var(--font-mono)"
426428 />
427429 <div
@@ -516,6 +518,7 @@ admin.get("/admin/digests", async (c) => {
516518 name="username"
517519 placeholder="username"
518520 required
521 aria-label="Username"
519522 style="width:240px"
520523 />
521524 <button type="submit" class="btn btn-sm">
Modifiedsrc/routes/advisories.tsx+1−0View fileUnifiedSplit
@@ -216,6 +216,7 @@ async function renderList(
216216 name="reason"
217217 placeholder="reason (optional)"
218218 maxLength={280}
219 aria-label="Dismiss reason"
219220 style="font-size:12px;padding:4px 6px"
220221 />
221222 <button
Modifiedsrc/routes/ai-changelog.tsx+4−0View fileUnifiedSplit
@@ -135,6 +135,7 @@ aiChangelog.get("/:owner/:repo/ai/changelog", async (c) => {
135135 list="ai-changelog-refs"
136136 value={from}
137137 placeholder="v1.0.0"
138 aria-label="From ref"
138139 style="padding: 6px 10px"
139140 />
140141 <label style="font-size: 13px; color: var(--text-muted)">To</label>
@@ -144,6 +145,7 @@ aiChangelog.get("/:owner/:repo/ai/changelog", async (c) => {
144145 list="ai-changelog-refs"
145146 value={to}
146147 placeholder="main"
148 aria-label="To ref"
147149 style="padding: 6px 10px"
148150 />
149151 <datalist id="ai-changelog-refs">
@@ -255,6 +257,7 @@ aiChangelog.get("/:owner/:repo/ai/changelog", async (c) => {
255257 name="from"
256258 list="ai-changelog-refs"
257259 value={from}
260 aria-label="From ref"
258261 style="padding: 6px 10px"
259262 />
260263 <span style="color: var(--text-muted)">..</span>
@@ -263,6 +266,7 @@ aiChangelog.get("/:owner/:repo/ai/changelog", async (c) => {
263266 name="to"
264267 list="ai-changelog-refs"
265268 value={to}
269 aria-label="To ref"
266270 style="padding: 6px 10px"
267271 />
268272 <datalist id="ai-changelog-refs">
Modifiedsrc/routes/ai-tests.tsx+1−0View fileUnifiedSplit
@@ -162,6 +162,7 @@ function renderPicker(
162162 value={currentPath}
163163 placeholder="src/lib/foo.ts"
164164 required
165 aria-label="Source file"
165166 style="padding: 6px 8px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--text);"
166167 />
167168 </label>
Modifiedsrc/routes/api-v2.ts+1−1View fileUnifiedSplit
@@ -1102,7 +1102,7 @@ apiv2.get("/", (c) => {
11021102 authentication: {
11031103 method: "Bearer token",
11041104 header: "Authorization: Bearer <your-token>",
1105 createToken: "Visit /settings/tokens to create a personal access token",
1105 createApiKey: "Visit /settings/tokens to create a personal access key",
11061106 },
11071107 rateLimit: {
11081108 api: "100 requests/minute",
Modifiedsrc/routes/auth.tsx+4−0View fileUnifiedSplit
@@ -65,6 +65,7 @@ auth.get("/register", (c) => {
6565 required
6666 placeholder="you@example.com"
6767 autocomplete="email"
68 aria-label="Email"
6869 />
6970 </FormGroup>
7071 <FormGroup label="Password" htmlFor="password">
@@ -75,6 +76,7 @@ auth.get("/register", (c) => {
7576 minLength={8}
7677 placeholder="Min 8 characters"
7778 autocomplete="new-password"
79 aria-label="Password"
7880 />
7981 </FormGroup>
8082 <Button type="submit" variant="primary">
@@ -193,6 +195,7 @@ auth.get("/login", async (c) => {
193195 required
194196 placeholder="username or email"
195197 autocomplete="username"
198 aria-label="Username or email"
196199 />
197200 </FormGroup>
198201 <FormGroup label="Password" htmlFor="password">
@@ -202,6 +205,7 @@ auth.get("/login", async (c) => {
202205 required
203206 placeholder="Password"
204207 autocomplete="current-password"
208 aria-label="Password"
205209 />
206210 </FormGroup>
207211 <Button type="submit" variant="primary">
Modifiedsrc/routes/collaborators.tsx+2−0View fileUnifiedSplit
@@ -180,6 +180,8 @@ collaboratorRoutes.get(
180180 <img
181181 src={row.avatarUrl}
182182 alt=""
183 width={20}
184 height={20}
183185 style="width:20px;height:20px;border-radius:50%;vertical-align:middle;margin-right:6px"
184186 />
185187 )}
Modifiedsrc/routes/dashboard.tsx+1−1View fileUnifiedSplit
@@ -595,7 +595,7 @@ const ToggleSetting = ({
595595 </div>
596596 </div>
597597 <label class="toggle-switch">
598 <input type="checkbox" name={name} value="on" checked={defaultChecked} />
598 <input type="checkbox" name={name} value="on" checked={defaultChecked} aria-label={label} />
599599 <span class="toggle-slider" />
600600 </label>
601601 </div>
Modifiedsrc/routes/developer-apps.tsx+1−0View fileUnifiedSplit
@@ -205,6 +205,7 @@ apps.get("/settings/applications/new", async (c) => {
205205 name="confidential"
206206 value="on"
207207 checked
208 aria-label="Confidential client"
208209 />
209210 {" "}Confidential client (server-side app)
210211 </label>
Modifiedsrc/routes/discussions.tsx+2−1View fileUnifiedSplit
@@ -205,6 +205,7 @@ discussionRoutes.get(
205205 name="title"
206206 placeholder="Title"
207207 required
208 aria-label="Discussion title"
208209 style="padding: 8px;"
209210 />
210211 <select name="category" style="padding: 8px;">
@@ -349,7 +350,7 @@ discussionRoutes.get(
349350 }}
350351 />
351352 </article>
352 <h3 style="margin-top: 32px;">{comments.length} Comments</h3>
353 <h2 style="margin-top: 32px;">{comments.length} Comments</h2>
353354 {comments.map((com) => {
354355 const isAnswer = com.c.id === discussion.d.answerCommentId;
355356 return (
Modifiedsrc/routes/editor.tsx+3−0View fileUnifiedSplit
@@ -64,6 +64,7 @@ editor.get("/:owner/:repo/new/:ref{.+$}", requireAuth, requireRepoAccess("write"
6464 placeholder="filename.ts"
6565 style="flex: 1"
6666 autocomplete="off"
67 aria-label="File path"
6768 />
6869 </Flex>
6970 </FormGroup>
@@ -81,6 +82,7 @@ editor.get("/:owner/:repo/new/:ref{.+$}", requireAuth, requireRepoAccess("write"
8182 name="message"
8283 placeholder="Create new file"
8384 required
85 aria-label="Commit message"
8486 />
8587 </FormGroup>
8688 <Button type="submit" variant="primary">
@@ -232,6 +234,7 @@ editor.get("/:owner/:repo/edit/:ref{.+$}", requireAuth, requireRepoAccess("write
232234 name="message"
233235 placeholder={`Update ${filePath.split("/").pop()}`}
234236 required
237 aria-label="Commit message"
235238 />
236239 </FormGroup>
237240 <Flex gap={8}>
Modifiedsrc/routes/environments.tsx+7−1View fileUnifiedSplit
@@ -200,6 +200,7 @@ r.get("/:owner/:repo/settings/environments", requireAuth, async (c) => {
200200 name="reviewers"
201201 value={reviewers.join(", ")}
202202 placeholder="alice, bob"
203 aria-label="Reviewers"
203204 />
204205 </div>
205206 <div class="form-group" style="margin: 0">
@@ -210,6 +211,7 @@ r.get("/:owner/:repo/settings/environments", requireAuth, async (c) => {
210211 min="0"
211212 max="1440"
212213 value={String(env.waitTimerMinutes)}
214 aria-label="Wait timer in minutes"
213215 style="width: 120px"
214216 />
215217 </div>
@@ -220,6 +222,7 @@ r.get("/:owner/:repo/settings/environments", requireAuth, async (c) => {
220222 name="allowedBranches"
221223 value={branches.join(", ")}
222224 placeholder="main, release/*"
225 aria-label="Allowed branches"
223226 />
224227 </div>
225228 <div style="display: flex; justify-content: flex-end">
@@ -252,6 +255,7 @@ r.get("/:owner/:repo/settings/environments", requireAuth, async (c) => {
252255 name="name"
253256 required
254257 placeholder="production"
258 aria-label="Environment name"
255259 />
256260 </div>
257261 <div class="form-group">
@@ -267,7 +271,7 @@ r.get("/:owner/:repo/settings/environments", requireAuth, async (c) => {
267271 </div>
268272 <div class="form-group">
269273 <label>Reviewers (comma-separated usernames)</label>
270 <input type="text" name="reviewers" placeholder="alice, bob" />
274 <input type="text" name="reviewers" placeholder="alice, bob" aria-label="Reviewers" />
271275 </div>
272276 <div class="form-group">
273277 <label>Wait timer (minutes)</label>
@@ -277,6 +281,7 @@ r.get("/:owner/:repo/settings/environments", requireAuth, async (c) => {
277281 min="0"
278282 max="1440"
279283 value="0"
284 aria-label="Wait timer in minutes"
280285 style="width: 120px"
281286 />
282287 </div>
@@ -286,6 +291,7 @@ r.get("/:owner/:repo/settings/environments", requireAuth, async (c) => {
286291 type="text"
287292 name="allowedBranches"
288293 placeholder="main, release/*"
294 aria-label="Allowed branches"
289295 />
290296 </div>
291297 <button type="submit" class="btn btn-primary">
Modifiedsrc/routes/explore.tsx+1−0View fileUnifiedSplit
@@ -120,6 +120,7 @@ explore.get("/explore", async (c) => {
120120 name="q"
121121 value={q}
122122 placeholder="Search repositories..."
123 aria-label="Search repositories"
123124 style="flex:1;padding:8px 12px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);font-size:14px"
124125 />
125126 <Button type="submit" variant="primary">
Modifiedsrc/routes/gates.tsx+2−1View fileUnifiedSplit
@@ -192,7 +192,7 @@ gates.get("/:owner/:repo/gates/settings", requireAuth, async (c) => {
192192 <label
193193 style="display: flex; gap: 12px; padding: 12px 14px; border-bottom: 1px solid var(--border); cursor: pointer"
194194 >
195 <input type="checkbox" name={name} value="1" checked={checked} />
195 <input type="checkbox" name={name} value="1" checked={checked} aria-label={label} />
196196 <div>
197197 <div style="font-weight: 500">{label}</div>
198198 {desc && (
@@ -337,6 +337,7 @@ gates.get("/:owner/:repo/gates/settings", requireAuth, async (c) => {
337337 name="pattern"
338338 required
339339 placeholder="release/* or main"
340 aria-label="Branch protection pattern"
340341 />
341342 </div>
342343 <div style="display: flex; flex-wrap: wrap; gap: 16px">
Modifiedsrc/routes/gists.tsx+5−0View fileUnifiedSplit
@@ -141,6 +141,7 @@ gistRoutes.get("/gists/new", requireAuth, async (c) => {
141141 type="text"
142142 name="description"
143143 placeholder="Gist description..."
144 aria-label="Gist description"
144145 style="padding: 8px;"
145146 />
146147 <div style="display: flex; gap: 16px;">
@@ -159,6 +160,7 @@ gistRoutes.get("/gists/new", requireAuth, async (c) => {
159160 name="filename[]"
160161 placeholder="filename.ext"
161162 required
163 aria-label="Filename"
162164 style="padding: 6px; width: 300px;"
163165 />
164166 <textarea
@@ -425,12 +427,14 @@ gistRoutes.get("/gists/:slug/edit", requireAuth, async (c) => {
425427 name="description"
426428 value={gist.description}
427429 placeholder="Description"
430 aria-label="Gist description"
428431 style="padding: 8px;"
429432 />
430433 <input
431434 type="text"
432435 name="message"
433436 placeholder="Revision message (optional)"
437 aria-label="Revision message"
434438 style="padding: 8px;"
435439 />
436440 <div id="files">
@@ -441,6 +445,7 @@ gistRoutes.get("/gists/:slug/edit", requireAuth, async (c) => {
441445 name="filename[]"
442446 value={f.filename}
443447 required
448 aria-label="Filename"
444449 style="padding: 6px; width: 300px;"
445450 />
446451 <textarea
Modifiedsrc/routes/import-bulk.tsx+3−0View fileUnifiedSplit
@@ -135,6 +135,7 @@ importBulkRoutes.get("/import/bulk", requireAuth, async (c) => {
135135 name="githubOrg"
136136 required
137137 placeholder="my-company"
138 aria-label="GitHub org"
138139 style="padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 14px; width: 100%"
139140 />
140141 </div>
@@ -149,6 +150,7 @@ importBulkRoutes.get("/import/bulk", requireAuth, async (c) => {
149150 required
150151 placeholder="ghp_xxxxxxxxxxxx"
151152 autocomplete="off"
153 aria-label="GitHub personal access token"
152154 style="padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 14px; font-family: var(--font-mono); width: 100%"
153155 />
154156 </div>
@@ -159,6 +161,7 @@ importBulkRoutes.get("/import/bulk", requireAuth, async (c) => {
159161 </label>
160162 <select
161163 name="visibility"
164 aria-label="Visibility filter"
162165 style="padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 14px; width: 100%"
163166 >
164167 <option value="both" selected>Both (public + private)</option>
Modifiedsrc/routes/import.tsx+4−0View fileUnifiedSplit
@@ -136,6 +136,7 @@ importRoutes.get("/import", requireAuth, requireAdmin, async (c) => {
136136 name="github_username"
137137 required
138138 placeholder="GitHub username or org"
139 aria-label="GitHub username or org"
139140 style="flex: 1; padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 14px"
140141 />
141142 <button type="submit" class="btn btn-primary">
@@ -157,6 +158,7 @@ importRoutes.get("/import", requireAuth, requireAdmin, async (c) => {
157158 name="repo_url"
158159 required
159160 placeholder="https://github.com/owner/repo"
161 aria-label="Repository URL"
160162 style="flex: 1; padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 14px"
161163 />
162164 <button type="submit" class="btn btn-primary">
@@ -179,6 +181,7 @@ importRoutes.get("/import", requireAuth, requireAdmin, async (c) => {
179181 name="github_username"
180182 required
181183 placeholder="GitHub username"
184 aria-label="GitHub username"
182185 style="padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 14px; width: 100%"
183186 />
184187 </div>
@@ -188,6 +191,7 @@ importRoutes.get("/import", requireAuth, requireAdmin, async (c) => {
188191 name="github_token"
189192 required
190193 placeholder="ghp_xxxxxxxxxxxx (GitHub personal access token)"
194 aria-label="GitHub personal access token"
191195 style="padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 14px; font-family: var(--font-mono); width: 100%"
192196 />
193197 </div>
Modifiedsrc/routes/issues.tsx+1−0View fileUnifiedSplit
@@ -205,6 +205,7 @@ issueRoutes.get(
205205 required
206206 placeholder="Title"
207207 style="font-size:16px;padding:10px 14px"
208 aria-label="Issue title"
208209 />
209210 </FormGroup>
210211 <FormGroup>
Modifiedsrc/routes/marketplace.tsx+4−3View fileUnifiedSplit
@@ -63,6 +63,7 @@ marketplace.get("/marketplace", async (c) => {
6363 name="q"
6464 value={q}
6565 placeholder="Search apps"
66 aria-label="Search apps"
6667 style="width:320px"
6768 />{" "}
6869 <button type="submit" class="btn">
@@ -307,7 +308,7 @@ marketplace.get("/developer/apps-new", requireAuth, async (c) => {
307308 <form method="post" action="/developer/apps-new" class="panel" style="padding:16px">
308309 <div class="form-group">
309310 <label>Name</label>
310 <input type="text" name="name" required style="width:100%" />
311 <input type="text" name="name" required aria-label="App name" style="width:100%" />
311312 </div>
312313 <div class="form-group">
313314 <label>Description</label>
@@ -315,11 +316,11 @@ marketplace.get("/developer/apps-new", requireAuth, async (c) => {
315316 </div>
316317 <div class="form-group">
317318 <label>Homepage URL</label>
318 <input type="url" name="homepageUrl" style="width:100%" />
319 <input type="url" name="homepageUrl" aria-label="Homepage URL" style="width:100%" />
319320 </div>
320321 <div class="form-group">
321322 <label>Webhook URL (optional)</label>
322 <input type="url" name="webhookUrl" style="width:100%" />
323 <input type="url" name="webhookUrl" aria-label="Webhook URL" style="width:100%" />
323324 </div>
324325 <div class="form-group">
325326 <label>Permissions</label>
Modifiedsrc/routes/mirrors.tsx+1−0View fileUnifiedSplit
@@ -141,6 +141,7 @@ mirrors.get("/:owner/:repo/settings/mirror", requireAuth, async (c) => {
141141 name="is_enabled"
142142 value="1"
143143 checked={mirror ? mirror.isEnabled : true}
144 aria-label="Enabled"
144145 />
145146 <span>Enabled</span>
146147 </label>
Modifiedsrc/routes/onboarding.tsx+2−1View fileUnifiedSplit
@@ -12,6 +12,7 @@ import type { AuthEnv } from "../middleware/auth";
1212import { eq, sql } from "drizzle-orm";
1313import { db } from "../db";
1414import { repositories, sshKeys, apiTokens, users } from "../db/schema";
15import { config } from "../lib/config";
1516import {
1617 Container,
1718 WelcomeHero,
@@ -149,7 +150,7 @@ onboardingRoutes.get("/getting-started", softAuth, requireAuth, async (c) => {
149150 <Card style="padding:16px;margin-bottom:20px">
150151 <h3 style="font-size:14px;margin:0 0 8px 0">Push an existing project</h3>
151152 <CopyBlock
152 text={`git remote add gluecron http://localhost:3000/${user.username}/your-repo.git\ngit push -u gluecron main`}
153 text={`git remote add gluecron ${config.appBaseUrl}/${user.username}/your-repo.git\ngit push -u gluecron main`}
153154 label="Commands"
154155 />
155156 </Card>
Modifiedsrc/routes/orgs.tsx+4−0View fileUnifiedSplit
@@ -371,6 +371,7 @@ orgRoutes.get("/orgs/:slug/people", async (c) => {
371371 placeholder="username to add"
372372 required
373373 maxLength={64}
374 aria-label="Username to add"
374375 style="flex: 1"
375376 />
376377 <select name="role">
@@ -510,6 +511,7 @@ orgRoutes.get("/orgs/:org/settings", softAuth, requireAuth, async (c) => {
510511 required
511512 maxLength={39}
512513 pattern="[a-z0-9][a-z0-9-]{0,38}"
514 aria-label="Team slug"
513515 />
514516 <input
515517 type="text"
@@ -517,6 +519,7 @@ orgRoutes.get("/orgs/:org/settings", softAuth, requireAuth, async (c) => {
517519 placeholder="Team name"
518520 required
519521 maxLength={80}
522 aria-label="Team name"
520523 />
521524 <button type="submit" class="btn btn-primary">
522525 Create team
@@ -634,6 +637,7 @@ orgRoutes.get("/orgs/:org/members/invite", softAuth, requireAuth, async (c) => {
634637 placeholder="username"
635638 required
636639 maxLength={64}
640 aria-label="Username to invite"
637641 style="flex: 1"
638642 />
639643 <select name="role">
Modifiedsrc/routes/pages.tsx+1−0View fileUnifiedSplit
@@ -279,6 +279,7 @@ pagesRoute.get(
279279 name="enabled"
280280 value="1"
281281 checked={settings.enabled}
282 aria-label="Enable GitHub Pages"
282283 />
283284 {" "}Enable GitHub Pages
284285 </label>
Modifiedsrc/routes/passkeys.tsx+1−0View fileUnifiedSplit
@@ -128,6 +128,7 @@ passkeys.get("/settings/passkeys", async (c) => {
128128 name="name"
129129 defaultValue={k.name}
130130 maxLength={60}
131 aria-label="Passkey name"
131132 style="width: 160px"
132133 />
133134 <button type="submit" class="btn btn-sm">
Modifiedsrc/routes/projects.tsx+6−2View fileUnifiedSplit
@@ -159,6 +159,7 @@ projectRoutes.get(
159159 name="title"
160160 placeholder="Title"
161161 required
162 aria-label="Project title"
162163 style="padding: 8px;"
163164 />
164165 <textarea
@@ -309,12 +310,12 @@ projectRoutes.get(
309310 <div class="kanban">
310311 {columns.map((col) => (
311312 <div class="kcol">
312 <h4>
313 <h2>
313314 <span>{col.name}</span>
314315 <span style="color: var(--text-muted); font-size: 13px;">
315316 {(itemsByCol[col.id] || []).length}
316317 </span>
317 </h4>
318 </h2>
318319 {(itemsByCol[col.id] || []).map((it) => (
319320 <div class="kcard">
320321 <div>
@@ -355,6 +356,7 @@ projectRoutes.get(
355356 <button
356357 type="submit"
357358 class="btn"
359 aria-label="Delete item"
358360 style="font-size: 11px; padding: 2px 6px;"
359361 >
360362 ×
@@ -376,6 +378,7 @@ projectRoutes.get(
376378 name="title"
377379 placeholder="New card title"
378380 required
381 aria-label="New card title"
379382 style="padding: 4px; font-size: 12px;"
380383 />
381384 <button
@@ -401,6 +404,7 @@ projectRoutes.get(
401404 name="name"
402405 placeholder="New column"
403406 required
407 aria-label="New column name"
404408 style="padding: 6px;"
405409 />
406410 <button type="submit" class="btn">
Modifiedsrc/routes/protected-tags.tsx+1−0View fileUnifiedSplit
@@ -134,6 +134,7 @@ protectedTagsRoutes.get(
134134 name="pattern"
135135 required
136136 placeholder="v* or release-*"
137 aria-label="Tag protection pattern"
137138 style="font-family:var(--font-mono)"
138139 />
139140 </div>
Modifiedsrc/routes/pulls.tsx+1−0View fileUnifiedSplit
@@ -248,6 +248,7 @@ pulls.get(
248248 required
249249 placeholder="Title"
250250 style="font-size:16px;padding:10px 14px"
251 aria-label="Pull request title"
251252 />
252253 </FormGroup>
253254 <FormGroup>
Modifiedsrc/routes/releases.tsx+4−3View fileUnifiedSplit
@@ -221,11 +221,12 @@ releasesRoute.get("/:owner/:repo/releases/new", requireAuth, requireRepoAccess("
221221 required
222222 placeholder="v1.0.0"
223223 pattern="[A-Za-z0-9._\\-]+"
224 aria-label="Tag"
224225 />
225226 </div>
226227 <div class="form-group">
227228 <label>Target branch / commit</label>
228 <select name="target">
229 <select name="target" aria-label="Target branch">
229230 {branches.map((b) => (
230231 <option value={b} selected={b === repoRow.defaultBranch}>
231232 {b}
@@ -235,11 +236,11 @@ releasesRoute.get("/:owner/:repo/releases/new", requireAuth, requireRepoAccess("
235236 </div>
236237 <div class="form-group">
237238 <label>Release name</label>
238 <input type="text" name="name" required placeholder="v1.0.0 — the big one" />
239 <input type="text" name="name" required placeholder="v1.0.0 — the big one" aria-label="Release name" />
239240 </div>
240241 <div class="form-group">
241242 <label>Previous tag (for AI changelog)</label>
242 <select name="previousTag">
243 <select name="previousTag" aria-label="Previous tag">
243244 <option value="">(auto — last tag)</option>
244245 {tags.map((t) => (
245246 <option value={t.name}>{t.name}</option>
Modifiedsrc/routes/repo-settings.tsx+3−0View fileUnifiedSplit
@@ -117,6 +117,7 @@ repoSettings.get("/:owner/:repo/settings", requireAuth, requireRepoAccess("admin
117117 name="visibility"
118118 value="public"
119119 checked={!repo.isPrivate}
120 aria-label="Public"
120121 />
121122 <div class="vis-label">Public</div>
122123 </label>
@@ -126,6 +127,7 @@ repoSettings.get("/:owner/:repo/settings", requireAuth, requireRepoAccess("admin
126127 name="visibility"
127128 value="private"
128129 checked={repo.isPrivate}
130 aria-label="Private"
129131 />
130132 <div class="vis-label">Private</div>
131133 </label>
@@ -197,6 +199,7 @@ repoSettings.get("/:owner/:repo/settings", requireAuth, requireRepoAccess("admin
197199 name="new_owner"
198200 placeholder="new-owner-username"
199201 required
202 aria-label="New owner username"
200203 style="width:60%"
201204 />{" "}
202205 <button type="submit" class="btn">
Modifiedsrc/routes/required-checks.tsx+1−0View fileUnifiedSplit
@@ -161,6 +161,7 @@ required.get(
161161 name="checkName"
162162 required
163163 placeholder="GateTest"
164 aria-label="Check name"
164165 style="font-family:var(--font-mono)"
165166 />
166167 </div>
Modifiedsrc/routes/saved-replies.tsx+1−0View fileUnifiedSplit
@@ -115,6 +115,7 @@ replies.get("/settings/replies", async (c) => {
115115 required
116116 value={r.shortcut}
117117 maxLength={64}
118 aria-label="Shortcut"
118119 />
119120 </div>
120121 <div class="form-group">
Modifiedsrc/routes/search.tsx+1−0View fileUnifiedSplit
@@ -148,6 +148,7 @@ search.get("/search", async (c) => {
148148 name="q"
149149 value={q}
150150 placeholder="Search repositories, users, issues, PRs…"
151 aria-label="Search"
151152 style="width: 100%; padding: 10px 14px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 14px"
152153 autofocus
153154 />
Modifiedsrc/routes/semantic-search.tsx+1−0View fileUnifiedSplit
@@ -159,6 +159,7 @@ semanticSearch.get("/:owner/:repo/search/semantic", async (c) => {
159159 name="q"
160160 value={q}
161161 placeholder="Ask a question or describe what you're looking for…"
162 aria-label="Search"
162163 style="width: 100%; padding: 10px 14px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 14px"
163164 autofocus
164165 />
Modifiedsrc/routes/settings-2fa.tsx+2−0View fileUnifiedSplit
@@ -140,6 +140,7 @@ settings2fa.get("/settings/2fa", async (c) => {
140140 <label for="password">Password</label>
141141 <input
142142 type="password"
143 id="password"
143144 name="password"
144145 required
145146 autocomplete="current-password"
@@ -238,6 +239,7 @@ async function showEnrolPage(c: any, user: any, error?: string) {
238239 <label for="code">6-digit code</label>
239240 <input
240241 type="text"
242 id="code"
241243 name="code"
242244 required
243245 pattern="[0-9]{6}"
Modifiedsrc/routes/settings.tsx+4−0View fileUnifiedSplit
@@ -99,6 +99,7 @@ settings.get("/settings", (c) => {
9999 name="notify_email_on_mention"
100100 value="1"
101101 checked={user.notifyEmailOnMention}
102 aria-label="Someone @mentions me or requests a review"
102103 />
103104 <span>
104105 Someone <code>@mentions</code> me or requests a review
@@ -112,6 +113,7 @@ settings.get("/settings", (c) => {
112113 name="notify_email_on_assign"
113114 value="1"
114115 checked={user.notifyEmailOnAssign}
116 aria-label="I am assigned to an issue or PR"
115117 />
116118 <span>I am assigned to an issue or PR</span>
117119 </label>
@@ -123,6 +125,7 @@ settings.get("/settings", (c) => {
123125 name="notify_email_on_gate_fail"
124126 value="1"
125127 checked={user.notifyEmailOnGateFail}
128 aria-label="A gate fails on one of my repositories"
126129 />
127130 <span>A gate fails on one of my repositories</span>
128131 </label>
@@ -134,6 +137,7 @@ settings.get("/settings", (c) => {
134137 name="notify_email_digest_weekly"
135138 value="1"
136139 checked={user.notifyEmailDigestWeekly}
140 aria-label="Weekly digest email"
137141 />
138142 <span>
139143 Weekly digest —{" "}
Modifiedsrc/routes/sponsors.tsx+3−1View fileUnifiedSplit
@@ -96,6 +96,7 @@ sponsors.get("/sponsors/:username", async (c) => {
9696 placeholder="Amount in cents (e.g. 500 = $5)"
9797 min="100"
9898 required
99 aria-label="Sponsorship amount in cents"
99100 style="width:60%"
100101 />{" "}
101102 <button type="submit" class="btn btn-primary">
@@ -324,7 +325,7 @@ sponsors.get("/settings/sponsors", requireAuth, async (c) => {
324325 >
325326 <div class="form-group">
326327 <label>Name</label>
327 <input type="text" name="name" required style="width:100%" />
328 <input type="text" name="name" required aria-label="Tier name" style="width:100%" />
328329 </div>
329330 <div class="form-group">
330331 <label>Description</label>
@@ -338,6 +339,7 @@ sponsors.get("/settings/sponsors", requireAuth, async (c) => {
338339 min="0"
339340 placeholder="500 = $5/mo"
340341 required
342 aria-label="Monthly amount in cents"
341343 />
342344 </div>
343345 <button type="submit" class="btn btn-primary">
Modifiedsrc/routes/sso.tsx+2−0View fileUnifiedSplit
@@ -120,6 +120,7 @@ sso.get("/admin/sso", requireAuth, async (c) => {
120120 name="enabled"
121121 value="1"
122122 checked={!!cfg?.enabled}
123 aria-label="Enable SSO sign-in on /login"
123124 />
124125 <span>Enable SSO sign-in on /login</span>
125126 </label>
@@ -226,6 +227,7 @@ sso.get("/admin/sso", requireAuth, async (c) => {
226227 name="auto_create_users"
227228 value="1"
228229 checked={cfg ? cfg.autoCreateUsers : true}
230 aria-label="Auto-create users on first SSO sign-in"
229231 />
230232 <span>
231233 Auto-create local accounts on first SSO sign-in (turn off to
Modifiedsrc/routes/symbols.tsx+2−0View fileUnifiedSplit
@@ -115,6 +115,7 @@ symbols.get("/:owner/:repo/symbols", async (c) => {
115115 name="q"
116116 placeholder="Search symbol name..."
117117 required
118 aria-label="Search symbol name"
118119 style="flex:1"
119120 />
120121 <button type="submit" class="btn">
@@ -228,6 +229,7 @@ symbols.get("/:owner/:repo/symbols/search", async (c) => {
228229 value={q}
229230 placeholder="Search symbol name..."
230231 required
232 aria-label="Search symbol name"
231233 style="flex:1"
232234 />
233235 <button type="submit" class="btn">
Modifiedsrc/routes/team-collaborators.tsx+2−0View fileUnifiedSplit
@@ -202,6 +202,8 @@ teamCollaboratorRoutes.get(
202202 <img
203203 src={row.avatarUrl}
204204 alt=""
205 width={20}
206 height={20}
205207 style="width:20px;height:20px;border-radius:50%;vertical-align:middle;margin-right:6px"
206208 />
207209 )}
Modifiedsrc/routes/tokens.tsx+1−0View fileUnifiedSplit
@@ -137,6 +137,7 @@ tokens.get("/settings/tokens", async (c) => {
137137 name="scopes"
138138 value={scope}
139139 checked={scope === "repo"}
140 aria-label={scope}
140141 />{" "}
141142 {scope}
142143 </label>
Modifiedsrc/routes/web.tsx+5−2View fileUnifiedSplit
@@ -7,6 +7,7 @@ import { Hono } from "hono";
77import { html } from "hono/html";
88import { eq, and, desc, inArray, sql } from "drizzle-orm";
99import { db } from "../db";
10import { config } from "../lib/config";
1011import {
1112 users,
1213 repositories,
@@ -99,7 +100,7 @@ web.get("/new", requireAuth, (c) => {
99100 <form method="post" action="/new">
100101 <div class="form-group">
101102 <label>Owner</label>
102 <input type="text" value={user.username} disabled class="input-disabled" />
103 <input type="text" value={user.username} disabled aria-label="Owner" class="input-disabled" />
103104 </div>
104105 <div class="form-group">
105106 <label for="name">Repository name</label>
@@ -515,7 +516,7 @@ web.get("/:owner/:repo", async (c) => {
515516 <div class="empty-state">
516517 <h2>Empty repository</h2>
517518 <p>Get started by pushing code:</p>
518 <pre>{`git remote add gluecron http://localhost:3000/${owner}/${repo}.git
519 <pre>{`git remote add gluecron ${config.appBaseUrl}/${owner}/${repo}.git
519520git push -u gluecron main`}</pre>
520521 </div>
521522 </Layout>
@@ -554,6 +555,7 @@ git push -u gluecron main`}</pre>
554555 name="name"
555556 placeholder="new-repo-name"
556557 required
558 aria-label="New repository name"
557559 style="width:200px"
558560 />
559561 <button type="submit" class="btn btn-primary">
@@ -1180,6 +1182,7 @@ web.get("/:owner/:repo/search", async (c) => {
11801182 name="q"
11811183 value={q}
11821184 placeholder="Search code..."
1185 aria-label="Search code"
11831186 style="flex: 1; padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 14px"
11841187 />
11851188 <button type="submit" class="btn btn-primary">
Modifiedsrc/routes/wikis.tsx+3−0View fileUnifiedSplit
@@ -246,6 +246,7 @@ wikiRoutes.get("/:owner/:repo/wiki/new", requireAuth, async (c) => {
246246 name="title"
247247 placeholder="Page title"
248248 required
249 aria-label="Page title"
249250 style="padding: 8px;"
250251 />
251252 <textarea
@@ -426,6 +427,7 @@ wikiRoutes.get(
426427 name="title"
427428 value={page.title}
428429 required
430 aria-label="Page title"
429431 style="padding: 8px;"
430432 />
431433 <textarea
@@ -439,6 +441,7 @@ wikiRoutes.get(
439441 type="text"
440442 name="message"
441443 placeholder="Revision message (optional)"
444 aria-label="Revision message"
442445 style="padding: 8px;"
443446 />
444447 <button type="submit" class="btn btn-primary">
Modifiedsrc/views/client-js.ts+1−1View fileUnifiedSplit
@@ -259,7 +259,7 @@ export const clientJs = `
259259 else if (diff < 43200) el.textContent = Math.floor(diff / 1440) + 'd ago';
260260 });
261261 }
262 setInterval(updateTimes, 60000);
262 var _timesInterval = setInterval(updateTimes, 60000);
263263
264264 // ─── Confirmation on Dangerous Actions ─────────────────────────────────
265265
Modifiedsrc/views/layout.tsx+2−2View fileUnifiedSplit
@@ -205,7 +205,7 @@ const navScript = `
205205 var item = filtered[i];
206206 var cls = i === selected ? 'cmdk-item cmdk-active' : 'cmdk-item';
207207 var bg = i === selected ? 'background:var(--bg);' : '';
208 html += '<div class="' + cls + '" data-idx="' + i + '" data-href="' + item.href + '"' +
208 html += '<div class="' + cls + '" data-idx="' + i + '" data-url="' + item.href + '"' +
209209 ' style="padding:10px 16px;cursor:pointer;border-bottom:1px solid var(--border);' + bg + '">' +
210210 '<div>' + item.label + '</div>' +
211211 '<div style="font-size:11px;color:var(--text-muted)">' + item.href + '</div>' +
@@ -241,7 +241,7 @@ const navScript = `
241241 var t = e.target;
242242 if (t && t.id === 'cmdk-backdrop') { closePalette(); return; }
243243 var item = t && t.closest && t.closest('.cmdk-item');
244 if (item) { go(item.getAttribute('data-href')); }
244 if (item) { go(item.getAttribute('data-url')); }
245245 });
246246
247247 document.addEventListener('input', function(e){
Modifiedsrc/views/ui.tsx+4−0View fileUnifiedSplit
@@ -148,6 +148,7 @@ export const Input: FC<{
148148 minLength?: number;
149149 maxLength?: number;
150150 style?: string;
151 "aria-label"?: string;
151152}> = (props) => (
152153 <input
153154 type={props.type || "text"}
@@ -164,6 +165,7 @@ export const Input: FC<{
164165 maxLength={props.maxLength}
165166 class={props.disabled ? "input-disabled" : ""}
166167 style={props.style}
168 aria-label={props["aria-label"] || props.placeholder || props.name}
167169 />
168170);
169171
@@ -459,6 +461,8 @@ export const Avatar: FC<{
459461 <img
460462 src={url}
461463 alt={name}
464 width={size}
465 height={size}
462466 style={`width:${size}px;height:${size}px;border-radius:50%;object-fit:cover`}
463467 loading="lazy"
464468 />
465469