chati-dev 4.2.2 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/README.md +80 -53
  2. package/bin/chati.js +170 -5
  3. package/framework/agents/build/dev.md +509 -76
  4. package/framework/agents/deploy/devops.md +40 -25
  5. package/framework/agents/discover/brief.md +158 -22
  6. package/framework/agents/discover/brownfield-wu.md +26 -14
  7. package/framework/agents/discover/greenfield-wu.md +102 -14
  8. package/framework/agents/plan/architect-data-engineer.md +6 -6
  9. package/framework/agents/plan/architect-system.md +46 -12
  10. package/framework/agents/plan/architect.md +40 -20
  11. package/framework/agents/plan/detail.md +38 -24
  12. package/framework/agents/plan/phases.md +36 -26
  13. package/framework/agents/plan/tasks.md +114 -33
  14. package/framework/agents/plan/ux-brand-architect.md +240 -8
  15. package/framework/agents/plan/ux-component-engineer.md +28 -13
  16. package/framework/agents/plan/ux-researcher.md +7 -6
  17. package/framework/agents/plan/ux.md +55 -22
  18. package/framework/agents/quality/qa-implementation.md +143 -74
  19. package/framework/agents/quality/qa-planning.md +115 -42
  20. package/framework/agents/quality/qa-visual.md +448 -0
  21. package/framework/agents/shared/visualizer.md +128 -0
  22. package/framework/config.yaml +7 -6
  23. package/framework/constitution.md +127 -44
  24. package/framework/context/governance.md +12 -7
  25. package/framework/context/quality.md +6 -5
  26. package/framework/context/root.md +6 -6
  27. package/framework/data/entity-registry.yaml +377 -4
  28. package/framework/data/model-limits.json +19 -0
  29. package/framework/domains/agents/qa-visual.yaml +74 -0
  30. package/framework/domains/constitution.yaml +46 -2
  31. package/framework/domains/workflows/greenfield-fullstack.yaml +2 -2
  32. package/framework/hooks/advance-trigger.js +131 -0
  33. package/framework/hooks/brief-validator.js +83 -0
  34. package/framework/hooks/constitution-guard.js +24 -5
  35. package/framework/hooks/license-guard.js +62 -27
  36. package/framework/hooks/mode-governance.js +13 -2
  37. package/framework/hooks/model-governance.js +1 -0
  38. package/framework/hooks/post-dev.js +207 -0
  39. package/framework/hooks/prism-engine.js +274 -105
  40. package/framework/hooks/reasoning-escalator.js +371 -0
  41. package/framework/hooks/reference-trigger.js +117 -0
  42. package/framework/hooks/session-digest.js +50 -1
  43. package/framework/hooks/settings.json +32 -1
  44. package/framework/hooks/style-guard.js +25 -6
  45. package/framework/hooks/team-quality-gate.js +19 -12
  46. package/framework/hooks/undercover-guard.js +4 -2
  47. package/framework/i18n/en.yaml +3 -3
  48. package/framework/i18n/es.yaml +3 -3
  49. package/framework/i18n/fr.yaml +3 -3
  50. package/framework/i18n/pt.yaml +3 -3
  51. package/framework/intelligence/context-engine.md +4 -5
  52. package/framework/intelligence/decision-engine.md +1 -1
  53. package/framework/intelligence/hooks-performance.md +3 -3
  54. package/framework/migrations/v1.0-to-v1.1.yaml +1 -1
  55. package/framework/migrations/v1.4-to-v2.0.yaml +11 -11
  56. package/framework/migrations/v4.0-to-v4.1.yaml +2 -2
  57. package/framework/migrations/v4.2-to-v4.3.yaml +29 -0
  58. package/framework/orchestrator/chati-router.js +441 -0
  59. package/framework/orchestrator/chati-update.md +40 -40
  60. package/framework/orchestrator/chati.md +295 -94
  61. package/framework/rules/human-writing-style.md +47 -0
  62. package/framework/scaffold/motion-premium/README.md +99 -0
  63. package/framework/scaffold/motion-premium/app/globals.css.template +400 -0
  64. package/framework/scaffold/motion-premium/app/layout.tsx.template +110 -0
  65. package/framework/scaffold/motion-premium/components/animation/BackgroundCrossfadeProvider.tsx.template +170 -0
  66. package/framework/scaffold/motion-premium/components/animation/LenisProvider.tsx.template +49 -0
  67. package/framework/scaffold/motion-premium/components/animation/PageTransitionWrapper.tsx.template +83 -0
  68. package/framework/scaffold/motion-premium/components/animation/Preloader.tsx.template +171 -0
  69. package/framework/scaffold/motion-premium/components/ui/Container.tsx.template +69 -0
  70. package/framework/scaffold/motion-premium/components/ui/PageSection.tsx.template +74 -0
  71. package/framework/scaffold/motion-premium/lib/animations/gsap.ts.template +112 -0
  72. package/framework/scaffold/motion-premium/lib/animations/refreshCoordinator.ts.template +75 -0
  73. package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +119 -0
  74. package/framework/scaffold/motion-premium/lib/animations/useGsapContext.ts.template +92 -0
  75. package/framework/scaffold/motion-premium/lib/animations/useScrollSnapStepper.ts.template +265 -0
  76. package/framework/scaffold/motion-premium/lib/animations/useSmoothScroll.ts.template +67 -0
  77. package/framework/scaffold/motion-premium/lib/brand.ts.template +43 -0
  78. package/framework/scaffold/motion-premium/scaffold.yaml +178 -0
  79. package/framework/scaffold/motion-premium-3d/README.md +90 -0
  80. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoCanvas.tsx.template +81 -0
  81. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoClient.tsx.template +75 -0
  82. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/page.tsx.template +26 -0
  83. package/framework/scaffold/motion-premium-3d/components/3d/CameraRig.tsx.template +100 -0
  84. package/framework/scaffold/motion-premium-3d/components/3d/CanvasProvider.tsx.template +85 -0
  85. package/framework/scaffold/motion-premium-3d/components/3d/InvalidateOnScroll.tsx.template +51 -0
  86. package/framework/scaffold/motion-premium-3d/components/3d/MeshCrossfade.tsx.template +79 -0
  87. package/framework/scaffold/motion-premium-3d/components/3d/ScrollCrossfade.tsx.template +88 -0
  88. package/framework/scaffold/motion-premium-3d/components/3d/ScrollScene.tsx.template +121 -0
  89. package/framework/scaffold/motion-premium-3d/components/webgl/SceneFallback.tsx.template +65 -0
  90. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +68 -0
  91. package/framework/scaffold/motion-premium-3d/lib/webgl/detect.ts.template +69 -0
  92. package/framework/scaffold/motion-premium-3d/scaffold.yaml +136 -0
  93. package/framework/schemas/session.schema.json +109 -21
  94. package/framework/scripts/reference-capture.js +430 -0
  95. package/framework/scripts/visual-qa.js +686 -0
  96. package/framework/tasks/orchestrator-handoff.md +1 -1
  97. package/framework/tasks/orchestrator-resume.md +1 -1
  98. package/framework/tasks/orchestrator-route.md +1 -1
  99. package/framework/tasks/orchestrator-status.md +3 -3
  100. package/framework/tasks/qa-planning-gate-define.md +1 -1
  101. package/framework/templates/brandbook-html-tmpl.md +2 -2
  102. package/framework/templates/brandbook-tmpl.yaml +1 -1
  103. package/framework/templates/component-spec-tmpl.yaml +1 -1
  104. package/framework/templates/design-token-tmpl.yaml +1 -1
  105. package/framework/templates/icon-system-tmpl.yaml +1 -1
  106. package/framework/templates/team-planning-tasks.yaml +6 -5
  107. package/framework/workflows/brownfield-discovery.yaml +2 -2
  108. package/framework/workflows/brownfield-fullstack.yaml +15 -11
  109. package/framework/workflows/brownfield-service.yaml +14 -10
  110. package/framework/workflows/brownfield-ui.yaml +15 -11
  111. package/framework/workflows/greenfield-fullstack.yaml +16 -13
  112. package/framework/workflows/quick-flow.yaml +3 -3
  113. package/framework/workflows/standard-flow.yaml +12 -9
  114. package/package.json +12 -6
  115. package/src/autonomy/autonomous-gate.js +1 -0
  116. package/src/autonomy/build-state.js +1 -2
  117. package/src/autonomy/progress-reporter.js +1 -1
  118. package/src/config/agent-customizer.js +11 -3
  119. package/src/config/claude-settings-generator.js +27 -7
  120. package/src/config/context-file-generator.js +41 -21
  121. package/src/config/framework-adapter.js +1 -0
  122. package/src/config/gemini-hooks-generator.js +19 -7
  123. package/src/config/mcp-configs.js +1 -0
  124. package/src/context/layers/l1-global.js +2 -1
  125. package/src/dashboard/data-reader.js +4 -3
  126. package/src/dashboard/layout.js +2 -1
  127. package/src/decision/analyzer.js +6 -30
  128. package/src/decision/engine.js +4 -28
  129. package/src/decision/registry-healer.js +3 -2
  130. package/src/decision/registry-updater.js +23 -14
  131. package/src/extensions/loader.js +2 -8
  132. package/src/gates/g1-planning-complete.js +2 -1
  133. package/src/gates/g2-qa-planning.js +2 -1
  134. package/src/gates/g3-implementation.js +2 -1
  135. package/src/gates/g4-qa-implementation.js +3 -2
  136. package/src/gates/g5-deploy-ready.js +2 -1
  137. package/src/health/engine.js +4 -3
  138. package/src/installer/core.js +431 -81
  139. package/src/installer/preflight.js +131 -0
  140. package/src/installer/provider-overlay.js +3 -3
  141. package/src/installer/scaffold-applier.js +358 -0
  142. package/src/installer/templates.js +46 -29
  143. package/src/installer/validator.js +17 -12
  144. package/src/intelligence/registry-manager.js +22 -29
  145. package/src/intelligence/timeline.js +11 -6
  146. package/src/license/commands.js +1 -1
  147. package/src/license/wait.js +102 -0
  148. package/src/memory/agent-memory.js +81 -0
  149. package/src/memory/dream.js +32 -1
  150. package/src/merger/replace-merger.js +28 -15
  151. package/src/orchestrator/agent-selector.js +2 -1
  152. package/src/orchestrator/cli.js +1880 -71
  153. package/src/orchestrator/doctor.js +270 -0
  154. package/src/orchestrator/handoff-engine.js +4 -3
  155. package/src/orchestrator/index.js +2 -0
  156. package/src/orchestrator/pipeline-manager.js +306 -15
  157. package/src/orchestrator/session-manager.js +339 -6
  158. package/src/tasks/handoff.js +3 -2
  159. package/src/telemetry/config.js +4 -3
  160. package/src/telemetry/schema.js +1 -0
  161. package/src/terminal/collector.js +3 -2
  162. package/src/terminal/index.js +1 -2
  163. package/src/terminal/isolation.js +52 -18
  164. package/src/terminal/prompt-builder.js +42 -25
  165. package/src/terminal/run-parallel.js +1 -1
  166. package/src/terminal/run-team.js +3 -3
  167. package/src/terminal/team-task-list.js +43 -4
  168. package/src/upgrade/backup.js +3 -2
  169. package/src/upgrade/checker.js +3 -2
  170. package/src/upgrade/migrator.js +65 -7
  171. package/src/upgrade/tracked-files-detector.js +86 -0
  172. package/src/upgrade/user-messages.js +94 -0
  173. package/src/utils/config-parser.js +2 -1
  174. package/src/utils/feature-flags.js +2 -1
  175. package/src/utils/flatten-entities.js +69 -0
  176. package/src/utils/framework-dir.js +16 -0
  177. package/src/utils/model-id.js +85 -0
  178. package/src/utils/provider-limits.js +84 -23
  179. package/src/utils/schema-validator.js +1 -1
  180. package/src/wizard/i18n.js +5 -4
  181. package/src/wizard/index.js +14 -0
  182. package/assets/logo - c/303/263pia.png +0 -0
  183. package/assets/logo.svg +0 -42
  184. package/assets/logo2.png +0 -0
  185. package/assets/social-preview.png +0 -0
  186. package/scripts/bundle-framework.js +0 -69
  187. package/scripts/changelog-generator.js +0 -222
  188. package/scripts/codebase-mapper.js +0 -728
  189. package/scripts/commit-message-generator.js +0 -167
  190. package/scripts/coverage-analyzer.js +0 -260
  191. package/scripts/dependency-analyzer.js +0 -280
  192. package/scripts/doctor/checks/agents.js +0 -77
  193. package/scripts/doctor/checks/constitution.js +0 -41
  194. package/scripts/doctor/checks/domain-alignment.js +0 -58
  195. package/scripts/doctor/checks/prism-layers.js +0 -84
  196. package/scripts/doctor/checks/registry.js +0 -55
  197. package/scripts/doctor/checks/schemas.js +0 -61
  198. package/scripts/doctor/fixes/reference-fix.js +0 -100
  199. package/scripts/doctor/fixes/registry-fix.js +0 -56
  200. package/scripts/doctor/index.js +0 -212
  201. package/scripts/framework-analyzer.js +0 -308
  202. package/scripts/generate-constitution-domain.js +0 -253
  203. package/scripts/generate-signing-key.js +0 -33
  204. package/scripts/health-check.js +0 -481
  205. package/scripts/ide-sync.js +0 -326
  206. package/scripts/performance-analyzer.js +0 -325
  207. package/scripts/plan-tracker.js +0 -278
  208. package/scripts/populate-entity-registry.js +0 -481
  209. package/scripts/pr-review.js +0 -317
  210. package/scripts/rollback-manager.js +0 -310
  211. package/scripts/semantic-lint.js +0 -328
  212. package/scripts/sign-manifest.js +0 -53
  213. package/scripts/stuck-detector.js +0 -343
  214. package/scripts/test-quality-assessment.js +0 -257
  215. package/scripts/validate-agents.js +0 -368
  216. package/scripts/validate-package.js +0 -505
  217. package/scripts/validate-tasks.js +0 -465
  218. package/src/autonomy/worktree-manager.js +0 -250
  219. package/src/intelligence/decision-engine.js +0 -256
  220. package/src/intelligence/document-sharder.js +0 -221
  221. package/src/intelligence/elicitation.js +0 -265
@@ -0,0 +1,67 @@
1
+ /**
2
+ * lib/animations/useSmoothScroll.ts — scaffold motion-premium v1.0.0
3
+ *
4
+ * Initializes Lenis + GSAP integration exactly once per app lifecycle.
5
+ * Must be mounted in a top-level client component (phase 3: LenisProvider).
6
+ *
7
+ * Non-obvious choices:
8
+ *
9
+ * - `destroyed` flag closure + check inside `.then` — React 19 + strict mode
10
+ * double-mount problem. `initLenis()` is async (dynamic import of 'lenis').
11
+ * If the component mounts, unmounts, and remounts quickly (strict mode does
12
+ * this on first render in dev), the first .then callback may resolve AFTER
13
+ * the first unmount ran. Without the flag, the stale instance gets written
14
+ * to lenisRef.current and the cleanup function for the first mount destroys
15
+ * the NEW (second mount's) instance. Flag + immediate `lenis.destroy()` in
16
+ * the `if (destroyed)` branch guarantees at most one live Lenis, always.
17
+ *
18
+ * - `useRef<Awaited<ReturnType<typeof initLenis>> | null>` — typing the ref
19
+ * as the awaited return of the async function avoids duplicating the Lenis
20
+ * type import here. If `initLenis` is refactored later, the ref type
21
+ * follows automatically.
22
+ *
23
+ * - Returns the ref, not the instance — consumers (LenisProvider) can pass
24
+ * the ref through React context without re-rendering when the internal
25
+ * Lenis instance changes (refs don't trigger renders).
26
+ */
27
+
28
+ 'use client';
29
+
30
+ import { useEffect, useRef } from 'react';
31
+ import { initLenis } from './gsap';
32
+
33
+ /**
34
+ * Hook that initializes Lenis smooth scroll once per app lifecycle.
35
+ * Must be called in a top-level client component (e.g. LenisProvider in phase 3).
36
+ * Handles cleanup on unmount; race-safe under React strict mode.
37
+ *
38
+ * Returns a ref that holds the live Lenis instance, or null before init
39
+ * completes / after unmount.
40
+ */
41
+ export function useSmoothScroll() {
42
+ const lenisRef = useRef<Awaited<ReturnType<typeof initLenis>> | null>(null);
43
+
44
+ useEffect(() => {
45
+ let destroyed = false;
46
+
47
+ initLenis().then((lenis) => {
48
+ // Race guard: if the component already unmounted before Lenis finished
49
+ // loading (dynamic import), destroy the fresh instance immediately.
50
+ if (destroyed) {
51
+ lenis.destroy();
52
+ return;
53
+ }
54
+ lenisRef.current = lenis;
55
+ });
56
+
57
+ return () => {
58
+ destroyed = true;
59
+ if (lenisRef.current) {
60
+ lenisRef.current.destroy();
61
+ lenisRef.current = null;
62
+ }
63
+ };
64
+ }, []);
65
+
66
+ return lenisRef;
67
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Brand tokens — single source of truth for project colors.
3
+ *
4
+ * Scaffold-applied file; user-owned (protected from upgrades via hash diff
5
+ * in .chati/manifest.json). Edit freely.
6
+ *
7
+ * To re-sync these values into app/globals.css (the CSS custom properties
8
+ * consumed by Tailwind v4), run:
9
+ *
10
+ * npx chati-dev scaffold motion-premium --sync-brand
11
+ *
12
+ * The scaffold-applier reads this file, resolves each placeholder in
13
+ * globals.css.template (e.g. {{PRIMARY_HEX}}), and rewrites only the brand
14
+ * --color-* var block. Your spacing/radius/shadow customizations are preserved.
15
+ *
16
+ * See scaffold/motion-premium/scaffold.yaml for the full placeholder map.
17
+ */
18
+
19
+ export const brand = {
20
+ colors: {
21
+ /** Primary brand color (hex). Used for CTAs, focus rings, primary actions. */
22
+ primary: '{{PRIMARY_HEX}}',
23
+ /** Primary as "R, G, B" triple — required for rgba(var(--color-primary-rgb), alpha) constructs in shadows. */
24
+ primaryRgb: '{{PRIMARY_RGB}}',
25
+ /** Primary hover state (usually lighter/more saturated). */
26
+ primaryHover: '{{PRIMARY_HOVER_HEX}}',
27
+ /** Deepest background layer — page body default. */
28
+ bgDeep: '{{BG_DEEP_HEX}}',
29
+ /** Primary background level — main content surface. */
30
+ bgPrimary: '{{BG_PRIMARY_HEX}}',
31
+ /** Elevated background — cards, modals, panels. */
32
+ bgElevated: '{{BG_ELEVATED_HEX}}',
33
+ /** Primary text color — high-contrast copy. */
34
+ textPrimary: '{{TEXT_PRIMARY_HEX}}',
35
+ /** Secondary text — supporting copy, metadata. */
36
+ textSecondary: '{{TEXT_SECONDARY_HEX}}',
37
+ /** Inverse text — sits on primary-colored backgrounds (buttons etc). */
38
+ textInverse: '{{TEXT_INVERSE_HEX}}',
39
+ },
40
+ } as const;
41
+
42
+ export type Brand = typeof brand;
43
+ export type BrandColor = keyof typeof brand.colors;
@@ -0,0 +1,178 @@
1
+ # Scaffold Premium — motion-premium v1.0.0
2
+ #
3
+ # Declarative manifest for the motion-premium scaffold preset. Consumed by
4
+ # the scaffold-applier at apply time (Phase 4) to resolve placeholders and
5
+ # emit files into the target project.
6
+ #
7
+ # Contract: this schema is STABLE within v1.x. Additions only (new optional
8
+ # fields). Breaking changes bump the major version and provide migrations.
9
+
10
+ id: motion-premium
11
+ version: 1.0.0
12
+ description: "Premium animation infrastructure: GSAP + Lenis + cinematic providers. Opinionated for Next.js / React 18+ / Tailwind v4."
13
+
14
+ framework:
15
+ chati_dev: ">=4.3.0"
16
+
17
+ stack:
18
+ build_tools: ["nextjs", "react-vite"]
19
+ tailwind: ">=4.0.0"
20
+ typescript: ">=5.0.0"
21
+ node: ">=18.0.0"
22
+ react: ">=18.0.0"
23
+ # Runtime peer deps imported directly by templates. Bumping policy: every two
24
+ # minor bumps, re-validate templates. GSAP 3.12 is the stable line post-3.11;
25
+ # framer-motion 11 is the React 19 support line; lenis 1.1 introduced the
26
+ # `wrapper`/`content` refactor templates rely on.
27
+ gsap: "^3.12.5"
28
+ "framer-motion": "^11.0.0"
29
+ lenis: "^1.1.0"
30
+ # v4.3.1+: Playwright required for QA-Visual screenshot-based gate.
31
+ # Without it, visual-qa.js falls back to source-code analysis (gateway: WEAK).
32
+ # Run `npx playwright install chromium` once after npm install for the binary.
33
+ playwright: "^1.44.0"
34
+
35
+ # Placeholder resolution — keys reference brand source fields.
36
+ # Resolved once at scaffold apply time (Decision 3: install-time, one-shot).
37
+ # To re-sync CSS vars after editing brand.ts, run: scaffold motion-premium --sync-brand
38
+ placeholders:
39
+ PRIMARY_HEX:
40
+ source: brand.colors.primary
41
+ default: "#7C3AED"
42
+ type: hex
43
+ PRIMARY_RGB:
44
+ source: brand.colors.primaryRgb
45
+ default: "124, 58, 237"
46
+ type: rgb_triple
47
+ PRIMARY_HOVER_HEX:
48
+ source: brand.colors.primaryHover
49
+ default: "#6D28D9"
50
+ type: hex
51
+ BG_DEEP_HEX:
52
+ source: brand.colors.bgDeep
53
+ default: "#0A0A0F"
54
+ type: hex
55
+ BG_PRIMARY_HEX:
56
+ source: brand.colors.bgPrimary
57
+ default: "#111118"
58
+ type: hex
59
+ BG_ELEVATED_HEX:
60
+ source: brand.colors.bgElevated
61
+ default: "#1A1A24"
62
+ type: hex
63
+ TEXT_PRIMARY_HEX:
64
+ source: brand.colors.textPrimary
65
+ default: "#FAFAFA"
66
+ type: hex
67
+ TEXT_SECONDARY_HEX:
68
+ source: brand.colors.textSecondary
69
+ default: "#A1A1AA"
70
+ type: hex
71
+ TEXT_INVERSE_HEX:
72
+ source: brand.colors.textInverse
73
+ default: "#0A0A0F"
74
+ type: hex
75
+
76
+ # Brand source resolution priority (Decision 3). First match wins.
77
+ brand_sources:
78
+ - path: lib/brand.ts
79
+ type: typescript
80
+ description: "Existing brand tokens (brownfield or post-F3 scaffold install)"
81
+ - path: artifacts/4-UX/brandbook.md
82
+ type: markdown
83
+ description: "UX-authored brandbook (greenfield, post-UX phase)"
84
+ - path: artifacts/4-UX/brandbook.yaml
85
+ type: yaml
86
+ description: "Alternate brandbook format"
87
+ - type: interactive
88
+ prompt: true
89
+ description: "Fallback: ask user for each placeholder with default hints"
90
+
91
+ # Files shipped by this scaffold. Phase 1: foundation (2). Phase 2: animations (6). Phase 3: providers (5). Phase 4: ui contract (2). Total: 15.
92
+ files:
93
+ - source: app/globals.css.template
94
+ target: app/globals.css
95
+ phase: foundation
96
+ override: partial
97
+ notes: "Brand --color-* vars auto-synced from brand.ts; spacing/radius/shadow user-editable."
98
+ - source: lib/brand.ts.template
99
+ target: lib/brand.ts
100
+ phase: foundation
101
+ override: full
102
+ notes: "User-owned. Protected from upgrades via hash diff in manifest.json."
103
+ - source: lib/animations/gsap.ts.template
104
+ target: lib/animations/gsap.ts
105
+ phase: animations
106
+ override: none
107
+ notes: "Module-level plugin registration + Lenis/GSAP integration. Scaffold-owned — the 13 sauce patterns live here."
108
+ - source: lib/animations/tokens.ts.template
109
+ target: lib/animations/tokens.ts
110
+ phase: animations
111
+ override: partial
112
+ notes: "Adjust values, keep the exported shape (EASINGS/EASING_CURVES/DURATIONS/STAGGERS/SCROLL_TRIGGER_DEFAULTS)."
113
+ - source: lib/animations/useGsapContext.ts.template
114
+ target: lib/animations/useGsapContext.ts
115
+ phase: animations
116
+ override: none
117
+ notes: "Scoped context + reduced-motion safeguard + ctx.revert cleanup. Core pattern — do not modify."
118
+ - source: lib/animations/useSmoothScroll.ts.template
119
+ target: lib/animations/useSmoothScroll.ts
120
+ phase: animations
121
+ override: none
122
+ notes: "Race-guarded async Lenis init. Used by LenisProvider in phase 3."
123
+ - source: lib/animations/useScrollSnapStepper.ts.template
124
+ target: lib/animations/useScrollSnapStepper.ts
125
+ phase: animations
126
+ override: none
127
+ notes: "Wheel accumulator + touch + entry cooldown for panel-snap pins. Imported into components that use pinned sequences."
128
+ - source: lib/animations/refreshCoordinator.ts.template
129
+ target: lib/animations/refreshCoordinator.ts
130
+ phase: animations
131
+ override: none
132
+ notes: "Debounced ScrollTrigger.refresh() singleton. Coalesces mount-burst refreshes."
133
+ - source: components/animation/LenisProvider.tsx.template
134
+ target: components/animation/LenisProvider.tsx
135
+ phase: providers
136
+ override: none
137
+ notes: "Root client component for Lenis init + scrollRestoration manual enforcement."
138
+ - source: components/animation/BackgroundCrossfadeProvider.tsx.template
139
+ target: components/animation/BackgroundCrossfadeProvider.tsx
140
+ phase: providers
141
+ override: partial
142
+ notes: "User customizes color stops via props; ScrollTrigger logic is scaffold-owned."
143
+ - source: components/animation/PageTransitionWrapper.tsx.template
144
+ target: components/animation/PageTransitionWrapper.tsx
145
+ phase: providers
146
+ override: none
147
+ notes: "Framer Motion opacity fade on pathname change + post-exit scroll reset."
148
+ - source: components/animation/Preloader.tsx.template
149
+ target: components/animation/Preloader.tsx
150
+ phase: providers
151
+ override: partial
152
+ notes: "User supplies children (logo slot), videoSelector, durations; readiness gate + RAF are scaffold-owned."
153
+ - source: app/layout.tsx.template
154
+ target: app/layout.tsx
155
+ phase: providers
156
+ override: partial
157
+ notes: "Scaffold ships provider nesting + pre-hydration scroll/motion scripts. User fills metadata, fonts, chrome, analytics."
158
+ - source: components/ui/Container.tsx.template
159
+ target: components/ui/Container.tsx
160
+ phase: ui-contract
161
+ override: none
162
+ notes: "@contract: sole authority for max-width + horizontal padding. Never edit."
163
+ - source: components/ui/PageSection.tsx.template
164
+ target: components/ui/PageSection.tsx
165
+ phase: ui-contract
166
+ override: none
167
+ notes: "Vertical rhythm wrapper. noPadding / noContainer escape hatches for heroes and full-bleed layouts."
168
+
169
+ # Contract — principles this scaffold encodes.
170
+ # Violating any of these during dev breaks the premium guarantees.
171
+ principles:
172
+ - "One Container authority — components/ui/Container.tsx is the sole max-width + horizontal padding owner (Phase 4)"
173
+ - "Fluid type from day one — Utopia clamp scale across all text-* tokens, no retrofit"
174
+ - "xl breakpoint overridden to 75rem — eliminates the 1200–1279px dead zone (MacBook Air M4)"
175
+ - "Mobile-first stack; promote to desktop via matchMedia gates, not CSS-only"
176
+ - "Reduced motion first-class — @media (prefers-reduced-motion) + data-gsap-target safety net"
177
+ - "Single RAF loop — gsap.ticker drives Lenis (Phase 2)"
178
+ - "Scope isolation via gsap.context(fn, containerRef) (Phase 2)"
@@ -0,0 +1,90 @@
1
+ # motion-premium-3d
2
+
3
+ React Three Fiber + Three.js scroll-driven 3D layer for premium sites. Extends `motion-premium` — apply that one FIRST (the 2D base), then this one on top.
4
+
5
+ ## Setup
6
+
7
+ After `npm install`, run once to download the chromium browser binary used by QA-Visual screenshots:
8
+
9
+ ```bash
10
+ npx playwright install chromium
11
+ ```
12
+
13
+ QA-Visual Phase 3c uses real browser screenshots to count `canvas_hosts` and `scene_fallbacks` in the rendered DOM. Without chromium, R3F visual QA is blind.
14
+
15
+ ## When to apply
16
+
17
+ Apply when the project brief or Animation Inventory cites **explicit 3D intent**:
18
+
19
+ - Named 3D reference sites (Oryzo camera rig, Igloo Inc scenes, many Awwwards winners).
20
+ - Phrase-level signals: "scroll-synced 3D hero", "camera pull", "product rotation", "scroll-driven WebGL", "interactive scene".
21
+ - Animation Inventory rows whose "Reproducible via" column points at `scaffold/motion-premium-3d/*`.
22
+
23
+ The Fase 10 auto-gate fires for this preset when `scaffold_signals.motion_premium_3d.confidence >= 0.8` (higher threshold than 2D motion-premium's 0.7 per ADR-3D-07 — false positives here cost ~600KB of bundle pressed onto a project that did not need it).
24
+
25
+ ## When NOT to apply
26
+
27
+ - 2D-only projects. Apply motion-premium alone.
28
+ - Mobile-first experiences where GPU budget is the primary constraint. The WebGL fallback (ADR-3D-05) handles low-end cases, but you pay the detection cost everywhere.
29
+ - Static marketing pages without scroll choreography.
30
+ - Projects on providers other than Claude Code where R3F's peer-dep matrix is hard to verify at install.
31
+
32
+ ## What it ships
33
+
34
+ 12 templates across 4 phases (webgl / canvas / scroll-scene / demo). Final inventory:
35
+
36
+ ```
37
+ lib/webgl/detect.ts.template
38
+ components/webgl/WebGLContext.tsx.template
39
+ components/webgl/SceneFallback.tsx.template
40
+ components/3d/CanvasProvider.tsx.template
41
+ components/3d/InvalidateOnScroll.tsx.template
42
+ components/3d/ScrollScene.tsx.template
43
+ components/3d/CameraRig.tsx.template
44
+ components/3d/ScrollCrossfade.tsx.template
45
+ components/3d/MeshCrossfade.tsx.template
46
+ app/(3d)/scroll-demo/page.tsx.template
47
+ app/(3d)/scroll-demo/ScrollDemoClient.tsx.template
48
+ app/(3d)/scroll-demo/ScrollDemoCanvas.tsx.template
49
+ ```
50
+
51
+ All 12 entries match `scaffold.yaml` `files[]` exactly. All target paths are DISJOINT from motion-premium. The two presets compose.
52
+
53
+ ## Override classification
54
+
55
+ | File pattern | Override | Notes |
56
+ |---|---|---|
57
+ | `lib/webgl/*` | none | Scaffold-owned — detection logic is invariant |
58
+ | `components/webgl/WebGLContext` | none | Context shape consumed by every 3D template |
59
+ | `components/webgl/SceneFallback` | partial | Extend with children (headline/CTA); keep role/aria contract |
60
+ | `components/3d/CanvasProvider` | none | Single-RAF contract + WebGL gate — do not modify |
61
+ | `components/3d/InvalidateOnScroll` | none | RAF bridge from gsap.ticker to R3F invalidate() |
62
+ | `components/3d/ScrollScene` | none | Pinned section + progress ref — scaffold-owned |
63
+ | `components/3d/CameraRig` | partial | User supplies keyframes[] via props; lerp internals fixed |
64
+ | `components/3d/ScrollCrossfade` | partial | Customise stops[] / at[] for uneven spacing |
65
+ | `components/3d/MeshCrossfade` | partial | Mesh geometry / materials are project-specific |
66
+ | `app/(3d)/scroll-demo/page` | partial | Route-group entry — customise or remove for production |
67
+ | `app/(3d)/scroll-demo/ScrollDemoClient` | partial | Client wrapper + overlay HTML — user extends |
68
+ | `app/(3d)/scroll-demo/ScrollDemoCanvas` | partial | Canvas subtree (CanvasProvider + InvalidateOnScroll + CameraRig + MeshCrossfade + Preload) |
69
+
70
+ ## ADRs governing this preset
71
+
72
+ - **ADR-3D-01** — Separate preset, not extension
73
+ - **ADR-3D-02** — R3F + drei, not vanilla Three.js
74
+ - **ADR-3D-03** — Single RAF via `frameloop="demand"`
75
+ - **ADR-3D-04** — `next/dynamic({ ssr: false })` for all Canvas templates
76
+ - **ADR-3D-05** — WebGL detection + graceful degradation
77
+ - **ADR-3D-06** — Peer-deps pinned by minor (three `^0.160`, R3F `^8.15`)
78
+ - **ADR-3D-07** — New auto-gate signal with confidence threshold 0.8
79
+ - **ADR-3D-08** — `prefers-reduced-motion: reduce` MUST render the fallback
80
+
81
+ Full context in `.planning/motion-premium-3d-v1.md`.
82
+
83
+ ## How to apply
84
+
85
+ ```
86
+ npx chati-dev scaffold motion-premium # 2D base first
87
+ npx chati-dev scaffold motion-premium-3d # 3D layer on top
88
+ ```
89
+
90
+ Or via the Fase 10 auto-gate after qa-planning, when signals align.
@@ -0,0 +1,81 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * ScrollDemoCanvas — dynamically imported via next/dynamic({ ssr: false })
5
+ * from ScrollDemoClient. Owns the Canvas subtree.
6
+ *
7
+ * Bundles:
8
+ * @react-three/fiber — Canvas, useThree, useFrame
9
+ * @react-three/drei — Preload (utility)
10
+ * three — Vector3, geometries, materials
11
+ * Rest of the app does not import these; the dynamic boundary isolates
12
+ * the ~600KB 3D bundle from pages that do not need it.
13
+ */
14
+
15
+ import { Preload } from '@react-three/drei';
16
+ import { CanvasProvider } from '@/components/3d/CanvasProvider';
17
+ import { InvalidateOnScroll } from '@/components/3d/InvalidateOnScroll';
18
+ import { CameraRig } from '@/components/3d/CameraRig';
19
+ import { MeshCrossfade } from '@/components/3d/MeshCrossfade';
20
+ import { SceneFallback } from '@/components/webgl/SceneFallback';
21
+
22
+ /**
23
+ * Keyframes for the demo camera path. Replace with your own keyframes
24
+ * when customising — CameraRig lerps position + lookAt + optional FOV.
25
+ */
26
+ const DEMO_KEYFRAMES = [
27
+ { at: 0, position: [0, 0, 8] as [number, number, number], lookAt: [0, 0, 0] as [number, number, number], fov: 50 },
28
+ { at: 0.5, position: [4, 2, 6] as [number, number, number], lookAt: [0, 0, 0] as [number, number, number], fov: 55 },
29
+ { at: 1, position: [0, 4, 4] as [number, number, number], lookAt: [0, 0, 0] as [number, number, number], fov: 60 },
30
+ ];
31
+
32
+ export default function ScrollDemoCanvas() {
33
+ return (
34
+ <CanvasProvider fallback={<SceneFallback fullBleed accessibleLabel="3D scene unavailable" />}>
35
+ <InvalidateOnScroll />
36
+
37
+ <ambientLight intensity={0.4} />
38
+ <directionalLight position={[5, 5, 5]} intensity={0.8} />
39
+
40
+ <CameraRig keyframes={DEMO_KEYFRAMES} />
41
+
42
+ {/*
43
+ MeshCrossfade: single slot; only the active variant mounts.
44
+ Replace primitives with your own glTF meshes (useGLTF from drei).
45
+ */}
46
+ <MeshCrossfade
47
+ variants={[
48
+ {
49
+ at: 0,
50
+ render: () => (
51
+ <mesh>
52
+ <boxGeometry args={[1.5, 1.5, 1.5]} />
53
+ <meshStandardMaterial color="#7C3AED" />
54
+ </mesh>
55
+ ),
56
+ },
57
+ {
58
+ at: 0.5,
59
+ render: () => (
60
+ <mesh>
61
+ <sphereGeometry args={[1, 32, 32]} />
62
+ <meshStandardMaterial color="#A78BFA" />
63
+ </mesh>
64
+ ),
65
+ },
66
+ {
67
+ at: 1,
68
+ render: () => (
69
+ <mesh>
70
+ <torusGeometry args={[1, 0.4, 16, 64]} />
71
+ <meshStandardMaterial color="#EDE9FE" />
72
+ </mesh>
73
+ ),
74
+ },
75
+ ]}
76
+ />
77
+
78
+ <Preload all />
79
+ </CanvasProvider>
80
+ );
81
+ }
@@ -0,0 +1,75 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * ScrollDemoClient — working example wiring together every P2-P4
5
+ * template. A single pinned scroll scene with:
6
+ * - WebGL gate (P2: WebGLContext + SceneFallback)
7
+ * - Canvas mount with single-RAF (P3: CanvasProvider +
8
+ * InvalidateOnScroll)
9
+ * - Scroll-driven camera (P4: CameraRig)
10
+ * - Mesh crossfade (P4: MeshCrossfade)
11
+ *
12
+ * Real-world scenes replace the box/sphere/torus primitives with
13
+ * glTF meshes loaded via drei's `useGLTF` (not imported here to keep
14
+ * the demo light; see motion-premium-3d README for asset pipeline
15
+ * guidance).
16
+ *
17
+ * Preload hint: drei's <Preload all /> tells R3F to compile all
18
+ * materials in the scene at mount time, eliminating the flash that
19
+ * otherwise happens when a new material appears mid-scroll.
20
+ */
21
+
22
+ import { useRef } from 'react';
23
+ import dynamic from 'next/dynamic';
24
+ import { Preload } from '@react-three/drei';
25
+ import { Container } from '@/components/ui/Container';
26
+ import { WebGLProvider } from '@/components/webgl/WebGLContext';
27
+ import { SceneFallback } from '@/components/webgl/SceneFallback';
28
+ import {
29
+ ScrollScene,
30
+ ScrollSceneContext,
31
+ type ScrollSceneRef,
32
+ } from '@/components/3d/ScrollScene';
33
+
34
+ // ADR-3D-04: R3F / Three.js dependencies must load client-only. Wrapping
35
+ // the scene in next/dynamic with ssr: false ensures Next never attempts
36
+ // to run Canvas code on the server, and the loading state paints the
37
+ // considered fallback instead of a blank canvas during hydration.
38
+ const ScrollDemoCanvas = dynamic(() => import('./ScrollDemoCanvas'), {
39
+ ssr: false,
40
+ loading: () => <SceneFallback fullBleed accessibleLabel="Loading scroll demo" />,
41
+ });
42
+
43
+ export default function ScrollDemoClient() {
44
+ const sceneRef = useRef<ScrollSceneRef>(null);
45
+
46
+ return (
47
+ <WebGLProvider>
48
+ <ScrollScene ref={sceneRef} length={3} scrub={true}>
49
+ <Container className="relative z-10 flex h-full flex-col justify-end pb-24">
50
+ <p className="text-sm uppercase tracking-wider opacity-60">motion-premium-3d demo</p>
51
+ <h1 className="text-display-lg max-w-2xl">
52
+ Scroll to animate the scene.
53
+ </h1>
54
+ </Container>
55
+
56
+ <div className="absolute inset-0">
57
+ <ScrollSceneContext.Provider value={sceneRef.current?.progress ?? null}>
58
+ <ScrollDemoCanvas />
59
+ </ScrollSceneContext.Provider>
60
+ </div>
61
+ </ScrollScene>
62
+ </WebGLProvider>
63
+ );
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Sub-components defined below are referenced only via next/dynamic above.
68
+ // They are exported from the Client module so the dynamic import is cheap
69
+ // (single chunk). In production demos, prefer a separate file for clearer
70
+ // code-split boundaries.
71
+ // ---------------------------------------------------------------------------
72
+
73
+ export function ScrollDemoCanvasInline() {
74
+ return <Preload all />;
75
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * /scroll-demo — demo route for motion-premium-3d.
3
+ *
4
+ * Server Component by design: metadata exports live here; the actual
5
+ * scroll-scene + Canvas tree live in the sibling Client Component
6
+ * (ScrollDemoClient). This separation keeps the route group
7
+ * "(3d)" URL-transparent while respecting the Next.js 16 rule that
8
+ * `next/dynamic({ ssr: false })` must be invoked from a Client boundary.
9
+ *
10
+ * Users typically remove this route after validating the scaffold on
11
+ * their own content. The `(3d)` route group ( -parens- ) means URLs
12
+ * stay at `/scroll-demo`; dropping the whole `app/(3d)/` folder is a
13
+ * one-step undo.
14
+ */
15
+
16
+ import type { Metadata } from 'next';
17
+ import ScrollDemoClient from './ScrollDemoClient';
18
+
19
+ export const metadata: Metadata = {
20
+ title: 'Scroll Demo — motion-premium-3d',
21
+ description: 'Scaffold demo: pinned scroll scene, camera rig tied to scroll progress, scene crossfade. Remove when validated.',
22
+ };
23
+
24
+ export default function ScrollDemoPage() {
25
+ return <ScrollDemoClient />;
26
+ }
@@ -0,0 +1,100 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * CameraRig — interpolates camera position, target, and optional FOV
5
+ * across keyframes driven by the ScrollSceneContext progress ref.
6
+ *
7
+ * Must mount inside <CanvasProvider> AND inside <ScrollScene>'s
8
+ * context. Reads progress on every frame via useFrame — zero React
9
+ * re-renders, no state library.
10
+ *
11
+ * Keyframe format: an array of { at: 0..1, position, lookAt, fov? }
12
+ * sorted by `at`. Between keyframes, linear interpolation (or custom
13
+ * easing via `ease` on the keyframe). Before the first/after the last,
14
+ * the camera clamps to that keyframe's pose.
15
+ *
16
+ * Usage:
17
+ * <CameraRig
18
+ * keyframes={[
19
+ * { at: 0, position: [0, 0, 8], lookAt: [0, 0, 0], fov: 50 },
20
+ * { at: 0.5, position: [4, 2, 6], lookAt: [0, 0, 0] },
21
+ * { at: 1, position: [0, 4, 3], lookAt: [0, 0, 0], fov: 60 },
22
+ * ]}
23
+ * />
24
+ */
25
+
26
+ import { useContext, useMemo } from 'react';
27
+ import { useThree, useFrame } from '@react-three/fiber';
28
+ import { Vector3, PerspectiveCamera as ThreePerspectiveCamera } from 'three';
29
+ import { ScrollSceneContext } from './ScrollScene';
30
+
31
+ export interface CameraKeyframe {
32
+ at: number; // 0..1
33
+ position: [number, number, number];
34
+ lookAt: [number, number, number];
35
+ fov?: number;
36
+ /** Custom easing on the segment ending at this keyframe (optional). */
37
+ ease?: (t: number) => number;
38
+ }
39
+
40
+ export interface CameraRigProps {
41
+ keyframes: CameraKeyframe[];
42
+ }
43
+
44
+ const DEFAULT_EASE = (t: number) => t; // linear; override per keyframe
45
+
46
+ export function CameraRig({ keyframes }: CameraRigProps) {
47
+ const progressRef = useContext(ScrollSceneContext);
48
+ const camera = useThree((s) => s.camera);
49
+
50
+ // Sorted copy + Vector3 presets — done once.
51
+ const kf = useMemo(() => {
52
+ const sorted = [...keyframes].sort((a, b) => a.at - b.at);
53
+ return sorted.map((k) => ({
54
+ at: k.at,
55
+ position: new Vector3(...k.position),
56
+ lookAt: new Vector3(...k.lookAt),
57
+ fov: k.fov,
58
+ ease: k.ease || DEFAULT_EASE,
59
+ }));
60
+ }, [keyframes]);
61
+
62
+ // Scratch vectors reused every frame (no per-frame allocation).
63
+ const posScratch = useMemo(() => new Vector3(), []);
64
+ const lookAtScratch = useMemo(() => new Vector3(), []);
65
+
66
+ useFrame(() => {
67
+ if (!progressRef || kf.length === 0) return;
68
+ const t = Math.max(0, Math.min(1, progressRef.current.progress));
69
+
70
+ // Find the bracketing keyframe pair.
71
+ let a = kf[0];
72
+ let b = kf[kf.length - 1];
73
+ for (let i = 0; i < kf.length - 1; i++) {
74
+ if (t >= kf[i].at && t <= kf[i + 1].at) {
75
+ a = kf[i];
76
+ b = kf[i + 1];
77
+ break;
78
+ }
79
+ }
80
+
81
+ const range = b.at - a.at;
82
+ const local = range === 0 ? 0 : (t - a.at) / range;
83
+ const eased = b.ease(local);
84
+
85
+ posScratch.lerpVectors(a.position, b.position, eased);
86
+ lookAtScratch.lerpVectors(a.lookAt, b.lookAt, eased);
87
+
88
+ camera.position.copy(posScratch);
89
+ camera.lookAt(lookAtScratch);
90
+
91
+ // FOV interpolation (only meaningful on perspective cameras).
92
+ if (a.fov != null && b.fov != null && (camera as ThreePerspectiveCamera).isPerspectiveCamera) {
93
+ const fov = a.fov + (b.fov - a.fov) * eased;
94
+ (camera as ThreePerspectiveCamera).fov = fov;
95
+ (camera as ThreePerspectiveCamera).updateProjectionMatrix();
96
+ }
97
+ });
98
+
99
+ return null;
100
+ }