chati-dev 4.3.1 → 4.4.0

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 (147) hide show
  1. package/README.md +18 -13
  2. package/bin/chati.js +12 -4
  3. package/framework/agents/build/dev.md +5 -1
  4. package/framework/agents/discover/greenfield-wu.md +16 -1
  5. package/framework/agents/plan/tasks.md +31 -0
  6. package/framework/agents/plan/ux-brand-architect.md +21 -2
  7. package/framework/agents/plan/ux-component-engineer.md +10 -0
  8. package/framework/agents/quality/qa-implementation.md +1 -1
  9. package/framework/agents/quality/qa-planning.md +1 -1
  10. package/framework/agents/quality/qa-visual.md +24 -21
  11. package/framework/config.yaml +5 -4
  12. package/framework/constitution.md +58 -11
  13. package/framework/context/governance.md +12 -10
  14. package/framework/context/root.md +2 -2
  15. package/framework/data/entity-registry.yaml +12 -4
  16. package/framework/data/qa-rubrics.yaml +335 -0
  17. package/framework/domains/constitution.yaml +3 -3
  18. package/framework/executors/json-validate.js +80 -0
  19. package/framework/executors/npm-script.js +72 -0
  20. package/framework/executors/path-check.js +67 -0
  21. package/framework/executors/registry.yaml +36 -0
  22. package/framework/hooks/advance-trigger.js +47 -3
  23. package/framework/hooks/constitution-guard.js +9 -4
  24. package/framework/hooks/git-push-authority.js +113 -0
  25. package/framework/hooks/license-guard.js +59 -9
  26. package/framework/hooks/model-governance.js +40 -17
  27. package/framework/hooks/prism-engine.js +78 -11
  28. package/framework/hooks/session-digest.js +12 -3
  29. package/framework/hooks/settings.json +7 -3
  30. package/framework/hooks/style-guard.js +52 -3
  31. package/framework/hooks/team-quality-gate.js +43 -5
  32. package/framework/i18n/en.yaml +3 -3
  33. package/framework/i18n/es.yaml +3 -3
  34. package/framework/i18n/fr.yaml +3 -3
  35. package/framework/i18n/pt.yaml +3 -3
  36. package/framework/intelligence/confidence.yaml +85 -6
  37. package/framework/intelligence/context-engine.md +9 -5
  38. package/framework/intelligence/decision-engine.md +1 -1
  39. package/framework/orchestrator/chati-router.js +1 -1
  40. package/framework/orchestrator/chati-update.md +19 -3
  41. package/framework/orchestrator/chati.md +19 -1
  42. package/framework/quality-gates/planning-gate.md +2 -0
  43. package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
  44. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
  45. package/framework/scaffold/saas-dashboard/README.md +58 -0
  46. package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
  47. package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
  48. package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
  49. package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
  50. package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
  51. package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
  52. package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
  53. package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
  54. package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
  55. package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
  56. package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
  57. package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
  58. package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
  59. package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
  60. package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
  61. package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
  62. package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
  63. package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
  64. package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
  65. package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
  66. package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
  67. package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
  68. package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
  69. package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
  70. package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
  71. package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
  72. package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
  73. package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
  74. package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
  75. package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
  76. package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
  77. package/framework/schemas/session.schema.json +5 -0
  78. package/framework/schemas/task.schema.json +9 -0
  79. package/framework/scripts/visual-qa.js +87 -1
  80. package/framework/tasks/orchestrator-health.md +4 -4
  81. package/framework/tasks/qa-impl-verdict.md +11 -5
  82. package/framework/templates/qa-gate-tmpl.yaml +24 -11
  83. package/package.json +3 -3
  84. package/src/config/context-file-generator.js +0 -6
  85. package/src/dashboard/renderer.js +0 -36
  86. package/src/executors/runner.js +204 -0
  87. package/src/installer/core.js +2 -1
  88. package/src/installer/templates.js +5 -6
  89. package/src/intelligence/registry-manager.js +1 -1
  90. package/src/license/client.js +9 -3
  91. package/src/license/commands.js +12 -2
  92. package/src/license/machine-id.js +42 -1
  93. package/src/memory/gotchas.js +58 -40
  94. package/src/memory/magic-docs.js +1 -1
  95. package/src/memory/session-digest.js +9 -4
  96. package/src/orchestrator/cli.js +174 -14
  97. package/src/orchestrator/doctor.js +98 -16
  98. package/src/orchestrator/pipeline-manager.js +44 -22
  99. package/src/orchestrator/session-manager.js +56 -8
  100. package/src/telemetry/sender.js +7 -19
  101. package/src/terminal/run-team.js +3 -3
  102. package/src/upgrade/tracked-files-detector.js +34 -16
  103. package/src/utils/feature-flags.js +1 -1
  104. package/src/utils/flatten-entities.js +4 -40
  105. package/src/utils/schema-validator.js +0 -14
  106. package/src/wizard/i18n.js +3 -3
  107. package/src/api/index.js +0 -120
  108. package/src/autonomy/autonomous-gate.js +0 -294
  109. package/src/autonomy/build-loop.js +0 -281
  110. package/src/autonomy/build-state.js +0 -286
  111. package/src/autonomy/cause-analyzer.js +0 -177
  112. package/src/autonomy/escalation.js +0 -214
  113. package/src/autonomy/index.js +0 -51
  114. package/src/autonomy/mode-manager.js +0 -225
  115. package/src/autonomy/mode-suggester.js +0 -283
  116. package/src/autonomy/progress-reporter.js +0 -275
  117. package/src/autonomy/safety-net.js +0 -370
  118. package/src/config/agent-customizer.js +0 -231
  119. package/src/decision/analyzer.js +0 -291
  120. package/src/decision/engine.js +0 -250
  121. package/src/decision/index.js +0 -38
  122. package/src/decision/registry-healer.js +0 -468
  123. package/src/decision/registry-updater.js +0 -339
  124. package/src/extensions/loader.js +0 -145
  125. package/src/extensions/registry.js +0 -134
  126. package/src/gates/circuit-breaker.js +0 -151
  127. package/src/gates/g1-planning-complete.js +0 -154
  128. package/src/gates/g2-qa-planning.js +0 -156
  129. package/src/gates/g3-implementation.js +0 -215
  130. package/src/gates/g4-qa-implementation.js +0 -240
  131. package/src/gates/g5-deploy-ready.js +0 -181
  132. package/src/gates/gate-base.js +0 -185
  133. package/src/gates/index.js +0 -46
  134. package/src/health/auto-fix.js +0 -216
  135. package/src/health/engine.js +0 -246
  136. package/src/merger/semantic-merger.js +0 -292
  137. package/src/preview/detector.js +0 -238
  138. package/src/preview/index.js +0 -20
  139. package/src/preview/launcher.js +0 -235
  140. package/src/preview/log-buffer.js +0 -103
  141. package/src/quality/metrics-collector.js +0 -281
  142. package/src/quality/test-runner.js +0 -366
  143. package/src/tasks/executor.js +0 -195
  144. package/src/tasks/index.js +0 -4
  145. package/src/tasks/loader.js +0 -210
  146. package/src/tasks/router.js +0 -182
  147. package/src/utils/event-bus.js +0 -126
@@ -0,0 +1,301 @@
1
+ /*
2
+ * Topbar.tsx -- Application Topbar
3
+ * ============================================================
4
+ * WHY THIS FILE EXISTS
5
+ * --------------------
6
+ * The topbar is the sticky horizontal bar that sits at the top
7
+ * of the main content column (not the full viewport width).
8
+ * It serves four roles:
9
+ *
10
+ * 1. Navigation context: the breadcrumb trail tells the user
11
+ * where they are in the application hierarchy.
12
+ *
13
+ * 2. Sidebar control: the hamburger/collapse button is the
14
+ * only interactive element that controls sidebar state.
15
+ * On mobile it opens the overlay drawer; on desktop it
16
+ * toggles icon-only mode.
17
+ *
18
+ * 3. Global search: the search button opens the CommandPalette
19
+ * (Cmd+K shortcut). The button is keyboard-accessible and
20
+ * shows the keyboard shortcut hint.
21
+ *
22
+ * 4. User affordances: notification bell (with unread count
23
+ * badge) and a user menu trigger (avatar + chevron).
24
+ *
25
+ * POSITIONING
26
+ * -----------
27
+ * The Topbar uses position:sticky with top:0. It sits in the
28
+ * second grid column (shell-main) alongside the scrollable
29
+ * <main>. This means it scrolls with the sidebar but stays
30
+ * fixed relative to the main content viewport. z-index is
31
+ * --z-topbar (40) so it appears above content but below modals.
32
+ *
33
+ * BACKDROP BLUR
34
+ * -------------
35
+ * backdrop-filter: blur(8px) creates a frosted-glass effect
36
+ * when content scrolls behind the topbar. We also set a
37
+ * semi-transparent bg-overlay background (NOT full opacity)
38
+ * to let the blur be visible. This is intentional: full-opacity
39
+ * backgrounds would make the blur invisible.
40
+ *
41
+ * BREADCRUMB
42
+ * ----------
43
+ * The Breadcrumb component is imported from its own file where
44
+ * it derives crumbs from the Next.js pathname. Topbar owns the
45
+ * placement but not the logic.
46
+ *
47
+ * SEARCH KEYBOARD SHORTCUT
48
+ * -------------------------
49
+ * A useEffect registers the Cmd+K (Mac) / Ctrl+K (Windows/Linux)
50
+ * shortcut globally. The shortcut state is lifted to this
51
+ * component because Topbar is always mounted, making it the
52
+ * right place for global keyboard shortcuts that do not belong
53
+ * to a specific page.
54
+ *
55
+ * ARIA
56
+ * ----
57
+ * The topbar is a <header> element (banner landmark). The
58
+ * hamburger button has aria-expanded and aria-controls tied
59
+ * to the sidebar nav. The notification button uses aria-label
60
+ * that includes the count ("12 unread notifications") so
61
+ * screen readers announce the count without the user needing
62
+ * to read the visual badge.
63
+ */
64
+
65
+ 'use client'
66
+
67
+ import React, { useState, useEffect, useCallback, useRef } from 'react'
68
+ import { useCommandPalette } from '../interaction/CommandPalette';
69
+ import Link from 'next/link'
70
+ import {
71
+ Menu,
72
+ Search,
73
+ Bell,
74
+ ChevronDown,
75
+ Settings,
76
+ LogOut,
77
+ User,
78
+ Command,
79
+ type LucideIcon,
80
+ } from 'lucide-react'
81
+ import { useSidebar } from './SidebarContext'
82
+ import { Breadcrumb } from './Breadcrumb'
83
+ import { ThemeToggle } from './ThemeToggle'
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Types
87
+ // ---------------------------------------------------------------------------
88
+
89
+ // Notification count: in a real app this would come from a server action
90
+ // or a real-time subscription. Static for the preset.
91
+ const NOTIFICATION_COUNT = 4
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // User menu items
95
+ // ---------------------------------------------------------------------------
96
+
97
+ type UserMenuItem = { label: string; href: string; icon: LucideIcon; destructive?: boolean };
98
+
99
+ const USER_MENU_ITEMS: UserMenuItem[] = [
100
+ { label: 'Your Profile', href: '/settings/profile', icon: User },
101
+ { label: 'Settings', href: '/settings', icon: Settings },
102
+ { label: 'Sign out', href: '/auth/sign-out', icon: LogOut, destructive: true },
103
+ ]
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Component
107
+ // ---------------------------------------------------------------------------
108
+
109
+ export function Topbar() {
110
+ const { toggle, openMobile, isCollapsed } = useSidebar()
111
+ const [userMenuOpen, setUserMenuOpen] = useState(false)
112
+ const userMenuRef = useRef<HTMLDivElement>(null)
113
+
114
+ // The CommandPaletteProvider (mounted in layout) owns the palette open state
115
+ // and the global Cmd+K / Ctrl+K shortcut. The top bar just opens it.
116
+ const { open: openCommandPalette } = useCommandPalette()
117
+
118
+ // ---- Close user menu on click-outside -----------------------------------
119
+ useEffect(() => {
120
+ function handleClickOutside(e: MouseEvent) {
121
+ if (userMenuRef.current && !userMenuRef.current.contains(e.target as Node)) {
122
+ setUserMenuOpen(false)
123
+ }
124
+ }
125
+ if (userMenuOpen) {
126
+ document.addEventListener('mousedown', handleClickOutside)
127
+ return () => document.removeEventListener('mousedown', handleClickOutside)
128
+ }
129
+ }, [userMenuOpen])
130
+
131
+ // ---- Close user menu on Escape ------------------------------------------
132
+ const handleUserMenuKeyDown = useCallback(
133
+ (e: React.KeyboardEvent) => {
134
+ if (e.key === 'Escape') {
135
+ setUserMenuOpen(false)
136
+ }
137
+ },
138
+ []
139
+ )
140
+
141
+ // ---- Determine if we are on mobile (client-only) ------------------------
142
+ // We detect mobile by CSS media query rather than viewport width JS so
143
+ // the hamburger behavior (openMobile vs toggle) is correct on resize.
144
+ const handleSidebarTrigger = useCallback(() => {
145
+ if (window.matchMedia('(max-width: 767px)').matches) {
146
+ openMobile()
147
+ } else {
148
+ toggle()
149
+ }
150
+ }, [toggle, openMobile])
151
+
152
+ return (
153
+ <header className="topbar" role="banner">
154
+ <div className="topbar-inner">
155
+ {/* ---- Left: sidebar trigger + breadcrumb ---- */}
156
+ <div className="topbar-left">
157
+ {/*
158
+ * Sidebar toggle button. On mobile: opens the overlay drawer.
159
+ * On desktop: toggles icon-only collapse.
160
+ * aria-expanded reflects sidebar state (desktop only).
161
+ * aria-controls links to the sidebar nav element.
162
+ */}
163
+ <button
164
+ type="button"
165
+ onClick={handleSidebarTrigger}
166
+ aria-label="Toggle navigation"
167
+ aria-expanded={!isCollapsed}
168
+ aria-controls="sidebar-nav"
169
+ className="topbar-icon-btn"
170
+ >
171
+ <Menu size={20} aria-hidden="true" />
172
+ </button>
173
+
174
+ {/* Breadcrumb trail: derives crumbs from Next.js pathname */}
175
+ <Breadcrumb />
176
+ </div>
177
+
178
+ {/* ---- Right: search + notifications + user menu ---- */}
179
+ <div className="topbar-right">
180
+ {/*
181
+ * Search / command palette trigger.
182
+ * Shows Cmd+K hint on desktop (hidden on mobile via CSS).
183
+ */}
184
+ <button
185
+ type="button"
186
+ onClick={openCommandPalette}
187
+ aria-label="Open command palette"
188
+ aria-keyshortcuts="Control+k Meta+k"
189
+ className="topbar-search-btn"
190
+ >
191
+ <Search size={16} aria-hidden="true" className="topbar-search-icon" />
192
+ <span className="topbar-search-label">Search...</span>
193
+ <kbd className="topbar-search-kbd" aria-hidden="true">
194
+ <Command size={10} />
195
+ K
196
+ </kbd>
197
+ </button>
198
+
199
+ {/*
200
+ * Notification bell.
201
+ * aria-label includes the count so SR users hear it.
202
+ * The visual badge is aria-hidden (count is in the label).
203
+ */}
204
+ <button
205
+ type="button"
206
+ aria-label={
207
+ NOTIFICATION_COUNT > 0
208
+ ? `${NOTIFICATION_COUNT} unread notifications`
209
+ : 'Notifications, none unread'
210
+ }
211
+ className="topbar-icon-btn topbar-icon-btn--relative"
212
+ >
213
+ <Bell size={20} aria-hidden="true" />
214
+ {NOTIFICATION_COUNT > 0 && (
215
+ <span className="topbar-notif-badge" aria-hidden="true">
216
+ {NOTIFICATION_COUNT > 9 ? '9+' : NOTIFICATION_COUNT}
217
+ </span>
218
+ )}
219
+ </button>
220
+
221
+ {/*
222
+ * User menu: avatar + name + dropdown.
223
+ * role="button" + aria-haspopup + aria-expanded for the trigger.
224
+ * The dropdown is a role="menu" with role="menuitem" children.
225
+ */}
226
+ <div
227
+ ref={userMenuRef}
228
+ className="topbar-user-menu"
229
+ onKeyDown={handleUserMenuKeyDown}
230
+ >
231
+ <button
232
+ type="button"
233
+ id="user-menu-trigger"
234
+ aria-haspopup="menu"
235
+ aria-expanded={userMenuOpen}
236
+ aria-controls="user-menu-dropdown"
237
+ onClick={() => setUserMenuOpen((v) => !v)}
238
+ className="topbar-user-trigger"
239
+ >
240
+ {/* Avatar */}
241
+ <span className="topbar-avatar" aria-hidden="true">
242
+ GA
243
+ </span>
244
+ {/* Name: hidden on mobile */}
245
+ <span className="topbar-user-name">Gabriel A.</span>
246
+ <ChevronDown
247
+ size={14}
248
+ aria-hidden="true"
249
+ className={[
250
+ 'topbar-chevron',
251
+ userMenuOpen ? 'topbar-chevron--open' : '',
252
+ ]
253
+ .filter(Boolean)
254
+ .join(' ')}
255
+ />
256
+ </button>
257
+
258
+ {/* Dropdown menu */}
259
+ {userMenuOpen && (
260
+ <div
261
+ id="user-menu-dropdown"
262
+ role="menu"
263
+ aria-labelledby="user-menu-trigger"
264
+ className="topbar-dropdown"
265
+ >
266
+ {USER_MENU_ITEMS.map((item) => {
267
+ const Icon = item.icon
268
+ return (
269
+ <Link
270
+ key={item.href}
271
+ href={item.href}
272
+ role="menuitem"
273
+ onClick={() => setUserMenuOpen(false)}
274
+ className={[
275
+ 'topbar-dropdown-item',
276
+ item.destructive ? 'topbar-dropdown-item--destructive' : '',
277
+ ]
278
+ .filter(Boolean)
279
+ .join(' ')}
280
+ >
281
+ <Icon size={15} aria-hidden="true" />
282
+ <span>{item.label}</span>
283
+ </Link>
284
+ )
285
+ })}
286
+ </div>
287
+ )}
288
+ </div>
289
+ </div>
290
+ </div>
291
+
292
+ {/*
293
+ * CommandPalette portal slot: rendered here so it is inside the
294
+ * <header> DOM subtree for correct ARIA ownership. In production
295
+ * this would use Next.js dynamic import for code splitting.
296
+ * The actual CommandPalette component (D_interaction builder) is
297
+ * imported here; for this preset it is a placeholder trigger slot.
298
+ */}
299
+ </header>
300
+ )
301
+ }
@@ -0,0 +1,324 @@
1
+ /**
2
+ * lib/brand.ts -- saas-dashboard preset v1.0.0
3
+ *
4
+ * WHY THIS FILE EXISTS
5
+ * --------------------
6
+ * CSS custom properties are excellent for theming but opaque to TypeScript.
7
+ * When a Recharts component needs the violet series color, it cannot read
8
+ * a CSS variable at the component author's type-check time. This module
9
+ * provides the same values as typed constants so charting libraries,
10
+ * framer-motion animation objects, and utility functions can consume them
11
+ * with full IDE autocomplete and compile-time safety.
12
+ *
13
+ * CONTRACT: mirror, never diverge
14
+ * --------------------------------
15
+ * Every constant here mirrors a CSS custom property in globals.css.
16
+ * They must always match. If you add a token to globals.css, add it here too.
17
+ * The authoritative source is globals.css (rendered in the browser);
18
+ * this file is its TypeScript shadow.
19
+ *
20
+ * IMMUTABILITY
21
+ * ------------
22
+ * All exports use "as const" to produce literal types. This allows
23
+ * TypeScript to infer exact string/number values instead of widening
24
+ * to `string` or `number`, enabling exhaustiveness checks in components.
25
+ *
26
+ * USAGE EXAMPLES
27
+ * --------------
28
+ * // Recharts area chart series:
29
+ * import { COLORS } from '@/lib/brand'
30
+ * <Area stroke={COLORS.viz[0]} fill={COLORS.viz[0]} />
31
+ *
32
+ * // Framer-motion duration:
33
+ * import { DURATION, EASING } from '@/lib/brand'
34
+ * animate={{ opacity: 1 }}
35
+ * transition={{ duration: DURATION.normal / 1000, ease: EASING.out }}
36
+ *
37
+ * // Stagger children:
38
+ * import { staggerChildren } from '@/lib/brand'
39
+ * const childVariants = staggerChildren(6) // 6 items, 40ms apart
40
+ */
41
+
42
+ /* ================================================================
43
+ COLOR TOKENS
44
+ ================================================================ */
45
+
46
+ /**
47
+ * Raw neutral scale values.
48
+ * Components should prefer semantic tokens (SEMANTIC_COLORS) over
49
+ * these. The raw scale is exported for design tooling and token
50
+ * documentation generation only.
51
+ */
52
+ export const NEUTRAL = {
53
+ 0: '#ffffff',
54
+ 50: '#f7f8fa',
55
+ 100: '#eef0f4',
56
+ 200: '#dde1e9',
57
+ 300: '#c4cad6',
58
+ 400: '#9aa3b4',
59
+ 500: '#6b7689',
60
+ 600: '#4e5769',
61
+ 700: '#343d4f',
62
+ 800: '#1f2635',
63
+ 850: '#171d2b',
64
+ 900: '#0f1420',
65
+ 950: '#080c14',
66
+ 1000: '#000000',
67
+ } as const;
68
+
69
+ export type NeutralStep = keyof typeof NEUTRAL;
70
+
71
+ /**
72
+ * Accent (violet 258deg) values.
73
+ * Role 1: interactive affordance (buttons, links, active states).
74
+ * Role 2: brand emphasis (tinted backgrounds, selected rows).
75
+ */
76
+ export const ACCENT = {
77
+ base: '#7c5af8',
78
+ hover: '#9070ff',
79
+ active: '#6644e8',
80
+ subtle: '#1e1640',
81
+ muted: '#4a3490',
82
+ } as const;
83
+
84
+ /**
85
+ * Status palette: success, warning, danger, info.
86
+ * Each status has a full-saturation value (text/icons) and
87
+ * a subtle value (background tints).
88
+ */
89
+ export const STATUS = {
90
+ success: '#22c55e',
91
+ successSubtle: '#052e16',
92
+ warning: '#f59e0b',
93
+ warningSubtle: '#27200a',
94
+ danger: '#ef4444',
95
+ dangerSubtle: '#2a0a0a',
96
+ info: '#38bdf8',
97
+ infoSubtle: '#082030',
98
+ } as const;
99
+
100
+ /**
101
+ * Data visualisation series: 6 perceptually-spaced hues.
102
+ * The array order matters: series[0] is always the primary (violet/accent),
103
+ * which creates visual brand coherence in charts. Subsequent hues are
104
+ * ordered to maximise distinguishability at 4px line weight.
105
+ */
106
+ export const VIZ_SERIES = [
107
+ '#7c5af8', // 0: violet (accent, brand-tied)
108
+ '#38bdf8', // 1: sky
109
+ '#22c55e', // 2: emerald
110
+ '#f59e0b', // 3: amber
111
+ '#f472b6', // 4: pink
112
+ '#fb923c', // 5: orange
113
+ ] as const;
114
+
115
+ export type VizSeriesIndex = 0 | 1 | 2 | 3 | 4 | 5;
116
+
117
+ /**
118
+ * Semantic color map for use in JS contexts.
119
+ * Keys mirror the CSS custom property names (without the --color- prefix).
120
+ */
121
+ export const COLORS = {
122
+ /* Surface layers (dark mode defaults) */
123
+ bgBase: NEUTRAL[900],
124
+ bgRaised: NEUTRAL[800],
125
+ bgOverlay: NEUTRAL[850],
126
+ bgSunken: NEUTRAL[950],
127
+ bgHighlight: ACCENT.subtle,
128
+
129
+ /* Text */
130
+ textPrimary: NEUTRAL[50],
131
+ textSecondary: NEUTRAL[400],
132
+ textMuted: NEUTRAL[500],
133
+ textInverse: NEUTRAL[950],
134
+ textAccent: ACCENT.hover,
135
+
136
+ /* Borders */
137
+ borderDefault: NEUTRAL[700],
138
+ borderEmphasis: NEUTRAL[600],
139
+ borderAccent: ACCENT.muted,
140
+
141
+ /* Status */
142
+ ...STATUS,
143
+
144
+ /* Data viz */
145
+ viz: VIZ_SERIES,
146
+
147
+ /* Focus */
148
+ focusRing: ACCENT.base,
149
+ } as const;
150
+
151
+ export type ColorKey = keyof typeof COLORS;
152
+
153
+ /* ================================================================
154
+ MOTION TOKENS
155
+ ================================================================ */
156
+
157
+ /**
158
+ * Duration values in MILLISECONDS.
159
+ * Divide by 1000 when passing to framer-motion (which uses seconds).
160
+ *
161
+ * Usage with framer-motion:
162
+ * transition={{ duration: DURATION.normal / 1000, ease: EASING.out }}
163
+ */
164
+ export const DURATION = {
165
+ instant: 80,
166
+ fast: 120,
167
+ normal: 200,
168
+ medium: 300,
169
+ slow: 400,
170
+ layout: 500,
171
+ } as const;
172
+
173
+ export type DurationKey = keyof typeof DURATION;
174
+
175
+ /**
176
+ * Easing curves as cubic-bezier arrays for framer-motion.
177
+ * Framer-motion accepts [x1, y1, x2, y2] tuples directly.
178
+ * The string form is also exported for CSS transition declarations.
179
+ */
180
+ export const EASING = {
181
+ /** Entering: spring-like deceleration. Use for elements appearing on screen. */
182
+ out: [0.16, 1, 0.3, 1] as [number, number, number, number],
183
+ /** Leaving: builds momentum. Use for elements leaving the screen. */
184
+ in: [0.4, 0, 1, 1] as [number, number, number, number],
185
+ /** Toggling: symmetric. Use for accordions, sliders, state flips. */
186
+ inOut: [0.4, 0, 0.2, 1] as [number, number, number, number],
187
+ /** Delight: slight overshoot. Use for badge appear, success pop. */
188
+ spring: [0.34, 1.56, 0.64, 1] as [number, number, number, number],
189
+ /** Subtle: minimal energy. Use for low-profile hover fades. */
190
+ subtle: [0.25, 0, 0, 1] as [number, number, number, number],
191
+ } as const;
192
+
193
+ /** CSS string form of easing curves for direct use in style attributes */
194
+ export const EASING_CSS = {
195
+ out: 'cubic-bezier(0.16, 1, 0.3, 1)',
196
+ in: 'cubic-bezier(0.4, 0, 1, 1)',
197
+ inOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
198
+ spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
199
+ subtle: 'cubic-bezier(0.25, 0, 0, 1)',
200
+ } as const;
201
+
202
+ export type EasingKey = keyof typeof EASING;
203
+
204
+ /**
205
+ * Stagger delay: 40ms between list items.
206
+ * Below 30ms items appear simultaneous; above 60ms feels mechanical.
207
+ */
208
+ export const STAGGER_BASE = 40; // milliseconds
209
+
210
+ /**
211
+ * Returns a framer-motion staggerChildren configuration for N items.
212
+ * Delay starts at 0 and increments by STAGGER_BASE for each child.
213
+ *
214
+ * Usage:
215
+ * const container = {
216
+ * animate: staggerChildren(items.length),
217
+ * }
218
+ * <motion.ul variants={container} animate="animate">
219
+ * {items.map((item, i) => (
220
+ * <motion.li key={i} variants={itemVariant}>{item}</motion.li>
221
+ * ))}
222
+ * </motion.ul>
223
+ */
224
+ export function staggerChildren(count: number) {
225
+ return {
226
+ staggerChildren: STAGGER_BASE / 1000,
227
+ delayChildren: 0,
228
+ };
229
+ }
230
+
231
+ /* ================================================================
232
+ SPACING TOKENS
233
+ ================================================================ */
234
+
235
+ /**
236
+ * Spacing values in pixels, mirroring the --space-* CSS tokens.
237
+ * Use these in JS contexts: canvas drawing, Recharts margins,
238
+ * programmatic layout calculations.
239
+ */
240
+ export const SPACING = {
241
+ px: 1,
242
+ 0: 0,
243
+ 1: 4,
244
+ 2: 8,
245
+ 3: 12,
246
+ 4: 16,
247
+ 5: 20,
248
+ 6: 24,
249
+ 7: 28,
250
+ 8: 32,
251
+ 9: 36,
252
+ 10: 40,
253
+ 11: 44,
254
+ 12: 48,
255
+ 14: 56,
256
+ 16: 64,
257
+ 20: 80,
258
+ 24: 96,
259
+ 32: 128,
260
+ 40: 160,
261
+ 48: 192,
262
+ 56: 224,
263
+ 64: 256,
264
+ } as const;
265
+
266
+ export type SpacingStep = keyof typeof SPACING;
267
+
268
+ /** Semantic spacing aliases */
269
+ export const SPACING_SEMANTIC = {
270
+ cardPad: SPACING[6], // 24px
271
+ cardGap: SPACING[5], // 20px
272
+ sectionGap: SPACING[8], // 32px
273
+ topbarH: SPACING[12], // 48px
274
+ sidebarW: 240, // expanded
275
+ sidebarWCol: SPACING[16], // 64px collapsed
276
+ } as const;
277
+
278
+ /* ================================================================
279
+ BREAKPOINTS
280
+ ================================================================ */
281
+
282
+ /**
283
+ * Breakpoint values in pixels, mirroring Tailwind v4 defaults
284
+ * except xl which is overridden to 1200px (75rem).
285
+ *
286
+ * Use these in JS contexts: ResizeObserver thresholds,
287
+ * matchMedia queries, chart responsive adjustments.
288
+ *
289
+ * Note: xl is 1200px not 1280px -- see globals.css @theme block.
290
+ */
291
+ export const BREAKPOINTS = {
292
+ sm: 640,
293
+ md: 768,
294
+ lg: 1024,
295
+ xl: 1200, /* overridden from Tailwind default 1280 */
296
+ '2xl': 1536,
297
+ } as const;
298
+
299
+ export type BreakpointKey = keyof typeof BREAKPOINTS;
300
+
301
+ /* ================================================================
302
+ CHART HELPERS
303
+ ================================================================ */
304
+
305
+ /**
306
+ * Default Recharts chart margins using spacing tokens.
307
+ * Ensures consistent chart padding across all AreaChart/BarChart instances.
308
+ */
309
+ export const CHART_MARGINS = {
310
+ top: SPACING[2], // 8px
311
+ right: SPACING[3], // 12px
312
+ bottom: SPACING[2], // 8px
313
+ left: SPACING[2], // 8px -- adjusted by formatY label width at runtime
314
+ } as const;
315
+
316
+ /**
317
+ * Recharts axis tick style using type and color tokens.
318
+ * Pass to <XAxis tickStyle={AXIS_TICK_STYLE}> etc.
319
+ */
320
+ export const AXIS_TICK_STYLE = {
321
+ fill: COLORS.textMuted,
322
+ fontSize: 12, // matches --type-caption: 0.75rem
323
+ fontFamily: 'Inter Variable, system-ui, sans-serif',
324
+ } as const;