partial (source-backed; browser and runner evidence remain open)

Spec: F64 — Framer Motion (Animations & Transitions)

Status: partial (source-backed; browser and runner evidence remain open)

1. Problem & CEO Job

Trust the signal (job 3) — abrupt state transitions can make the workspace feel unstable. Short, purposeful motion and an honest reduced-motion path make existing interactions easier to follow without competing with content.

2. User Stories & Acceptance Criteria

  • As a CEO, when a page loads and content replaces a skeleton, I want content to fade/translate in smoothly, so that my eyes track where new information appears.

    • Given a list page loading, skeleton items animate out as real items animate in (stagger)
    • Given a mutation success that adds an item to a list, the new item slides into position
  • As a CEO, when I navigate between pages, I want a subtle page transition, so that I perceive the navigation as fluid.

    • Given a page navigation, the outgoing page fades out and the incoming page fades in (200ms, ease-in-out)
  • As a CEO, when I open a modal or dialog, I want it to scale in from the center, so that my focus is drawn to the overlay.

    • Given a dialog opening, the backdrop fades in and the dialog scales up from 0.95 to 1.0 over the approved 200ms token.

3. Scope Boundaries

  • In: Installation (framer-motion), AnimatedPage wrapper for page transitions, AnimatedList wrapper for list enter/exit animations, shadcn dialog/animation overrides where tasteful
  • Out: Complex orchestrated sequences (nothing >300ms), GPU-bound parallax/scrolling effects, anything that competes with loading time (animations fire after data arrives)

4. Data Model

n/a.

5. API Contract

n/a.

6. UI / UX

  • Page transitions: Layout.tsx keys the routed outlet by pathname and applies the CSS .lumi-route-enter transition at the shell boundary; this replaced the old Framer Motion wrapper after a Suspense opacity-freeze bug
  • List animations: AnimatedList component wraps motion.div with layout prop + staggerChildren 0.05s — applied to Today, Tasks, Commitments, Risks, Decisions, Inbox lists
  • New item appearance: Add a layout animation so existing items shift down smoothly when a new item inserts
  • Dialogs: shadcn <DialogContent> already uses framer-motion — keep defaults, adjust duration from 150ms to 200ms
  • i18n: n/a
  • TASTE.md pre-flight: Animations must respect prefers-reduced-motion — wrap all animation config in a useReducedMotion check. If reduced motion is preferred, animate only opacity (no translate/scale) at half duration.

7. Test Plan

#CaseTypeWhere
1Animations respect prefers-reduced-motionfrontendfrontend/src/test/AnimatedPage.test.tsx
2Page transition fires on navigatione2eVisual check via screenshot comparison
3List stagger animation on data loade2eVisual check
4Page transition does not block content on /work/todaye2efrontend/tests/e2e/crm.spec.ts — F64 — Framer Motion
5Navigate /work/today → /work/tasks: both pages render headinge2efrontend/tests/e2e/crm.spec.ts — page transition does not block content when navigating
6No JS TypeError from animation components on /work/taskse2efrontend/tests/e2e/crm.spec.ts — animated list items appear without JS error

8. Current Behavior

The current source is partially shipped: AnimatedList.tsx and AnimatedItem use useReducedMotion, CSS motion surfaces disable non-essential motion under prefers-reduced-motion, routed content uses the keyed .lumi-route-enter CSS transition, and the shared dialog overlay/content use the approved 200ms duration. AnimatedPage.tsx is a transparent compatibility wrapper; browser execution is not claimed.

9. Error States

  • Reduced motion: animations degrade to 0 opacity at 100ms — no accessibility regression
  • Slow devices: animations are capped at 200ms max — they complete or skip before the user notices

10. Diagrams & Docs Touched

  • TASTE.md — add animation rule (reduced motion, duration cap)
  • DESIGN.md — add motion tokens section (duration, easing curves)

11. Implementation Audit

#ClaimStatusEvidence
1framer-motion 12.40 installed✅ matchpackage.json
2AnimatedPage.tsx — transparent compatibility wrapper✅ source matchfrontend/src/components/AnimatedPage.tsx; route transition moved to shell CSS after freeze fix
3AnimatedList.tsx — staggered list animation wrappers✅ matchfrontend/src/components/AnimatedList.tsx
4Layout.tsx — keyed shell transition around routed Outlet✅ source matchfrontend/src/layouts/Layout.tsx + frontend/src/styles/content.css
5AnimatedPage applied to all 14 authenticated app pages✅ matchToday, Tasks, Decisions, Commitments, Risks, Approvals, Companies, FollowUps, Horizon, Projects, Pulse, Recurring, SignalInbox, Sources, Teams
6AnimatedPage.test.tsx and motion convention tests✅ source matchfrontend/src/test/_shared/; Vitest execution remains open
7TypeScript errors⚠️ gaptsc --noEmit is not retained in this checkout because frontend dependencies are absent

12. Open Questions / Decisions

  • Animate list items on mount (stagger) or only on change (layout animation)? Decision: Both. Stagger on initial page load, layout animation on subsequent adds/removes.

13. Bug / Change Log

2026-07-10 — Perf: shell motion wiring moved to m + LazyMotion(domAnimation) (W-SYS-01/04)

Type: Performance Change

Summary:

  • The two shell-reachable framer-motion consumers — CommandDock.tsx (mounted by Layout on every authed route) and CapabilityPalette.tsx — converted from full motion.* components to m.* inside <LazyMotion features={domAnimation} strict>; the shared feature set lives in the new frontend/src/lib/motionFeatures.ts.
  • Behavior-identical: same variants/transitions/useReducedMotion branches; strict throws in dev if a full motion.* component regresses into these trees.
  • Cuts the eager motion chunk 41.6 → 28.4 kB gz on every authed route (drops the unused drag + layout-projection engine). Async feature loading (the ~5 kB shape) is Rolldown-blocked — full record in docs/tests/evidence/web/W-SYS-04/bundles.md, 2026-07-10 section.
  • New-code guidance: inside these trees use m.*, not motion.*. AnimatedList/AnimatedPage (page/list transitions, this spec’s §2 ACs) are untouched.

2026-07-16 — Contract Graph audit

Reconciled the stale Framer Motion page-wrapper claim with current source. The route enter motion now lives in CSS at the shell boundary, AnimatedPage is a transparent compatibility wrapper, and browser and dependency gaps remain explicit; the shared dialog duration is contract-backed at 200ms.

2026-07-02 — Docs: correct stale test file name (batch 2)

Type: Documentation Fix

Summary:

  • §7 row 1 referenced frontend/src/test/Animation.test.tsx; the actual file is frontend/src/test/AnimatedPage.test.tsx. Corrected.
  • No code changes.

2026-07-02 — Spec drift fix (batch10)

Type: Documentation Change

Summary:

  • Checked ACs 1–3 (§2) per §11 audit evidence: AnimatedList.tsx (row 3) confirms list stagger and new-item layout animations; AnimatedPage.tsx + Layout.tsx AnimatePresence + all-pages coverage (rows 2/4/5) confirm page-transition fade.
  • Checked AC 4 (dialog scale 0.95→1.0) after updating the shared DialogOverlay/DialogContent duration to 200ms and the F64 source proof.

2026-06-15 — Frontend route namespaced

Frontend route namespaced: /today -> /work/today and /tasks -> /work/tasks (app-wide IA restructure; API paths unchanged).

2026-06-13 — E2E tests added for page transitions (T3-E r11)

Type: Test

Summary:

  • Added F64 — Framer Motion (page transitions) E2E describe block in frontend/tests/e2e/crm.spec.ts.
  • Tests: home page content visible after transition, cross-page navigation renders content, no JS errors from animated components.
  • Covers §7 test plan items 4–6.

Impact:

  • E2E gate for F64 Framer Motion now active. Animation must not block page content or throw errors.