Files
waggle-os/docs/ux-refactor/path-exec-phase-D-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

8.2 KiB
Raw Permalink Blame History

Path-to-9 execution · PHASE D — the motion system (Pillar 1) + home scroll-reveal

Contract: docs/ux-refactor/path-to-9-2026-07-07.md v3 §Pillar 1 (5/5 endorsed, incl. the

PATH-2 conditions: default route tier + hero morphs, focus/AT spec IN the component,

interruptibility as acceptance criteria, commissioned waggle-settle prototype-first).

The unanimous R19 #1 gap. Highest regression risk → scoped, feature-flagged, staged.

All motion uses lib/motion/tokens.ts. Both themes. Reduced-motion per REDUCED map.

Recon (verified): router = react-router <Routes> in App.tsx; the layout route / = AppShell whose single <Outlet/> (AppShell.tsx:449) renders the active route inside <main class="relative z-10 flex-1 overflow-hidden">. location, useNavigate, AnimatePresence already imported. ChatHost keep-alive portal is SEPARATE from the Outlet (must not be wrapped — it protects in-flight SSE). Phase-0 spring family + --mo-* tokens + REDUCED map exist. Hover tier = the shipped hive-interactive-style classes on cards (Wave T/U/V). AmbientHiveGlow shipped Phase C (home breath).

STAGE 1 (parallel; the two heavy motion lanes are independent)

Lane RT — route-transition system (Pillar 1.1; PATH-2 design blocking)

Files: NEW components/os/RouteTransition.tsx (+ test), components/os/AppShell.tsx (wrap the Outlet only), lib/feature-flags.ts or a local flag (verify the flag pattern), tests.

  1. DEFAULT tier — fade-through crossfade + persistent chrome on ALL top-level route changes: wrap <Outlet/> in <AnimatePresence mode="popLayout"> keyed by a ROUTE-GROUP key (NOT the full pathname — a workspace tab change within /workspaces/:id must not crossfade the whole surface; derive the key from the top segment: home/workspaces/ memory/agents/marketplace/settings/…). Enter/exit = opacity fade-through (150200ms, --mo-base + EASE_OUT); the sidebar + StatusBar are OUTSIDE the animated subtree (persistent chrome — they never fade). NOT a global router rewrite.
  2. Interruptibility + input-primacy (PATH-2 design blocking — ACCEPTANCE, tested): a route change mid-transition redirects immediately (AnimatePresence popLayout allows the new child in without waiting for exit); navigation/input NEVER waits on an exit animation. Add a test: fire two navigations in quick succession → the final route wins, no lock. Keep mode="popLayout" (not "wait") precisely so exits don't block enters.
  3. Focus + assistive-tech spec IN the component (PATH-2 a11y blocking): on route commit, move focus to the destination surface's primary heading / <main> landmark (focus a tabIndex={-1} ref); set the EXITING subtree inert+aria-hidden for its exit duration (focus/SR cursor can never land in it); announce the route via a polite live region (route label). Ship these INSIDE RouteTransition, not as a follow-up.
  4. Reduced-motion: crossfade → instant swap (REDUCED.routeTransition = 'crossfade-only' means: under reduce, no opacity animation — instant), focus + announce still fire.
  5. Feature-flag the whole thing (default ON in dev, but a kill switch) so a regression is one flag flip. Verify the ChatHost keep-alive still works (SSE survives a route change — do not wrap ChatHost).

Lane HM — hero shared-element morphs (Pillar 1.1 hero cases)

Files: apps/AllWorkspacesApp.tsx + the workspace route/surface (layoutId on the card→ surface), apps/memory/* (Trust↔Memories tab morph), tests. Depends on Lane RT's RouteTransition existing (Stage 1 both start; HM reads RT's exports — if RT isn't merged yet, HM implements the layoutId pairs and notes the integration point).

  1. card→workspace-open: the workspace card's hex avatar + name share a layoutId with the destination workspace header, so opening a card GROWS it into the surface (framer layoutId + LayoutGroup). Scope to this ONE pair; fall back to the default crossfade if the destination header isn't mounted.
  2. Trust↔Memories tab morph: the shared hero/container morphs between the two memory tabs (layout animation on the tab panel), not a hard cut.
  3. Interruptibility + reduced-motion (instant) as Lane RT item 2/4. Both morphs motion-safe.

STAGE 2 (parallel; after Stage 1)

Lane HV — hover amplitude: "honey responds to touch" (Pillar 1.2; brand HIGH ×3 rounds)

Files: the shared hover-tier class/util (grep where cards define hover — likely inline Tailwind on AllWorkspacesApp/SuggestedAgentCards/ExtensionCard; if there's a shared hive-interactive class in index.css, edit there; else create ONE and adopt), tests.

  1. Bump the hover tier amplitude to a felt "honey blooms under the cursor": on hover/ focus-visible → lift (translate-y 2→4px), shadow step, honey border-warm, AND a soft honey glow bloom (box-shadow with --shadow-honey/--honey-glow), 120160ms --mo-fast + spring feel. ONE implementation adopted by workspace cards, agent cards, marketplace rows, provider tiles. Reduced-motion: keep color/shadow, drop translate.
  2. Keep the brand judge's WIN ("respectful, not gratuitous") — bloom is subtle, not neon. Light theme: verify the glow reads on ivory (Phase-A light --honey-glow / deeper honey).

Lane SR — home day-story scroll-reveal (Pillar 1.1 entrance; R20 design HIGH)

Files: apps/HomeCockpit.tsx (+ the home section components), tests.

  1. The home "day story" (I REMEMBER → START HERE → memory-review → WHILE YOU SLEPT → pick-up cards) reveals on scroll: each section rises 8px + fades in as it enters the viewport (IntersectionObserver + the card-enter / STAGGER grammar), once per section per visit. So the scripted scroll reads as designed motion, not a static page. Reduced-motion: all visible immediately, no rise/fade. Do not re-trigger on scroll-up.

STAGE 3 (the signature gesture — prototype-first, PATH-2 brand blocking)

Lane WS — the waggle-settle, commissioned + prototyped STANDALONE

Files: NEW components/os/warm/WaggleSettle.tsx (+ test) + a /motion-spec section demonstrating it on exactly ONE moment (memory saved). Do NOT propagate to other moments in this phase — the prototype is judged first (a mini standalone review), THEN rolled out per the Phase-0 SIGNATURE frequency taxonomy in a later step.

  1. A recognizably waggle-derived micro-choreography: a brief directional waggle-run (the bee's figure-eight dance DNA — a small side-to-side + forward settle) → settle, ~300400ms (DUR.settle, SPRING.expressive), honey. Reduced-motion: instant state + color pulse (no path motion). It must read as "the waggle," not a generic scale-pop.
  2. Wire it to ONE moment only: the memory-saved confirmation (find the save-memory success path — grep). Render it there behind the SIGNATURE.full taxonomy gate (per-session cooldown). Everything else waits for the standalone judge verdict.

VERIFY STAGE (adversarial)

  • V1-motion-system: RouteTransition — is the crossfade interruptible (the double-nav test), is the focus/AT spec implemented (focus moves to heading, exiting tree inert, route announced), does reduced-motion degrade to instant, does ChatHost SSE survive a route change? Hero morphs — do the layoutId pairs animate (code-read + the 12fps capture if available)?
  • V2-hover-scroll: hover amplitude felt (inspect the class), light-theme glow reads, reduced-motion drops translate; scroll-reveal fires once per section, reduced-motion off.
  • V3-a11y-regression: the highest risk — a keyboard-only pass through 3 route changes (focus lands on the destination heading each time, no focus trapped in an exiting tree, route announced); reduced-motion Playwright pass asserting no route-transition opacity animation under reduce.

Orchestrator after verify

tsc web + full vitest + all ux-gates (contrast/text-guard/warm/runtime) + reduced-motion gate + browser smoke (route changes crossfade, one card→workspace morph, hover bloom) → commit → standalone waggle-settle mini-judge (is it "the waggle"? kitsch check) → if approved, roll out per taxonomy → kit-v7 capture → R22 judge (9-ATTEMPT: min ≥ 9.0). If R22 min < 9 but ≥ 8.5, one convergent-fix round; if it plateaus, stop + re-analyze.