24 KiB
PR3 Recon — SCREEN 01 · Home / Cockpit (ship Variation A "Editorial")
Maps the current Home/Cockpit implementation against the warm-Hive design spec for SCREEN 01. Scope: what renders today, the live data contract, the test contract that must keep passing, the Editorial target with exact copy, the gap list, and the reuse/build plan.
Primary files:
- Current UI:
apps/web/src/components/os/apps/HomeCockpit.tsx(the real Home; this is the one PR3 rebuilds) - Route wrapper:
apps/web/src/routes/HomeRoute.tsx CockpitApp.tsx(apps/web/src/components/os/apps/CockpitApp.tsx) is a separate system-health dashboard (Mission-Control-style), NOT the Home surface — see note in §1.- Server contract:
packages/server/src/local/routes/home.ts - FE types:
apps/web/src/lib/types.ts:266-331 - Test contract:
apps/web/src/test/p2-home-desktop.test.tsx - Design:
docs/design_handoff_waggle_app/SCREENS.md§"01 · Home / Cockpit" +design-files/screens/home.html(view#view-a)
1. Current structure — what HomeCockpit.tsx renders today (section by section)
HomeCockpit is the default /home surface (HomeRoute.tsx:13). It is a single
scrolling column, p-6 max-w-3xl mx-auto on the sub-states and max-w-4xl mx-auto on
the normal render (HomeCockpit.tsx:580). Data loads in load()
(HomeCockpit.tsx:467-497): adapter.getHomeBriefing() then a best-effort
adapter.getHomeOvernight(); deferred until useService().connecting settles
(cold-load 401 race guard, :499-507).
Render states (root, :509-650):
- Loading —
CockpitSkeleton(:84-98),data-testid="home-cockpit-loading". - Permission denied (403) —
:514-527,data-testid="home-cockpit-permission-denied". - Load error / offline —
:531-554,data-testid="home-cockpit-error"+ Retry. - First-run empty —
FirstRunEmpty(:101-125),data-testid="home-cockpit-empty", "Create your first workspace" CTA. - Normal —
:579-649,data-testid="home-cockpit".
Normal render, top to bottom:
GreetingHeader(:136-159, rendered:581): H1 =briefing.greeting(text-2xl font-display font-bold), sub =formatBriefingDate(briefing.date)rendered as a human date (:130-134). Right side: optional offline "Local only" pill + a staticCtrl+Kchip (:153-156). No mono date row, no live dot, no streak chip.- Attention banner (
:583-599,data-testid="home-cockpit-attention-banner"): only whenovernight.failures.length > 0. Honey-tintedrole="alert". - J08 review banner (
:601-625,data-testid="home-cockpit-review-banner"): only whenbriefing.needsReviewCount > 0; "N imported memories need your review" + Review CTA that dispatcheswaggle:open-app {appId:'memory', filter:'unreviewed'}(:573-577). RecentWorkspacesPanel(:162-227, rendered:627-632): section head "You were working on" (:174-176), 2-col grid (sm:grid-cols-2). Each card: name +groupchip, optional 2-linesummary,lastActiverelative time +pendingCount"pending" warning, a "Continue →" button (:208-215), and aWorkspaceActionsMenukebab (G1 rename/archive/delete). Card body click →onOpenDesktop. Returns null when 0 cards.OvernightPanel(:230-288, rendered:637, suppressed when offline): section head "Overnight", a 3-counter grid (Memories consolidated / Artifacts created / Automations completed), then a failures block (deep-links each failure to the Automation Center logs,:271-280). Hidden when no activity.SuggestedActionsPanel(:321-343, rendered:639): section head "Suggested next actions", wrap of pill buttons frombriefing.suggestedActions, each →onContinue(a.workspaceId, a.sessionId).UpNextPanel(:291-318, rendered:643): section head "Up next", list of up to 6 items (event/task/schedule icons). Returns null when 0 items.QuickCapturePanel(:346-434, rendered:645): "Quick capture" — a 4-kind segmented selector (note/task/link/file) + text input + Capture button →adapter.quickCapture(). Not in the Editorial design.ActiveModelsTile(:437-448, rendered:647): tiny "Active models: …" chips row whenbriefing.activeModelspresent.
Styling today: uses shadcn semantic classes (bg-secondary/30, border-border/30,
text-muted-foreground) and inline var(--sem-intelligence) / --sem-attention /
--sem-work / --sem-healthy / --sem-risk semantic tokens. Layout is dense and
utilitarian (10-13px text, rounded-xl, compact panels) — the opposite of the
Editorial spec's calm, large-type, story-led 920px column.
Note on
CockpitApp.tsx: despite the name, this is the system-health/ops dashboard (System Health, Cost, Memory Weaver, Cron, Connectors, Audit Trail, ComplianceDashboard) on a 30s refresh. It is not the Home surface and not in scope for SCREEN 01 — it corresponds to "Mission Control" (SCREENS §16) / "surfaces" (§08). Listed in the task only to disambiguate; PR3 Home work happens entirely inHomeCockpit.tsx.
2. Data contract — HomeBriefing shape + feeding routes (real vs mocked)
Types (FE mirror apps/web/src/lib/types.ts:266-331; server home.ts:43-95)
interface HomeBriefing {
greeting: string; // server builds via buildTimeAwareGreeting + personalizeGreeting (home.ts:401-407)
userName?: string; // from IdentityLayer on personal mind (home.ts:261-270); optional
date: string; // now.toISOString() (home.ts:412) — RAW ISO, FE must humanize
recentWorkspaces: RecentWorkspaceCard[];
suggestedActions: SuggestedAction[];
upNext: UpNextItem[];
activeModels?: string[]; // OPTIONAL — server briefing NEVER populates it (no producer in home.ts)
isFirstRun: boolean; // ranked.length === 0 (home.ts:418)
needsReviewCount?: number; // J08 unreviewed personal-mind frames (home.ts:382-389)
}
interface RecentWorkspaceCard {
id; name; group; // group = workspace.group string
summary?; // state.recentDecisions[0].content (home.ts:314-316)
lastActive: string; // ISO; ranking timestamp
pendingCount: number; // state.pending.length + state.blocked.length
continueSessionId?; // NOTE: server never sets it (home.ts card builder omits) → Continue has no session seed
}
interface SuggestedAction { label; workspaceId; sessionId?; kind; } // kind always 'next-action' from server
interface UpNextItem { id; label; workspaceId?; at?; kind: 'event'|'task'|'schedule'; }
// server only ever emits kind:'schedule' from cron; 'event'/'task' are type-only
// server NEVER sets `at` → UpNextPanel's time column is always empty today
interface OvernightSummary { // GET /api/home/overnight (separate call)
consolidated; artifactsCreated; automationsCompleted;
failures: OvernightFailure[]; // {id,label,automationId?,error,at} from cron execution history
window?: { from; to };
}
Routes feeding Home
| Route | Method | Adapter | Source / realness |
|---|---|---|---|
/api/home/briefing |
GET | adapter.getHomeBriefing() (adapter.ts:612-615) |
Real. Server-side cross-workspace fan-out (home.ts:257-423); personal-only (A2), excludes team/archived. Reuses buildWorkspaceState, buildTimeAwareGreeting, buildUpcomingSchedules, IdentityLayer, cron store, audit DB. |
/api/home/overnight |
GET | adapter.getHomeOvernight(since?) (adapter.ts:617-621) |
Real. Audit-event counts (memory_write → consolidated; file-write tool_call → artifacts) + cron execution history → automations/failures (home.ts:425-503). |
/api/quick-capture |
POST | adapter.quickCapture() (adapter.ts:623-627) |
Real. Used by QuickCapturePanel (not in Editorial layout). |
Real vs mocked / missing, per design need
- REAL: greeting, userName, date (ISO), recentWorkspaces (name/group/summary/lastActive/pendingCount), suggestedActions, upNext (schedule labels only), overnight counters + failures, needsReviewCount.
- NOT produced by the server (treat as absent):
continueSessionId— never set → "Continue" can't target a session (already a known gap,HomeRoute.tsx:16-19).UpNextItem.at(time) andevent/taskkinds — never set; onlyschedulefrom cron.activeModels— typed-optional, no producer; tile never shows from the real route.
- NO DATA ANYWHERE (must mock for the Editorial design):
- 🔥 streak chip —
grepacrosspackages/server/srcandapps/web/srcfinds nostreakfield. Habit-loop spec (SCREENS §15) says the streak lives on Home but it is unimplemented. Needs-mock (or a follow-up backend field). - Overnight "story" sentence + run chips with labels ("Teardown drafted · 9 competitors") — server gives raw counts, not a composed narrative or per-run labels. The narrative must be composed client-side from
OvernightSummary(counts + failures), and the first "Teardown drafted" run chip has no backing field (would need the top suggestedAction/workspace summary as a proxy) → partial mock. - Workspace card hex avatar glyph / "agent live" badge — no
glyph/livefield; derive glyph fromname[0], and there is no per-card live-agent flag → derive / mock badge.
- 🔥 streak chip —
3. Test contract — apps/web/src/test/p2-home-desktop.test.tsx
These assertions constrain the rebuild (the describe('HomeCockpit (P2)') block,
:73-129). All must keep passing. The harness renders HomeCockpit bare with mocked
adapter, useService ({connecting:false, connected:true}), offline=false, and a
stub ShellContext (:38-46).
Required behaviors / DOM contract:
- Root testid — after load,
screen.getByTestId('home-cockpit')must exist (:81). Keepdata-testid="home-cockpit"on the normal-render root. - J08 review banner (
:84-98) — withneedsReviewCount:3,data-testid="home-cockpit-review-banner"renders text containing "3 imported memories need your review", anddata-testid="home-cockpit-review-cta"click dispatches exactlywaggle:open-appwith detail{ appId:'memory', filter:'unreviewed' }. Keep the banner, its copy pattern, the CTA testid, and the event payload. - Banner omitted at 0/undefined (
:100-103) — nohome-cockpit-review-bannerwhenneedsReviewCountis undefined. - Up next omitted when empty (
:105-111) — nohome-cockpit-upnextwhenupNextis[]or undefined. - Up next present with items (
:113-118) —home-cockpit-upnextcontains the item label ("Weekly digest") when ≥1 item. - Human date (
:120-128) — the raw ISO (2026-06-11T07:42:13.512Z) must not appear; the date must render vianew Date(iso).toLocaleDateString(undefined, {weekday:'long', month:'long', day:'numeric'}). KeepformatBriefingDatesemantics; the mono date row must humanize, not print ISO.
The mocked briefing factory (:57-67) defines the minimum shape the component must
tolerate: {greeting, userName:'Marko', date, recentWorkspaces:[], suggestedActions:[], upNext:[], isFirstRun:false, needsReviewCount:0}. The Editorial rebuild must still
render home-cockpit with all-empty arrays (no crash on empty overnight/workspaces).
Implication: PR3 may freely restyle and re-lay-out, but must preserve these
testids + behaviors: home-cockpit, home-cockpit-review-banner,
home-cockpit-review-cta (+ event payload), home-cockpit-upnext (present/absent
rules), and human-date rendering. The up next empty/present rule means the Editorial
"Up next" (if kept) stays conditional. Other testids (home-cockpit-overnight,
home-cockpit-suggested, home-cockpit-continue-*, quickcapture testids) are not
asserted in this file — they can be renamed/removed if their features are reshaped.
4. Design spec (Editorial / Variation A) — target sections + exact copy
From SCREENS.md §01 (ship = Variation A) and home.html #view-a (:157-186).
Layout: single centered column, max-width 920px, 46px top padding
(home.html:31). Sections top to bottom:
4.1 Greeting (home.html:158-161)
- Mono date row (
--honey, uppercase,.1emtracking) with a live dot (.dot-live,--healthy) on the left, and a right-aligned 🔥 streak chip (pill,--honey-washbg,--honey-lineborder,--honeytext). - H1 Hanken 600,
clamp(34px,5vw,52px), line-height 1.02; the keyword "ahead" is honey (em, not italic;--honey). - Exact copy: date
"Friday · June 14 · 8:42"(compose frombriefing.date); streak"🔥 12-day streak"; H1 = "Good morning, Mara." / "You're ahead of yesterday."- Maps to: greeting →
briefing.greeting(real, already personalized server-side; the design's literal "Good morning, Mara." isbuildTimeAwareGreeting's output). "You're ahead of yesterday" second line + the honey "ahead" → needs-mock / composed (no "ahead vs yesterday" signal exists). Date → real (briefing.date, reformatted with time). Streak → needs-mock.
- Maps to: greeting →
4.2 Overnight hero card (home.html:163-172)
- Radius
--r-xl, gradient--surface → --surface-2,--shadow, soft honey radial glow top-right (::after). Mono eyebrow with--inteldot. - Eyebrow (exact): "While you slept".
- Story line (exact): Hanken 600, clamp 21→28px, max-width 30ch; honey key numbers (
b): "Waggle finished the Q2 competitor teardown, folded 14 new memories into the hive, and ran into one snag worth a look." - Run chips (exact, status dot + label):
"Teardown drafted · 9 competitors"(dot--healthy)"14 memories consolidated"(dot--intel)"2 artifacts created"(dot--work)"1 export failed"(dot--risk)- Maps to: counters are real (
overnight.consolidated,.artifactsCreated,failures.length). The composed sentence + the "Teardown drafted · 9 competitors" label are needs-mock/composed (no narrative producer; no per-run "9 competitors" field). Build the sentence from counts + failures client-side; degrade gracefully when overnight is null/empty (must still renderhome-cockpit).
4.3 "Pick up where you left off" (home.html:174-175, 287-299)
- Section head = mono uppercase
--text-dimwith trailing hairline rule (.sec-h). - 2-col grid (
.ws-grid) of.ws-card: hex avatar (honey gradient, glyph = first letter), title (16px, 650), time (mono--text-dim, e.g. "2h ago"), summary (13.5px--text-muted), footer = "Continue →" (honey) + optional status badge (pendhoney-wash "3 to review" /livehealthy-wash "agent live"). Hover: honey border +translateY(-3px)+--shadow.- Maps to: real —
recentWorkspaces[].name,.summary,formatRelative(lastActive),.pendingCount(→ "N to review"/"N pending"). Hex glyph = derive fromname[0]. "agent live" badge → no field, mock/omit. Section head copy must change "You were working on" → "Pick up where you left off".
- Maps to: real —
4.4 "Waggle suggests" (home.html:177-178, 301-306)
- Section head "Waggle suggests". Stacked
.moverows: tinted icon tile (--*-washbg), title (14.5px 600) + sub (12.5px--text-muted), arrow that slides on hover. - Exact sample copy (design data): "Review the competitor teardown" / "9
competitors · ready since 02:40 · ~6 min read"; "Send the board update" / "Draft
built from this week's work in Q2 Board Deck"; "Confirm 3 imported memories" / "From
Tuesday's pricing call — Waggle wants your sign-off".
- Maps to: title → real
suggestedActions[].label. The sub-line has no backing field (server emits label only,kind:'next-action') → needs-mock/derive (e.g. workspace name + relative time). Section head copy "Suggested next actions" → "Waggle suggests". Rows become stacked (not pills).
- Maps to: title → real
4.5 Ask bar (home.html:180-185)
- Full-width pill (
.ask), honey "+" icon left, text input (placeholder "Start something new — "draft the board update from this week's work"…""), mono "⌘K" hint, honey round send button ("→"). Focus → honey border + glow.- Maps to: net-new on Home. No current "ask bar" on HomeCockpit. Wire send →
onContinue/new-chat or open ⌘K (PR2CommandCenter). This replaces the current QuickCapturePanel as the primary input affordance. The "+" can keep a quick-capture role, but the design's primary intent is "start a task".
- Maps to: net-new on Home. No current "ask bar" on HomeCockpit. Wire send →
4.6 Not in Editorial (drop or relocate)
- Quick capture segmented panel — replaced by the ask bar; the
adapter.quickCaptureAPI can be kept behind the "+" icon or dropped from Home. - Active models tile — not in Editorial (and route never populates it). Drop.
- Attention/review banners — not literally in the Editorial mock, but the J08 review banner is test-locked (keep it; can be styled as a run-chip-adjacent attention row or kept above the hero). The overnight-failure attention banner maps naturally onto the "1 export failed" run chip + (optionally) Variation C's risk alert pattern.
5. Gap list
| Design section | Current state | Gap | Data available? | Severity |
|---|---|---|---|---|
| 920px centered column, 46px top pad | max-w-4xl mx-auto p-6 dense |
Re-layout to 920px / pt-[46px], larger type scale |
n/a (layout) | MED |
| Mono date row + live dot | Plain text-sm sub under H1, no dot |
Add mono uppercase honey date row + .dot-live |
date = real; format with weekday+time | MED |
| 🔥 streak chip (right pill) | Absent | Add streak chip | No data — needs-mock or new backend field | HIGH |
| H1 with honey "ahead" keyword | H1 = raw greeting, no honey span | Two-line H1; honey-span a keyword | greeting real; "ahead of yesterday" composed | MED |
| Overnight hero card (gradient + glow) | OvernightPanel = 3 plain counters + failures, suppressed offline, hidden when empty |
Rebuild as hero card w/ eyebrow + story + run chips; must still render when empty/null | counts real; story + run-chip labels composed/mock | HIGH |
| Run chips (4, status-dotted) | Counter tiles only | Render chips w/ status dots; map counts | 3 of 4 real (consolidated/artifacts/failed); "Teardown drafted · 9 competitors" mock | MED |
| "Pick up where you left off" head | "You were working on" | Copy + .sec-h hairline style |
n/a (copy) | LOW |
| Workspace cards: hex avatar + 16px title + Continue→ + badge | Cards w/ name+group chip, Continue button, kebab | Add hex avatar (glyph from name), restyle; keep Continue + kebab | name/summary/time/pending real; glyph derived; "agent live" mock/omit | MED |
| "Waggle suggests" stacked rows w/ sub-line | "Suggested next actions" pills, label-only | Copy + restyle to .move rows; add sub-line |
label real; sub-line composed/mock | MED |
| Ask bar (pill, +/⌘K/send) | None on Home (QuickCapture panel instead) | Net-new ask bar; wire to chat/⌘K | n/a (action wiring) | MED |
continueSessionId for targeted resume |
Plumbed but server omits | Continue lands at chat root, not a session | Not produced by home.ts |
LOW |
| Quick capture panel | Present | Remove/relocate behind "+" | n/a | LOW |
| Active models tile | Present, never populated | Remove from Home | route never sets activeModels |
LOW |
| J08 review banner (test-locked) | Present + correct | Preserve testids/copy/event while restyling | needsReviewCount real | (keep) |
| Overnight-failure attention banner | Present | Reconcile with "1 export failed" run chip | failures real | LOW |
Gap count (distinct design-vs-impl gaps): 11 (excludes the two "keep as-is" test-locked rows and the layout-only re-layout row counted once).
6. Reuse + build
6.1 Reuse (already shipped in PR1 — do NOT recreate)
All warm-Hive tokens the design references already exist in
apps/web/src/index.css and apps/web/src/waggle-theme.css:
- Surfaces/lines/text:
--bg --bg-2 --surface --surface-2 --line --line-soft --line-strong --text-2 --text-muted --text-dim(index.css:149-153). - Honey + washes:
--honey --honey-bright --honey-deep --honey-wash --honey-line --honey-glow(index.css:99,155-159). - Semantics + washes:
--work --intel --healthy --attention --risk+ each*-wash(index.css:161-169). - Radii:
--r:12 --r-lg:18 --r-xl:26(index.css:177-179). - Shadows:
--shadow --shadow-sm --shadow-lg --shadow-pop(index.css:171-174). - Fonts: Tailwind
font-display/font-sans→ Hanken Grotesk,font-mono→ JetBrains Mono (tailwind.config.ts:87-90). Hanken/JetBrains@importalready inindex.css. - Utilities:
.hexclip-path (waggle-theme.css:140),.combhoneycomb bg (:143-146),.dot-live+@keyframes breathe(index.css:428-435),.kbd/mono helper (waggle-theme.css:161). - Existing component primitives:
WorkspaceActionsMenu(kebab CRUD — keep on cards),formatRelative/formatBriefingDate(HomeCockpit.tsx:69-81,130-134), the adapter trio (getHomeBriefing/getHomeOvernight/quickCapture),useOfflineStatus,useService().connectingrace guard, shadcnui/(button/input/card available if wanted), lucide icons,waggle:open-appdeep-link shim. ⌘K lives inCommandCenter.tsx(PR2) — wire the ask-bar "⌘K" hint / send to it.
6.2 Net-new (build for Editorial)
- Mono date row with live dot + 🔥 streak chip (streak value mocked/constant until a backend field exists — flag in code).
- Honey-keyword H1 (two-line; second line composed; honey-span helper).
- Overnight hero card — a new presentational component: eyebrow "While you slept" + composed story sentence (from
OvernightSummarycounts + failures) + 4 run chips. Must render gracefully when overnight is null/empty (no crash; the test renders withgetHomeOvernight → null). - Run-chip component (status dot + label).
- Hex-avatar workspace card restyle (
.ws-cardlook: hex glyph, hover lift, "Continue →", badge). .movesuggestion row restyle (icon tile + title + sub + sliding arrow); compose sub-line from workspace/time.- Ask bar (pill input + honey "+" + "⌘K" hint + honey send) — replaces QuickCapture as the primary input; wire send to chat/⌘K.
6.3 Recommended build approach
- Keep
HomeCockpit.tsxas the component (route + props + load() + all five render states + the test-locked testids/banners stay). Restyle/replace only the normal render body (:579-649) and the sub-state shells to the warm Editorial look. - Container: swap
max-w-4xl mx-auto p-6→ a centeredmax-w-[920px] mx-auto px-8 pt-[46px] pb-20wrapper; optionally drop a.combhoneycomb layer behind it. - Extract small presentational subcomponents within the file (or co-located
home/dir, per CLAUDE.md "many small files"):GreetingHeader(rework),OvernightHero(new, replacesOvernightPanel),RunChip,WorkspaceCard(hex restyle of the existing card),SuggestRow(restyle ofSuggestedActionsPanel),AskBar(new). KeepUpNextPanelconditional (test 4/5) or fold it into the dash — simplest is to retain it below suggestions to satisfy the present/absent tests. - Compose, don't fetch: the overnight story sentence and run-chip labels
are derived client-side from the existing
OvernightSummary+ topsuggestedAction/workspace summary. Mark the streak and "ahead of yesterday" as explicit// TODO(backend): no data source yetconstants so the mock is honest. - Preserve behavior: J08 banner (copy +
home-cockpit-review-ctaevent payload), human-date,home-cockpitroot testid,home-cockpit-upnextpresent/absent rule. - Dark-first (per BUILD-PLAN §6); verify light via the existing ratchet
(
light-mode-tokens.test.ts). Gates:tsc -p apps/web/tsconfig.app.json0,npm run test(FE) green incl.p2-home-desktop.test.tsx, lint clean.
Honesty flags for the rebuild: (a) streak has no data anywhere — it is a pure mock until a backend field lands; (b) the overnight narrative + "9 competitors" run-chip and the suggestion sub-lines are composed/mocked, not server-provided; (c)
continueSessionIdis still omitted server-side so "Continue" lands at chat root.