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),AnimatedPagewrapper for page transitions,AnimatedListwrapper 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.tsxkeys the routed outlet by pathname and applies the CSS.lumi-route-entertransition at the shell boundary; this replaced the old Framer Motion wrapper after a Suspense opacity-freeze bug - List animations:
AnimatedListcomponent wrapsmotion.divwithlayoutprop +staggerChildren0.05s — applied to Today, Tasks, Commitments, Risks, Decisions, Inbox lists - New item appearance: Add a
layoutanimation 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 auseReducedMotioncheck. If reduced motion is preferred, animate only opacity (no translate/scale) at half duration.
7. Test Plan
| # | Case | Type | Where |
|---|---|---|---|
| 1 | Animations respect prefers-reduced-motion | frontend | frontend/src/test/AnimatedPage.test.tsx |
| 2 | Page transition fires on navigation | e2e | Visual check via screenshot comparison |
| 3 | List stagger animation on data load | e2e | Visual check |
| 4 | Page transition does not block content on /work/today | e2e | frontend/tests/e2e/crm.spec.ts — F64 — Framer Motion |
| 5 | Navigate /work/today → /work/tasks: both pages render heading | e2e | frontend/tests/e2e/crm.spec.ts — page transition does not block content when navigating |
| 6 | No JS TypeError from animation components on /work/tasks | e2e | frontend/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
| # | Claim | Status | Evidence |
|---|---|---|---|
| 1 | framer-motion 12.40 installed | ✅ match | package.json |
| 2 | AnimatedPage.tsx — transparent compatibility wrapper | ✅ source match | frontend/src/components/AnimatedPage.tsx; route transition moved to shell CSS after freeze fix |
| 3 | AnimatedList.tsx — staggered list animation wrappers | ✅ match | frontend/src/components/AnimatedList.tsx |
| 4 | Layout.tsx — keyed shell transition around routed Outlet | ✅ source match | frontend/src/layouts/Layout.tsx + frontend/src/styles/content.css |
| 5 | AnimatedPage applied to all 14 authenticated app pages | ✅ match | Today, Tasks, Decisions, Commitments, Risks, Approvals, Companies, FollowUps, Horizon, Projects, Pulse, Recurring, SignalInbox, Sources, Teams |
| 6 | AnimatedPage.test.tsx and motion convention tests | ✅ source match | frontend/src/test/_shared/; Vitest execution remains open |
| 7 | TypeScript errors | ⚠️ gap | tsc --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) andCapabilityPalette.tsx— converted from fullmotion.*components tom.*inside<LazyMotion features={domAnimation} strict>; the shared feature set lives in the newfrontend/src/lib/motionFeatures.ts. - Behavior-identical: same variants/transitions/
useReducedMotionbranches;strictthrows in dev if a fullmotion.*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.*, notmotion.*.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 isfrontend/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 infrontend/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.