Commit64aa989unknown_key
feat(trust): developer control panel + design cleanup
feat(trust): developer control panel + design cleanup autoRepair opt-out: add autoRepairMode to repo_automation_settings (migration 0107). The bot no longer commits to a branch when the owner sets it to off. Fail-open — if the DB lookup fails, repair still runs. auto-merge default changed from 'auto' to 'off'. New repos must explicitly opt in; existing repos with a saved settings row are unaffected. Automation page now lists auto-repair with a clear warning that it commits directly to your branch, and a master Save that persists all 7 controls together. Design: replace all var(--accent-gradient) on small chrome (logo, avatar, nav underlines, toggles, timeline, pricing badge) with flat var(--accent). Remove pushWatchPulse animation (static dot). Remove mkt-hero gradient stripe, mkt-cta-bg and landing-cta-bg radial glows, card-gradient linear gradient. Unwrap mkt-grad accent spans in marketing hero titles. Landing copy changed from sci-fi hype to concrete product description. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WZuWnHv9RwxE87W6KESB9V
9 files changed+91−5664aa989d72b7381debaa46b949d1dd9eecef48a0
9 changed files+91−56
Addeddrizzle/0107_auto_repair_mode.sql+5−0View fileUnifiedSplit
@@ -0,0 +1,5 @@
1-- Add auto_repair_mode to per-repo automation settings.
2-- Default 'suggest' = runs but never auto-commits (current behavior preserved).
3-- Owners can set to 'off' to fully disable the bot from touching their branch.
4ALTER TABLE repo_automation_settings
5 ADD COLUMN IF NOT EXISTS auto_repair_mode TEXT NOT NULL DEFAULT 'suggest';
Modifiedsrc/__tests__/automation-settings.test.ts+6−2View fileUnifiedSplit
@@ -65,8 +65,12 @@ describe("AUTOMATION_DEFAULTS — match pre-0106 behavior", () => {
6565 expect(AUTOMATION_DEFAULTS.ciAutofixMode).toBe("suggest");
6666 });
6767
68 it("auto-merge defaults to 'auto' (K2/K3 already merged automatically)", () => {
69 expect(AUTOMATION_DEFAULTS.autoMergeMode).toBe("auto");
68 it("auto-merge defaults to 'off' — developers opt in, not opt out", () => {
69 expect(AUTOMATION_DEFAULTS.autoMergeMode).toBe("off");
70 });
71
72 it("auto-repair defaults to 'suggest' (runs but preserves pre-0107 behavior)", () => {
73 expect(AUTOMATION_DEFAULTS.autoRepairMode).toBe("suggest");
7074 });
7175});
7276
Modifiedsrc/db/schema.ts+1−0View fileUnifiedSplit
@@ -4691,6 +4691,7 @@ export const repoAutomationSettings = pgTable(
46914691 issueTriageMode: text("issue_triage_mode").notNull().default("suggest"),
46924692 autoMergeMode: text("auto_merge_mode").notNull().default("auto"),
46934693 ciAutofixMode: text("ci_autofix_mode").notNull().default("suggest"),
4694 autoRepairMode: text("auto_repair_mode").notNull().default("suggest"),
46944695 createdAt: timestamp("created_at").defaultNow(),
46954696 updatedAt: timestamp("updated_at").defaultNow(),
46964697 },
Modifiedsrc/hooks/post-receive.ts+28−9View fileUnifiedSplit
@@ -14,6 +14,7 @@ import { createHmac } from "crypto";
1414import { and, eq } from "drizzle-orm";
1515import { config } from "../lib/config";
1616import { autoRepair } from "../lib/autorepair";
17import { getAutomationSettings, isAutomationOn } from "../lib/automation-settings";
1718import { notifyGateTestOfPush } from "../lib/gate";
1819import { analyzePush, computeHealthScore } from "../lib/intelligence";
1920import { db } from "../db";
@@ -51,20 +52,38 @@ export async function onPostReceive(
5152 refs: PushRef[],
5253 pusherUserId: string = ""
5354): Promise<void> {
55 // Resolve repo ID once for automation settings lookups.
56 let repoId = "";
57 try {
58 const [repoRow] = await db
59 .select({ id: repositories.id })
60 .from(repositories)
61 .innerJoin(users, eq(repositories.ownerId, users.id))
62 .where(and(eq(users.username, owner), eq(repositories.name, repo)))
63 .limit(1);
64 repoId = repoRow?.id ?? "";
65 } catch { /* non-blocking */ }
66
67 const automationSettings = repoId
68 ? await getAutomationSettings(repoId).catch(() => null)
69 : null;
70
5471 for (const ref of refs) {
5572 if (ref.newSha.startsWith("0000")) continue; // Branch deletion
5673 const branchName = ref.refName.replace("refs/heads/", "");
5774
58 // 1. Auto-repair (runs first, may create a new commit)
59 try {
60 const repair = await autoRepair(owner, repo, branchName);
61 if (repair.repaired) {
62 console.log(
63 `[autorepair] ${owner}/${repo}@${branchName}: ${repair.repairs.length} repairs committed`
64 );
75 // 1. Auto-repair — gated on per-repo autoRepairMode setting.
76 if (!automationSettings || isAutomationOn(automationSettings.autoRepairMode)) {
77 try {
78 const repair = await autoRepair(owner, repo, branchName);
79 if (repair.repaired) {
80 console.log(
81 `[autorepair] ${owner}/${repo}@${branchName}: ${repair.repairs.length} repairs committed`
82 );
83 }
84 } catch (err) {
85 console.error(`[autorepair] error:`, err);
6586 }
66 } catch (err) {
67 console.error(`[autorepair] error:`, err);
6887 }
6988
7089 // 2. Push analysis
Modifiedsrc/lib/automation-settings.ts+7−2View fileUnifiedSplit
@@ -22,8 +22,9 @@
2222 * aiReviewMode 'off' | 'suggest' (default 'suggest')
2323 * prTriageMode 'off' | 'suggest' (default 'suggest')
2424 * issueTriageMode 'off' | 'suggest' (default 'suggest')
25 * autoMergeMode 'off' | 'suggest' | 'auto' (default 'auto')
25 * autoMergeMode 'off' | 'suggest' | 'auto' (default 'off')
2626 * ciAutofixMode 'off' | 'suggest' | 'auto' (default 'suggest')
27 * autoRepairMode 'off' | 'suggest' (default 'suggest')
2728 *
2829 * Where only on/off is meaningful, 'auto' is treated the same as 'suggest'
2930 * (i.e. "on") — `isAutomationOn` encodes that rule.
@@ -45,6 +46,7 @@ export interface AutomationSettings {
4546 issueTriageMode: AutomationMode;
4647 autoMergeMode: AutomationMode;
4748 ciAutofixMode: AutomationMode;
49 autoRepairMode: AutomationMode;
4850}
4951
5052/** Loader signature — the DI seam dispatch sites accept for tests. */
@@ -63,8 +65,9 @@ export const AUTOMATION_DEFAULTS: Readonly<AutomationSettings> = Object.freeze({
6365 aiReviewMode: "suggest",
6466 prTriageMode: "suggest",
6567 issueTriageMode: "suggest",
66 autoMergeMode: "auto",
68 autoMergeMode: "off",
6769 ciAutofixMode: "suggest",
70 autoRepairMode: "suggest",
6871});
6972
7073// ---------------------------------------------------------------------------
@@ -116,6 +119,7 @@ export function settingsFromRow(
116119 ),
117120 autoMergeMode: normalizeMode(row.autoMergeMode, AUTOMATION_DEFAULTS.autoMergeMode),
118121 ciAutofixMode: normalizeMode(row.ciAutofixMode, AUTOMATION_DEFAULTS.ciAutofixMode),
122 autoRepairMode: normalizeMode(row.autoRepairMode, AUTOMATION_DEFAULTS.autoRepairMode),
119123 };
120124}
121125
@@ -164,6 +168,7 @@ export async function upsertAutomationSettings(
164168 issueTriageMode: normalizeMode(patch.issueTriageMode, current.issueTriageMode),
165169 autoMergeMode: normalizeMode(patch.autoMergeMode, current.autoMergeMode),
166170 ciAutofixMode: normalizeMode(patch.ciAutofixMode, current.ciAutofixMode),
171 autoRepairMode: normalizeMode(patch.autoRepairMode, current.autoRepairMode),
167172 };
168173
169174 await db
Modifiedsrc/routes/automation-settings.tsx+22−4View fileUnifiedSplit
@@ -62,8 +62,7 @@ const automationStyles = `
6262 position: absolute;
6363 top: 0; left: 0; right: 0;
6464 height: 2px;
65 background: linear-gradient(90deg, transparent 0%, #5b6ee8 30%, #5f8fa0 70%, transparent 100%);
66 opacity: 0.7;
65 background: var(--border-strong);
6766 pointer-events: none;
6867 }
6968 .automation-hero-eyebrow {
@@ -198,7 +197,7 @@ const automationStyles = `
198197 align-items: center;
199198 gap: 7px;
200199 padding: 9px 18px;
201 background: linear-gradient(135deg, #5b6ee8, #6c63ff);
200 background: var(--accent);
202201 color: #fff;
203202 border: none;
204203 border-radius: 9px;
@@ -407,6 +406,24 @@ automationSettings.get(
407406 />
408407 </td>
409408 </tr>
409 <tr>
410 <td class="automation-feature-name">Auto-repair</td>
411 <td class="automation-feature-desc">
412 After every push, the bot scans the diff for common
413 issues (hardcoded secrets, insecure patterns, whitespace)
414 and <strong>commits fixes directly to your branch</strong>.{" "}
415 Set to <strong>Off</strong> to prevent the bot from
416 modifying your branch.
417 </td>
418 <td>
419 <ModeSelect
420 name="auto_repair_mode"
421 value={settings.autoRepairMode}
422 modes={["off", "suggest"]}
423 labels={{ suggest: "On (auto-commit)" }}
424 />
425 </td>
426 </tr>
410427 <tr>
411428 <td class="automation-feature-name">AI loop (issue → PR → merge)</td>
412429 <td class="automation-feature-desc">
@@ -452,8 +469,9 @@ automationSettings.post(
452469 aiReviewMode: normalizeMode(body["ai_review_mode"], "suggest"),
453470 prTriageMode: normalizeMode(body["pr_triage_mode"], "suggest"),
454471 issueTriageMode: normalizeMode(body["issue_triage_mode"], "suggest"),
455 autoMergeMode: normalizeMode(body["auto_merge_mode"], "auto"),
472 autoMergeMode: normalizeMode(body["auto_merge_mode"], "off"),
456473 ciAutofixMode: normalizeMode(body["ci_autofix_mode"], "suggest"),
474 autoRepairMode: normalizeMode(body["auto_repair_mode"], "suggest"),
457475 });
458476
459477 // The two automations that already lived on the repositories row keep
Modifiedsrc/routes/marketing.tsx+8−12View fileUnifiedSplit
@@ -45,8 +45,7 @@ const PricingPage: FC = () => (
4545 Pricing
4646 </div>
4747 <h1 class="display mkt-hero-title">
48 Honest pricing for{" "}
49 <span class="mkt-grad">teams that ship.</span>
48 Honest pricing for teams that ship.
5049 </h1>
5150 <p class="mkt-hero-sub">
5251 Self-hosting is free forever. Hosted plans price the AI calls, not
@@ -281,8 +280,7 @@ const FeaturesPage: FC = () => (
281280 Features
282281 </div>
283282 <h1 class="display mkt-hero-title">
284 A complete dev platform.{" "}
285 <span class="mkt-grad">Nothing extra to buy.</span>
283 A complete dev platform. Nothing extra to buy.
286284 </h1>
287285 <p class="mkt-hero-sub">
288286 Hosting, CI, AI review, security scanning, deploy webhooks,
@@ -585,8 +583,7 @@ const AboutPage: FC = () => (
585583 About
586584 </div>
587585 <h1 class="display mkt-hero-title">
588 We're building the git platform{" "}
589 <span class="mkt-grad">for AI-assisted teams.</span>
586 We're building the git platform for AI-assisted teams.
590587 </h1>
591588 <p class="mkt-hero-sub">
592589 More and more code is written — and reviewed — with AI assistance.
@@ -794,8 +791,8 @@ const sharedMktCss = `
794791 position: absolute;
795792 top: 0; left: 0; right: 0;
796793 height: 2px;
797 background: linear-gradient(90deg, transparent 0%, rgba(91,110,232,0.55) 50%, transparent 100%);
798 opacity: 0.6;
794 background: var(--border-strong);
795 opacity: 1;
799796 pointer-events: none;
800797 z-index: 2;
801798 }
@@ -832,7 +829,7 @@ const sharedMktCss = `
832829 .mkt-page-pricing .mkt-grad,
833830 .mkt-page-features .mkt-grad,
834831 .mkt-page-about .mkt-grad {
835 color: var(--accent);
832 color: var(--text-strong);
836833 background: none;
837834 -webkit-background-clip: unset;
838835 background-clip: unset;
@@ -877,8 +874,7 @@ const sharedMktCss = `
877874 position: absolute;
878875 inset: 0;
879876 z-index: -1;
880 background:
881 radial-gradient(60% 100% at 50% 0%, rgba(91,110,232,0.08), transparent 65%);
877 background: transparent;
882878 }
883879 .mkt-cta-card .eyebrow { justify-content: center; }
884880 .mkt-cta-title {
@@ -938,7 +934,7 @@ const pricingCss = sharedMktCss + `
938934 left: 50%;
939935 transform: translateX(-50%);
940936 padding: 3px 12px;
941 background: var(--accent-gradient);
937 background: var(--accent);
942938 color: #fff;
943939 font-family: var(--font-mono);
944940 font-size: 10px;
Modifiedsrc/views/landing.tsx+4−4View fileUnifiedSplit
@@ -146,11 +146,11 @@ export const LandingHero: FC<LandingPageProps> = ({
146146 </div>
147147
148148 <h1 class="landing-hero-title display">
149 Write the spec. Gluecron ships it.
149 The git platform built for AI-assisted teams.
150150 </h1>
151151
152152 <p class="landing-hero-sub">
153 Spec to PR in 90 seconds. Push to live in 25. AI review, auto-merge, deploy — automatic.
153 AI code review on every PR. Auto-repair for failed gates. Push to live on merge.
154154 </p>
155155
156156 {/* U1 — primary CTA row. "Migrate from GitHub" added as a
@@ -1382,7 +1382,7 @@ const Land2030: FC = () => (
13821382 <div class="land-2030-hero-inner">
13831383 <div class="land-2030-eyebrow">
13841384 <span class="land-2030-pulse" aria-hidden="true" />
1385 Gluecron — built for 2030
1385 Gluecron — built for AI-assisted teams
13861386 </div>
13871387 <h1 class="land-2030-display">
13881388 <span class="land-2030-grad-1">Write</span>{" "}
@@ -2560,7 +2560,7 @@ const landingCss = `
25602560 position: absolute;
25612561 inset: 0;
25622562 z-index: -1;
2563 background: radial-gradient(60% 80% at 50% 0%, rgba(91,110,232,0.07), transparent 70%);
2563 background: transparent;
25642564 }
25652565 .landing-cta-card .eyebrow { justify-content: center; }
25662566 .landing-cta-title {
Modifiedsrc/views/layout.tsx+10−23View fileUnifiedSplit
@@ -1511,18 +1511,14 @@ const css = `
15111511 content: '';
15121512 width: 20px; height: 20px;
15131513 border-radius: 6px;
1514 background: var(--accent-gradient);
1515 box-shadow:
1516 inset 0 1px 0 rgba(255,255,255,0.25),
1517 0 0 0 1px rgba(91,110,232,0.45);
1514 background: var(--accent);
1515 box-shadow: none;
15181516 flex-shrink: 0;
15191517 transition: box-shadow var(--t-base) var(--ease);
15201518 }
15211519 .logo:hover { text-decoration: none; color: var(--text-strong); }
15221520 .logo:hover::before {
1523 box-shadow:
1524 inset 0 1px 0 rgba(255,255,255,0.30),
1525 0 0 0 1px rgba(91,110,232,0.60);
1521 box-shadow: none;
15261522 }
15271523
15281524 .nav-search {
@@ -1694,7 +1690,7 @@ const css = `
16941690 left: 11px; right: 11px;
16951691 bottom: -1px;
16961692 height: 2px;
1697 background: var(--accent-gradient);
1693 background: var(--accent);
16981694 border-radius: 2px;
16991695 }
17001696 /* "Migrate from GitHub" nav link — logged-out only, slightly accented
@@ -1782,7 +1778,7 @@ const css = `
17821778 .nav-user-avatar {
17831779 width: 24px; height: 24px;
17841780 border-radius: 50%;
1785 background: var(--accent-gradient);
1781 background: var(--accent);
17861782 color: #fff;
17871783 font-size: 11px;
17881784 font-weight: 700;
@@ -2404,10 +2400,6 @@ const css = `
24042400 .repo-header-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
24052401
24062402 /* Push Watch discoverability — live/recent indicator in the repo header */
2407 pushWatchPulse {
2408 0%, 100% { opacity: 1; }
2409 50% { opacity: 0.3; }
2410 }
24112403 .repo-header-live-badge {
24122404 display: inline-flex;
24132405 align-items: center;
@@ -2428,7 +2420,7 @@ const css = `
24282420 border: 1px solid rgba(218, 54, 51, 0.35);
24292421 }
24302422 .repo-header-live-badge--live .repo-header-live-dot {
2431 animation: pushWatchPulse 1.2s ease-in-out infinite;
2423 background: #22c55e;
24322424 display: inline-block;
24332425 }
24342426 .repo-header-live-badge--recent {
@@ -2437,9 +2429,6 @@ const css = `
24372429 border: 1px solid rgba(91, 110, 232, 0.22);
24382430 }
24392431 .repo-header-live-badge--recent:hover { color: var(--accent); }
2440 (prefers-reduced-motion: reduce) {
2441 .repo-header-live-badge--live .repo-header-live-dot { animation: none; }
2442 }
24432432
24442433 .repo-nav {
24452434 display: flex;
@@ -2473,7 +2462,7 @@ const css = `
24732462 right: 14px;
24742463 bottom: -1px;
24752464 height: 2px;
2476 background: var(--accent-gradient);
2465 background: var(--accent);
24772466 border-radius: 2px;
24782467 }
24792468 /* AI links in the right-side cluster — sparkle accent */
@@ -3098,7 +3087,7 @@ const css = `
30983087 width: 12px;
30993088 height: 12px;
31003089 border-radius: var(--r-full);
3101 background: var(--accent-gradient);
3090 background: var(--accent);
31023091 border: 2px solid var(--bg);
31033092 box-shadow: 0 0 0 1px var(--border-strong);
31043093 }
@@ -3142,7 +3131,7 @@ const css = `
31423131 transition: all var(--t-base) var(--ease);
31433132 }
31443133 .toggle-switch input:checked + .toggle-slider {
3145 background: var(--accent-gradient);
3134 background: var(--accent);
31463135 border-color: transparent;
31473136 box-shadow: 0 0 0 1px rgba(91,110,232,0.4);
31483137 }
@@ -3298,9 +3287,7 @@ const css = `
32983287 .card.card-p-lg { padding: var(--space-6); }
32993288 .card.card-elevated { box-shadow: var(--elev-2); }
33003289 .card.card-gradient {
3301 background:
3302 linear-gradient(135deg, rgba(91,110,232,0.05), transparent 60%),
3303 var(--bg-elevated);
3290 background: var(--bg-elevated);
33043291 }
33053292 .card.card-gradient::before { opacity: 1; }
33063293 .notice {
33073294