Commitc6018a5unknown_key
feat(integration): wire discoverability for all shipped features — nav + dashboard + /help + /vs-github
12 files changed+859−28c6018a5c746c1c5ecd0ddf5afa15b5c5851f5e5a
12 changed files+859−28
Modifiedsrc/routes/admin-diagnose.tsx+39−0View fileUnifiedSplit
@@ -1245,6 +1245,45 @@ diagnose.get("/admin/diagnose", async (c) => {
12451245 })}
12461246 </div>
12471247
1248 <div class="health-test">
1249 <h3>AI background tasks</h3>
1250 <p>
1251 These tasks run continuously inside the autopilot tick — no
1252 external scheduler. Each one fires on every signal it cares
1253 about (CI failure / gate finding / monitor heartbeat) and
1254 degrades gracefully when <code>ANTHROPIC_API_KEY</code> is
1255 unset.
1256 </p>
1257 <ul style="margin: 8px 0 0; padding-left: 20px; line-height: 1.7; font-size: 13.5px;">
1258 <li>
1259 <strong>AI CI healer</strong> — on every failed workflow
1260 run, Claude reads the failure log + recent diff and proposes
1261 targeted file edits. Source: <code>src/lib/ai-ci-healer.ts</code>.
1262 </li>
1263 <li>
1264 <strong>AI patch generator</strong> — when GateTest or
1265 advisory scan reports a finding, this generates a concrete
1266 diff PR proposing the fix. Source: <code>src/lib/ai-patch-generator.ts</code>.
1267 </li>
1268 <li>
1269 <strong>AI proactive monitor</strong> — sweeps every repo
1270 looking for stale TODOs, suspicious patterns, and stuck PRs;
1271 files issues automatically. Findings surface in{" "}
1272 <a href="/settings/audit">/settings/audit</a>. Source:{" "}
1273 <code>src/lib/ai-proactive-monitor.ts</code>.
1274 </li>
1275 <li>
1276 <strong>AI build tasks</strong> — picks up issues labelled
1277 <code>ai:build</code> and ships a PR for them. Source:{" "}
1278 <code>src/lib/ai-build-tasks.ts</code>.
1279 </li>
1280 </ul>
1281 <p style="margin-top: 12px;">
1282 See <a href="/admin/autopilot">/admin/autopilot</a> for the
1283 per-task tick log and force-run controls.
1284 </p>
1285 </div>
1286
12481287 <div class="health-test">
12491288 <h3>Test email delivery</h3>
12501289 <p>
Modifiedsrc/routes/admin.tsx+5−1View fileUnifiedSplit
@@ -2683,10 +2683,14 @@ admin.get("/admin", async (c) => {
26832683 <span class="admin-action-icon">{Icons.sso}</span>
26842684 Enterprise SSO
26852685 </a>
2686 <a href="/admin/autopilot" class="admin-action">
2686 <a href="/admin/autopilot" class="admin-action" title="CI healer, patch generator, proactive monitor, AI build tasks">
26872687 <span class="admin-action-icon">{Icons.bot}</span>
26882688 Autopilot
26892689 </a>
2690 <a href="/admin/diagnose" class="admin-action" title="Live status of the AI CI healer, patch generator, and proactive monitor">
2691 <span class="admin-action-icon">{Icons.bot}</span>
2692 AI background tasks
2693 </a>
26902694 <a href="/connect/claude" class="admin-action is-primary">
26912695 <span class="admin-action-icon">{Icons.bot}</span>
26922696 Connect Claude
Modifiedsrc/routes/audit.tsx+6−0View fileUnifiedSplit
@@ -789,6 +789,12 @@ function AuditPage({
789789 <span class="audit-title-grad">Trail.</span>
790790 </h1>
791791 <p class="audit-sub">{subtitle}</p>
792 <p class="audit-sub" style="margin-top: 8px; font-size: 13px;">
793 <strong>Also surfaces:</strong> findings from the AI proactive
794 monitor (<code>action=ai.monitor.*</code>) — stale TODOs, stuck
795 PRs, suspicious patterns it filed automatically. Filter by
796 actor <code>system</code> to see only the monitor's events.
797 </p>
792798 </div>
793799 </section>
794800
Modifiedsrc/routes/code-scanning.tsx+7−0View fileUnifiedSplit
@@ -415,6 +415,13 @@ codeScanning.get("/:owner/:repo/security", async (c) => {
415415 dependency audits, and security gates. {latestByName.size}{" "}
416416 scanner{latestByName.size === 1 ? "" : "s"} watching this repo.
417417 </p>
418 <p class="sec-sub" style="margin-top: 8px; font-size: 13px;">
419 <strong>AI patch generator:</strong> when any scanner flags a
420 finding, the autopilot's patch generator can propose a fix
421 PR automatically. See its live status on{" "}
422 <a href="/admin/diagnose">/admin/diagnose</a> (site admins
423 only) or watch for PRs authored by <code>gluecron[bot]</code>.
424 </p>
418425 </div>
419426 </section>
420427
Modifiedsrc/routes/dashboard.tsx+168−0View fileUnifiedSplit
@@ -391,6 +391,11 @@ dashboard.get("/dashboard", requireAuth, async (c) => {
391391 {/* ─── L9: AI hours-saved hero widget ─── */}
392392 <AiHoursSavedWidget week={savingsWeek} lifetime={savingsLifetime} />
393393
394 {/* ─── Quick actions — surfaces the 5 most-leverage AI features so
395 they're discoverable from the dashboard without diving into the
396 AI dropdown in the top nav. Scoped CSS under .qa- prefix. ─── */}
397 <QuickActionsPanel firstRepo={repos[0]} username={user.username} />
398
394399 {/* ─── Stats Bar ─── */}
395400 <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--space-3); margin-bottom: var(--space-8)">
396401 <StatBox
@@ -1197,4 +1202,167 @@ const DashboardSkeleton = () => (
11971202 </>
11981203);
11991204
1205// ─── Quick actions — 5-card panel of the most-leverage AI features.
1206// Surfaces the post-K1 AI surfaces (Voice, Standups, Refactors, repo
1207// chat, Pulls dashboard) so signed-in users can discover them without
1208// hunting through the top-nav dropdown. CSS scoped under `.qa-` to
1209// avoid bleed; no shared component touched.
1210const QuickActionsPanel = ({
1211 firstRepo,
1212 username,
1213}: {
1214 firstRepo: { name: string } | undefined;
1215 username: string;
1216}) => {
1217 const chatHref = firstRepo
1218 ? `/${username}/${firstRepo.name}/chat`
1219 : "/explore";
1220 const chatSub = firstRepo
1221 ? `Open ${firstRepo.name} rubber-duck chat`
1222 : "Pick a repo, then ask anything";
1223 return (
1224 <div class="qa-panel" aria-label="Quick AI actions">
1225 <style
1226 dangerouslySetInnerHTML={{
1227 __html: `
1228 .qa-panel {
1229 position: relative;
1230 margin-bottom: var(--space-6);
1231 padding: var(--space-4) var(--space-5) var(--space-5);
1232 background: var(--bg-elevated);
1233 border: 1px solid var(--border);
1234 border-radius: 14px;
1235 overflow: hidden;
1236 }
1237 .qa-panel::before {
1238 content: '';
1239 position: absolute;
1240 top: 0; left: 0; right: 0;
1241 height: 1px;
1242 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
1243 opacity: 0.65;
1244 pointer-events: none;
1245 }
1246 .qa-eyebrow {
1247 font-size: 11px;
1248 font-weight: 600;
1249 letter-spacing: 0.08em;
1250 text-transform: uppercase;
1251 color: var(--accent);
1252 margin-bottom: 4px;
1253 }
1254 .qa-title {
1255 font-family: var(--font-display);
1256 font-size: 17px;
1257 font-weight: 700;
1258 letter-spacing: -0.018em;
1259 margin: 0 0 var(--space-3);
1260 color: var(--text-strong);
1261 }
1262 .qa-grid {
1263 display: grid;
1264 grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
1265 gap: var(--space-2);
1266 }
1267 .qa-card {
1268 display: flex;
1269 flex-direction: column;
1270 gap: 4px;
1271 padding: var(--space-3) var(--space-3);
1272 background: var(--bg);
1273 border: 1px solid var(--border);
1274 border-radius: 10px;
1275 color: var(--text);
1276 text-decoration: none;
1277 transition: border-color 160ms ease, transform 160ms ease, background 160ms ease;
1278 position: relative;
1279 overflow: hidden;
1280 }
1281 .qa-card::before {
1282 content: '';
1283 position: absolute;
1284 top: 0; left: 0; right: 0;
1285 height: 1px;
1286 background: linear-gradient(90deg, transparent 0%, rgba(140,109,255,0.55) 30%, rgba(54,197,214,0.55) 70%, transparent 100%);
1287 opacity: 0;
1288 transition: opacity 160ms ease;
1289 }
1290 .qa-card:hover {
1291 border-color: rgba(140,109,255,0.40);
1292 text-decoration: none;
1293 transform: translateY(-1px);
1294 background: linear-gradient(180deg, rgba(140,109,255,0.04), transparent);
1295 }
1296 .qa-card:hover::before { opacity: 1; }
1297 .qa-card-label {
1298 font-size: 13.5px;
1299 font-weight: 600;
1300 color: var(--text-strong);
1301 display: flex;
1302 align-items: center;
1303 gap: 6px;
1304 }
1305 .qa-card-sub {
1306 font-size: 12px;
1307 color: var(--text-muted);
1308 line-height: 1.4;
1309 }
1310 .qa-card-icon {
1311 display: inline-flex;
1312 width: 18px; height: 18px;
1313 align-items: center;
1314 justify-content: center;
1315 border-radius: 5px;
1316 background: linear-gradient(135deg, rgba(140,109,255,0.18), rgba(54,197,214,0.14));
1317 color: #b69dff;
1318 font-size: 11px;
1319 font-weight: 700;
1320 flex-shrink: 0;
1321 }
1322 `,
1323 }}
1324 />
1325 <div class="qa-eyebrow">Quick actions</div>
1326 <h3 class="qa-title">Ship faster with Claude</h3>
1327 <div class="qa-grid">
1328 <a href="/voice" class="qa-card">
1329 <span class="qa-card-label">
1330 <span class="qa-card-icon" aria-hidden="true">{"\u{1F3A4}"}</span>
1331 Talk to ship
1332 </span>
1333 <span class="qa-card-sub">Voice → PR in one take</span>
1334 </a>
1335 <a href="/standups" class="qa-card">
1336 <span class="qa-card-label">
1337 <span class="qa-card-icon" aria-hidden="true">{"\u{1F4DD}"}</span>
1338 Today's standup
1339 </span>
1340 <span class="qa-card-sub">Daily AI brief, fresh on demand</span>
1341 </a>
1342 <a href="/refactors" class="qa-card">
1343 <span class="qa-card-label">
1344 <span class="qa-card-icon" aria-hidden="true">{"⚡"}</span>
1345 Refactor everywhere
1346 </span>
1347 <span class="qa-card-sub">One brief → PRs across all repos</span>
1348 </a>
1349 <a href={chatHref} class="qa-card">
1350 <span class="qa-card-label">
1351 <span class="qa-card-icon" aria-hidden="true">{"\u{1F4AC}"}</span>
1352 Chat with a repo
1353 </span>
1354 <span class="qa-card-sub">{chatSub}</span>
1355 </a>
1356 <a href="/pulls" class="qa-card">
1357 <span class="qa-card-label">
1358 <span class="qa-card-icon" aria-hidden="true">{"\u{1F500}"}</span>
1359 Watch the PR queue
1360 </span>
1361 <span class="qa-card-sub">Global pulls across your repos</span>
1362 </a>
1363 </div>
1364 </div>
1365 );
1366};
1367
12001368export default dashboard;
Modifiedsrc/routes/help.tsx+307−0View fileUnifiedSplit
@@ -284,8 +284,18 @@ help.get("/help", (c) => {
284284 <a href="#tokens">Personal access tokens</a>
285285 <a href="#gates">Gates & AI review</a>
286286 <a href="#ai-native">AI-native flow</a>
287 <a href="#ai-surfaces">New AI surfaces</a>
288 <a href="#repo-chat">Chat with a repo</a>
289 <a href="#previews">Branch previews</a>
290 <a href="#migrations">Migration assistant</a>
291 <a href="#slash-commands">PR slash commands</a>
292 <a href="#release-notes">AI release notes</a>
293 <a href="#ai-commits">AI commit messages</a>
294 <a href="#agents">Agent multiplayer</a>
295 <a href="#semantic">Semantic search API</a>
287296 <a href="#shortcuts">Keyboard shortcuts</a>
288297 <a href="#api">API</a>
298 <a href="#build-agents">Build-agent integration</a>
289299 </div>
290300 </nav>
291301
@@ -546,6 +556,282 @@ help.get("/help", (c) => {
546556 </div>
547557 </section>
548558
559 {/* ─── New AI surfaces (global) ─── */}
560 <section id="ai-surfaces" class="help-section">
561 <div class="help-section-head">
562 <div class="help-section-eyebrow">Discover</div>
563 <h2 class="help-section-title">New AI surfaces</h2>
564 <p class="help-section-desc">
565 Seven cross-repo dashboards that ship with Gluecron — find
566 them under the <strong>AI</strong> dropdown in the top nav,
567 plus the always-on <em>Pulls</em>, <em>Issues</em>,{" "}
568 <em>Activity</em>, and <em>Inbox</em> tabs.
569 </p>
570 </div>
571 <div class="help-section-body">
572 <div class="help-item">
573 <strong><a href="/pulls">/pulls</a> — global pull-request
574 dashboard.</strong> Every open PR across every repo you own
575 or follow, grouped by review state. Filter by author, label,
576 or repo. Replaces tab-flicking between repos when you just
577 want to know "what needs me right now."
578 </div>
579 <div class="help-item">
580 <strong><a href="/issues">/issues</a> — global issue
581 dashboard.</strong> Same shape as <code>/pulls</code> for
582 issues. Combine the <code>ai:build</code> filter with{" "}
583 <em>assigned to me</em> to scan everything Claude is queued
584 to build for you.
585 </div>
586 <div class="help-item">
587 <strong><a href="/inbox">/inbox</a> — unified inbox.</strong>{" "}
588 Mentions, review requests, CI failures, and AI-generated
589 actions in one stream. Filters at the top:{" "}
590 <code>filter=mentions</code>, <code>filter=review</code>,{" "}
591 <code>filter=ci</code>, <code>filter=ai</code>. The badge
592 count next to <em>Inbox</em> in the nav is unread items.
593 </div>
594 <div class="help-item">
595 <strong><a href="/activity">/activity</a> — your
596 timeline.</strong> A chronological feed of every push,
597 merge, comment, and AI action across your repos. Useful for
598 writing your own weekly recap and for spotting when an
599 autopilot task fired without you noticing.
600 </div>
601 <div class="help-item">
602 <strong><a href="/standups">/standups</a> — daily AI
603 brief.</strong> Claude writes a short standup at your
604 configured time (defaults to 09:00 UTC) summarising
605 yesterday's pushes, today's open PRs, and anything blocking.
606 Toggle the cadence in <a href="/settings">Settings →
607 Standups</a>. Hit <em>Refresh</em> on the page to regenerate
608 for the current window.
609 </div>
610 <div class="help-item">
611 <strong><a href="/voice">/voice</a> — voice-to-PR.</strong>{" "}
612 Hit record, speak a feature spec, stop — Claude transcribes,
613 picks the target repo, drafts the diff, and opens a draft
614 PR. The whole loop fits in a single browser tab; no native
615 app, no Whisper setup. Uses the browser's MediaRecorder API
616 under the hood — Chrome, Safari 17+, Firefox.
617 </div>
618 <div class="help-item">
619 <strong><a href="/refactors">/refactors</a> — multi-repo
620 refactor agent.</strong> Paste a brief like "rename the{" "}
621 <code>cents</code> field to <code>amountMinor</code> across
622 every API repo." Claude walks each repo, generates a patch,
623 and opens a draft PR per repo. Tracking dashboard shows
624 per-repo status (drafted / pushed / merged / failed).
625 </div>
626 <div class="help-item">
627 <strong><a href="/specs">/specs</a> — spec-to-PR loop.</strong>
628 Every <code>.gluecron/specs/*.md</code> file across your
629 repos shows up here. Add a spec, push it, then either run
630 the spec-to-PR generator from the page or label the file
631 and let autopilot do it overnight.
632 </div>
633 </div>
634 </section>
635
636 {/* ─── Chat with a repo ─── */}
637 <section id="repo-chat" class="help-section">
638 <div class="help-section-head">
639 <div class="help-section-eyebrow">Per-repo</div>
640 <h2 class="help-section-title">Chat with a repo</h2>
641 <p class="help-section-desc">
642 Rubber-duck a question against any single repo. Powered by
643 the semantic code index — Claude pulls the relevant files
644 on every turn so answers stay grounded in your actual code.
645 </p>
646 </div>
647 <div class="help-section-body">
648 <div class="help-item">
649 Visit <code>/:owner/:repo/chat</code> on any repo you can
650 read (the dashboard <em>Quick actions</em> panel has a
651 one-click link). Ask "where does auth happen?", "what does
652 this Drizzle migration do?", "draft a test for {`<file>`}".
653 The chat scrolls forever and the context is freshly retrieved
654 on every message — so file moves and recent pushes are
655 immediately visible to Claude.
656 </div>
657 <div class="help-item">
658 <strong>Foundation: semantic search.</strong>{" "}
659 <code>/:owner/:repo/semantic-search?q=…</code> is the
660 underlying retrieval. Chunks are embedded with{" "}
661 <code>voyage-code-3</code> when <code>VOYAGE_API_KEY</code>{" "}
662 is set; otherwise a lexical fallback (still useful — same
663 chunking and ranking, just no embeddings).
664 </div>
665 </div>
666 </section>
667
668 {/* ─── Branch previews ─── */}
669 <section id="previews" class="help-section">
670 <div class="help-section-head">
671 <div class="help-section-eyebrow">Per-repo</div>
672 <h2 class="help-section-title">Branch previews</h2>
673 <p class="help-section-desc">
674 Every non-default branch with a Dockerfile or a{" "}
675 <code>.gluecron/preview.yml</code> gets an ephemeral preview
676 URL. Spin up, share, tear down — automatically on push.
677 </p>
678 </div>
679 <div class="help-section-body">
680 <div class="help-item">
681 Open <code>/:owner/:repo/previews</code> to see the list of
682 live previews for the repo. Each row shows the branch, the
683 last commit, the preview URL, and the most recent build log.
684 Previews self-destruct when the branch is deleted or merged.
685 </div>
686 <div class="help-item">
687 <strong>Linked from PRs.</strong> Whenever a PR is opened
688 against the default branch, the PR header surfaces the
689 preview URL so reviewers click straight through to a running
690 instance instead of pulling locally.
691 </div>
692 </div>
693 </section>
694
695 {/* ─── Migration assistant ─── */}
696 <section id="migrations" class="help-section">
697 <div class="help-section-head">
698 <div class="help-section-eyebrow">Per-repo</div>
699 <h2 class="help-section-title">Migration assistant</h2>
700 <p class="help-section-desc">
701 When you touch a Drizzle schema, Claude proposes the
702 corresponding migration SQL and a one-line rollback plan.
703 No more "did I forget the migration?" PRs.
704 </p>
705 </div>
706 <div class="help-section-body">
707 <div class="help-item">
708 Visit <code>/:owner/:repo/migrations/propose</code> and
709 point the assistant at a recent schema change. It returns
710 the SQL diff, an up/down script, and a checklist of edge
711 cases (NULL→NOT NULL, type widening, default backfill). Save
712 the suggestion straight into <code>drizzle/NNNN_*.sql</code>{" "}
713 when you're happy with it.
714 </div>
715 <div class="help-item">
716 <strong>Discoverability.</strong> Surfaced from{" "}
717 <code>Settings → Integrations</code> on the repo, and
718 auto-pinged when a push modifies <code>src/db/schema.ts</code>{" "}
719 without a sibling migration file.
720 </div>
721 </div>
722 </section>
723
724 {/* ─── PR slash commands ─── */}
725 <section id="slash-commands" class="help-section">
726 <div class="help-section-head">
727 <div class="help-section-eyebrow">PR power</div>
728 <h2 class="help-section-title">PR slash commands</h2>
729 <p class="help-section-desc">
730 Type <code>/</code> at the start of a PR comment to invoke
731 Claude inline. The composer hint shows the list as you type.
732 </p>
733 </div>
734 <div class="help-section-body">
735 <div class="help-item">
736 <strong>Available commands.</strong>
737 <pre class="help-code">
738{`/review Re-run the AI reviewer on the latest diff
739/summarise Drop a fresh PR summary into the description
740/test-plan Generate a test plan from the diff
741/explain-this Plain-English summary of one file or hunk
742/risk Score the diff for breaking-change risk
743/migrate Propose a Drizzle migration for schema changes
744/release-notes Draft release notes covering this PR only
745/help List every command`}
746 </pre>
747 </div>
748 <div class="help-item">
749 Commands are deterministic when <code>ANTHROPIC_API_KEY</code>{" "}
750 is missing (fallbacks summarise file lists; no model output).
751 They also work from the CLI: <code>gluecron pr cmd /review</code>.
752 </div>
753 </div>
754 </section>
755
756 {/* ─── AI release notes ─── */}
757 <section id="release-notes" class="help-section">
758 <div class="help-section-head">
759 <div class="help-section-eyebrow">Releases</div>
760 <h2 class="help-section-title">AI release notes</h2>
761 <p class="help-section-desc">
762 The release form has a <em>Generate notes</em> button that
763 drafts a polished changelog from every PR merged since the
764 previous tag.
765 </p>
766 </div>
767 <div class="help-section-body">
768 <div class="help-item">
769 From <code>/:owner/:repo/releases/new</code>, type a new tag
770 name and click <em>Generate notes</em>. Claude calls{" "}
771 <code>POST /api/v2/repos/:owner/:repo/releases/notes</code>{" "}
772 with the from/to tags and returns markdown grouped by{" "}
773 <em>Features</em>, <em>Fixes</em>, <em>Internal</em>. Edit
774 before publishing or accept as-is.
775 </div>
776 </div>
777 </section>
778
779 {/* ─── Agent multiplayer ─── */}
780 <section id="agents" class="help-section">
781 <div class="help-section-head">
782 <div class="help-section-eyebrow">Multi-agent</div>
783 <h2 class="help-section-title">Agent multiplayer</h2>
784 <p class="help-section-desc">
785 Mint scoped tokens for AI agents, give each its own branch
786 namespace + daily budget, and let them coordinate through
787 the lease API so two Claudes never touch the same file.
788 </p>
789 </div>
790 <div class="help-section-body">
791 <div class="help-item">
792 <strong>Manage agents</strong> at{" "}
793 <a href="/settings/agents">/settings/agents</a>. Each agent
794 gets a name, a branch namespace
795 (<code>refs/heads/<namespace>*</code>), and a daily
796 spend cap. Tokens are shown once on creation — copy them
797 immediately. The same page lists the most recent leases
798 (work-in-progress markers) per agent.
799 </div>
800 <div class="help-item">
801 <strong>Lease API.</strong>{" "}
802 <code>POST /api/v2/agents/leases</code> acquires a lease on
803 an issue, PR, or file path. Agents see currently-held
804 leases via <code>GET /api/v2/agents/leases</code> and back
805 off when a conflict is hit. Full protocol is in{" "}
806 <code>docs/multiplayer.md</code> on the gluecron source repo.
807 </div>
808 </div>
809 </section>
810
811 {/* ─── Semantic search API ─── */}
812 <section id="semantic" class="help-section">
813 <div class="help-section-head">
814 <div class="help-section-eyebrow">API</div>
815 <h2 class="help-section-title">Semantic search API</h2>
816 <p class="help-section-desc">
817 The same index that powers <em>Chat with a repo</em> is
818 available as a JSON endpoint.
819 </p>
820 </div>
821 <div class="help-section-body">
822 <div class="help-item">
823 <pre class="help-code">
824{`curl -H "Authorization: Bearer glc_…" \\
825 "https://<your-host>/api/v2/repos/<owner>/<repo>/semantic-search?q=password+hashing&limit=10"`}
826 </pre>
827 Returns a JSON array of <code>{`{ path, startLine, endLine, snippet, score }`}</code>{" "}
828 objects sorted by relevance. When <code>VOYAGE_API_KEY</code>{" "}
829 is unset on the server the endpoint falls back to lexical
830 ranking — useful for offline / air-gapped installs.
831 </div>
832 </div>
833 </section>
834
549835 {/* ─── Setup AI commits ─── */}
550836 <section id="ai-commits" class="help-section">
551837 <div class="help-section-head">
@@ -641,6 +927,27 @@ gluecron hook uninstall commit-msg`}
641927 </div>
642928 </section>
643929
930 {/* ─── Build-agent integration spec ─── */}
931 <section id="build-agents" class="help-section">
932 <div class="help-section-head">
933 <div class="help-section-eyebrow">For AI vendors</div>
934 <h2 class="help-section-title">Build-agent integration</h2>
935 <p class="help-section-desc">
936 Public spec for AI build-agent vendors (Holden Mercer,
937 Cursor, Claude Code, etc.) who want to read issues, open
938 PRs, and post review comments via the Gluecron API.
939 </p>
940 </div>
941 <div class="help-section-body">
942 <div class="help-item">
943 The full integration contract lives at{" "}
944 <a href="/docs/build-agent-integration">/docs/build-agent-integration</a>{" "}
945 — endpoint list, auth scopes, webhook payloads, and the
946 <code>ai:build</code> label convention.
947 </div>
948 </div>
949 </section>
950
644951 <p class="help-footnote">
645952 Something missing? Open an issue on gluecron's source repo.
646953 </p>
Modifiedsrc/routes/search.tsx+6−0View fileUnifiedSplit
@@ -736,6 +736,12 @@ search.get("/search", async (c) => {
736736 <strong>Tip:</strong> Press <code>/</code> from any page to focus
737737 the global search.
738738 </span>
739 <span class="search-page-empty-tip-row">
740 <strong>Semantic search:</strong> the global tab is keyword-only.
741 For embedding-backed code search, open a repo and click{" "}
742 <em>Semantic search</em> in the AI surfaces row, or hit{" "}
743 <code>/:owner/:repo/semantic-search?q=…</code> directly.
744 </span>
739745 <span class="search-page-empty-tip-row">
740746 Looking for something to browse?{" "}
741747 <a href="/explore">Head to Explore</a>.
Modifiedsrc/routes/settings-agents.tsx+37−0View fileUnifiedSplit
@@ -276,6 +276,43 @@ settingsAgents.get("/settings/agents", async (c) => {
276276
277277 const body = `
278278 <style>${styles}</style>
279 <style>
280 .sa-subnav {
281 display: flex;
282 gap: 6px;
283 max-width: 920px;
284 margin: 0 auto;
285 padding: var(--space-4) var(--space-4) 0;
286 }
287 .sa-subnav a {
288 display: inline-flex;
289 align-items: center;
290 padding: 6px 12px;
291 font-size: 13px;
292 color: var(--text-muted);
293 background: var(--bg-elevated);
294 border: 1px solid var(--border);
295 border-radius: 7px;
296 text-decoration: none;
297 transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
298 }
299 .sa-subnav a:hover {
300 color: var(--text-strong);
301 border-color: rgba(140,109,255,0.45);
302 text-decoration: none;
303 }
304 .sa-subnav a.is-active {
305 background: linear-gradient(135deg, rgba(140,109,255,0.14), rgba(54,197,214,0.10));
306 color: var(--text-strong);
307 border-color: rgba(140,109,255,0.45);
308 font-weight: 600;
309 }
310 </style>
311 <nav class="sa-subnav" aria-label="Settings sections">
312 <a href="/settings">Profile</a>
313 <a href="/settings/keys">SSH keys</a>
314 <a href="/settings/agents" class="is-active" aria-current="page">Agents</a>
315 </nav>
279316 <div class="sa-wrap">
280317 <div class="sa-hero">
281318 <h1>Agent sessions</h1>
Modifiedsrc/routes/settings.tsx+5−2View fileUnifiedSplit
@@ -598,10 +598,13 @@ function SettingsHero(props: {
598598}
599599
600600/** Pill-row sub-navigation, hand-built so we don't depend on shared components. */
601function SettingsSubnav(props: { active: "profile" | "keys" }) {
602 const items: Array<{ key: "profile" | "keys"; href: string; label: string }> = [
601export type SettingsSubnavKey = "profile" | "keys" | "agents";
602
603export function SettingsSubnav(props: { active: SettingsSubnavKey }) {
604 const items: Array<{ key: SettingsSubnavKey; href: string; label: string }> = [
603605 { key: "profile", href: "/settings", label: "Profile" },
604606 { key: "keys", href: "/settings/keys", label: "SSH keys" },
607 { key: "agents", href: "/settings/agents", label: "Agents" },
605608 ];
606609 return (
607610 <nav class="settings-subnav" aria-label="Settings sections">
Modifiedsrc/routes/vs-github.tsx+40−0View fileUnifiedSplit
@@ -41,6 +41,46 @@ interface Category {
4141}
4242
4343const CATEGORIES: Category[] = [
44 {
45 title: "Closed-loop AI (no GitHub equivalent)",
46 rows: [
47 {
48 feature: "Spec-to-PR (spec file → draft PR)",
49 gh: { verdict: "no", note: "Not available" },
50 gc: { verdict: "yes", note: ".gluecron/specs → /specs" },
51 },
52 {
53 feature: "Voice-to-PR (talk → diff)",
54 gh: { verdict: "no", note: "Not available" },
55 gc: { verdict: "yes", note: "/voice — MediaRecorder + Claude" },
56 },
57 {
58 feature: "Multi-repo refactor agent",
59 gh: { verdict: "no", note: "Not available" },
60 gc: { verdict: "yes", note: "/refactors — one brief, N PRs" },
61 },
62 {
63 feature: "Auto-healing CI",
64 gh: { verdict: "no", note: "Not available" },
65 gc: { verdict: "yes", note: "AI CI healer on every failed run" },
66 },
67 {
68 feature: "Per-PR live co-editing",
69 gh: { verdict: "no", note: "Not available" },
70 gc: { verdict: "yes", note: "Presence pill + cursor ribbons" },
71 },
72 {
73 feature: "Agent multiplayer (scoped tokens + leases)",
74 gh: { verdict: "no", note: "Not available" },
75 gc: { verdict: "yes", note: "/settings/agents + lease API" },
76 },
77 {
78 feature: "AI commit messages",
79 gh: { verdict: "partial", note: "Copilot CLI (separate product)" },
80 gc: { verdict: "yes", note: "Native — gluecron commit / git hook" },
81 },
82 ],
83 },
4484 {
4585 title: "AI-native workflow",
4686 rows: [
Modifiedsrc/routes/web.tsx+92−0View fileUnifiedSplit
@@ -2891,6 +2891,98 @@ web.get("/:owner/:repo", async (c) => {
28912891 </div>
28922892 )}
28932893 <RepoNav owner={owner} repo={repo} active="code" />
2894 {/* ─── Per-repo AI surfaces — RepoNav is locked, so the discovery
2895 row sits just below the nav as a slim CTA strip. Scoped under
2896 `.repo-ai-cta-` so the styles can't bleed onto other pages. ─── */}
2897 <style
2898 dangerouslySetInnerHTML={{
2899 __html: `
2900 .repo-ai-cta-row {
2901 display: flex;
2902 flex-wrap: wrap;
2903 gap: 8px;
2904 margin: 12px 0 18px;
2905 padding: 10px 14px;
2906 background: linear-gradient(135deg, rgba(140,109,255,0.06), rgba(54,197,214,0.04));
2907 border: 1px solid var(--border);
2908 border-radius: 10px;
2909 position: relative;
2910 overflow: hidden;
2911 }
2912 .repo-ai-cta-row::before {
2913 content: '';
2914 position: absolute;
2915 top: 0; left: 0; right: 0;
2916 height: 1px;
2917 background: linear-gradient(90deg, transparent 0%, rgba(140,109,255,0.45) 30%, rgba(54,197,214,0.45) 70%, transparent 100%);
2918 opacity: 0.7;
2919 pointer-events: none;
2920 }
2921 .repo-ai-cta-label {
2922 font-size: 11px;
2923 font-weight: 600;
2924 letter-spacing: 0.06em;
2925 text-transform: uppercase;
2926 color: var(--accent);
2927 align-self: center;
2928 padding-right: 8px;
2929 border-right: 1px solid var(--border);
2930 margin-right: 4px;
2931 }
2932 .repo-ai-cta {
2933 display: inline-flex;
2934 align-items: center;
2935 gap: 6px;
2936 padding: 5px 10px;
2937 font-size: 12.5px;
2938 font-weight: 500;
2939 color: var(--text);
2940 background: var(--bg);
2941 border: 1px solid var(--border);
2942 border-radius: 7px;
2943 text-decoration: none;
2944 transition: border-color 140ms ease, background 140ms ease, color 140ms ease;
2945 }
2946 .repo-ai-cta:hover {
2947 border-color: rgba(140,109,255,0.45);
2948 color: var(--text-strong);
2949 background: rgba(140,109,255,0.06);
2950 text-decoration: none;
2951 }
2952 .repo-ai-cta-icon {
2953 opacity: 0.75;
2954 font-size: 12px;
2955 }
2956 @media (max-width: 640px) {
2957 .repo-ai-cta-row { flex-direction: column; align-items: stretch; }
2958 .repo-ai-cta-label { border-right: 0; border-bottom: 1px solid var(--border); padding-bottom: 6px; margin-right: 0; }
2959 }
2960 `,
2961 }}
2962 />
2963 <nav class="repo-ai-cta-row" aria-label="AI surfaces for this repository">
2964 <span class="repo-ai-cta-label">AI surfaces</span>
2965 <a class="repo-ai-cta" href={`/${owner}/${repo}/chat`} title="Rubber-duck chat grounded in this repo">
2966 <span class="repo-ai-cta-icon" aria-hidden="true">{"\u{1F4AC}"}</span>
2967 Chat
2968 </a>
2969 <a class="repo-ai-cta" href={`/${owner}/${repo}/previews`} title="Ephemeral preview URLs per branch">
2970 <span class="repo-ai-cta-icon" aria-hidden="true">{"\u{1F30D}"}</span>
2971 Previews
2972 </a>
2973 <a class="repo-ai-cta" href={`/${owner}/${repo}/migrations/propose`} title="AI proposes the Drizzle migration for a schema change">
2974 <span class="repo-ai-cta-icon" aria-hidden="true">{"⛁"}</span>
2975 Migrations
2976 </a>
2977 <a class="repo-ai-cta" href={`/${owner}/${repo}/semantic-search`} title="Embedding-backed code search">
2978 <span class="repo-ai-cta-icon" aria-hidden="true">{"✨"}</span>
2979 Semantic search
2980 </a>
2981 <a class="repo-ai-cta" href={`/${owner}/${repo}/releases/new`} title="Draft release notes with AI">
2982 <span class="repo-ai-cta-icon" aria-hidden="true">{"\u{1F3F7}"}</span>
2983 AI release notes
2984 </a>
2985 </nav>
28942986 <div class="repo-home-grid">
28952987 <div class="repo-home-main">
28962988 <BranchSwitcher
Modifiedsrc/views/layout.tsx+147−25View fileUnifiedSplit
@@ -222,28 +222,6 @@ export const Layout: FC<
222222 <a href="/activity" class="nav-link">
223223 Activity
224224 </a>
225 <a href="/standups" class="nav-link">
226 Standups
227 </a>
228 <a href="/voice" class="nav-link" style="display:inline-flex;align-items:center;gap:5px">
229 <svg
230 width="13"
231 height="13"
232 viewBox="0 0 24 24"
233 fill="none"
234 stroke="currentColor"
235 stroke-width="2.2"
236 stroke-linecap="round"
237 stroke-linejoin="round"
238 aria-hidden="true"
239 >
240 <rect x="9" y="2" width="6" height="13" rx="3" />
241 <path d="M19 11v1a7 7 0 0 1-14 0v-1" />
242 <line x1="12" y1="19" x2="12" y2="23" />
243 <line x1="8" y1="23" x2="16" y2="23" />
244 </svg>
245 Voice
246 </a>
247225 <a href="/inbox" class="nav-link" style="position:relative">
248226 Inbox
249227 {notificationCount && notificationCount > 0 ? (
@@ -255,12 +233,61 @@ export const Layout: FC<
255233 </span>
256234 ) : null}
257235 </a>
236 {/* AI dropdown — consolidates Standups, Voice, Refactors,
237 Specs, Ask AI to keep the top-level nav lean. Hover-open
238 with click+keyboard fallback via navAiDropdownScript. */}
239 <div class="nav-ai-dropdown" data-nav-ai>
240 <button
241 type="button"
242 class="nav-link nav-ai-trigger"
243 aria-haspopup="true"
244 aria-expanded="false"
245 data-nav-ai-trigger
246 >
247 <span style="display:inline-flex;align-items:center;gap:5px">
248 <svg
249 width="13"
250 height="13"
251 viewBox="0 0 24 24"
252 fill="none"
253 stroke="currentColor"
254 stroke-width="2.2"
255 stroke-linecap="round"
256 stroke-linejoin="round"
257 aria-hidden="true"
258 >
259 <path d="M12 2l2.39 5.95L20 9l-4.5 3.9L17 19l-5-3.2L7 19l1.5-6.1L4 9l5.61-1.05L12 2z" />
260 </svg>
261 AI
262 <span aria-hidden="true" style="font-size:9px;opacity:0.7">▾</span>
263 </span>
264 </button>
265 <div class="nav-ai-menu" role="menu" data-nav-ai-menu>
266 <a href="/standups" role="menuitem" class="nav-ai-item">
267 <span class="nav-ai-item-label">Standups</span>
268 <span class="nav-ai-item-sub">Daily AI brief</span>
269 </a>
270 <a href="/voice" role="menuitem" class="nav-ai-item">
271 <span class="nav-ai-item-label">Voice</span>
272 <span class="nav-ai-item-sub">Talk to ship a PR</span>
273 </a>
274 <a href="/refactors" role="menuitem" class="nav-ai-item">
275 <span class="nav-ai-item-label">Refactors</span>
276 <span class="nav-ai-item-sub">Multi-repo agent</span>
277 </a>
278 <a href="/specs" role="menuitem" class="nav-ai-item">
279 <span class="nav-ai-item-label">Specs</span>
280 <span class="nav-ai-item-sub">Spec-to-PR loop</span>
281 </a>
282 <a href="/ask" role="menuitem" class="nav-ai-item">
283 <span class="nav-ai-item-label">Ask AI</span>
284 <span class="nav-ai-item-sub">Cross-repo chat</span>
285 </a>
286 </div>
287 </div>
258288 <a href="/import" class="nav-link">
259289 Import
260290 </a>
261 <a href="/refactors" class="nav-link">
262 Refactors
263 </a>
264291 <a href="/new" class="btn btn-sm btn-primary">
265292 + New
266293 </a>
@@ -399,6 +426,7 @@ export const Layout: FC<
399426 <script dangerouslySetInnerHTML={{ __html: pwaKillSwitchScript }} />
400427
401428
429
402430 </body>
403431 </html>
404432 );
@@ -875,6 +903,35 @@ const navScript = `
875903 })();
876904`;
877905
906// AI dropdown — keyboard- and click-accessible menu in the top nav.
907// CSS handles the hover-open behaviour for pointer users; this script
908// adds click-to-toggle for touch, Escape-to-close, and outside-click-
909// to-close. Lives in its own IIFE so it never interferes with navScript.
910const navAiDropdownScript = `
911 (function(){
912 var open = false;
913 var root = document.querySelector('[data-nav-ai]');
914 if (!root) return;
915 var trigger = root.querySelector('[data-nav-ai-trigger]');
916 var menu = root.querySelector('[data-nav-ai-menu]');
917 if (!trigger || !menu) return;
918 function setOpen(next){
919 open = !!next;
920 root.classList.toggle('is-open', open);
921 trigger.setAttribute('aria-expanded', open ? 'true' : 'false');
922 }
923 trigger.addEventListener('click', function(e){ e.preventDefault(); setOpen(!open); });
924 document.addEventListener('click', function(e){
925 if (!open) return;
926 if (root.contains(e.target)) return;
927 setOpen(false);
928 });
929 document.addEventListener('keydown', function(e){
930 if (open && e.key === 'Escape') { e.preventDefault(); setOpen(false); trigger.focus(); }
931 });
932 })();
933`;
934
878935const css = `
879936 /* ================================================================
880937 * Gluecron design system — 2026.05 "Editorial-Technical"
@@ -1449,6 +1506,71 @@ const css = `
14491506 }
14501507 .deploy-pill-empty .deploy-pill-dot { background: #9ca3af; }
14511508
1509 /* ── AI dropdown (nav consolidation) ── */
1510 .nav-ai-dropdown {
1511 position: relative;
1512 display: inline-flex;
1513 align-items: center;
1514 }
1515 .nav-ai-trigger {
1516 background: transparent;
1517 border: 0;
1518 font: inherit;
1519 cursor: pointer;
1520 color: var(--text-muted);
1521 font-size: var(--t-sm);
1522 font-weight: 500;
1523 padding: 7px 11px;
1524 border-radius: var(--r-sm);
1525 line-height: 1.2;
1526 }
1527 .nav-ai-trigger:hover {
1528 color: var(--text-strong);
1529 background: var(--bg-hover);
1530 }
1531 .nav-ai-menu {
1532 position: absolute;
1533 top: calc(100% + 6px);
1534 right: 0;
1535 min-width: 220px;
1536 background: var(--bg-secondary);
1537 border: 1px solid var(--border-strong);
1538 border-radius: 10px;
1539 box-shadow: 0 12px 32px rgba(0,0,0,0.40), 0 0 0 1px rgba(140,109,255,0.10);
1540 padding: 6px;
1541 display: none;
1542 z-index: var(--z-overlay, 100);
1543 }
1544 .nav-ai-dropdown:hover .nav-ai-menu,
1545 .nav-ai-dropdown.is-open .nav-ai-menu,
1546 .nav-ai-dropdown:focus-within .nav-ai-menu {
1547 display: block;
1548 }
1549 .nav-ai-item {
1550 display: flex;
1551 flex-direction: column;
1552 gap: 1px;
1553 padding: 8px 10px;
1554 border-radius: 6px;
1555 color: var(--text);
1556 text-decoration: none;
1557 transition: background var(--t-fast) var(--ease);
1558 }
1559 .nav-ai-item:hover {
1560 background: var(--bg-hover);
1561 text-decoration: none;
1562 color: var(--text-strong);
1563 }
1564 .nav-ai-item-label {
1565 font-size: var(--t-sm);
1566 font-weight: 600;
1567 color: var(--text-strong);
1568 }
1569 .nav-ai-item-sub {
1570 font-size: 11.5px;
1571 color: var(--text-muted);
1572 }
1573
14521574 .nav-link {
14531575 position: relative;
14541576 color: var(--text-muted);
14551577