Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commit3681186unknown_key

Merge pull request #110 from ccantynz-alt/claude/site-audit-competitive-pctlwg

Merge pull request #110 from ccantynz-alt/claude/site-audit-competitive-pctlwg

audit: full-platform audit 2026-06-10 — green suite restored, docs re…
CC LABS App committed on June 10, 2026Parents: 2436d1d 74a8784
18 files changed+3948536811869e38e8b3e3309f37dcabb2f90685333e4
18 changed files+394−85
Modified.env.example+15−0View fileUnifiedSplit
102102APP_VERSION=
103103# Injected at build time; the deployed git commit SHA shown on the status page.
104104GIT_COMMIT=
105
106# ── Documented 2026-06-10 (audit pass) — vars used in src/lib/config.ts ──
107# Opt-in: AI scans push diffs and opens issues for problems it finds.
108AI_AUTO_ISSUES=
109# Opt-in: dependency scanner runs on push (advisory cross-reference).
110DEPENDENCY_SCAN_ENABLED=
111# Directory for the OCI container-registry blob store. Default: ./oci-store
112OCI_STORE_PATH=
113# Wildcard domain for PR preview deployments (e.g. previews.example.com).
114# Empty disables branch previews.
115PREVIEW_DOMAIN=
116# Redis/Valkey connection for cross-node SSE fan-out. Empty = in-process
117# broadcaster (single-node only). Either name is accepted.
118REDIS_URL=
119VALKEY_URL=
AddedAUDIT-2026-06-10.md+218−0View fileUnifiedSplit
1# Gluecron Full-Platform Audit — 2026-06-10
2
3**Branch:** `claude/site-audit-competitive-pctlwg` (from `main` @ `2436d1d`)
4**Method:** six parallel audit agents (repair flywheel, docs freshness, wiring/mechanics, design/UX, architecture/security, live market scan) + direct verification of every load-bearing claim (test suite, typecheck, migration runner, home-route render path).
5**Supersedes:** `AUDIT.md` (2026-05-16), `AUDIT-v2.md` (2026-05-16), `AUDIT_REPORT.md` (2026-05-13).
6
7---
8
9## 0. Executive summary
10
11**Verdict: the platform is real, broad, and largely wired — but three things stand between today and "confidently ahead of GitHub":**
12
131. **The repair flywheel is built but not closed.** Every schema, tier, and helper exists; the cache lookup and the outcome-feedback loop have **zero callers**. Until those ~5 wiring points land, every repeat failure costs a 3–5 s Claude call instead of a <100 ms cache hit, and the system learns nothing from its own fixes. This is the single highest-leverage build item on the board. (§3)
142. **Quality signal was red.** The suite had 28 failing tests + 5 errors and 2 TypeScript errors — all stale assertions left behind by the Landing-2030 reboot, plus Playwright specs leaking into `bun test`. **Fixed in this session: 2767 pass / 0 fail / 122 skip, `tsc` clean.** A green suite is table stakes for due diligence. (§1)
153. **Scale posture is single-node.** Repos on local disk, in-memory rate limiting, workflow runner spawning on the web node. Fine to ~5k repos / 100 concurrent users; documented fix path below. An acquirer's tech DD will find this in week one — better that our own audit says it first, with the plan attached. (§5)
16
17Design is genuinely on-brief (quiet, light-default, restrained violet — not cyberpunk), security fundamentals are strong (argv-array git calls, hashed tokens, AES-GCM secrets, sanitized markdown), and the June-2026 market scan says our MCP-native + self-hosted-AI-parity positioning is the most defensible wedge available. (§6, §7)
18
19---
20
21## 1. Verification mechanics (what was actually run)
22
23| Check | Before this session | After this session |
24|---|---|---|
25| `bun test` | 2758 pass / **28 fail / 5 errors** / 122 skip | **2767 pass / 0 fail / 0 errors** / 122 skip |
26| `bunx tsc --noEmit` | 2 errors (`health-score.test.ts` TS2367) | clean |
27| Lockfile | `bun.lock` missing `@playwright/test` | reconciled |
28
29**Root cause of the 28 failures — none were product breakage.** `GET /` now renders the self-contained `Landing2030Page` (`src/views/landing-2030.tsx`, wired at `src/routes/web.tsx:1865`), which does not include the master Layout CSS. Tests still asserted on the legacy `LandingPage` hero (`hero-primary-ctas`, dxt CTA, Live-now block), Layout design tokens on `/`, and the old `/status` degraded headline (`"Service degraded"` → now `"Degraded performance"` / `"Major outage"`, `src/routes/status.tsx:356`). All retargeted to the current contract (Layout assertions now hit `/help`). The 5 errors were Playwright `e2e/*.spec.ts` files crashing under Bun's runner; new `bunfig.toml` scopes `bun test` to `src/` (e2e stays on `bun run e2e`, VS Code extension suite on `bun test editor-extensions`).
30
31**Lesson to encode:** the Landing-2030 reboot shipped without running the suite, or shipped red. Gate enforcement should apply to our own repo first — that's the product's whole pitch.
32
33---
34
35## 2. Wiring & mechanics audit
36
37**Confidence: high. 188 of 190 route modules are mounted in `src/app.tsx`; all recently shipped AI features are fully wired** (ai-loop via the autopilot `ai-loop-sweep` task, AI Workspace `app.tsx:203`, cross-repo impact `app.tsx:199`, repo health `app.tsx:195`, NL search `app.tsx:167`, pair programmer `app.tsx:130` — each with its migration 0101–0104 present).
38
39Verified end-to-end: the 14-step `src/hooks/post-receive.ts` chain (auto-repair → intelligence → gates → semantic index → AI auto-issues → preview builds → dep scan → doc-drift → onboarding → cloud deploys → Crontech webhook (HMAC, 6-attempt backoff) → server targets → **self-deploy**). `scripts/self-deploy.sh` exists (15.5 KB, executable) and is invoked via `Bun.spawn` at `post-receive.ts:241-267`, gated on `SELF_HOST_REPO` + push to `refs/heads/main` — the CLAUDE.md "self-driven deploys" claim is true.
40
41Issues found:
42
43| # | Finding | Action |
44|---|---|---|
45| W1 | `src/routes/migration-assistant.tsx` exists but is never imported/mounted (superseded by `migrate.tsx`) | Delete in a cleanup block |
46| W2 | `src/lib/changelog-generator.ts` has zero importers (`ai-generators.ts generateChangelog` is the live path) | Delete in a cleanup block |
47| W3 | Migration prefixes duplicated (0077×3, 0078×2) and 0085 missing | **Safe — verified directly:** `src/db/migrate.ts:137` tracks applied migrations by full filename, so each file applies exactly once. **Do NOT renumber** — renaming an applied file would re-run it. Convention only: next migrations continue from 0105. |
48| W4 | 6 env vars used in `src/lib/config.ts` were undocumented | `.env.example` updated this session |
49| W5 | ~20 TODO markers in src/, none blocking | No action |
50
51---
52
53## 3. The repair flywheel (owner priority) — built, not closed
54
55Owner vision: record everything; at 10k–20k repos, recognize recurring breakages and fix them deterministically **in seconds, without an AI call**, reserving AI for novel failures.
56
57**Status: the architecture is ~80 % present and ~0 % operational as a learning loop.**
58
59What exists and works:
60- `repair_flywheel` table (`drizzle/0039_repair_flywheel.sql`) — failure SHA-256 fingerprint, `repair_tier` (cached/mechanical/ai-sonnet/human), outcome, `cache_hit_count`, cross-repo `is_public_pattern`, and the hot-path composite index `(repo_id, failure_signature, outcome)`. The data model is exactly right for the vision.
61- Tier 1 mechanical repairs (`src/lib/auto-repair-mechanical.ts`) — lockfile drift, formatting, import-order classification + worktree-safe fixes.
62- Tier 2 AI repair (`src/lib/auto-repair.ts`) and CI autofix (`src/lib/ci-autofix.ts`) — real Sonnet calls, patch-as-comment.
63- Recording: `recordRepair()` writes attempts with fingerprints.
64- `recurring_patterns` (0096) — Claude-detected recurring issues, used for PR warnings.
65
66What is broken (the gaps, with file:line):
67
68| Gap | Evidence |
69|---|---|
70| **Tier-0 cache is never consulted** | `findCachedRepair()` `src/lib/repair-flywheel.ts:112` — zero callers anywhere. Every failure goes straight to mechanical/AI. |
71| **Outcomes are never recorded** | `updateOutcome()` `repair-flywheel.ts:252` — zero callers. Every row stays `outcome='pending'` forever; success-rate stats are blind; the cache can never be trusted. |
72| **The tier orchestrator is dead code** | `repairGateFailure()` `src/lib/auto-repair.ts:412-587` (the Tier-1→2 fallthrough) is defined and never called. Live call sites only repair secrets/security (`src/lib/gate.ts:424-450`); ordinary CI test failures bypass tiers and go straight to Sonnet via `triggerCiAutofix` (`src/lib/ci-autofix.ts:155`). |
73| **No AI→deterministic promotion** | Nothing moves a repeatedly successful AI fix into the Tier-0 cache or flips `is_public_pattern`. |
74| **No operator surface** | `getFlywheelStats()` (`repair-flywheel.ts:286-380`) has no route — no `/admin/repair-flywheel`. |
75| **ai-loop skips the cache too** | `src/lib/ai-loop.ts:441` calls `triggerCiAutofix` without a cache lookup. |
76
77**Closing plan (proposed BLOCK N1 — small, surgical, ~5 wiring points):**
781. `ci-autofix.ts` (~line 156): fingerprint the parsed error; `findCachedRepair()` first; on a high-success-rate hit, apply the cached patch and record a `cached`-tier row — no AI call.
792. `ai-loop.ts:441`: same cache-first check before dispatching autofix.
803. Post-gate-rerun correlation: when a gate re-runs after a repair, match the new `gate_runs` row back to the open `repair_flywheel` row and call `updateOutcome('success'|'failed')`. This is the linchpin.
814. Promotion: on N successes for a signature, mark the pattern public (cross-repo) and increment `cache_hit_count` on every hit.
825. `/admin/repair-flywheel` route exposing `getFlywheelStats()` — hit rate, AI-vs-cache ratio, $-saved. (Also a powerful acquisition metric: "x % of failures fixed without a model call.")
83
84Also: repairs should write `audit_log` rows (no `repair_*` actions exist today), so the "record everything" half of the vision holds.
85
86---
87
88## 4. Architecture & security
89
90**Strong fundamentals (verified):**
91- No command injection: all git subprocess calls use argv arrays (`src/git/repository.ts:42-58`); no `bash -c` interpolation of user input.
92- Auth: all 10 sampled mutating routes gated (`requireAuth` / `repo-access` 5-tier hierarchy / API scopes); session cookies `httpOnly` + `secure` (prod) + `sameSite=Lax`.
93- Secrets: workflow secrets AES-256-GCM with per-encryption IVs (`src/lib/workflow-secrets-crypto.ts`); PATs and OAuth tokens stored SHA-256-hashed.
94- XSS: markdown pipeline escapes HTML/attrs and rejects `javascript:`/`data:` hrefs (`src/lib/markdown.ts`); no user-data `dangerouslySetInnerHTML`.
95- Global error handler upholds the "no 500 reaches the UI" invariant (`src/app.tsx:886-906`).
96
97**Findings to fix:**
98
99| Sev | Finding | Fix |
100|---|---|---|
101| HIGH | **SSRF**: webhook delivery (`src/lib/webhook-delivery.ts:201`) and mirror fetch (`src/lib/mirrors.ts`) fetch user-supplied URLs with no private-range block (127.0.0.1, 10/8, 172.16/12, 192.168/16, 169.254/16, ::1) | ~2 days: shared `assertPublicUrl()` helper + DNS-resolution check, applied to webhooks, mirrors, imports |
102| HIGH | Single-node coupling: bare repos on local disk (`repository.ts:38`), in-memory rate limiter (`rate-limit.ts:15`), workflow runner in-process on the web node, 500-entry session cache. SSE already has a Redis fallback (`sse.ts:76-150`) — the others don't | Document the supported topology now (done here); BLOCK N3: Redis-backed rate-limit + sessions, extract workflow runner to a worker process, repo-storage strategy (shared volume now, object-store later) |
103| MED | Silent async failures: post-receive side-effects swallow errors with bare `.catch(() => {})` — operators can't see when AI analysis/indexing silently dies | Route through `reportError` + a failures counter on `/metrics` |
104| MED | N+1 queries on PR/issue list pages (per-row risk-score + comment-count queries) | Batch with joins; add a slow-query log |
105| MED | Test gaps: no Smart-HTTP protocol tests; `mcp-tools-expanded.ts` (2,470 lines of write tools) has no test file | BLOCK N4: MCP write-tool suite + git-protocol smoke tests |
106| LOW | `src/routes/pulls.tsx` is 6,654 lines (merge logic also duplicated in `pr-merge.ts` + MCP merge tool — known §7 Bible follow-up) | Consolidate on `performMerge()` |
107
108Dependency hygiene is good: 8 runtime deps, all mainstream, no abandonware.
109
110---
111
112## 5. Design & UX (brief: quiet confidence, believable, not cyberpunk)
113
114Two design systems are live:
115- **App + legacy marketing surfaces**`src/views/layout.tsx` "Editorial-Technical" system: 60+ CSS custom properties, slate-noir `#08090f` / violet `#8c6dff` / hairline borders, Inter + Inter Tight + JetBrains Mono, light/dark with pre-paint script, 52 ARIA attributes, `prefers-reduced-motion` respected, 12:1–15:1 text contrast. Scored **8.2/10 against the brief** — restraint comparable to Linear/Vercel-tier marketing; no neon, no glow-noise.
116- **The actual home page**`Landing2030Page` (`src/views/landing-2030.tsx`): white-background, ink-on-paper, light-only, headline "The git host built for 2030," fact-led copy ("Spec to PR in 90 seconds"), one product-card mock, two CTAs. This is precisely the "believable, it-just-works" register the owner asked for (closest public analog: vapi.ai's developer-infra restraint — note: **vapron.ai does not exist**; vapi.ai is the real benchmark).
117
118Highest-impact polish items (ordered):
1191. Social proof strip on the landing (grayscale logos / "powering N repos" with real counters) — the one missing trust signal for acquirers.
1202. Consolidate scattered "vs GitHub" copy into the existing `/vs-github` page; keep the landing comparison to one pull-quote.
1213. Unify modal/dropdown shadow recipes to one `--modal-shadow` token.
1224. CTA label case consistency ("Start building" / "Sign in" — title-vs-sentence drift).
1235. Disable decorative animations on mobile/slow networks (`prefers-reduced-motion` + width guard).
1246. Empty-state noise reduction (dashed borders + dot grids accumulate on data-less dashboards).
1257. Status dot in the footer linking `/status` (operational-health-as-brand).
1268. `:focus-visible` outlines on landing CTAs (currently box-shadow only).
1279. Brand naming is already consistent (lowercase `gluecron`) — keep it that way in new copy.
12810. Decide the long-term fate of the legacy `LandingPage`/marketing routes — two design systems is one too many for an acquirer walkthrough.
129
130---
131
132## 6. Documentation freshness (fixed this session)
133
134| Doc | Was | Now |
135|---|---|---|
136| `BUILD_BIBLE.md:3` | "Last updated 2026-05-29" | 2026-06-10 + living-document rule |
137| `BUILD_BIBLE.md` §4.1 | "95 tables" | 161 tables (verified by count) |
138| `BUILD_BIBLE.md` §5.1 | "1491 tests pass as of 2026-05-13" | 2767/0/122 as of 2026-06-10 + runner scoping note |
139| `CLAUDE.md` architecture tree | implied ~20 route files | annotated: illustrative; ~190 route files, 161 tables |
140| `AUDIT.md`, `AUDIT-v2.md`, `AUDIT_REPORT.md` | presented as current | stamped SUPERSEDED → this file |
141| `.env.example` | 6 config vars undocumented | documented |
142
143Verified accurate, no change needed: `README.md` (15 MCP read-tools claim matches `defaultTools()`), `ROADMAP.md`, `docs/STRATEGY.md`, `DEPLOY.md`.
144
145**Standing rule going forward:** every audit/spec doc carries a date in its filename or header, and the Bible's header timestamp moves in the same commit that changes reality.
146
147---
148
149## 7. Market scan (June 2026) and the capability gap list
150
151Full sourced report retained from the research agent; condensed here.
152
153**Where the market is:** GitHub absorbed Copilot Workspace into the GA coding agent + Agent HQ (multi-vendor agents — Anthropic/OpenAI/Google/Cognition/xAI — under one mission-control panel; agentic code review on all PRs since March 2026, review→agent autofix handoff). GitLab Duo Agent Platform GA'd Jan 2026 with credits-based pricing and agentic SAST fix PRs. Cursor (~$29B, >$2B ARR) runs Bugbot on ~2M PRs/month with >35 % of its autofix PRs merging. OpenAI Codex reviews GitHub PRs natively with `AGENTS.md` steering. Claude Code passed ~$2.5B annualized with MCP as the de-facto agent-integration standard (~9,400 public servers). **Closed-loop autonomy (review → fix → CI → self-heal) is the 2026 frontier — which is exactly the flywheel §3 closes.**
154
155**Gluecron's defensible wedges (per the scan):**
1561. **MCP-native git host with a deep write surface + governance** — per-token scopes, full agent audit trail, org policy on tool access. The "MCP governance gap" is documented and unowned by any incumbent.
1572. **Self-hosted with day-one AI parity** (GHES lags github.com on AI; we don't).
1583. **Agent identity & provenance** — agents as first-class actors with attribution ("agent X under human Y"), signed agent commits, agent activity feed.
159
160**Capability checklist for market leadership (T = table stakes, D = differentiator; ✅ = shipped, 🟡 = partial):**
161
162| # | Capability | T/D | Status |
163|---|---|---|---|
164| 1 | Issue→PR coding agent (sandboxed, tests run) | T | ✅ spec-to-PR + ai:build label |
165| 2 | Auto AI review on every PR, inline comments | T | ✅ |
166| 3 | **Review→fix closed loop on same PR** | T | 🟡 ci-autofix posts patches; flywheel not closed (§3) |
167| 4 | Severity-filtered review (P0/P1-only mode, effort levels) | T | ❌ |
168| 5 | Honor `AGENTS.md`/`CLAUDE.md` steering files in all AI flows | T | 🟡 partial |
169| 6 | **Multi-vendor agent panel** (Claude + Codex + Gemini + Devin side-by-side, compare PRs) | D | ❌ — GitHub Agent HQ is the template; no self-hosted product has it |
170| 7 | Parallel agent sessions in isolated worktrees + diff-compare view | D | 🟡 PR sandboxes exist; no compare UI |
171| 8 | **Self-healing CI** (red pipeline → agent reads logs → fix → re-run) | T | 🟡 = §3 flywheel close |
172| 9 | Natural-language CI authoring | D | ❌ — pre-GA everywhere; leadership claimable |
173| 10 | Security scan + AI autofix default-on | T | ✅ gates + repair |
174| 11 | Autonomous maintenance agents (dep bumps ✅, dead-code, flaky-test quarantine, coverage PRs) | D | 🟡 |
175| 12 | First-class MCP server over the whole host surface | T | ✅ 15 + 10 write tools |
176| 13 | **MCP governance**: OAuth remote MCP, per-token tool scopes, agent-action audit log, org tool policy | D | 🟡 auth exists; scoping/policy/audit-views don't |
177| 14 | Agent identity & provenance (first-class agent actors, attribution) | D | 🟡 app-bots exist; §7 Bible notes marker comments still credit humans |
178| 15 | Persistent context spaces (pin docs/issues/files as reusable agent grounding) | D | ❌ |
179| 16 | Headless API/SDK so any agent CLI treats Gluecron as a first-class remote | D | ✅ REST v2 + GraphQL + CLI |
180| 17 | Consumption/credit billing rail for agent work | T | 🟡 plans/quotas exist; no metering |
181| 18 | Agent analytics: % agent-authored merged code, fix-acceptance rate, cycle-time delta | T | 🟡 DORA + hours-saved exist; not agent-cut |
182| 19 | Per-repo review memory ("never flag this again") | D | ❌ |
183| 20 | AI issue triage (label/dedupe/severity/route-to-agent) | D | ✅ |
184| 21 | Auto-merge policy gated on AI review + CI green | T | ✅ K2/K3 |
185| 22 | Self-hosted full AI parity | D | ✅ — say it louder in marketing |
186| 23 | Browser/runtime verification subagent (screenshot evidence on PRs) | D | ❌ — nobody attaches this to hosting review yet |
187| 24 | Daily automated market/competitor scan feeding the roadmap | D | 🟡 `advancement-scanner` lib exists and is tested — point it at competitor changelogs + RSS and open `ai:build` issues automatically |
188| 25 | Repair-flywheel economics on the admin dashboard (% fixed without AI, seconds-to-fix) | D | ❌ until §3 closes |
189
190**Recommended build order (next blocks):**
191- **N1 — Close the repair flywheel** (§3, ~1 week). Unlocks #3, #8, #25 and the headline acquisition metric.
192- **N2 — SSRF guard + MCP write-tool test suite** (~1 week). Closes both HIGH security/test findings.
193- **N3 — Multi-node readiness** (Redis rate-limit/sessions, worker-process workflow runner, storage doc) (~3 weeks).
194- **N4 — MCP governance + agent provenance** (#13, #14) (~2–3 weeks). The differentiator no incumbent owns.
195- **N5 — Multi-vendor agent panel** (#6, #7) (~3–4 weeks). The "Agent HQ for the self-hosted world" story.
196- **N6 — Review noise controls + per-repo review memory** (#4, #19) (~2 weeks). The #1 buyer complaint in the AI-review market.
197- **N7 — Daily market scanner** (#24): schedule `advancement-scanner` in autopilot against competitor feeds; weekly digest to the owner.
198
199---
200
201## 8. Acquisition-readiness checklist
202
203Signals the 2025–26 M&A record says to manufacture (Cursor/Cognition/Graphite precedents):
204
205| Signal | Status |
206|---|---|
207| Green test suite + clean typecheck on `main` | ✅ as of this session — keep it gated |
208| % of merged code that is agent-authored (platform metric) | ❌ build with N1/#18 |
209| AI-review fix-acceptance / auto-merge rate (Cursor publicizes 80 % / 35 %) | ❌ data exists in tables; needs the analytics cut |
210| MCP tool-call volume from third-party agents (proves *platform*, not feature) | 🟡 log exists; no dashboard |
211| 2–3 enterprise logos on self-hosted | ❌ go-to-market |
212| Consumption billing live | 🟡 plans exist, metering doesn't |
213| Clean IP / no exclusive model-vendor dependency | 🟡 Anthropic-first; Voyage optional; keep model layer swappable (`MODEL_*` constants already centralize this) |
214| Single current audit doc + reconciled Bible | ✅ this file |
215
216---
217
218*Next scheduled audit: 2026-07-10 or after BLOCK N3, whichever comes first. Keep this file's date in the filename; supersede, don't edit history.*
ModifiedAUDIT-v2.md+2−0View fileUnifiedSplit
1> **SUPERSEDED (2026-06-10):** historical record only. The current audit is `AUDIT-2026-06-10.md` at the repo root.
2
13# Gluecron Audit v2 — 2026-05-16
24
35Five parallel agents (schema-vs-code drift, route smoke crawl, form
ModifiedAUDIT.md+2−0View fileUnifiedSplit
1> **SUPERSEDED (2026-06-10):** historical record only. The current audit is `AUDIT-2026-06-10.md` at the repo root.
2
13# Gluecron Audit — 2026-05-16
24
35Comprehensive end-to-end audit run on branch `claude/fix-aa-loop-issue-PonMQ`
ModifiedAUDIT_REPORT.md+2−0View fileUnifiedSplit
1> **SUPERSEDED (2026-06-10):** historical record only. The current audit is `AUDIT-2026-06-10.md` at the repo root.
2
13# AUDIT REPORT — gluecron.com
24
35**Date:** 2026-05-13
ModifiedBUILD_BIBLE.md+53−3View fileUnifiedSplit
11# GLUECRON BUILD BIBLE
22
3**Last updated: 2026-05-29T03:00:00Z**
3**Last updated: 2026-06-10T00:00:00Z** (full-platform audit session — see `AUDIT-2026-06-10.md`)
44
55**This file is the single source of truth for the GlueCron build.**
66
77**Every Claude agent MUST read this file in full before touching code. No exceptions.**
88
9> **Living-document rule (owner directive, 2026-06-10):** this Bible describes
10> reality; it does not veto progress. When the Bible and the codebase disagree,
11> the codebase wins and the Bible gets reconciled in the same session. The
12> locked-blocks policy (§4) protects shipped, load-bearing work from being
13> *undone* — it must never be read as a reason to block, delay, or shrink a new
14> advancement. If a new feature needs to touch a locked surface additively,
15> build it; only destructive/semantic changes to locked work need owner
16> sign-off.
17
918GlueCron is a GitHub replacement — AI-native code intelligence, green ecosystem enforcement, git hosting, automated CI. It is production infrastructure for multiple downstream platforms. Production cannot stop.
1019
1120> **2030 Vision:** The most advanced git host on the market. Every developer who uses Gluecron ships 2× faster than one who doesn't. Zero caching issues, lightning-fast push-to-live pipeline, AI on every workflow step. Designed for the Claude Code era — any session, any repo, zero friction.
388397- `src/app.tsx` — route composition, middleware order, error handlers
389398- `src/index.ts` — Bun server entry
390399- `src/lib/config.ts` — env getters (late-binding)
391- `src/db/schema.ts` — 95 tables. New tables only via new migration.
400- `src/db/schema.ts` — 161 tables (verified 2026-06-10). New tables only via new migration.
392401- `src/db/index.ts` — lazy proxy DB connection
393402- `src/db/migrate.ts` — migration runner
394403- `drizzle/0000_initial.sql`, `drizzle/0001_green_ecosystem.sql` — migrations
665674```bash
666675bun install
667676bun dev # hot reload
668bun test # 1491 tests currently pass (0 failed, 2 skipped) as of 2026-05-13 after BLOCK K (3 sub-blocks) + BLOCK L (10 sub-blocks). +218 tests vs the 1273 pre-K baseline. Run `bun install` first.
677bun test # 2767 pass / 0 fail / 122 skip as of 2026-06-10 (audit session). bunfig.toml scopes bun test to src/ — Playwright e2e specs run via `bun run e2e`; the VS Code extension suite via `bun test editor-extensions`. Run `bun install` first.
669678bun run db:migrate
670679```
671680
712721
713722## 7. IN-FLIGHT
714723
724**2026-06-10 — Full-platform audit session (branch `claude/site-audit-competitive-pctlwg`):**
725
726Deliverable: `AUDIT-2026-06-10.md` (root) — code, architecture, design, docs,
727wiring, repair-flywheel, and June-2026 competitive market scan. Supersedes
728`AUDIT.md`, `AUDIT-v2.md`, `AUDIT_REPORT.md` (all marked historical).
729
730Shipped this session:
731- **Suite back to green** — 2767 pass / 0 fail / 122 skip (was 28 fail + 5
732 errors). All failures were stale tests asserting on the legacy `LandingPage`
733 / master-CSS contract after `GET /` switched to the self-contained
734 `Landing2030Page`, plus old `/status` degraded-headline copy. Updated:
735 `u-polish`, `visual-coherence`, `demo-page`, `dxt-extension`,
736 `green-ecosystem` (theme), `pwa`, `landing-live-feed`, `status-seo`,
737 `health-score` (2 TS2367 errors). New `bunfig.toml` scopes `bun test` to
738 `src/` so Playwright e2e specs stop producing 5 phantom fails + 5 errors.
739- **Docs reconciled + timestamped** — header date, table count (95→161),
740 test-count claim, living-document rule (§1 preamble), `.env.example` gained
741 6 undocumented vars (AI_AUTO_ISSUES, DEPENDENCY_SCAN_ENABLED, OCI_STORE_PATH,
742 PREVIEW_DOMAIN, REDIS_URL/VALKEY_URL).
743
744Highest-priority findings (full detail + file:line in AUDIT-2026-06-10.md):
7451. **Repair flywheel is built but not closed-loop.** `findCachedRepair()`
746 (src/lib/repair-flywheel.ts:112) has zero callers; `updateOutcome()`
747 (repair-flywheel.ts:252) is never invoked, so every repair stays `pending`
748 and the Tier-0 cache can never learn. `repairGateFailure()`
749 (src/lib/auto-repair.ts:412) — the Tier-1→2 orchestrator — is dead code.
750 ci-autofix calls Sonnet on every failure without consulting the cache.
751 This is THE gap blocking the "10k repos, fix known breakages in seconds
752 without AI" vision. Treat as the next build block.
7532. **SSRF**: webhook delivery (src/lib/webhook-delivery.ts:201) and mirror
754 fetch don't block private-IP ranges. ~2-day fix.
7553. Migration files with duplicate numeric prefixes (0077×3, 0078×2) are SAFE —
756 migrate.ts tracks by full filename — but do not renumber applied files.
7574. Orphans: `src/routes/migration-assistant.tsx` (never mounted),
758 `src/lib/changelog-generator.ts` (zero importers; ai-generators.ts is the
759 live path). Confirm + delete in a cleanup block.
7605. Scale posture is single-node (local-disk repos, in-memory rate limiter,
761 in-process workflow runner). Documented in the audit's due-diligence
762 section with effort estimates.
763
764
715765**2026-05-28 — Platform redesign + Claude Code integration session (branch `claude/jolly-heisenberg-2sg1Q`):**
716766
717767Changes shipped this session (all pushed, zero TS errors, GateTest green):
ModifiedCLAUDE.md+4−0View fileUnifiedSplit
6161
6262## Architecture
6363
64> Note (2026-06-10): the tree below is illustrative, not exhaustive — the
65> platform has grown to ~190 route files in `src/routes/` and 161 tables in
66> `src/db/schema.ts`. `BUILD_BIBLE.md` §2 is the authoritative feature map.
67
6468```
6569src/
6670 index.ts Entry point (Bun server)
Modifiedbun.lock+10−1View fileUnifiedSplit
1616 "ssh2": "^1.16.0",
1717 },
1818 "devDependencies": {
19 "@playwright/test": "^1.49.0",
1920 "@types/bun": "^1.3.14",
2021 "@types/k6": "^2.0.0",
2122 "drizzle-kit": "^0.31.10",
120121
121122 "@peculiar/x509": ["@peculiar/x509@1.14.3", "", { "dependencies": { "@peculiar/asn1-cms": "^2.6.0", "@peculiar/asn1-csr": "^2.6.0", "@peculiar/asn1-ecc": "^2.6.0", "@peculiar/asn1-pkcs9": "^2.6.0", "@peculiar/asn1-rsa": "^2.6.0", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "pvtsutils": "^1.3.6", "reflect-metadata": "^0.2.2", "tslib": "^2.8.1", "tsyringe": "^4.10.0" } }, "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA=="],
122123
124 "@playwright/test": ["@playwright/test@1.60.0", "", { "dependencies": { "playwright": "1.60.0" }, "bin": { "playwright": "cli.js" } }, "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag=="],
125
123126 "@simplewebauthn/server": ["@simplewebauthn/server@13.3.0", "", { "dependencies": { "@hexagon/base64": "^1.1.27", "@levischuck/tiny-cbor": "^0.2.2", "@peculiar/asn1-android": "^2.6.0", "@peculiar/asn1-ecc": "^2.6.1", "@peculiar/asn1-rsa": "^2.6.1", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "@peculiar/x509": "^1.14.3" } }, "sha512-MLHYFrYG8/wK2i+86XMhiecK72nMaHKKt4bo+7Q1TbuG9iGjlSdfkPWKO5ZFE/BX+ygCJ7pr8H/AJeyAj1EaTQ=="],
124127
125128 "@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="],
152155
153156 "fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="],
154157
155 "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
158 "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
156159
157160 "get-tsconfig": ["get-tsconfig@4.13.7", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q=="],
158161
166169
167170 "nan": ["nan@2.27.0", "", {}, "sha512-hC+0LidcL3XE4rp1C4H54KujgXKzbfyTngZTwBByQxsOxCEKZT0MPQ4hOKUH2jU1OYstqdDH4onyHPDzcV0XdQ=="],
168171
172 "playwright": ["playwright@1.60.0", "", { "dependencies": { "playwright-core": "1.60.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA=="],
173
174 "playwright-core": ["playwright-core@1.60.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA=="],
175
169176 "postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="],
170177
171178 "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="],
204211
205212 "tsx/esbuild": ["esbuild@0.28.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.0", "@esbuild/android-arm": "0.28.0", "@esbuild/android-arm64": "0.28.0", "@esbuild/android-x64": "0.28.0", "@esbuild/darwin-arm64": "0.28.0", "@esbuild/darwin-x64": "0.28.0", "@esbuild/freebsd-arm64": "0.28.0", "@esbuild/freebsd-x64": "0.28.0", "@esbuild/linux-arm": "0.28.0", "@esbuild/linux-arm64": "0.28.0", "@esbuild/linux-ia32": "0.28.0", "@esbuild/linux-loong64": "0.28.0", "@esbuild/linux-mips64el": "0.28.0", "@esbuild/linux-ppc64": "0.28.0", "@esbuild/linux-riscv64": "0.28.0", "@esbuild/linux-s390x": "0.28.0", "@esbuild/linux-x64": "0.28.0", "@esbuild/netbsd-arm64": "0.28.0", "@esbuild/netbsd-x64": "0.28.0", "@esbuild/openbsd-arm64": "0.28.0", "@esbuild/openbsd-x64": "0.28.0", "@esbuild/openharmony-arm64": "0.28.0", "@esbuild/sunos-x64": "0.28.0", "@esbuild/win32-arm64": "0.28.0", "@esbuild/win32-ia32": "0.28.0", "@esbuild/win32-x64": "0.28.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw=="],
206213
214 "tsx/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
215
207216 "tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
208217
209218 "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
Addedbunfig.toml+8−0View fileUnifiedSplit
1# Scope `bun test` to the unit/integration suite. The Playwright specs in
2# e2e/ use @playwright/test's runner (`bun run e2e`) and crash with
3# "Playwright Test did not expect test.beforeAll()" when Bun's runner
4# loads them — they were producing 5 phantom fails + 5 errors in every
5# `bun test` run. The VS Code extension suite still runs explicitly via
6# `bun test editor-extensions`.
7[test]
8root = "src"
Modifiedsrc/__tests__/demo-page.test.ts+2−2View fileUnifiedSplit
4040 it("body contains all three tile headings", async () => {
4141 const res = await app.request("/demo");
4242 const body = await res.text();
43 expect(body).toContain("Issues queued for AI build");
44 expect(body).toContain("PRs auto-merged in the last 24h");
43 expect(body).toContain("Issues being built by AI right now");
44 expect(body).toContain("PRs auto-merged the instant gates passed");
4545 expect(body).toContain("AI reviews posted today");
4646 });
4747
Modifiedsrc/__tests__/dxt-extension.test.ts+6−7View fileUnifiedSplit
157157 });
158158});
159159
160describe("Block Q1 — landing CTA", () => {
161 it("GET / renders the 'Add to Claude Desktop' CTA pointing at /gluecron.dxt", async () => {
162 const res = await app.request("/");
160describe("Block Q1 — .dxt discoverability", () => {
161 // 2026-06-10: the landing-page CTA was demoted in the 2030 landing
162 // reboot (GET / now serves the self-contained Landing2030Page). The
163 // bundle stays discoverable from the docs surface.
164 it("GET /docs/mcp-server links to /gluecron.dxt", async () => {
165 const res = await app.request("/docs/mcp-server");
163166 expect(res.status).toBe(200);
164167 const body = await res.text();
165 expect(body).toContain("Add to Claude Desktop");
166168 expect(body).toContain('href="/gluecron.dxt"');
167 // The CTA carries the test hook + the download attribute so browsers
168 // know to save rather than navigate.
169 expect(body).toContain('data-testid="cta-dxt"');
170169 });
171170});
Modifiedsrc/__tests__/green-ecosystem.test.ts+4−2View fileUnifiedSplit
310310 expect(res.status).toBe(400);
311311 });
312312
313 it("home page includes the pre-paint theme script + data-theme attribute", async () => {
314 const res = await app.request("/");
313 it("Layout pages include the pre-paint theme script + data-theme attribute", async () => {
314 // 2026-06-10: "/" now serves the self-contained Landing2030Page;
315 // the Layout theme contract is asserted on /help instead.
316 const res = await app.request("/help");
315317 const html = await res.text();
316318 expect(html).toContain("data-theme");
317319 expect(html).toContain("theme-icon-");
Modifiedsrc/__tests__/health-score.test.ts+2−2View fileUnifiedSplit
3030describe("health-score component maxima", () => {
3131 test("security max is 30", () => {
3232 // No advisories → full security score
33 const noAdv = 0;
33 const noAdv: number = 0;
3434 const secScore = noAdv === 0 ? 30 : noAdv === 1 ? 20 : noAdv <= 2 ? 15 : noAdv <= 4 ? 8 : 0;
3535 expect(secScore).toBe(30);
3636 });
3737
3838 test("5+ advisories → 0 pts", () => {
39 const many = 5;
39 const many: number = 5;
4040 const secScore = many === 0 ? 30 : many === 1 ? 20 : many <= 2 ? 15 : many <= 4 ? 8 : 0;
4141 expect(secScore).toBe(0);
4242 });
Modifiedsrc/__tests__/landing-live-feed.test.ts+7−10View fileUnifiedSplit
319319});
320320
321321describe("Block M1 — landing route integration", () => {
322 it("GET / renders the Live now block with stubbed demo-activity", async () => {
322 // 2026-06-10: GET / now serves the self-contained Landing2030Page;
323 // the legacy LandingPage (and its Live-now block, covered above at the
324 // component level) no longer renders on the home route. The route
325 // integration contract is therefore: / responds 200 with the 2030 hero.
326 it("GET / renders the 2030 landing page", async () => {
323327 setLiveFeedStubs({
324328 queued: [
325329 {
338342 const res = await app.request("/");
339343 expect(res.status).toBe(200);
340344 const body = await res.text();
341 // Section heading + pulse always render. Stubbed row data is
342 // intentionally NOT asserted: the production route may call into
343 // the real listing helpers via cached bindings rather than the
344 // mocked surface, so the universally reliable assertion is that
345 // the block + script are wired in.
346 expect(body).toContain("Live now");
347 expect(body).toContain("landing-livenow-grid");
348 expect(body).toContain("/api/v2/demo/queued");
349 expect(body).toContain("setInterval");
345 expect(body).toContain("hero-actions");
346 expect(body).toContain('href="/register"');
350347 });
351348});
Modifiedsrc/__tests__/pwa.test.ts+5−3View fileUnifiedSplit
9191 // 2026-05-16 — PWA ripped out. The layout used to inject manifest +
9292 // SW registration scripts; now it injects a kill-switch that
9393 // unregisters any pre-existing SW. These tests pin the new contract.
94 // 2026-06-10: asserted on /help — "/" now serves the self-contained
95 // Landing2030Page which carries no Layout scripts at all.
9496 it("home page does NOT include the manifest link", async () => {
95 const res = await app.request("/");
97 const res = await app.request("/help");
9698 const body = await res.text();
9799 expect(body).not.toContain('rel="manifest"');
98100 });
99101
100102 it("home page does NOT call serviceWorker.register", async () => {
101 const res = await app.request("/");
103 const res = await app.request("/help");
102104 const body = await res.text();
103105 expect(body).not.toContain("serviceWorker.register");
104106 });
105107
106108 it("home page includes the kill-switch (unregisters legacy SWs)", async () => {
107 const res = await app.request("/");
109 const res = await app.request("/help");
108110 const body = await res.text();
109111 expect(body).toContain("getRegistrations");
110112 expect(body).toContain("reg.unregister");
Modifiedsrc/__tests__/status-seo.test.ts+2−1View fileUnifiedSplit
1616 // Either the green or red headline should appear
1717 expect(
1818 body.includes("All systems operational") ||
19 body.includes("Service degraded")
19 body.includes("Degraded performance") ||
20 body.includes("Major outage")
2021 ).toBe(true);
2122});
2223
Modifiedsrc/__tests__/u-polish.test.ts+42−48View fileUnifiedSplit
11/**
22 * Block U — Senior polish pass smoke tests.
33 *
4 * U1: hero rebuild — exactly two primary CTAs in the buttons row,
5 * tertiary text-link row sits underneath with the demoted demo +
6 * vs-github affordances, install snippet sits BELOW the CTAs
7 * wrapped in the "For power users" panel.
4 * 2026-06-10 update: `GET /` now serves the self-contained
5 * `Landing2030Page` (src/views/landing-2030.tsx) — the legacy
6 * `LandingPage` + master Layout CSS no longer render on the home route.
7 * U1 assertions therefore target the 2030 hero contract; U2/U4 master-CSS
8 * assertions target /help, a public Layout-rendered page that needs no DB.
9 *
10 * U1: hero — exactly two CTAs in the hero actions row, trust line
11 * beneath, product-card mock below the CTAs.
812 * U2: button CSS — hover lifts every .btn by translateY(-1px) with a
913 * soft drop shadow; focus-visible uses box-shadow (not outline);
1014 * primary CTA shimmers via background-position; disabled buttons
1923import app from "../app";
2024
2125const HOME = "/";
26// Public, Layout-rendered, DB-free page that carries the master CSS.
27const LAYOUT_PAGE = "/help";
2228
2329async function fetchHomeHtml(): Promise<string> {
2430 const res = await app.request(HOME);
2632 return await res.text();
2733}
2834
29describe("Block U1 — landing hero rebuild", () => {
30 it("renders exactly two primary CTAs in the main button row", async () => {
35async function fetchLayoutHtml(): Promise<string> {
36 const res = await app.request(LAYOUT_PAGE);
37 expect(res.status).toBe(200);
38 return await res.text();
39}
40
41describe("Block U1 — landing hero (2030 reboot)", () => {
42 it("renders exactly two CTAs in the hero actions row", async () => {
3143 const body = await fetchHomeHtml();
32 // Extract the primary CTA row using the data-testid we shipped.
33 const start = body.indexOf('data-testid="hero-primary-ctas"');
44 const start = body.indexOf('class="hero-actions');
3445 expect(start).toBeGreaterThan(-1);
35 // The wrapper opens with that attribute on a div; find the matching
36 // closing </div>. Heuristic: it's the first </div> after a tight
37 // window of <a … class="btn …"> elements.
38 const tail = body.slice(start, start + 2400);
39 // Count the <a class="btn …"> anchors inside the row.
46 // The actions row is a tight window of <a … class="btn …"> anchors.
47 const tail = body.slice(start, start + 600);
4048 const anchorMatches = tail.match(/<a[^>]*class="btn[^"]*"/g) || [];
41 expect(anchorMatches.length).toBeGreaterThanOrEqual(2);
42 // Sanity-check the two CTAs we explicitly preserve:
49 expect(anchorMatches.length).toBe(2);
4350 expect(tail).toContain('href="/register"');
44 expect(tail).toContain('href="/gluecron.dxt"');
45 // The tertiary row is OUTSIDE this slice; we assert the demoted
46 // demo + vs-github hrefs are NOT in the primary slice.
47 const primaryRowEnd = tail.indexOf("hero-tertiary-row");
48 const primaryOnly =
49 primaryRowEnd > 0 ? tail.slice(0, primaryRowEnd) : tail.slice(0, 800);
50 expect(primaryOnly).not.toContain('href="/demo"');
51 expect(primaryOnly).not.toContain('href="/vs-github"');
51 expect(tail).toContain('href="#loop"');
5252 });
5353
54 it("renders the tertiary text-link row with demo + vs-github affordances", async () => {
54 it("renders the trust line beneath the CTAs", async () => {
5555 const body = await fetchHomeHtml();
56 expect(body).toContain('data-testid="hero-tertiary-row"');
57 expect(body).toContain('data-testid="cta-tertiary-demo"');
58 expect(body).toContain('data-testid="cta-tertiary-vs"');
59 // The legacy labels survive on the demoted row so L10 regression
60 // guards keep passing.
61 expect(body).toContain("Try the live demo");
62 expect(body).toContain("Compare to GitHub");
56 const ctas = body.indexOf('class="hero-actions');
57 const trust = body.indexOf('class="hero-trust');
58 expect(ctas).toBeGreaterThan(-1);
59 expect(trust).toBeGreaterThan(ctas);
60 expect(body).toContain("Self-hosted · Git-native · Claude-first");
6361 });
6462
65 it("places the install snippet inside a 'For power users' panel below the CTAs", async () => {
63 it("places the product-card mock below the CTAs", async () => {
6664 const body = await fetchHomeHtml();
67 const ctas = body.indexOf('data-testid="hero-primary-ctas"');
68 const installPanel = body.indexOf("Power users install panel");
69 const installLabel = body.indexOf("For power users");
65 const ctas = body.indexOf('class="hero-actions');
66 const card = body.indexOf('class="hero-card');
7067 expect(ctas).toBeGreaterThan(-1);
71 expect(installPanel).toBeGreaterThan(-1);
72 expect(installLabel).toBeGreaterThan(-1);
73 // Install panel sits AFTER the CTAs — that's the whole U1 point.
74 expect(installPanel).toBeGreaterThan(ctas);
68 expect(card).toBeGreaterThan(ctas);
7569 });
7670});
7771
7872describe("Block U2 — button polish", () => {
7973 it("includes the universal hover-lift transform on .btn", async () => {
80 const body = await fetchHomeHtml();
74 const body = await fetchLayoutHtml();
8175 // The master CSS is inlined in <style> by Layout. We assert on the
8276 // hover-rule shape: `.btn:hover { … transform: translateY(-1px); … }`.
8377 expect(body).toMatch(/\.btn:hover\b[\s\S]*?transform:\s*translateY\(-1px\)/);
8478 });
8579
8680 it("uses a soft drop shadow on .btn:hover (not just border)", async () => {
87 const body = await fetchHomeHtml();
81 const body = await fetchLayoutHtml();
8882 expect(body).toMatch(/\.btn:hover\b[\s\S]*?box-shadow:\s*0\s+4px\s+12px/);
8983 });
9084
9185 it("uses box-shadow (not outline) for the .btn focus ring", async () => {
92 const body = await fetchHomeHtml();
86 const body = await fetchLayoutHtml();
9387 // Focus-visible rule must set box-shadow with the soft accent rgba.
9488 expect(body).toMatch(
9589 /\.btn:focus-visible\b[\s\S]*?box-shadow:\s*0\s+0\s+0\s+3px\s+rgba\(140,\s*109,\s*255,\s*0\.35\)/
9993 });
10094
10195 it("disables hover-lift on disabled buttons", async () => {
102 const body = await fetchHomeHtml();
96 const body = await fetchLayoutHtml();
10397 // The disabled rule sets transform:none AND opacity:0.5.
10498 expect(body).toMatch(/\.btn:disabled[\s\S]*?transform:\s*none/);
10599 expect(body).toMatch(/\.btn:disabled[\s\S]*?opacity:\s*0\.5/);
106100 });
107101
108102 it("shimmers the primary CTA via background-position transition", async () => {
109 const body = await fetchHomeHtml();
103 const body = await fetchLayoutHtml();
110104 // Primary button declares background-size 200% so the position
111105 // animation has somewhere to travel.
112106 expect(body).toMatch(/\.btn-primary\b[\s\S]*?background-size:\s*200%/);
119113
120114describe("Block U4 — view transitions", () => {
121115 it("includes the @view-transition opt-in", async () => {
122 const body = await fetchHomeHtml();
116 const body = await fetchLayoutHtml();
123117 expect(body).toContain("@view-transition");
124118 expect(body).toMatch(/@view-transition\s*\{\s*navigation:\s*auto;?\s*\}/);
125119 });
126120
127121 it("declares both fade-out and fade-in keyframes for ::view-transition-*", async () => {
128 const body = await fetchHomeHtml();
122 const body = await fetchLayoutHtml();
129123 expect(body).toContain("::view-transition-old(root)");
130124 expect(body).toContain("::view-transition-new(root)");
131125 expect(body).toContain("@keyframes vt-fade-out");
133127 });
134128
135129 it("disables the transition under prefers-reduced-motion", async () => {
136 const body = await fetchHomeHtml();
130 const body = await fetchLayoutHtml();
137131 // The reduced-motion block must mention the view-transition
138132 // pseudos and set animation-duration: 0s.
139133 expect(body).toMatch(
142136 });
143137
144138 it("attaches view-transition-name: root to body", async () => {
145 const body = await fetchHomeHtml();
139 const body = await fetchLayoutHtml();
146140 expect(body).toMatch(/\bbody\b\s*\{[^}]*view-transition-name:\s*root/);
147141 });
148142});
Modifiedsrc/__tests__/visual-coherence.test.tsx+10−6View fileUnifiedSplit
44 * Asserts the design-token + component contract introduced by the O3
55 * pass.
66 *
7 * 2026-06-10: token assertions moved from "/" to "/help" — the home
8 * route now serves the self-contained Landing2030Page without the
9 * master Layout CSS.
10 *
711 * NOTE: This test runs WITHOUT mock pollution — no `mock.module()`,
812 * no shared global state, no DB. It only hits in-process HTTP routes
913 * via `app.request()` and renders the canonical components directly
2529
2630describe("O3 — design token aliases in master CSS", () => {
2731 it("layout.tsx exposes the --space-* alias scale", async () => {
28 const res = await app.request("/");
32 const res = await app.request("/help");
2933 const body = await res.text();
3034 expect(body).toContain("--space-1:");
3135 expect(body).toContain("--space-2:");
3640 });
3741
3842 it("layout.tsx exposes the --radius-* alias scale", async () => {
39 const res = await app.request("/");
43 const res = await app.request("/help");
4044 const body = await res.text();
4145 expect(body).toContain("--radius-sm:");
4246 expect(body).toContain("--radius-md:");
4549 });
4650
4751 it("layout.tsx exposes the --font-size-* alias scale", async () => {
48 const res = await app.request("/");
52 const res = await app.request("/help");
4953 const body = await res.text();
5054 expect(body).toContain("--font-size-xs:");
5155 expect(body).toContain("--font-size-sm:");
5559 });
5660
5761 it("layout.tsx exposes the --leading-* aliases", async () => {
58 const res = await app.request("/");
62 const res = await app.request("/help");
5963 const body = await res.text();
6064 expect(body).toContain("--leading-tight:");
6165 expect(body).toContain("--leading-normal:");
6367 });
6468
6569 it("layout.tsx exposes the --z-* index scale", async () => {
66 const res = await app.request("/");
70 const res = await app.request("/help");
6771 const body = await res.text();
6872 expect(body).toContain("--z-nav:");
6973 expect(body).toContain("--z-modal:");
7175 });
7276
7377 it("ships the notice / email-preview / code-block utility classes", async () => {
74 const res = await app.request("/");
78 const res = await app.request("/help");
7579 const body = await res.text();
7680 expect(body).toContain(".notice");
7781 expect(body).toContain(".notice-warn");
7882