Files
waggle-os/docs/ux-refactor/path-exec-phase-B-spec-2026-07-07.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

9.1 KiB
Raw Permalink Blame History

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.12.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.

  1. 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 (before connecting settles, before the sidecar answers) marked stale-invisible (no visual difference), then refreshes silently. Day-0 (no cache) keeps today's skeleton path.
  2. 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.
  3. 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).
  4. 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 nextBriefingLanding gating in AppShell accordingly (armed additionally requires awayDays ≥ 7). Tests: <7d → no modal, cards in hero; ≥7d → modal (same data object as hero).
  5. 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 ready semantics 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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.

  1. Focus-reveal parity: workspace-card kebab + memory-row actions (✓/✎/🗑) reveal on :focus-within with visible focus-ring (the Phase-A --focus-ring token), matching the chat action-row pattern (Wave T). Tab-through must reach every action without a mouse.
  2. 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.
  3. ≤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.
  4. 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.

  1. 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).
  2. Marketplace: extensions list + facet state seed from cache on return; silent refresh; no re-skeleton within a session.
  3. 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).

  1. 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.
  2. 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.mjs against 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.