From 294f82ae0cad2317941062d6ffbafcbbf3f779e0 Mon Sep 17 00:00:00 2001
From: d3v07 <90106942+d3v07@users.noreply.github.com>
Date: Sun, 24 May 2026 14:39:29 -0400
Subject: [PATCH 01/21] =?UTF-8?q?test:=20integration=20coverage=20for=20se?=
=?UTF-8?q?ed=20=E2=86=92=20ChangeReportRepository=20boot=20path?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Addresses Copilot's only review finding on PR #11: the boot path that
wires loadSeeds() → buildApp({ seedChangeReports: getSeededChangeReports() })
had no automated test. Without this, the regression that PR #11 fixed
(lifecycle endpoints 404'ing on every seeded change id) could silently
reappear because:
- changes.lifecycle.test.ts seeds its own in-memory repo (doesn't touch
loadSeeds or getSeededChangeReports)
- billing.test.ts uses loadSeeds for org/user data but not change reports
- the --seed CLI integration test only covers createDevSeed()
New file: tests/api/seed-boot.test.ts (3 tests)
1. acknowledge succeeds on a seeded id when seedChangeReports is passed
2. acknowledge returns 404 when seedChangeReports is omitted (regression
guard — confirms the boot wiring is the only path keeping data flowing)
3. getSeededChangeReports() exposes the parsed seed contents
Full suite: 101/101 passing (was 98) · typecheck clean.
---
tests/api/seed-boot.test.ts | 62 +++++++++++++++++++++++++++++++++++++
1 file changed, 62 insertions(+)
create mode 100644 tests/api/seed-boot.test.ts
diff --git a/tests/api/seed-boot.test.ts b/tests/api/seed-boot.test.ts
new file mode 100644
index 0000000..aefd47a
--- /dev/null
+++ b/tests/api/seed-boot.test.ts
@@ -0,0 +1,62 @@
+import { describe, it, expect, beforeEach } from "vitest";
+import { resolve } from "node:path";
+import { buildApp } from "../../apps/api/src/server.js";
+import {
+ loadSeeds,
+ getSeededChangeReports,
+} from "../../apps/api/src/seed/loader.js";
+
+// Boot-path coverage for the fix in PR #11:
+// loadSeeds() populates the legacy ChangeReportStore; buildApp() must
+// hydrate the canonical ChangeReportRepository from the same data so
+// lifecycle endpoints find seeded ids instead of returning 404.
+//
+// Without this test, the regression Copilot flagged (lifecycle 404 on
+// every seeded change) could reappear silently.
+
+const BEARER = "Bearer demo_token_acme_corp_2026";
+const SEED_DIR = resolve(__dirname, "../../seed");
+const SEED_CHANGE_ID = "chg_seed_notion";
+
+beforeEach(() => {
+ loadSeeds({ seedDir: SEED_DIR });
+});
+
+describe("seed boot path hydrates ChangeReportRepository", () => {
+ it("acknowledge succeeds on a seeded change id", async () => {
+ const app = buildApp({ seedChangeReports: getSeededChangeReports() });
+
+ const res = await app.request(`/v1/changes/${SEED_CHANGE_ID}/acknowledge`, {
+ method: "POST",
+ headers: { Authorization: BEARER, "Content-Type": "application/json" },
+ body: JSON.stringify({ note: "Reviewed by vendor owner" }),
+ });
+
+ expect(res.status).toBe(200);
+ const body = (await res.json()) as { id: string; state: string; acknowledgedAt: string };
+ expect(body.id).toBe(SEED_CHANGE_ID);
+ expect(body.state).toBe("acknowledged");
+ expect(body.acknowledgedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
+ });
+
+ it("returns 404 when seed reports are NOT hydrated (regression guard)", async () => {
+ // Build WITHOUT seeding the canonical repo — this is the pre-fix behaviour
+ // and must always be a 404, not a 500. Confirms the boot wiring is the
+ // only thing standing between the seed data and the lifecycle routes.
+ const app = buildApp(); // no seedChangeReports → empty repo
+
+ const res = await app.request(`/v1/changes/${SEED_CHANGE_ID}/acknowledge`, {
+ method: "POST",
+ headers: { Authorization: BEARER, "Content-Type": "application/json" },
+ body: JSON.stringify({ note: "test" }),
+ });
+
+ expect(res.status).toBe(404);
+ });
+
+ it("getSeededChangeReports exposes the parsed seed contents", () => {
+ const reports = getSeededChangeReports();
+ expect(reports.length).toBeGreaterThan(0);
+ expect(reports.some((r) => r.id === SEED_CHANGE_ID)).toBe(true);
+ });
+});
From 8b5bba63ea5e249b1ecaf7ab4772cdd2a62ca760 Mon Sep 17 00:00:00 2001
From: d3v07 <90106942+d3v07@users.noreply.github.com>
Date: Sun, 24 May 2026 14:52:26 -0400
Subject: [PATCH 02/21] chore: apply changes
---
BUILD.md | 346 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 346 insertions(+)
create mode 100644 BUILD.md
diff --git a/BUILD.md b/BUILD.md
new file mode 100644
index 0000000..dfb0cc6
--- /dev/null
+++ b/BUILD.md
@@ -0,0 +1,346 @@
+# BUILD.md — Multi-Agent Execution Plan
+
+**Spec:** [plan.md](plan.md) (locked, do not re-debate during build).
+**Branch:** `saasb2b` · **Mode:** parallel where safe, sequential where required.
+
+---
+
+## Overview
+
+Five waves. **Strict file-ownership boundaries** prevent agent collisions
+(no two agents touch the same file in the same wave). **Hard gates** between
+waves: `pnpm typecheck && pnpm test` must be green before the next wave starts.
+**Max 3 concurrent agents per wave** (global rule: coordination cost dominates beyond that).
+
+```
+Wave 1 Foundation ───3 agents in parallel───► GATE: typecheck + grep clean
+Wave 2 App shell ───1 agent (sequential)──► GATE: shell renders, body.app-mode toggles
+Wave 3 Screens ───3 agents in parallel───► GATE: each route renders w/ seed data
+Wave 4 Cross-cutting ───2 agents in parallel───► GATE: ⌘K + role param both work
+Wave 5 Polish + review ───1 build + 1 reviewer───► GATE: AA clean, PR ready
+```
+
+---
+
+## Architecture Changes (file ownership matrix)
+
+| Path | Owner agent | Wave |
+|---|---|---|
+| `apps/web/src/styles/tokens.css` (new) | A | 1 |
+| `apps/web/src/styles/app.css` (new) | A | 1 |
+| `apps/web/src/styles.css` (delete) | A | 1 |
+| `apps/api/src/db/change-reports.ts` (delete) | B | 1 |
+| `apps/api/src/db/changeReports.ts` | B | 1 |
+| `apps/api/src/seed/loader.ts` | B | 1 |
+| `apps/api/src/routes/changes.ts` | B | 1 |
+| `public/app/**/*` (delete all) | C | 1 |
+| `apps/web/public/app/**/*` (delete all) | C | 1 |
+| Global "Unsyphn" → "Redline" (grep -ril) | C | 1 |
+| `apps/web/src/App.tsx` | D | 2 |
+| `apps/web/index.html` | D | 2 |
+| `apps/web/src/main.tsx` | D | 2 |
+| `apps/web/src/lib/role.ts` (new) | D | 2 |
+| `apps/web/src/screens/Portfolio.tsx` (new) | E | 3 |
+| `apps/web/src/components/VendorCard.tsx` (new) | E | 3 |
+| `apps/web/src/components/FleetStats.tsx` (new) | E | 3 |
+| `apps/web/src/screens/ChangeReport.tsx` (new) | F | 3 |
+| `apps/web/src/components/Drawer.tsx` (new) | F | 3 |
+| `apps/web/src/components/SeverityBadge.tsx` (new) | F | 3 |
+| `apps/web/src/screens/SensoBrief.tsx` (restyle) | G | 3 |
+| `apps/web/src/screens/StripeModal.tsx` (UX-1 fix) | G | 3 |
+| `apps/web/src/screens/Onboarding.tsx` (new, replaces VendorOnboarding) | G | 3 |
+| `apps/api/src/routes/evidence.ts` (add `/bundle.html`) | G | 3 |
+| `apps/web/src/components/CommandPalette.tsx` (new) | H | 4 |
+| `apps/web/src/lib/keyboard.ts` (new) | H | 4 |
+| `apps/web/src/components/RoleSwitcher.tsx` (new) | I | 4 |
+| `apps/web/src/lib/role.ts` (extend) | I | 4 |
+| `PAGE_AUDIT.md` (refresh) | J | 5 |
+
+**No path appears under more than one agent in the same wave.**
+Cross-wave reuse is fine because of hard gates.
+
+---
+
+## Implementation Phases
+
+### Wave 1 — Foundation (3 parallel)
+
+Run all three in one message. None depends on the others. Each has a tight,
+self-contained brief.
+
+#### Agent A — Design tokens & global CSS
+- **Type:** `frontend-architect`
+- **Owns:** `apps/web/src/styles/tokens.css`, `apps/web/src/styles/app.css`
+- **Brief:**
+ > Create `apps/web/src/styles/tokens.css` with the exact token block from
+ > [plan.md](plan.md) §1.1. Create `apps/web/src/styles/app.css` with global
+ > resets, body defaults (Helvetica Neue, font-weight 300), and `body.app-mode`
+ > dark scope. Delete `apps/web/src/styles.css`. Do NOT touch React components
+ > (the shell wave imports the new CSS). Verify: file paths exist, no other
+ > files modified.
+- **Risk:** Low (additive + one delete).
+
+#### Agent B — API bug fixes
+- **Type:** `backend-architect`
+- **Owns:** `apps/api/src/db/change-reports.ts`, `apps/api/src/db/changeReports.ts`, `apps/api/src/seed/loader.ts`, `apps/api/src/routes/changes.ts`
+- **Brief:**
+ > Fix the lifecycle 404 bug documented in REGRESSION_REPORT.md §A2. The
+ > legacy `db/change-reports.ts` and canonical `db/changeReports.ts` are two
+ > stores. Migrate `seed/loader.ts` and `routes/changes.ts` to use the
+ > canonical repo only. Delete `db/change-reports.ts`. Verify with
+ > `pnpm test` (lifecycle tests must pass on seeded data) and
+ > `curl -X POST .../v1/changes/chg_seed_notion/acknowledge` returns 200.
+- **Risk:** Medium (touches production state machine; tests are the safety net).
+
+#### Agent C — Static demo delete + brand rename
+- **Type:** `general-purpose`
+- **Owns:** `public/app/**/*`, `apps/web/public/app/**/*`, all "Unsyphn" occurrences
+- **Brief:**
+ > Two mechanical sweeps. (1) Delete `public/app/` and `apps/web/public/app/`
+ > directories entirely — audit confirmed non-load-bearing. (2) Global rename
+ > "Unsyphn" → "Redline" across `apps/`, `public/`, `packages/`. Use case-aware
+ > replacement (Unsyphn / UNSYPHN / unsyphn → Redline / REDLINE / redline).
+ > Skip `node_modules`, `dist`, `.git`. Verify: `grep -ril "unsyphn"` returns
+ > zero. Do NOT change any logo asset references yet (logo stays as
+ > `unsyphlogo.png` until Wave 2 swaps it).
+- **Risk:** Medium (broad search-replace; verify with grep before declaring done).
+
+**Wave 1 GATE (run after all three return):**
+```bash
+pnpm typecheck # must be 0 errors
+pnpm test # 98+ tests pass (B added lifecycle coverage)
+grep -ril "unsyphn" apps/ public/ packages/ # must be empty
+test ! -d public/app # must not exist
+test ! -d apps/web/public/app # must not exist
+test -f apps/web/src/styles/tokens.css
+test ! -f apps/web/src/styles.css
+```
+
+---
+
+### Wave 2 — App shell (1 agent)
+
+#### Agent D — Routes, mount, role plumbing
+- **Type:** `frontend-architect`
+- **Owns:** `apps/web/src/App.tsx`, `apps/web/index.html`, `apps/web/src/main.tsx`, `apps/web/src/lib/role.ts` (new)
+- **Brief:**
+ > Rewrite `App.tsx` against the 6-route IA in [plan.md](plan.md) §2:
+ > `/app` (Portfolio), `/app/vendor/:id`, `/app/change/:id`, `/app/evidence/:id`,
+ > `/app/policy`, `/app/onboarding`, `/app/settings`. Each route is a
+ > placeholder screen for this wave (`
Portfolio coming in W3
`).
+ > Add top bar with brand mark + nav + role dropdown slot. Import
+ > `styles/tokens.css` and `styles/app.css` in `main.tsx`. Toggle
+ > `body.app-mode` on `/app/*` routes via `useEffect`. Create
+ > `lib/role.ts` exporting `parseRole(search): Role` and `useRole(): Role`
+ > hook (reads `?role=`). Verify: every route returns 200, body class toggles
+ > correctly, landing at `/` unaffected.
+- **Risk:** Medium (router is the spine; placeholder screens prevent over-scoping).
+
+**Wave 2 GATE:**
+```bash
+pnpm dev:web &
+sleep 3
+curl -s localhost:4004/ | grep -q "Redline" # landing renders
+curl -s localhost:4004/app | grep -q "Portfolio" # placeholder route works
+# manual: body.app-mode applies on /app, not on /
+```
+
+---
+
+### Wave 3 — Screens (3 parallel)
+
+All three depend on Wave 2 (shell exists, tokens loaded). They own disjoint screens
+and components.
+
+#### Agent E — Portfolio + Vendor cards
+- **Type:** `frontend-architect`
+- **Owns:** `apps/web/src/screens/Portfolio.tsx`, `apps/web/src/components/VendorCard.tsx`, `apps/web/src/components/FleetStats.tsx`
+- **Brief:**
+ > Implement Portfolio per [plan.md](plan.md) §3 step 1. Fleet stats strip
+ > (`143 vendors · 12 changes · 3 P1 · $84k at-risk`) pulls from
+ > `/v1/dashboard/summary` (already exists). Vendor card grid (6–8 cards)
+ > with posture chip, renewal date, owner avatar. Click vendor →
+ > `/app/vendor/:id`. Click change chip → `/app/change/:id`. Use tokens from
+ > Wave 1. No new API endpoints — read what exists.
+- **Risk:** Low.
+
+#### Agent F — ChangeReport + Drawer + lifecycle wiring
+- **Type:** `frontend-architect`
+- **Owns:** `apps/web/src/screens/ChangeReport.tsx`, `apps/web/src/components/Drawer.tsx`, `apps/web/src/components/SeverityBadge.tsx`
+- **Brief:**
+ > Port `public/app/screen-change.jsx` to React TSX with new tokens. Header
+ > (vendor + severity badge), diff cards with citations, 4 lifecycle action
+ > buttons (acknowledge/snooze/resolve/escalate). Wire to the canonical
+ > `/v1/changes/:id/*` endpoints (Wave 1 Agent B fixed these). Escalate
+ > opens a modal (use Drawer component). Render at `/app/change/:id`.
+- **Risk:** Medium (lifecycle integration — verify with seed data).
+
+#### Agent G — Evidence + Bundle + Onboarding + Stripe fix
+- **Type:** `frontend-architect`
+- **Owns:** `apps/web/src/screens/SensoBrief.tsx`, `apps/web/src/screens/StripeModal.tsx`, `apps/web/src/screens/Onboarding.tsx` (new), `apps/api/src/routes/evidence.ts`
+- **Brief:**
+ > Three jobs.
+ > (1) Restyle `SensoBrief.tsx` to new tokens; preserve print stylesheet.
+ > Add "Generate Compliance Bundle" button.
+ > (2) Add `GET /v1/evidence/:id/bundle.html` to `routes/evidence.ts` —
+ > server-rendered printable HTML (ChangeReport + citations + routed actions).
+ > (3) Create `Onboarding.tsx` with 4 tier cards (Team/Business/Enterprise +
+ > Compliance Pack add-on per PDF §10). CTA opens existing `StripeModal`.
+ > Fix `StripeModal` UX-1 (× button inert in already-entitled branch) —
+ > one wire fix from REGRESSION_REPORT.
+- **Risk:** Medium (touches API + 3 React files; all logic boundaries are well-defined).
+
+**Wave 3 GATE:**
+```bash
+pnpm typecheck # 0 errors
+pnpm test # all green
+pnpm build # ≤ 320kB JS bundle
+# manual via preview MCP:
+# /app shows Portfolio with seed data
+# /app/change/chg_seed_notion shows ChangeReport, acknowledge button returns 200
+# /app/evidence/chg_seed_notion renders Senso brief
+# /app/onboarding shows 4 tier cards; Stripe modal opens + closes
+```
+
+---
+
+### Wave 4 — Cross-cutting (2 parallel)
+
+#### Agent H — Command palette + keyboard
+- **Type:** `frontend-architect`
+- **Owns:** `apps/web/src/components/CommandPalette.tsx`, `apps/web/src/lib/keyboard.ts`
+- **Brief:**
+ > Global `⌘K` palette. Items: Jump to vendor (fuzzy match against
+ > `/v1/dashboard/summary` vendor list), Open recent change, Switch role,
+ > Generate Bundle, Open settings. Up/Down navigates, Enter executes,
+ > Esc closes. Mount in App.tsx via React portal so it overlays any route.
+ > Keyboard registry must respect text input focus (don't fire ⌘K when typing).
+- **Risk:** Low (purely additive overlay).
+
+#### Agent I — Role switcher
+- **Type:** `frontend-architect`
+- **Owns:** `apps/web/src/components/RoleSwitcher.tsx`, `apps/web/src/lib/role.ts` (extend)
+- **Brief:**
+ > Top-bar dropdown (Procurement / Legal / Security / Finance). Selecting
+ > a role updates `?role=` query param via `history.replaceState`. Hook
+ > `useRole()` (created in Wave 2) returns current role. Three of four
+ > views can show placeholder copy in their screens for v1 — only
+ > Procurement is real. Mount into the App.tsx top bar slot Agent D left.
+- **Risk:** Low.
+
+**Wave 4 GATE:**
+```bash
+pnpm typecheck && pnpm test && pnpm build
+# manual: ⌘K opens palette from /app and /app/vendor/notion; role dropdown
+# updates URL; back/forward preserves role
+```
+
+---
+
+### Wave 5 — Polish + review
+
+#### Agent J — A11y + audit refresh
+- **Type:** `quality-engineer`
+- **Owns:** any file with an a11y violation; `PAGE_AUDIT.md`
+- **Brief:**
+ > Run axe on every route. Fix: missing labels, contrast violations,
+ > focus traps in drawer + palette, keyboard reach for all interactive
+ > elements, focus restore on modal close. Refresh `PAGE_AUDIT.md`
+ > to reflect new IA. Update score card. **Do not refactor unrelated
+ > code.** Report violations fixed, count by severity.
+- **Risk:** Low.
+
+#### Code review (final, by reviewer agent — not parallel with J)
+- **Type:** `code-reviewer`
+- **Brief:**
+ > Review the full `saasb2b` branch diff vs `main`. Focus: security
+ > (Stripe path, evidence public route auth, SSE token handling),
+ > silent failures, mock leftovers, dead code, TASTE check
+ > (Slate × Seven applied or drifted?). Block on CRITICAL; else
+ > produce sign-off table.
+
+**Wave 5 GATE (release):**
+- `pnpm typecheck && pnpm test && pnpm build` all green
+- `grep -ril "unsyphn"` empty
+- axe AA clean on every route
+- 3-min demo script runs end-to-end without dead ends
+- `code-reviewer` returns no CRITICAL findings
+- PR opened, branch ready to merge
+
+---
+
+## Concurrency rules
+
+1. **Max 3 agents in any one message.** Coordination cost beats marginal speed past 3.
+2. **No file appears under two owners in the same wave.** Re-read the matrix before dispatch.
+3. **Each agent gets its file list verbatim in its brief.** Agents must error rather than touch unlisted paths.
+4. **Hard gate between waves.** Do not start Wave N+1 until Wave N gate passes.
+5. **Background allowed, not required.** Wave 1 agents can run foreground (we need their work before Wave 2).
+6. **Worktree isolation NOT used** — file boundaries already prevent collisions; worktree adds merge cost without benefit here.
+
+---
+
+## Testing strategy
+
+### Per-wave verification
+- **Wave 1:** typecheck, full test suite, grep for forbidden strings, file-existence asserts.
+- **Wave 2:** dev server boots, both routes (landing + placeholder app) render, body class toggles.
+- **Wave 3:** typecheck + tests + build size budget; manual preview MCP walk of 4 routes.
+- **Wave 4:** typecheck + tests; manual ⌘K + role-switcher flow.
+- **Wave 5:** axe AA clean + code-reviewer sign-off.
+
+### Test coverage targets (global rules)
+- Unit (Vitest): `lib/role.ts` parser, `keyboard.ts` registry, `lib/evidence-bundle.ts` HTML render. Add tests when shape stabilizes (Wave 4+).
+- Integration: lifecycle endpoint (already tested; Agent B widens coverage).
+- E2E: 3-min demo script as a Playwright test (Wave 5, optional but high value).
+
+### What we don't test pre-build
+- Layout pixel-perfection — visual via preview MCP screenshots is enough.
+- Static screen content — covered by snapshot if it stabilizes; otherwise manual.
+
+---
+
+## Risks & Mitigations
+
+| Risk | Likelihood | Impact | Mitigation |
+|---|---|---|---|
+| Agents collide on shared file | Low (matrix prevents it) | High (lost work) | File-ownership matrix; each brief lists owned paths explicitly. |
+| Agent B's store migration breaks lifecycle in prod path | Medium | High | `pnpm test` covers it; Wave 1 gate requires green tests. Roll back single commit if red. |
+| Brand rename catches false positives ("Unsync", etc.) | Low | Low | Case-aware replace, exclude `node_modules`. Grep verifies cleanup. |
+| Wave 3 agents pull conflicting design decisions | Medium | Medium | Tokens are the single source of truth from Wave 1. Each agent reads plan.md §1 before starting. |
+| StripeModal UX-1 fix has unseen side effects | Low | Medium | Existing webhook tests in place; manual verification of both branches (entitled vs not). |
+| Static demo deletion removes something React app secretly imports | Low (audit checked) | Medium | Wave 1 ends with typecheck — failure indicates a hidden import; restore selectively. |
+| Wave 4 keyboard registry conflicts with native shortcuts | Medium | Low | Scope to `/app/*` routes; honor `e.target.tagName === "INPUT"`; document conflicts. |
+| Wave 5 a11y agent over-reaches and refactors | Medium | Medium | Brief is explicit: fix only violations, don't refactor. Reviewer agent catches it. |
+
+---
+
+## Success criteria
+
+The build is done when, in a single `pnpm dev` session:
+
+1. `/` renders the existing landing page on dark Slate paint.
+2. `/app` renders Portfolio with fleet stats and 6+ vendor cards.
+3. Clicking a vendor card → vendor detail (placeholder OK for v1).
+4. Clicking a change chip → `/app/change/:id` with working acknowledge/escalate.
+5. Escalate flow opens drawer → modal → confirmation → `/app/evidence/:id`.
+6. Evidence brief renders with Senso URL semantics + Generate Bundle button.
+7. Bundle button serves a print-ready HTML page.
+8. `/app/onboarding` → 4 tier cards → Stripe modal → test-mode checkout → success state with × that closes.
+9. `⌘K` opens palette anywhere in `/app/*`.
+10. Role dropdown updates `?role=` and back/forward preserves it.
+11. `pnpm typecheck && pnpm test && pnpm build` green.
+12. No "Unsyphn" string anywhere.
+13. axe AA clean on every route.
+14. `code-reviewer` agent returns no CRITICAL.
+
+---
+
+## STOP — awaiting your call
+
+Reply with one of:
+- **`yes`** / **`proceed`** → I dispatch Wave 1 (Agents A, B, C in parallel).
+- **`modify: `** → I revise this orchestration and stop again.
+- **`abort`** → drop it.
+
+I will not dispatch any agent until you confirm.
From a7fbd7b7dc694365e11cb0db3a569d9ed6068cfa Mon Sep 17 00:00:00 2001
From: d3v07 <90106942+d3v07@users.noreply.github.com>
Date: Sun, 24 May 2026 15:42:21 -0400
Subject: [PATCH 03/21] chore: apply changes
---
.claude/launch.json | 2 +-
BUILD.md | 16 +-
PAGE_AUDIT.md | 12 +-
README.md | 40 +-
REGRESSION_REPORT.md | 4 +-
apps/api/src/app.ts | 4 +-
apps/api/src/db/change-reports.ts | 29 -
apps/api/src/db/changeReports.ts | 9 +
apps/api/src/routes/evidence.ts | 413 +++++-
apps/api/src/seed/loader.ts | 2 -
apps/api/src/server.ts | 6 +-
apps/web/index.html | 32 +-
apps/web/public/app/app.css | 484 -------
apps/web/public/app/app.jsx | 207 ---
apps/web/public/app/app2.css | 1443 --------------------
apps/web/public/app/brand.jsx | 30 -
apps/web/public/app/browser-window.jsx | 114 --
apps/web/public/app/escalate.jsx | 96 --
apps/web/public/app/index.html | 69 -
apps/web/public/app/live.js | 187 ---
apps/web/public/app/routing.jsx | 40 -
apps/web/public/app/screen-change.jsx | 358 -----
apps/web/public/app/screen-evidence.jsx | 201 ---
apps/web/public/app/screen-onboarding.jsx | 351 -----
apps/web/public/app/screen-portfolio.jsx | 163 ---
apps/web/public/app/shared.jsx | 142 --
apps/web/public/app/tokens.css | 215 ---
apps/web/public/app/vendor-data.jsx | 578 --------
apps/web/src/App.tsx | 225 +--
apps/web/src/components/CommandPalette.tsx | 423 ++++++
apps/web/src/components/Drawer.tsx | 161 +++
apps/web/src/components/FleetStats.tsx | 112 ++
apps/web/src/components/RoleSwitcher.tsx | 177 +++
apps/web/src/components/SeverityBadge.tsx | 19 +
apps/web/src/components/VendorCard.tsx | 152 +++
apps/web/src/lib/keyboard.ts | 53 +
apps/web/src/lib/role.ts | 34 +
apps/web/src/main.tsx | 3 +-
apps/web/src/screens/ChangeReport.tsx | 485 +++++++
apps/web/src/screens/Onboard.tsx | 2 +-
apps/web/src/screens/Onboarding.tsx | 310 +++++
apps/web/src/screens/Portfolio.tsx | 142 ++
apps/web/src/screens/SensoBrief.tsx | 46 +-
apps/web/src/screens/StripeModal.tsx | 187 ++-
apps/web/src/screens/VendorOnboarding.tsx | 163 ---
apps/web/src/styles.css | 310 -----
apps/web/src/styles/app.css | 267 ++++
apps/web/src/styles/brief.css | 344 +++--
apps/web/src/styles/tokens.css | 70 +
apps/web/vite.config.ts | 53 +-
plan.md | 6 +-
public/app/app.css | 484 -------
public/app/app.jsx | 207 ---
public/app/app2.css | 1443 --------------------
public/app/brand.jsx | 30 -
public/app/browser-window.jsx | 114 --
public/app/escalate.jsx | 96 --
public/app/index.html | 69 -
public/app/live.js | 187 ---
public/app/routing.jsx | 40 -
public/app/screen-change.jsx | 358 -----
public/app/screen-evidence.jsx | 201 ---
public/app/screen-onboarding.jsx | 351 -----
public/app/screen-portfolio.jsx | 163 ---
public/app/shared.jsx | 142 --
public/app/tokens.css | 215 ---
public/app/vendor-data.jsx | 578 --------
public/index.html | 32 +-
tests/api/evidence.test.ts | 16 +
tests/api/seed-boot.test.ts | 8 +-
tests/web-static/live-sidecar.test.ts | 126 --
tests/web-static/static.test.ts | 110 --
72 files changed, 3485 insertions(+), 10476 deletions(-)
delete mode 100644 apps/api/src/db/change-reports.ts
delete mode 100644 apps/web/public/app/app.css
delete mode 100644 apps/web/public/app/app.jsx
delete mode 100644 apps/web/public/app/app2.css
delete mode 100644 apps/web/public/app/brand.jsx
delete mode 100644 apps/web/public/app/browser-window.jsx
delete mode 100644 apps/web/public/app/escalate.jsx
delete mode 100644 apps/web/public/app/index.html
delete mode 100644 apps/web/public/app/live.js
delete mode 100644 apps/web/public/app/routing.jsx
delete mode 100644 apps/web/public/app/screen-change.jsx
delete mode 100644 apps/web/public/app/screen-evidence.jsx
delete mode 100644 apps/web/public/app/screen-onboarding.jsx
delete mode 100644 apps/web/public/app/screen-portfolio.jsx
delete mode 100644 apps/web/public/app/shared.jsx
delete mode 100644 apps/web/public/app/tokens.css
delete mode 100644 apps/web/public/app/vendor-data.jsx
create mode 100644 apps/web/src/components/CommandPalette.tsx
create mode 100644 apps/web/src/components/Drawer.tsx
create mode 100644 apps/web/src/components/FleetStats.tsx
create mode 100644 apps/web/src/components/RoleSwitcher.tsx
create mode 100644 apps/web/src/components/SeverityBadge.tsx
create mode 100644 apps/web/src/components/VendorCard.tsx
create mode 100644 apps/web/src/lib/keyboard.ts
create mode 100644 apps/web/src/lib/role.ts
create mode 100644 apps/web/src/screens/ChangeReport.tsx
create mode 100644 apps/web/src/screens/Onboarding.tsx
create mode 100644 apps/web/src/screens/Portfolio.tsx
delete mode 100644 apps/web/src/screens/VendorOnboarding.tsx
delete mode 100644 apps/web/src/styles.css
create mode 100644 apps/web/src/styles/app.css
create mode 100644 apps/web/src/styles/tokens.css
delete mode 100644 public/app/app.css
delete mode 100644 public/app/app.jsx
delete mode 100644 public/app/app2.css
delete mode 100644 public/app/brand.jsx
delete mode 100644 public/app/browser-window.jsx
delete mode 100644 public/app/escalate.jsx
delete mode 100644 public/app/index.html
delete mode 100644 public/app/live.js
delete mode 100644 public/app/routing.jsx
delete mode 100644 public/app/screen-change.jsx
delete mode 100644 public/app/screen-evidence.jsx
delete mode 100644 public/app/screen-onboarding.jsx
delete mode 100644 public/app/screen-portfolio.jsx
delete mode 100644 public/app/shared.jsx
delete mode 100644 public/app/tokens.css
delete mode 100644 public/app/vendor-data.jsx
delete mode 100644 tests/web-static/live-sidecar.test.ts
delete mode 100644 tests/web-static/static.test.ts
diff --git a/.claude/launch.json b/.claude/launch.json
index 3d042e0..465bf25 100644
--- a/.claude/launch.json
+++ b/.claude/launch.json
@@ -13,7 +13,7 @@
"name": "web",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["--filter", "@redline/web", "dev"],
- "port": 4004,
+ "port": 4321,
"autoPort": false
}
]
diff --git a/BUILD.md b/BUILD.md
index dfb0cc6..0897d60 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -35,7 +35,7 @@ Wave 5 Polish + review ───1 build + 1 reviewer───► GATE: AA cl
| `apps/api/src/routes/changes.ts` | B | 1 |
| `public/app/**/*` (delete all) | C | 1 |
| `apps/web/public/app/**/*` (delete all) | C | 1 |
-| Global "Unsyphn" → "Redline" (grep -ril) | C | 1 |
+| Global "Redline" → "Redline" (grep -ril) | C | 1 |
| `apps/web/src/App.tsx` | D | 2 |
| `apps/web/index.html` | D | 2 |
| `apps/web/src/main.tsx` | D | 2 |
@@ -94,13 +94,13 @@ self-contained brief.
#### Agent C — Static demo delete + brand rename
- **Type:** `general-purpose`
-- **Owns:** `public/app/**/*`, `apps/web/public/app/**/*`, all "Unsyphn" occurrences
+- **Owns:** `public/app/**/*`, `apps/web/public/app/**/*`, all "Redline" occurrences
- **Brief:**
> Two mechanical sweeps. (1) Delete `public/app/` and `apps/web/public/app/`
> directories entirely — audit confirmed non-load-bearing. (2) Global rename
- > "Unsyphn" → "Redline" across `apps/`, `public/`, `packages/`. Use case-aware
- > replacement (Unsyphn / UNSYPHN / unsyphn → Redline / REDLINE / redline).
- > Skip `node_modules`, `dist`, `.git`. Verify: `grep -ril "unsyphn"` returns
+ > "Redline" → "Redline" across `apps/`, `public/`, `packages/`. Use case-aware
+ > replacement (Redline / REDLINE / redline → Redline / REDLINE / redline).
+ > Skip `node_modules`, `dist`, `.git`. Verify: `grep -ril "redline"` returns
> zero. Do NOT change any logo asset references yet (logo stays as
> `unsyphlogo.png` until Wave 2 swaps it).
- **Risk:** Medium (broad search-replace; verify with grep before declaring done).
@@ -109,7 +109,7 @@ self-contained brief.
```bash
pnpm typecheck # must be 0 errors
pnpm test # 98+ tests pass (B added lifecycle coverage)
-grep -ril "unsyphn" apps/ public/ packages/ # must be empty
+grep -ril "redline" apps/ public/ packages/ # must be empty
test ! -d public/app # must not exist
test ! -d apps/web/public/app # must not exist
test -f apps/web/src/styles/tokens.css
@@ -261,7 +261,7 @@ pnpm typecheck && pnpm test && pnpm build
**Wave 5 GATE (release):**
- `pnpm typecheck && pnpm test && pnpm build` all green
-- `grep -ril "unsyphn"` empty
+- `grep -ril "redline"` empty
- axe AA clean on every route
- 3-min demo script runs end-to-end without dead ends
- `code-reviewer` returns no CRITICAL findings
@@ -330,7 +330,7 @@ The build is done when, in a single `pnpm dev` session:
9. `⌘K` opens palette anywhere in `/app/*`.
10. Role dropdown updates `?role=` and back/forward preserves it.
11. `pnpm typecheck && pnpm test && pnpm build` green.
-12. No "Unsyphn" string anywhere.
+12. No "Redline" string anywhere.
13. axe AA clean on every route.
14. `code-reviewer` agent returns no CRITICAL.
diff --git a/PAGE_AUDIT.md b/PAGE_AUDIT.md
index 1e05477..22b6ea3 100644
--- a/PAGE_AUDIT.md
+++ b/PAGE_AUDIT.md
@@ -1,4 +1,4 @@
-# Page Audit — Unsyphn B2B SaaS
+# Page Audit — Redline B2B SaaS
Generated: 2026-05-24 · Branch: production
Status legend: `[ok]` working · `[brk]` broken/dead · `[wrn]` works but ugly/inconsistent · `[na]` not in scope
@@ -46,7 +46,7 @@ flows route to `/app/` (the static Babel-JSX demo).
### A.1 Nav
- A.1.1 [wrn] Brand mark `.nav .brand .mark` — conic-gradient circle, no logo image — **fix**: replace with `
` from `unsyphlogo.png`
-- A.1.2 [ok] Brand text "UNSYPHN / Subscription OS"
+- A.1.2 [ok] Brand text "REDLINE / Subscription OS"
- A.1.3 [brk] Product link — `href="#"` dead
- A.1.4 [brk] Vault link — `href="#"` dead
- A.1.5 [brk] Pricing link — `href="#"` dead
@@ -149,13 +149,13 @@ flows route to `/app/` (the static Babel-JSX demo).
- **K.0** [**CRITICAL**] `apps/web/index.html` missing `#root` + main.tsx script — entire React tree unreachable
- K.1 [brk] Default route "/" falls to `` = Add Vendor (not a dashboard)
-- K.4 [brk] Header brand text `.app__mark` says **"Redline"** — mismatch with landing "UNSYPHN"
+- K.4 [brk] Header brand text `.app__mark` says **"Redline"** — mismatch with landing "REDLINE"
- K.5 [ok] Header nav buttons wired
- K.7 [wrn] Breadcrumb is ``, should be `