9.1 KiB
Path-to-9 execution · PHASE B — instant power feel (Pillar 2)
Contract: docs/ux-refactor/path-to-9-2026-07-07.md v3 §Pillar 2 (5/5 panel-endorsed).
Prereq: Phase A merged (motion tokens exist — use SPRING/DUR/STAGGER + --mo-* vars for
ALL new motion; the text-color guard is live — new text uses tokens).
Recon facts (verified pre-spec): Home loads via adapter.getHomeBriefing() +
getHomeOvernight() in HomeCockpit L549-589, gated on connecting settle, NO
persistence. LoginBriefing has its own module-scope fetchBriefingData + prefetch
(Wave T) — a SECOND data source (the drift-bug class the contract kills). Chat send
already renders an optimistic turn (ChatApp L741 comment); briefing landing state
machine nextBriefingLanding lives in AppShell (Wave U); BootScreen has ready prop +
850ms floor (Wave U). Session caches exist for memory (memory-list-cache.ts) and the
workspace shelf (module-scope in AllWorkspacesApp).
Per-lane gate: related vitest green + eslint 0 errors + node scripts/ux-gates/text-color-guard.mjs
clean on touched files. NO benchmarks/, NO packages/hive-mind-*/. NO cross-lane files.
Lane H — home cache-first paint + one briefing truth + double-catch-up collapse (Pillar 2.1–2.4)
Files: apps/HomeCockpit.tsx, os/overlays/LoginBriefing.tsx, os/AppShell.tsx,
os/BootScreen.tsx, NEW lib/home-cache.ts, NEW lib/briefing-source.ts, tests.
The heart of the pillar — one lane so the data-unification isn't split across owners.
- Disk-persisted cache-first paint (
lib/home-cache.ts): persist the last successful Home payload (briefing + overnight + the recall highlights) to localStorage (versioned key, schema-guarded parse, size-capped). On mount, HomeCockpit renders the cached payload IMMEDIATELY (beforeconnectingsettles, before the sidecar answers) marked stale-invisible (no visual difference), then refreshes silently. Day-0 (no cache) keeps today's skeleton path. - Silent-refresh reconciliation CONTRACT (v3 P2.3 — this is a TESTED contract): when fresh data lands over a cached paint — (a) NO above-the-fold layout shift: fixed-slot hero grammar (the Wave T card contract, applied to the hero) so text swaps in place; (b) material deltas (counts, new items) animate via a small honey delta pulse (use SPRING.micro/DUR.fast from Phase A — this is Pillar 3.3's pulse, born here); (c) unit-test: cached render → fresh data with changed counts → assert no element above the fold unmounts/remounts (key stability) and the changed count carries the pulse class.
- ONE briefing truth (
lib/briefing-source.ts): extract LoginBriefing's fetch+shape (fetchBriefingData/prefetch, highlights, workspace summaries, brag counts) into this module. BOTH the home hero and the modal consume IT — one fetch, one filter set, one count. Delete the second source. The number-drift bug class dies here (regression-test: hero count === modal count from the same mock). - Double catch-up collapse (v3 P2.4): the "I REMEMBER" recall cards render
INSIDE the home hero as its first staggered entrance (STAGGER.brief from
Phase A tokens); the modal fires ONLY on ≥7-day absences (N=7 per contract —
compute from the same lastActive the greeting uses), reusing the SAME card
component + briefing-source data. Update
nextBriefingLandinggating in AppShell accordingly (armed additionally requires awayDays ≥ 7). Tests: <7d → no modal, cards in hero; ≥7d → modal (same data object as hero). - Warm boot floor removal: with cache-first paint, the BootScreen floor for
warm sessions drops — boot shows only until the shell mounts (brand flash
≤500ms; keep the full choreography for COLD/day-0 sessions where there is
nothing to paint). Keep Wave U's
readysemantics for the cold path.
Lane C — input-during-warmup + send-path budget + chat route-cache (Pillar 2.2, 2.5, 2.6-chat)
Files: apps/ChatApp.tsx, the chat state hook it uses (grep useChat/chat widget
state — verify real file), os/WorkspaceDesktopApp.tsx (entry interactivity only), tests.
- Composer accepts typing at paint: the composer input must be enabled the
moment it renders — never disabled behind
connecting/history-load. If a send fires before the sidecar is ready: QUEUE it (one-deep queue is fine) with a truthful per-message "waiting for connection…" state on the optimistic turn, dispatch on ready, NEVER error or silently drop. Test: type+send while mocked adapter is pending → message queued → adapter resolves → dispatched. - Composer never locks on send: verify (and lock with a test) that after send the input clears and accepts the next message immediately while the previous streams — no disabled window. If a lock exists, remove it.
- No dead clicks on cached surfaces: WorkspaceDesktopApp entry — clicking a tab/affordance during the entry skeleton either acts or shows a per-element pending affordance (aria-busy + subtle shimmer), never nothing. Audit the entry skeleton's click handling; fix silent swallows.
- Chat thread session cache: mirror memory-list-cache — thread messages keyed by (workspace, session) survive tab-away/return within the session; returning renders instantly then refreshes silently. No re-skeleton on return.
Lane K — keyboard-power layer (Pillar 2.7; double-yield with a11y)
Files: os/WorkspaceActionsMenu.tsx + os/ContextMenu.tsx (focus-reveal parity),
apps/memory/MemoryTrustManage.tsx (row-action focus parity ONLY — rest is Lane H/C
territory... verify no overlap; MemoryTrustManage is NOT touched by H/C in this phase),
os/overlays/KeyboardShortcutsHelp.tsx, hooks/useKeyboardShortcuts.ts, tests.
- Focus-reveal parity: workspace-card kebab + memory-row actions (✓/✎/🗑)
reveal on :focus-within with visible focus-ring (the Phase-A
--focus-ringtoken), matching the chat action-row pattern (Wave T). Tab-through must reach every action without a mouse. - Hit areas: kebab, memory row actions, modal ×, "Fix it now" → ≥40px effective hit target (padding/pseudo-element expansion, no visual size change needed). List each touched target + before/after size in the report.
- ≤2-keystroke paths: global shortcuts — new chat (existing? verify in useKeyboardShortcuts; add if missing) and "open last workspace"; both reachable from anywhere, documented in KeyboardShortcutsHelp.
- Shortcut cheat sheet: verify KeyboardShortcutsHelp opens on
?(and Cmd-/); add the new shortcuts; ensure it lists the focus-reveal patterns ("Tab reaches card actions").
Lane R — route-cache: marketplace + agents (Pillar 2.6)
Files: apps/MarketplaceApp.tsx, apps/AgentsApp.tsx, NEW lib/surface-cache.ts
(generalize the memory-list-cache pattern: keyed session cache + test reset), tests.
- Extract the proven pattern into
lib/surface-cache.ts(typed, keyed, module- scope,resetForTests). Do NOT migrate memory/workspaces onto it in this phase (churn without yield — note as follow-up). - Marketplace: extensions list + facet state seed from cache on return; silent refresh; no re-skeleton within a session.
- Agents: roster + suggested cards seed from cache on return; same contract.
Lane G2 — the hard interaction gate (v3 §3 verification)
NEW files: scripts/ux-gates/warm-interaction-gate.mjs, README update; root
package.json script ux:warm-gate. Playwright, mirrors capture-kit conventions
(seeded returning-user localStorage; disclose in output).
- Warm gate: measure app-start → (a) home content visible, (b) composer accepts a keystroke (type into it, assert value). FAIL if content >1000ms or brand flash >500ms or first keystroke rejected. Print a timing table.
- Cold-start variant: with the sidecar port BLOCKED (bad base URL env or route-abort), assert cached paint still renders content + typing queues (needs Lane H+C landed — the script probes, and reports which contracts hold; exits 1 only on regressions of landed contracts, with a --strict flag for full enforcement once H+C merge).
VERIFY STAGE (adversarial, after lanes)
- V1-instant: run
node scripts/ux-gates/warm-interaction-gate.mjsagainst the dev server (servers: vite 8080; sidecar recipe in the handoff) — report the ACTUAL timing table; kill the sidecar and run the cold variant; try to refute Lane H/C claims (type during warmup, click during skeletons, tab-away/return every cached surface). - V2-truth: refute the one-briefing-truth claim — grep for any remaining second fetch path; run the reconciliation tests; force a count change through the mock and check for above-fold remount; verify modal-vs-hero counts share one source.
- V3-keyboard: keyboard-only pass — Tab through home → workspaces → kebab → memory rows → chat send; confirm every action reachable, focus-ring visible (Phase-A token), no trap; report the actual traversal.
Orchestrator after verify
tsc web + server, full apps/web vitest, ux-gates all green, browser smoke, commit. Then: kit v6 build + R20 judge round (gate: min ≥ 8.0 per v3 §3) BEFORE Phase C.