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