chati-dev 4.3.1 → 4.4.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 (158) hide show
  1. package/README.md +18 -13
  2. package/bin/chati.js +28 -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 +60 -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/manifest.json +1351 -0
  40. package/framework/manifest.sig +1 -0
  41. package/framework/orchestrator/chati-router.js +1 -1
  42. package/framework/orchestrator/chati-update.md +19 -3
  43. package/framework/orchestrator/chati.md +24 -3
  44. package/framework/quality-gates/planning-gate.md +2 -0
  45. package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
  46. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
  47. package/framework/scaffold/saas-dashboard/README.md +58 -0
  48. package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
  49. package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
  50. package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
  51. package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
  52. package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
  53. package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
  54. package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
  55. package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
  56. package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
  57. package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
  58. package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
  59. package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
  60. package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
  61. package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
  62. package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
  63. package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
  64. package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
  65. package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
  66. package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
  67. package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
  68. package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
  69. package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
  70. package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
  71. package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
  72. package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
  73. package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
  74. package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
  75. package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
  76. package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
  77. package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
  78. package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
  79. package/framework/schemas/session.schema.json +5 -0
  80. package/framework/schemas/task.schema.json +9 -0
  81. package/framework/scripts/visual-qa.js +87 -1
  82. package/framework/tasks/orchestrator-health.md +4 -4
  83. package/framework/tasks/qa-impl-verdict.md +11 -5
  84. package/framework/templates/qa-gate-tmpl.yaml +24 -11
  85. package/package.json +4 -4
  86. package/src/config/claude-settings-generator.js +9 -6
  87. package/src/config/context-file-generator.js +0 -6
  88. package/src/dashboard/renderer.js +0 -36
  89. package/src/executors/runner.js +204 -0
  90. package/src/installer/core.js +35 -4
  91. package/src/installer/manifest.js +30 -4
  92. package/src/installer/signing-public-key.pem +1 -1
  93. package/src/installer/templates.js +5 -6
  94. package/src/installer/validator.js +2 -1
  95. package/src/intelligence/registry-manager.js +3 -2
  96. package/src/license/client.js +9 -3
  97. package/src/license/commands.js +12 -2
  98. package/src/license/machine-id.js +42 -1
  99. package/src/memory/gotchas.js +58 -40
  100. package/src/memory/magic-docs.js +7 -2
  101. package/src/memory/session-digest.js +9 -4
  102. package/src/orchestrator/cli.js +195 -23
  103. package/src/orchestrator/deviation-handler.js +5 -3
  104. package/src/orchestrator/doctor.js +98 -16
  105. package/src/orchestrator/pipeline-manager.js +44 -22
  106. package/src/orchestrator/session-manager.js +56 -8
  107. package/src/telemetry/sender.js +7 -19
  108. package/src/terminal/run-parallel.js +14 -14
  109. package/src/terminal/run-team.js +3 -3
  110. package/src/upgrade/backup.js +22 -44
  111. package/src/upgrade/migrator.js +51 -12
  112. package/src/upgrade/tracked-files-detector.js +34 -16
  113. package/src/utils/constitution-meta.js +12 -0
  114. package/src/utils/feature-flags.js +26 -2
  115. package/src/utils/flatten-entities.js +4 -40
  116. package/src/utils/schema-validator.js +0 -14
  117. package/src/wizard/i18n.js +3 -3
  118. package/src/api/index.js +0 -120
  119. package/src/autonomy/autonomous-gate.js +0 -294
  120. package/src/autonomy/build-loop.js +0 -281
  121. package/src/autonomy/build-state.js +0 -286
  122. package/src/autonomy/cause-analyzer.js +0 -177
  123. package/src/autonomy/escalation.js +0 -214
  124. package/src/autonomy/index.js +0 -51
  125. package/src/autonomy/mode-manager.js +0 -225
  126. package/src/autonomy/mode-suggester.js +0 -283
  127. package/src/autonomy/progress-reporter.js +0 -275
  128. package/src/autonomy/safety-net.js +0 -370
  129. package/src/config/agent-customizer.js +0 -231
  130. package/src/decision/analyzer.js +0 -291
  131. package/src/decision/engine.js +0 -250
  132. package/src/decision/index.js +0 -38
  133. package/src/decision/registry-healer.js +0 -468
  134. package/src/decision/registry-updater.js +0 -339
  135. package/src/extensions/loader.js +0 -145
  136. package/src/extensions/registry.js +0 -134
  137. package/src/gates/circuit-breaker.js +0 -151
  138. package/src/gates/g1-planning-complete.js +0 -154
  139. package/src/gates/g2-qa-planning.js +0 -156
  140. package/src/gates/g3-implementation.js +0 -215
  141. package/src/gates/g4-qa-implementation.js +0 -240
  142. package/src/gates/g5-deploy-ready.js +0 -181
  143. package/src/gates/gate-base.js +0 -185
  144. package/src/gates/index.js +0 -46
  145. package/src/health/auto-fix.js +0 -216
  146. package/src/health/engine.js +0 -246
  147. package/src/merger/semantic-merger.js +0 -292
  148. package/src/preview/detector.js +0 -238
  149. package/src/preview/index.js +0 -20
  150. package/src/preview/launcher.js +0 -235
  151. package/src/preview/log-buffer.js +0 -103
  152. package/src/quality/metrics-collector.js +0 -281
  153. package/src/quality/test-runner.js +0 -366
  154. package/src/tasks/executor.js +0 -195
  155. package/src/tasks/index.js +0 -4
  156. package/src/tasks/loader.js +0 -210
  157. package/src/tasks/router.js +0 -182
  158. package/src/utils/event-bus.js +0 -126
@@ -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
+ }