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,58 @@
1
+ # saas-dashboard scaffold preset
2
+
3
+ A premium SaaS dashboard and app-shell starting point. Where the motion-premium
4
+ presets target animated showcase sites, this preset targets the build-me-a-tool
5
+ category: a data-dense application shell that looks designed, not generated.
6
+
7
+ ## What it ships
8
+
9
+ 29 templates across foundation, primitives, shell, data, interaction, and a
10
+ composed sample page:
11
+
12
+ - **Foundation**: `app/globals.css` (the full token system, fluid type scale,
13
+ reduced-motion handling, the `xl` breakpoint override), `lib/brand.ts` (typed
14
+ tokens), and the single `Container` authority for max-width and padding.
15
+ - **Primitives**: `Button`, `Card`, `Input`, `Badge`, `Spinner`, each with full
16
+ resting, hover, active, disabled, and focus states.
17
+ - **Shell**: `AppShell`, `Sidebar` (collapsible, keyboard-accessible, responsive
18
+ drawer), `Topbar` (search trigger, breadcrumb, user menu), `NavItem`,
19
+ `ThemeToggle`, `Breadcrumb`.
20
+ - **Data**: `DataTable` (typed, sortable, sticky header, selection, empty and
21
+ loading states), `StatCard` and `StatCardGrid` (KPI with delta semantics and a
22
+ sparkline), `Chart` (a self-contained SVG area chart, no chart dependency).
23
+ - **Interaction**: `CommandPalette` (Cmd+K, fuzzy filter, keyboard nav, hand
24
+ rolled, no extra dependency), `Field` and `Form` (labeled inputs, validation
25
+ states, never placeholder-only labels).
26
+ - **Feedback**: `EmptyState`.
27
+ - **Sample**: `app/dashboard/page.tsx` composes the shell, stat cards, a chart,
28
+ and a data table to demonstrate the preset.
29
+
30
+ ## Design system
31
+
32
+ - **Color**: a calibrated dark palette built from a single neutral hue with even
33
+ perceptual luminance steps, a 6-color data-viz series, and a designed light
34
+ theme via `[data-theme="light"]` (never `filter: invert`). Every color is a
35
+ CSS custom property; components never carry a raw hex.
36
+ - **Type**: a 1.25 modular scale with fluid `clamp()` values, role-differentiated
37
+ line-heights, and optical tracking.
38
+ - **Space**: a 4px base unit tuned for data density.
39
+ - **Motion**: cubic-bezier easing matched to the metaphor, a duration scale, and
40
+ full reduced-motion support. No linear easing, no `transition: all`.
41
+
42
+ ## Quality contract
43
+
44
+ This preset is built to clear the QA-Visual rubric
45
+ (`chati.dev/data/qa-rubrics.yaml`) on every dimension: layout, typography, color,
46
+ spacing, interaction, motion, accessibility (WCAG 2.1 AA), and performance
47
+ (animate only `transform` and `opacity`).
48
+
49
+ ## Dependencies
50
+
51
+ `lucide-react` (icons) and `framer-motion` (micro-interactions). The chart and
52
+ the command palette are hand-rolled, so there is no chart library and no command
53
+ menu dependency.
54
+
55
+ ## Brandable
56
+
57
+ The accent color (`ACCENT_HEX`, `ACCENT_HOVER_HEX`) is resolved at apply time
58
+ from your brand source. The neutral and semantic scales are the designed system.
@@ -0,0 +1,546 @@
1
+ /**
2
+ * app/dashboard/page.tsx
3
+ * SaaS Dashboard Preset -- D_interaction phase (composed sample page)
4
+ *
5
+ * WHY THIS EXISTS
6
+ * ---------------
7
+ * A preset with only individual components is hard to evaluate. Teams adopting
8
+ * a scaffold need to see the components working together in a real layout:
9
+ * how tokens compose, how the grid breathes, how the type scale creates
10
+ * hierarchy across sections, and how the motion system feels at dashboard
11
+ * density.
12
+ *
13
+ * This file is the "show your work" page -- the proof that every design
14
+ * decision in the spec resolves coherently at the page level. It is ALSO
15
+ * the first thing a new developer opens, so it doubles as the definitive
16
+ * usage example for every component in the preset.
17
+ *
18
+ * LAYOUT ARCHITECTURE
19
+ * -------------------
20
+ * Section 1: StatCardGrid -- 4 KPI cards across, auto-fill responsive.
21
+ * Section 2: 2-column asymmetric grid -- Chart (wider) + secondary
22
+ * metric card (narrower). The asymmetry creates visual weight:
23
+ * the chart is the primary insight, the secondary card is context.
24
+ * Section 3: Full-width DataTable with realistic sample columns and
25
+ * pagination. Demonstrates sort indicators, status badges, and
26
+ * the empty state pattern.
27
+ *
28
+ * SPACING RATIONALE
29
+ * -----------------
30
+ * Sections are separated by --spacing-section (32px). Within sections,
31
+ * components are separated by --spacing-card-gap (20px). This two-level
32
+ * rhythm creates a clear hierarchy: sections are distinct regions,
33
+ * components within a section are related.
34
+ *
35
+ * The page wrapper uses padding-top to account for the sticky topbar
36
+ * (--spacing-topbar-h = 48px). This is set in AppShell's main scroll area,
37
+ * not here -- but it is documented here because the visual consequence
38
+ * (content not hiding behind topbar) is visible at this level.
39
+ *
40
+ * DATA
41
+ * ----
42
+ * All data is static TypeScript constants defined at the bottom of this
43
+ * file. No API calls, no SWR/React Query, no suspense boundaries for
44
+ * the sake of this demo. Replace with your data fetching layer.
45
+ *
46
+ * TOKEN USAGE
47
+ * -----------
48
+ * All spacing via CSS custom properties. No raw px values in JSX styles.
49
+ * Colors via semantic token layer (--color-text-*, --color-bg-*, etc.).
50
+ */
51
+
52
+ 'use client';
53
+
54
+ import { Container } from '@/components/shell/Container';
55
+ import { StatCard } from '@/components/data/StatCard';
56
+ import { StatCardGrid } from '@/components/data/StatCardGrid';
57
+ import { Chart } from '@/components/data/Chart';
58
+ import { DataTable } from '@/components/data/DataTable';
59
+ import { EmptyState } from '@/components/feedback/EmptyState';
60
+ import { Badge } from '@/components/primitives/Badge';
61
+ import { Button } from '@/components/primitives/Button';
62
+ import {
63
+ DollarSign,
64
+ Users,
65
+ TrendingUp,
66
+ Activity,
67
+ FileText,
68
+ Plus,
69
+ } from 'lucide-react';
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Mock data
73
+ // ---------------------------------------------------------------------------
74
+
75
+ const revenueSparkline = [12, 18, 14, 22, 19, 27, 24, 31, 28, 35, 33, 42];
76
+ const userSparkline = [340, 382, 360, 410, 395, 440, 420, 475, 460, 510, 490, 540];
77
+ const conversionSparkline = [3.1, 3.4, 3.2, 3.7, 3.5, 3.9, 3.8, 4.1, 3.9, 4.3, 4.2, 4.6];
78
+ const mrrSparkline = [8200, 8600, 8400, 9100, 8900, 9600, 9400, 10200, 9900, 10800, 10500, 11200];
79
+
80
+ const areaChartData = [
81
+ { month: 'Jan', revenue: 12400, users: 340 },
82
+ { month: 'Feb', revenue: 15200, users: 382 },
83
+ { month: 'Mar', revenue: 13800, users: 360 },
84
+ { month: 'Apr', revenue: 18100, users: 410 },
85
+ { month: 'May', revenue: 16900, users: 395 },
86
+ { month: 'Jun', revenue: 21400, users: 440 },
87
+ { month: 'Jul', revenue: 19800, users: 420 },
88
+ { month: 'Aug', revenue: 24200, users: 475 },
89
+ { month: 'Sep', revenue: 22100, users: 460 },
90
+ { month: 'Oct', revenue: 27800, users: 510 },
91
+ { month: 'Nov', revenue: 25400, users: 490 },
92
+ { month: 'Dec', revenue: 31600, users: 540 },
93
+ ];
94
+
95
+ const chartSeries = [
96
+ { key: 'revenue', label: 'Revenue', color: 'var(--color-viz-1)' },
97
+ { key: 'users', label: 'Active Users', color: 'var(--color-viz-2)' },
98
+ ];
99
+
100
+ export type TransactionRow = {
101
+ id: string;
102
+ customer: string;
103
+ email: string;
104
+ amount: number;
105
+ status: 'completed' | 'pending' | 'failed';
106
+ date: string;
107
+ plan: string;
108
+ };
109
+
110
+ const transactions: TransactionRow[] = [
111
+ { id: 'TXN-001', customer: 'Aria Nakamura', email: 'aria@synth.io', amount: 299, status: 'completed', date: '2026-06-07', plan: 'Pro' },
112
+ { id: 'TXN-002', customer: 'Luca Ferreira', email: 'luca@vela.co', amount: 99, status: 'completed', date: '2026-06-07', plan: 'Starter' },
113
+ { id: 'TXN-003', customer: 'Mira Osei', email: 'mira@datavault.com', amount: 899, status: 'pending', date: '2026-06-06', plan: 'Enterprise' },
114
+ { id: 'TXN-004', customer: 'Dev Patel', email: 'dev@cortex.ai', amount: 299, status: 'completed', date: '2026-06-06', plan: 'Pro' },
115
+ { id: 'TXN-005', customer: 'Soren Lindqvist', email: 'soren@nordic.se', amount: 99, status: 'failed', date: '2026-06-05', plan: 'Starter' },
116
+ { id: 'TXN-006', customer: 'Yuki Tanaka', email: 'yuki@flux.jp', amount: 299, status: 'completed', date: '2026-06-05', plan: 'Pro' },
117
+ { id: 'TXN-007', customer: 'Chiara Russo', email: 'chiara@bloom.it', amount: 899, status: 'completed', date: '2026-06-04', plan: 'Enterprise' },
118
+ { id: 'TXN-008', customer: 'Omar Hassan', email: 'omar@pulse.ae', amount: 99, status: 'pending', date: '2026-06-04', plan: 'Starter' },
119
+ ];
120
+
121
+ const tableColumns = [
122
+ {
123
+ key: 'customer' as const,
124
+ header: 'Customer',
125
+ render: (row: TransactionRow) => (
126
+ <div>
127
+ <div
128
+ style={{
129
+ fontSize: 'var(--type-small)',
130
+ fontWeight: 500,
131
+ color: 'var(--color-text-primary)',
132
+ }}
133
+ >
134
+ {row.customer}
135
+ </div>
136
+ <div
137
+ style={{
138
+ fontSize: 'var(--type-caption)',
139
+ color: 'var(--color-text-muted)',
140
+ marginTop: '2px',
141
+ }}
142
+ >
143
+ {row.email}
144
+ </div>
145
+ </div>
146
+ ),
147
+ },
148
+ {
149
+ key: 'plan' as const,
150
+ header: 'Plan',
151
+ render: (row: TransactionRow) => (
152
+ <Badge
153
+ variant={
154
+ row.plan === 'Enterprise'
155
+ ? 'accent'
156
+ : row.plan === 'Pro'
157
+ ? 'info'
158
+ : 'default'
159
+ }
160
+ size="sm"
161
+ >
162
+ {row.plan}
163
+ </Badge>
164
+ ),
165
+ },
166
+ {
167
+ key: 'amount' as const,
168
+ header: 'Amount',
169
+ render: (row: TransactionRow) => (
170
+ <span
171
+ style={{
172
+ fontFamily: 'var(--font-mono)',
173
+ fontSize: 'var(--type-mono)',
174
+ color: 'var(--color-text-primary)',
175
+ fontWeight: 500,
176
+ }}
177
+ >
178
+ ${row.amount.toLocaleString()}
179
+ </span>
180
+ ),
181
+ },
182
+ {
183
+ key: 'status' as const,
184
+ header: 'Status',
185
+ render: (row: TransactionRow) => (
186
+ <Badge
187
+ variant={
188
+ row.status === 'completed'
189
+ ? 'success'
190
+ : row.status === 'pending'
191
+ ? 'warning'
192
+ : 'danger'
193
+ }
194
+ dot
195
+ size="sm"
196
+ >
197
+ {row.status.charAt(0).toUpperCase() + row.status.slice(1)}
198
+ </Badge>
199
+ ),
200
+ },
201
+ {
202
+ key: 'date' as const,
203
+ header: 'Date',
204
+ render: (row: TransactionRow) => (
205
+ <span
206
+ style={{
207
+ fontSize: 'var(--type-caption)',
208
+ color: 'var(--color-text-muted)',
209
+ fontFamily: 'var(--font-mono)',
210
+ }}
211
+ >
212
+ {row.date}
213
+ </span>
214
+ ),
215
+ },
216
+ ];
217
+
218
+ // ---------------------------------------------------------------------------
219
+ // Page
220
+ // ---------------------------------------------------------------------------
221
+
222
+ export default function DashboardPage() {
223
+ return (
224
+ <Container>
225
+ {/* Page header */}
226
+ <header
227
+ style={{
228
+ paddingTop: 'var(--spacing-section)',
229
+ paddingBottom: 'var(--spacing-section)',
230
+ borderBottom: '1px solid var(--color-border-default)',
231
+ marginBottom: 'var(--spacing-section)',
232
+ }}
233
+ >
234
+ <div
235
+ style={{
236
+ display: 'flex',
237
+ alignItems: 'flex-start',
238
+ justifyContent: 'space-between',
239
+ gap: 'var(--spacing-card-gap)',
240
+ flexWrap: 'wrap',
241
+ }}
242
+ >
243
+ <div>
244
+ <h1
245
+ style={{
246
+ fontSize: 'var(--type-h1)',
247
+ lineHeight: 'var(--type-h1-lh)',
248
+ letterSpacing: 'var(--type-h1-ls)',
249
+ fontWeight: 700,
250
+ color: 'var(--color-text-primary)',
251
+ margin: 0,
252
+ }}
253
+ >
254
+ Dashboard
255
+ </h1>
256
+ <p
257
+ style={{
258
+ fontSize: 'var(--type-small)',
259
+ color: 'var(--color-text-secondary)',
260
+ margin: 'var(--space-2) 0 0',
261
+ }}
262
+ >
263
+ Welcome back. Here is what is happening with your product.
264
+ </p>
265
+ </div>
266
+
267
+ {/* Header actions */}
268
+ <div style={{ display: 'flex', gap: 'var(--space-3)', alignItems: 'center' }}>
269
+ <Button variant="secondary" iconLeft={<FileText size={16} aria-hidden="true" />}>
270
+ Export report
271
+ </Button>
272
+ <Button variant="primary" iconLeft={<Plus size={16} aria-hidden="true" />}>
273
+ New transaction
274
+ </Button>
275
+ </div>
276
+ </div>
277
+ </header>
278
+
279
+ {/* Section 1: KPI stat cards */}
280
+ <section aria-label="Key performance indicators">
281
+ <StatCardGrid>
282
+ <StatCard
283
+ label="Total Revenue"
284
+ value="$142,840"
285
+ delta="+11.2%"
286
+ previousValue="$128,400"
287
+ unit="USD"
288
+ icon={<DollarSign size={20} aria-hidden="true" />}
289
+ trend="up"
290
+ sparklineData={revenueSparkline}
291
+ />
292
+ <StatCard
293
+ label="Active Users"
294
+ value="5,420"
295
+ delta="+10.8%"
296
+ previousValue="4,890"
297
+ unit="users"
298
+ icon={<Users size={20} aria-hidden="true" />}
299
+ trend="up"
300
+ sparklineData={userSparkline}
301
+ />
302
+ <StatCard
303
+ label="Conversion Rate"
304
+ value="4.6%"
305
+ delta="+0.5pp"
306
+ previousValue="4.1%"
307
+ unit="percent"
308
+ icon={<TrendingUp size={20} aria-hidden="true" />}
309
+ trend="up"
310
+ sparklineData={conversionSparkline}
311
+ />
312
+ <StatCard
313
+ label="Monthly Recurring Revenue"
314
+ value="$11,200"
315
+ delta="+6.7%"
316
+ previousValue="$10,500"
317
+ unit="MRR"
318
+ icon={<Activity size={20} aria-hidden="true" />}
319
+ trend="up"
320
+ sparklineData={mrrSparkline}
321
+ />
322
+ </StatCardGrid>
323
+ </section>
324
+
325
+ {/* Section 2: Chart + secondary metric (asymmetric 2-col grid) */}
326
+ <section
327
+ aria-label="Revenue and users trend"
328
+ style={{ marginTop: 'var(--spacing-section)' }}
329
+ >
330
+ <div
331
+ style={{
332
+ display: 'grid',
333
+ gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 340px), 1fr))',
334
+ gap: 'var(--spacing-card-gap)',
335
+ alignItems: 'start',
336
+ }}
337
+ >
338
+ {/* Area chart -- primary insight, wider column */}
339
+ <div
340
+ style={{
341
+ gridColumn: 'span 2',
342
+ // On wide viewports this naturally spans 2 of 3 grid columns
343
+ // On narrow viewports (single column) it fills full width
344
+ }}
345
+ >
346
+ <Chart
347
+ data={areaChartData}
348
+ series={chartSeries}
349
+ xKey="month"
350
+ ariaLabel="Revenue and subscriptions over the last 12 months"
351
+ formatX={(v: unknown) => String(v)}
352
+ formatY={(v: number) =>
353
+ v > 999 ? `$${(v / 1000).toFixed(0)}k` : String(v)
354
+ }
355
+ height={280}
356
+ />
357
+ </div>
358
+
359
+ {/* Secondary metric card -- narrower, contextual */}
360
+ <div
361
+ style={{
362
+ backgroundColor: 'var(--color-bg-raised)',
363
+ border: '1px solid var(--color-border-default)',
364
+ borderRadius: 'var(--radius-lg)',
365
+ padding: 'var(--spacing-card-pad)',
366
+ boxShadow: 'var(--shadow-1)',
367
+ }}
368
+ >
369
+ <h2
370
+ style={{
371
+ fontSize: 'var(--type-h4)',
372
+ lineHeight: 'var(--type-h4-lh)',
373
+ letterSpacing: 'var(--type-h4-ls)',
374
+ fontWeight: 600,
375
+ color: 'var(--color-text-primary)',
376
+ margin: '0 0 var(--space-5)',
377
+ }}
378
+ >
379
+ Plan breakdown
380
+ </h2>
381
+
382
+ <ul
383
+ style={{
384
+ listStyle: 'none',
385
+ margin: 0,
386
+ padding: 0,
387
+ display: 'flex',
388
+ flexDirection: 'column',
389
+ gap: 'var(--space-4)',
390
+ }}
391
+ >
392
+ {[
393
+ { label: 'Enterprise', count: 12, pct: 18, color: 'var(--color-viz-1)' },
394
+ { label: 'Pro', count: 38, pct: 56, color: 'var(--color-viz-2)' },
395
+ { label: 'Starter', count: 18, pct: 26, color: 'var(--color-viz-3)' },
396
+ ].map((plan) => (
397
+ <li key={plan.label}>
398
+ <div
399
+ style={{
400
+ display: 'flex',
401
+ justifyContent: 'space-between',
402
+ marginBottom: 'var(--space-1)',
403
+ }}
404
+ >
405
+ <span
406
+ style={{
407
+ fontSize: 'var(--type-small)',
408
+ color: 'var(--color-text-secondary)',
409
+ }}
410
+ >
411
+ {plan.label}
412
+ </span>
413
+ <span
414
+ style={{
415
+ fontSize: 'var(--type-caption)',
416
+ fontFamily: 'var(--font-mono)',
417
+ color: 'var(--color-text-muted)',
418
+ }}
419
+ >
420
+ {plan.count} ({plan.pct}%)
421
+ </span>
422
+ </div>
423
+ {/* Progress bar -- animates only transform, not width */}
424
+ <div
425
+ style={{
426
+ height: '6px',
427
+ borderRadius: 'var(--radius-pill)',
428
+ backgroundColor: 'var(--color-bg-sunken)',
429
+ overflow: 'hidden',
430
+ }}
431
+ role="presentation"
432
+ aria-hidden="true"
433
+ >
434
+ <div
435
+ style={{
436
+ height: '100%',
437
+ width: '100%',
438
+ borderRadius: 'var(--radius-pill)',
439
+ backgroundColor: plan.color,
440
+ transformOrigin: 'left center',
441
+ transform: `scaleX(${plan.pct / 100})`,
442
+ transition: `transform var(--duration-layout) var(--ease-out)`,
443
+ }}
444
+ />
445
+ </div>
446
+ </li>
447
+ ))}
448
+ </ul>
449
+
450
+ <div
451
+ style={{
452
+ marginTop: 'var(--space-6)',
453
+ paddingTop: 'var(--space-5)',
454
+ borderTop: '1px solid var(--color-border-default)',
455
+ display: 'flex',
456
+ justifyContent: 'space-between',
457
+ alignItems: 'center',
458
+ }}
459
+ >
460
+ <span
461
+ style={{
462
+ fontSize: 'var(--type-caption)',
463
+ color: 'var(--color-text-muted)',
464
+ }}
465
+ >
466
+ Total customers
467
+ </span>
468
+ <span
469
+ style={{
470
+ fontSize: 'var(--type-h3)',
471
+ lineHeight: 'var(--type-h3-lh)',
472
+ fontWeight: 700,
473
+ color: 'var(--color-text-primary)',
474
+ fontFamily: 'var(--font-mono)',
475
+ }}
476
+ >
477
+ 68
478
+ </span>
479
+ </div>
480
+ </div>
481
+ </div>
482
+ </section>
483
+
484
+ {/* Section 3: DataTable */}
485
+ <section
486
+ aria-label="Recent transactions"
487
+ style={{
488
+ marginTop: 'var(--spacing-section)',
489
+ paddingBottom: 'var(--spacing-section)',
490
+ }}
491
+ >
492
+ <div
493
+ style={{
494
+ marginBottom: 'var(--space-5)',
495
+ display: 'flex',
496
+ alignItems: 'center',
497
+ justifyContent: 'space-between',
498
+ gap: 'var(--space-4)',
499
+ }}
500
+ >
501
+ <div>
502
+ <h2
503
+ style={{
504
+ fontSize: 'var(--type-h3)',
505
+ lineHeight: 'var(--type-h3-lh)',
506
+ letterSpacing: 'var(--type-h3-ls)',
507
+ fontWeight: 600,
508
+ color: 'var(--color-text-primary)',
509
+ margin: 0,
510
+ }}
511
+ >
512
+ Recent transactions
513
+ </h2>
514
+ <p
515
+ style={{
516
+ fontSize: 'var(--type-caption)',
517
+ color: 'var(--color-text-muted)',
518
+ margin: 'var(--space-1) 0 0',
519
+ }}
520
+ >
521
+ {transactions.length} transactions this week
522
+ </p>
523
+ </div>
524
+ </div>
525
+
526
+ <DataTable
527
+ caption="Recent transactions"
528
+ columns={tableColumns}
529
+ data={transactions}
530
+ emptyState={
531
+ <EmptyState
532
+ icon={<FileText size={28} aria-hidden="true" />}
533
+ title="No transactions yet"
534
+ description="When customers complete purchases, they will appear here."
535
+ primaryAction={{
536
+ label: 'Create test transaction',
537
+ onClick: () => {},
538
+ icon: <Plus size={16} aria-hidden="true" />,
539
+ }}
540
+ />
541
+ }
542
+ />
543
+ </section>
544
+ </Container>
545
+ );
546
+ }