@schandlergarcia/sf-web-components 1.9.67 → 1.9.68
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.
- package/.a4drules/skills/command-center-builder/SKILL.md +18 -141
- package/.a4drules/skills/command-center-builder/charts-visualization.md +1 -136
- package/.a4drules/skills/command-center-builder/completion-checklist.md +1 -61
- package/.a4drules/skills/command-center-builder/components-styling.md +1 -97
- package/.a4drules/skills/command-center-builder/data-forms-ai.md +1 -43
- package/.a4drules/skills/command-center-builder/getting-started.md +1 -125
- package/.a4drules/skills/command-center-builder/improved-build-process.md +1 -30
- package/.a4drules/skills/command-center-builder/page-layout.md +1 -145
- package/CHANGELOG.md +28 -11
- package/data/engine-command-center-prd.md +107 -295
- package/package.json +1 -1
|
@@ -81,24 +81,6 @@ import { MetricCard, ListCard, ActivityCard, WidgetCard } from "@/components/lib
|
|
|
81
81
|
</WidgetCard>
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
### Wrong example (hand-rolled HTML):
|
|
85
|
-
|
|
86
|
-
```jsx
|
|
87
|
-
// ❌ NEVER DO THIS — this is a hand-rolled card
|
|
88
|
-
<div className="bg-white border border-engine-border rounded-[10px] shadow-sm">
|
|
89
|
-
<div className="p-6 border-b border-engine-border">
|
|
90
|
-
<h2 className="text-lg font-semibold">Trip Activity</h2>
|
|
91
|
-
</div>
|
|
92
|
-
<div className="p-6 space-y-4">
|
|
93
|
-
{trips.map(trip => (
|
|
94
|
-
<div key={trip.id} className="flex gap-4 p-4 rounded-lg border ...">
|
|
95
|
-
...
|
|
96
|
-
</div>
|
|
97
|
-
))}
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
```
|
|
101
|
-
|
|
102
84
|
## Images
|
|
103
85
|
|
|
104
86
|
The company logo is at `src/assets/images/engine_logo.png`. Import it as a module and use it where a logo is needed (nav header, branding, etc.).
|
|
@@ -113,36 +95,9 @@ import engineLogo from "@/assets/images/engine_logo.png";
|
|
|
113
95
|
- **Do not use other asset images** (codey-*.png, etc.) unless the user asks for them.
|
|
114
96
|
- **Preserve aspect ratio** — always use `w-auto` with a fixed height, or `h-auto` with a fixed width. Never set both `w-*` and `h-*` to fixed values on the logo or any image, as this distorts the aspect ratio.
|
|
115
97
|
|
|
116
|
-
```jsx
|
|
117
|
-
// ✅ Correct — aspect ratio preserved
|
|
118
|
-
<img src={engineLogo} alt="Engine" className="h-8 w-auto" />
|
|
119
|
-
<img src={engineLogo} alt="Engine" className="w-24 h-auto" />
|
|
120
|
-
|
|
121
|
-
// ❌ Wrong — aspect ratio distorted
|
|
122
|
-
<img src={engineLogo} alt="Engine" className="w-8 h-8" />
|
|
123
|
-
<div className="w-8 h-8 bg-black rounded" /> // placeholder box instead of logo
|
|
124
|
-
```
|
|
125
|
-
|
|
126
98
|
## No Dashboard-Level Navigation
|
|
127
99
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
- ❌ `<nav className="bg-white border-b ...">` inside a dashboard page
|
|
131
|
-
- ❌ A header bar with logo + nav links + user avatar inside the dashboard
|
|
132
|
-
- ❌ Tab bars that act as top-level navigation (Overview, Travelers, Spend, etc.)
|
|
133
|
-
- ❌ `useState("overview")` / `useState("travelers")` to toggle between views — this IS tab navigation even without a `<nav>` element
|
|
134
|
-
- ❌ `{activeView === "overview" && (...)}` / `{activeView === "travelers" && (...)}` — content-swapping IS multi-tab navigation
|
|
135
|
-
- ✅ Dashboard starts directly with content (metrics, then primary content)
|
|
136
|
-
- ✅ If you need sub-sections, use the library `Tabs` component **inside a card** — not as a full-width page-level switcher
|
|
137
|
-
- ✅ Build a single-page dashboard — all content visible by scrolling. Do NOT split into multiple tab "pages" that swap content.
|
|
138
|
-
|
|
139
|
-
**Self-check:** If your dashboard has `useState` for an "active tab" or "active view" that conditionally renders different page sections, you are building multi-tab navigation. Delete it and put all content on one scrollable page.
|
|
140
|
-
|
|
141
|
-
**Self-check:** If your dashboard JSX starts with `<nav>`, a sticky tab bar, or a `<div>` that spans the full width with nav links, you are violating this rule. Delete it.
|
|
142
|
-
|
|
143
|
-
### Why no multi-tab dashboards?
|
|
144
|
-
|
|
145
|
-
Splitting a dashboard into 5 tab "pages" (Overview, Travelers, Spend, Policy, Eva) means each tab is a separate mini-app. This violates the single-page dashboard pattern. Instead, put **all sections on one scrollable page** using the vertical page structure (metrics → primary content → secondary content). If content is too long, prioritize the most important sections and use cards with `maxBodyHeight` to constrain tall sections.
|
|
100
|
+
No `<nav>`, header bar, tab bar, or content-swapping via `useState("activeTab")` inside dashboard pages. The app shell handles navigation. Dashboards are single scrollable pages — all content visible by scrolling.
|
|
146
101
|
|
|
147
102
|
## Where Dashboards Live & How to Wire Them
|
|
148
103
|
|
|
@@ -248,46 +203,14 @@ Only these grid patterns — do not invent new ones:
|
|
|
248
203
|
|
|
249
204
|
**Max 4 KPIs per row — NEVER 5 or more.** If you have 5+ metrics, split into two rows (e.g. 4 + 1, or 3 + 2). Always `items-start` on grids pairing different-height cards. Use `maxBodyHeight={px}` for long card bodies. Actions go in card `actions` slot. Place `FilterBar` near the data it controls.
|
|
250
205
|
|
|
251
|
-
## Layout Differentiation
|
|
252
|
-
|
|
253
|
-
Every dashboard needs a unique layout structure and at least one domain-specific "signature element" that couldn't exist in another dashboard. Vary section ordering, grid proportions, density, and visual rhythm.
|
|
254
|
-
|
|
255
206
|
## Map Layout
|
|
256
207
|
|
|
257
|
-
**Default
|
|
258
|
-
|
|
259
|
-
**If the PRD specifies a "visualization-hero" layout**, the map is full-width hero with glass overlays. The PRD always overrides this default pattern.
|
|
260
|
-
|
|
261
|
-
**Pattern: Map + sidebar at matched height**
|
|
262
|
-
|
|
263
|
-
```jsx
|
|
264
|
-
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
|
|
265
|
-
<div className="lg:col-span-2 relative overflow-hidden rounded-xl h-[300px]">
|
|
266
|
-
<GeoMap
|
|
267
|
-
markers={locations}
|
|
268
|
-
arcs={arcs}
|
|
269
|
-
overlays={overlays}
|
|
270
|
-
initialBounds={{ sw: [-130, 24], ne: [-65, 50], padding: 30 }}
|
|
271
|
-
theme="dark"
|
|
272
|
-
width={960}
|
|
273
|
-
height={480}
|
|
274
|
-
zoomable
|
|
275
|
-
className="h-full w-full"
|
|
276
|
-
/>
|
|
277
|
-
</div>
|
|
278
|
-
{/* Sidebar card — use maxBodyHeight to match map height */}
|
|
279
|
-
{/* Map h-[300px] ≈ ListCard maxBodyHeight={236} + ~64px header */}
|
|
280
|
-
<ListCard title="Activity" items={items} maxBodyHeight={236} showStatus />
|
|
281
|
-
</div>
|
|
282
|
-
```
|
|
208
|
+
**Default:** GeoMap in a wide/narrow grid (`lg:grid-cols-3` with `lg:col-span-2`). **If the PRD specifies "visualization-hero"**, the map is full-width hero — PRD overrides this default.
|
|
283
209
|
|
|
284
210
|
Key rules:
|
|
285
|
-
-
|
|
286
|
-
-
|
|
287
|
-
-
|
|
288
|
-
- **Always pass `arcs`** for flight routes and `overlays` for disruption zones — not just markers
|
|
289
|
-
- **Use ListCard** (not ActivityCard) for the sidebar — ListCard supports `maxBodyHeight` for scroll, ActivityCard does not
|
|
290
|
-
- Keep map height at 280–320px — not 400+
|
|
211
|
+
- Do NOT wrap GeoMap in ChartCard — GeoMap has its own styling
|
|
212
|
+
- Always pass `arcs` and `overlays`, not just markers
|
|
213
|
+
- Use `initialBounds` to auto-zoom: `{ sw: [lonMin, latMin], ne: [lonMax, latMax], padding: 30 }`
|
|
291
214
|
|
|
292
215
|
## Component Selection
|
|
293
216
|
|
|
@@ -496,14 +419,6 @@ const monthlyData = [
|
|
|
496
419
|
}
|
|
497
420
|
/>
|
|
498
421
|
|
|
499
|
-
// ✅ Custom renderChart function (for donut, horizontal bar, etc.)
|
|
500
|
-
function renderDonut(svgEl, data, dims, opts) {
|
|
501
|
-
// Use d3 to draw into svgEl
|
|
502
|
-
const d3svg = d3.select(svgEl);
|
|
503
|
-
d3svg.selectAll("*").remove();
|
|
504
|
-
// ... d3 drawing code
|
|
505
|
-
}
|
|
506
|
-
<ChartCard title="By Department" chart={<D3Chart data={deptData} renderChart={renderDonut} responsive height={280} />} />
|
|
507
422
|
```
|
|
508
423
|
|
|
509
424
|
### Available D3ChartTemplates
|
|
@@ -515,27 +430,9 @@ function renderDonut(svgEl, data, dims, opts) {
|
|
|
515
430
|
|
|
516
431
|
### GeoMap API
|
|
517
432
|
|
|
518
|
-
|
|
519
|
-
import { GeoMap } from "@/components/library";
|
|
520
|
-
|
|
521
|
-
// markers: array of { id, lon, lat, active?, label? }
|
|
522
|
-
const markers = [
|
|
523
|
-
{ id: "sf", lon: -122.4, lat: 37.8, active: true, label: "San Francisco" },
|
|
524
|
-
{ id: "nyc", lon: -74.0, lat: 40.7, active: true, label: "New York" },
|
|
525
|
-
];
|
|
526
|
-
|
|
527
|
-
<ChartCard title="Active Locations" chart={
|
|
528
|
-
<GeoMap
|
|
529
|
-
markers={markers}
|
|
530
|
-
theme="dark"
|
|
531
|
-
height={400}
|
|
532
|
-
width={800}
|
|
533
|
-
zoomable
|
|
534
|
-
/>
|
|
535
|
-
} />
|
|
536
|
-
```
|
|
433
|
+
Props: `width`, `height`, `projection` (`"naturalEarth"` | `"mercator"` | `"equirectangular"`), `theme` (`"dark"` | `"light"`), `markers`, `arcs`, `overlays`, `zoomable`, `minZoom`, `maxZoom`, `onMarkerClick`.
|
|
537
434
|
|
|
538
|
-
|
|
435
|
+
Marker shape: `{ id, lon, lat, active?, label? }`. Arc shape: `{ id, from: [lon, lat], to: [lon, lat], progress?, danger? }`. Overlay shape: `{ id, center: [lon, lat], radius }`.
|
|
539
436
|
|
|
540
437
|
### Common mistakes
|
|
541
438
|
|
|
@@ -592,37 +489,17 @@ Before finishing a dashboard page, verify ALL of these:
|
|
|
592
489
|
|
|
593
490
|
Instead, verify correctness by reviewing the code against the Pre-Completion Checklist above. Confirm that all three wiring files are updated (CommandCenter.tsx, Home.tsx, routes.tsx).
|
|
594
491
|
|
|
595
|
-
## Anti-Patterns
|
|
492
|
+
## Anti-Patterns
|
|
596
493
|
|
|
597
|
-
-
|
|
598
|
-
- Using
|
|
599
|
-
- Hand-rolling
|
|
600
|
-
-
|
|
601
|
-
-
|
|
602
|
-
- More than 4 KPIs in a single row (max is `lg:grid-cols-4`)
|
|
603
|
-
- Using `template` prop on D3Chart (doesn't exist) — use `renderChart={D3ChartTemplates.lineChart}`
|
|
494
|
+
- Importing from `src/components/ui/` (shadcn) or `lucide-react` — use `@/components/library` and `@heroicons/react`
|
|
495
|
+
- Using `cn()` helper — use Tailwind classes directly
|
|
496
|
+
- Hand-rolling HTML cards (`<div className="bg-white border ...">`) — use library components
|
|
497
|
+
- Using Recharts, Chart.js, or any third-party charts — use `D3Chart` + `D3ChartTemplates` or `GeoMap`
|
|
498
|
+
- Using `template` prop on D3Chart — use `renderChart={D3ChartTemplates.lineChart}`
|
|
604
499
|
- Passing D3Chart as children to ChartCard — use `chart={<D3Chart ... />}` prop
|
|
605
|
-
-
|
|
606
|
-
-
|
|
607
|
-
-
|
|
608
|
-
-
|
|
609
|
-
- Importing Lucide icons (`lucide-react`) into command center pages — use Heroicons (`@heroicons/react`) instead
|
|
610
|
-
- Using `cn()` helper in command center pages (that's shadcn's utility — use Tailwind classes directly)
|
|
611
|
-
- `useEffect` for data in schema components
|
|
612
|
-
- `bg-indigo-*` or raw colors for brand — use `brand-*` or `engine-*`
|
|
613
|
-
- Recreating providers (`AppThemeProvider`, `DataModeProvider`) in dashboard pages — `CommandCenter.tsx` already provides them
|
|
614
|
-
- Tables without search on 10+ rows, charts without tooltips, empty states without messages
|
|
615
|
-
- Hard-coded pixel widths or skipping dark mode
|
|
616
|
-
- `text-black`, `text-white`, `bg-black` — use slate scale
|
|
617
|
-
- `lg:grid-cols-6` for metrics (max 4), manually appending K/M/B
|
|
618
|
-
- `mb-6` between sections instead of `space-y-6` wrapper
|
|
619
|
-
- Same layout across dashboards, no signature element
|
|
620
|
-
- `ChatPanel` in dashboard grid — use `ChatBar`
|
|
500
|
+
- Adding `<nav>` or tab-swapping (`useState("activeTab")`) inside dashboards
|
|
501
|
+
- Recreating providers — `CommandCenter.tsx` already provides them
|
|
502
|
+
- Using `text-black`, `text-white`, `bg-black` — use slate scale
|
|
503
|
+
- Hardcoded data without `useDataSource` — always support sample/live switching
|
|
621
504
|
- `position: fixed` without `createPortal`
|
|
622
|
-
- `Math.random()` in sample data
|
|
623
|
-
- Re-declaring filter/form definitions inside component render
|
|
624
|
-
- Dismissing validator errors as "acceptable" or "justified" instead of fixing them — every error has a library component fix
|
|
625
|
-
- Hardcoded data without `useDataSource` for sample/live mode switching
|
|
626
|
-
- External image URLs (Unsplash, placeholders) unless user explicitly requests them
|
|
627
|
-
- Placeholder boxes (`<div className="w-8 h-8 bg-black rounded" />`) instead of the actual logo
|
|
628
|
-
- Fixed width AND height on images — always let one dimension be `auto` to preserve aspect ratio
|
|
505
|
+
- `Math.random()` in sample data
|
|
@@ -1,136 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
**All charts, graphs, maps, and visualizations MUST use library components.** Never use Recharts, Chart.js, Nivo, or any third-party charting library. Never hand-roll SVG, canvas, or custom chart markup. The ONLY charting tools allowed are `D3Chart` (via `D3ChartTemplates` or a custom `renderChart` function) and `GeoMap`.
|
|
4
|
-
|
|
5
|
-
| Visualization | MUST use | NEVER do |
|
|
6
|
-
|--------------|----------|----------|
|
|
7
|
-
| Line chart | `ChartCard` + `D3Chart` with `D3ChartTemplates.lineChart` | Custom `<svg>` with hand-drawn paths/circles |
|
|
8
|
-
| Grouped bar chart | `ChartCard` + `D3Chart` with `D3ChartTemplates.groupedBarChart` | Custom `<svg>` bar elements |
|
|
9
|
-
| Map / geographic | `GeoMap` | Custom SVG map or embedded iframe |
|
|
10
|
-
| Custom visualization | `ChartCard` + `D3Chart` with a custom `renderChart` function | Inline SVG/canvas |
|
|
11
|
-
|
|
12
|
-
### D3Chart API (CRITICAL — read carefully)
|
|
13
|
-
|
|
14
|
-
`D3Chart` does NOT use a `template` prop. It uses a `renderChart` callback function:
|
|
15
|
-
|
|
16
|
-
```jsx
|
|
17
|
-
<D3Chart
|
|
18
|
-
data={array} // data array
|
|
19
|
-
renderChart={D3ChartTemplates.lineChart} // function reference, NOT a string
|
|
20
|
-
responsive={true} // enables ResizeObserver
|
|
21
|
-
height={280} // height in px
|
|
22
|
-
/>
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### ChartCard + D3Chart usage
|
|
26
|
-
|
|
27
|
-
`ChartCard` takes a `chart` prop (a React element). Pass the `D3Chart` as the `chart` value:
|
|
28
|
-
|
|
29
|
-
```jsx
|
|
30
|
-
import { ChartCard, D3Chart, D3ChartTemplates } from "@/components/library";
|
|
31
|
-
|
|
32
|
-
// ✅ Line chart — data must have { x, y } shape (numeric x)
|
|
33
|
-
const spendData = [
|
|
34
|
-
{ x: 1, y: 2400 }, { x: 2, y: 3100 }, { x: 3, y: 2800 }, ...
|
|
35
|
-
];
|
|
36
|
-
<ChartCard
|
|
37
|
-
title="30-Day Spend"
|
|
38
|
-
subtitle="Engine savings"
|
|
39
|
-
chart={
|
|
40
|
-
<D3Chart
|
|
41
|
-
data={spendData}
|
|
42
|
-
renderChart={D3ChartTemplates.lineChart}
|
|
43
|
-
options={{ stroke: "#5BC8C8", xKey: "x", yKey: "y" }}
|
|
44
|
-
responsive
|
|
45
|
-
height={280}
|
|
46
|
-
/>
|
|
47
|
-
}
|
|
48
|
-
/>
|
|
49
|
-
|
|
50
|
-
// ✅ Grouped bar chart — data must have { x, group1, group2 } shape
|
|
51
|
-
const monthlyData = [
|
|
52
|
-
{ x: "Jan", Hotels: 12000, Flights: 8000 },
|
|
53
|
-
{ x: "Feb", Hotels: 14000, Flights: 9000 }, ...
|
|
54
|
-
];
|
|
55
|
-
<ChartCard
|
|
56
|
-
title="Monthly Spend"
|
|
57
|
-
chart={
|
|
58
|
-
<D3Chart
|
|
59
|
-
data={monthlyData}
|
|
60
|
-
renderChart={D3ChartTemplates.groupedBarChart}
|
|
61
|
-
options={{ xKey: "x", groups: ["Hotels", "Flights"], colors: ["#5BC8C8", "#6366F1"] }}
|
|
62
|
-
responsive
|
|
63
|
-
height={280}
|
|
64
|
-
/>
|
|
65
|
-
}
|
|
66
|
-
/>
|
|
67
|
-
|
|
68
|
-
// ✅ Custom renderChart function (for donut, horizontal bar, etc.)
|
|
69
|
-
function renderDonut(svgEl, data, dims, opts) {
|
|
70
|
-
// Use d3 to draw into svgEl
|
|
71
|
-
const d3svg = d3.select(svgEl);
|
|
72
|
-
d3svg.selectAll("*").remove();
|
|
73
|
-
// ... d3 drawing code
|
|
74
|
-
}
|
|
75
|
-
<ChartCard title="By Department" chart={<D3Chart data={deptData} renderChart={renderDonut} responsive height={280} />} />
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Available D3ChartTemplates
|
|
79
|
-
|
|
80
|
-
- `D3ChartTemplates.lineChart` — options: `{ xKey, yKey, stroke, strokeWidth, margin, showAxes, showGrid }`
|
|
81
|
-
- `D3ChartTemplates.groupedBarChart` — options: `{ xKey, groups, colors, barRadius, margin, yFormat, showGrid }`
|
|
82
|
-
|
|
83
|
-
**There is NO `D3ChartTemplates.LINE`, `.BAR`, `.DONUT`, `.AREA`.** Only `lineChart` and `groupedBarChart`. For donut/pie/horizontal bar, write a custom `renderChart` function using d3.
|
|
84
|
-
|
|
85
|
-
### GeoMap API
|
|
86
|
-
|
|
87
|
-
```jsx
|
|
88
|
-
import { GeoMap } from "@/components/library";
|
|
89
|
-
|
|
90
|
-
// markers: array of { id, lon, lat, active?, label? }
|
|
91
|
-
const markers = [
|
|
92
|
-
{ id: "sf", lon: -122.4, lat: 37.8, active: true, label: "San Francisco" },
|
|
93
|
-
{ id: "nyc", lon: -74.0, lat: 40.7, active: true, label: "New York" },
|
|
94
|
-
];
|
|
95
|
-
|
|
96
|
-
<ChartCard title="Active Locations" chart={
|
|
97
|
-
<GeoMap
|
|
98
|
-
markers={markers}
|
|
99
|
-
theme="dark"
|
|
100
|
-
height={400}
|
|
101
|
-
width={800}
|
|
102
|
-
zoomable
|
|
103
|
-
/>
|
|
104
|
-
} />
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
GeoMap props: `width`, `height`, `projection` (`"naturalEarth"` | `"mercator"` | `"equirectangular"`), `theme` (`"dark"` | `"light"`), `markers`, `arcs`, `overlays`, `zoomable`, `minZoom`, `maxZoom`, `onMarkerClick`.
|
|
108
|
-
|
|
109
|
-
### Common mistakes
|
|
110
|
-
|
|
111
|
-
```jsx
|
|
112
|
-
// ❌ WRONG — third-party chart libraries are forbidden
|
|
113
|
-
import { BarChart, Bar } from "recharts";
|
|
114
|
-
<BarChart data={data}><Bar dataKey="count" /></BarChart>
|
|
115
|
-
|
|
116
|
-
// ❌ WRONG — template prop doesn't exist
|
|
117
|
-
<D3Chart template={D3ChartTemplates.LINE} data={data} />
|
|
118
|
-
|
|
119
|
-
// ❌ WRONG — passing as children instead of chart prop
|
|
120
|
-
<ChartCard title="Spend"><D3Chart ... /></ChartCard>
|
|
121
|
-
|
|
122
|
-
// ❌ WRONG — non-numeric x values with lineChart (use groupedBarChart for categorical x)
|
|
123
|
-
<D3Chart data={[{x: "Jan", y: 100}]} renderChart={D3ChartTemplates.lineChart} />
|
|
124
|
-
|
|
125
|
-
// ✅ CORRECT
|
|
126
|
-
<ChartCard title="Spend" chart={<D3Chart data={data} renderChart={D3ChartTemplates.lineChart} responsive height={280} />} />
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
## Portals
|
|
130
|
-
|
|
131
|
-
Any `position: fixed` component **must** use `createPortal(... , document.body)`.
|
|
132
|
-
|
|
133
|
-
## Loading / Error / Empty States
|
|
134
|
-
|
|
135
|
-
Every data component handles loading. Error messages are human-readable. Empty states explain what would appear.
|
|
136
|
-
|
|
1
|
+
See SKILL.md — Charts & Visualizations section.
|
|
@@ -1,61 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Before finishing a dashboard page, verify ALL of these:
|
|
4
|
-
|
|
5
|
-
- [ ] **Every card/panel is a library component** (MetricCard, ListCard, ActivityCard, WidgetCard, TableCard, etc.) — no hand-rolled `<div className="bg-white border ...">` cards
|
|
6
|
-
- [ ] **Every chart/visualization is a library component** (ChartCard + D3Chart, GeoMap) — no hand-rolled SVG/canvas
|
|
7
|
-
- [ ] **No `<nav>`, header bar, or sticky tab bar** — navigation is handled by appLayout.tsx
|
|
8
|
-
- [ ] **Single scrollable page** — no multi-tab layout that swaps content
|
|
9
|
-
- [ ] **Max 4 KPIs per row** — if 5+, split across rows
|
|
10
|
-
- [ ] **Charts render** — using `renderChart` callback (not `template` prop), data format matches (numeric x for lineChart)
|
|
11
|
-
- [ ] **`<div className="space-y-6">` wraps all content** — no `mb-*` for section spacing
|
|
12
|
-
- [ ] **Dark mode works** — every custom element has `dark:` variants; no `text-black`, `text-white`, or `bg-black`
|
|
13
|
-
- [ ] **No inline `style={{}}` or `<style>` tags** — use Tailwind classes or CSS in global.css
|
|
14
|
-
- [ ] **No inline `<svg>`** — use Heroicons from `@heroicons/react`
|
|
15
|
-
- [ ] **`position: fixed` uses `createPortal`** — FABs, modals, toasts
|
|
16
|
-
- [ ] **Data uses `useDataSource`** — sample data defined outside the component
|
|
17
|
-
- [ ] **Only uses company logo** (`@/assets/images/engine_logo.png`) — no external URLs, no placeholder boxes
|
|
18
|
-
- [ ] **Images preserve aspect ratio** — one dimension auto, never both fixed
|
|
19
|
-
- [ ] **Imports only from `@/components/library`** — no shadcn, no Lucide, no `cn()`
|
|
20
|
-
- [ ] **Grid patterns match the approved list** — no custom grid layouts
|
|
21
|
-
|
|
22
|
-
## Post-Completion Verification
|
|
23
|
-
|
|
24
|
-
**DO NOT run `npm run validate:dashboard`, `npm run dev`, `npm run build`, or `tsc` during builds.** These waste time and are not required for phase completion.
|
|
25
|
-
|
|
26
|
-
Instead, verify correctness by reviewing the code against the Pre-Completion Checklist above. Confirm that all three wiring files are updated (CommandCenter.tsx, Home.tsx, routes.tsx).
|
|
27
|
-
|
|
28
|
-
## Anti-Patterns (never do these)
|
|
29
|
-
|
|
30
|
-
- Hand-rolling HTML cards instead of using library components (MetricCard, ListCard, etc.)
|
|
31
|
-
- Using Recharts, Chart.js, Nivo, or any chart library other than D3Chart/GeoMap
|
|
32
|
-
- Hand-rolling SVG/canvas charts instead of using ChartCard + D3Chart or GeoMap
|
|
33
|
-
- Adding a `<nav>`, header bar, sticky tab bar, or top navigation inside a dashboard page
|
|
34
|
-
- Multi-tab layouts that swap content (Overview/Travelers/Spend/etc tabs) — build one scrollable page
|
|
35
|
-
- More than 4 KPIs in a single row (max is `lg:grid-cols-4`)
|
|
36
|
-
- Using `template` prop on D3Chart (doesn't exist) — use `renderChart={D3ChartTemplates.lineChart}`
|
|
37
|
-
- Passing D3Chart as children to ChartCard — use `chart={<D3Chart ... />}` prop
|
|
38
|
-
- CSS Modules, styled-components, or inline `style={{}}` for layout
|
|
39
|
-
- `<style>` tags with keyframes or custom CSS inside components
|
|
40
|
-
- Inline `<svg>` elements when a Heroicon exists
|
|
41
|
-
- Importing shadcn components (`src/components/ui/`) into command center pages — `Button`, `Card`, `Input`, `Select`, `Alert`, `Skeleton`, `Label`, `Spinner`, etc. are ALL forbidden. Use the library equivalents from `@/components/library` (`UIButton`, `BaseCard`/`WidgetCard`, `UIInput`, `Select`, `Alert`, `Skeleton`, etc.)
|
|
42
|
-
- Importing Lucide icons (`lucide-react`) into command center pages — use Heroicons (`@heroicons/react`) instead
|
|
43
|
-
- Using `cn()` helper in command center pages (that's shadcn's utility — use Tailwind classes directly)
|
|
44
|
-
- `useEffect` for data in schema components
|
|
45
|
-
- `bg-indigo-*` or raw colors for brand — use `brand-*` or `engine-*`
|
|
46
|
-
- Recreating providers (`AppThemeProvider`, `DataModeProvider`) in dashboard pages — `CommandCenter.tsx` already provides them
|
|
47
|
-
- Tables without search on 10+ rows, charts without tooltips, empty states without messages
|
|
48
|
-
- Hard-coded pixel widths or skipping dark mode
|
|
49
|
-
- `text-black`, `text-white`, `bg-black` — use slate scale
|
|
50
|
-
- `lg:grid-cols-6` for metrics (max 4), manually appending K/M/B
|
|
51
|
-
- `mb-6` between sections instead of `space-y-6` wrapper
|
|
52
|
-
- Same layout across dashboards, no signature element
|
|
53
|
-
- `ChatPanel` in dashboard grid — use `ChatBar`
|
|
54
|
-
- `position: fixed` without `createPortal`
|
|
55
|
-
- `Math.random()` in sample data, different layouts based on data mode
|
|
56
|
-
- Re-declaring filter/form definitions inside component render
|
|
57
|
-
- Dismissing validator errors as "acceptable" or "justified" instead of fixing them — every error has a library component fix
|
|
58
|
-
- Hardcoded data without `useDataSource` for sample/live mode switching
|
|
59
|
-
- External image URLs (Unsplash, placeholders) unless user explicitly requests them
|
|
60
|
-
- Placeholder boxes (`<div className="w-8 h-8 bg-black rounded" />`) instead of the actual logo
|
|
61
|
-
- Fixed width AND height on images — always let one dimension be `auto` to preserve aspect ratio
|
|
1
|
+
See SKILL.md — Pre-Completion Checklist and Post-Completion Verification sections.
|
|
@@ -1,97 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
| Need | Component |
|
|
4
|
-
|------|-----------|
|
|
5
|
-
| Single KPI | `MetricCard` |
|
|
6
|
-
| Row of 2–4 KPIs | `MetricsStrip` (schema) / `MetricCard` grid |
|
|
7
|
-
| Tabular data | `TableCard` |
|
|
8
|
-
| Time series / distribution | `ChartCard` + `D3Chart` |
|
|
9
|
-
| Geographic / flight map | `GeoMap` |
|
|
10
|
-
| System health | `StatusCard` |
|
|
11
|
-
| Feed / item list | `ListCard` |
|
|
12
|
-
| Scrollable side panel | `FeedPanel` |
|
|
13
|
-
| Activity feed | `ActivityCard` |
|
|
14
|
-
| User avatar | `Avatar` |
|
|
15
|
-
| Text summary | `NarrativeSummary` (schema) / `SectionCard` |
|
|
16
|
-
| Actions | `ActionList` (schema) / `UIButton` group |
|
|
17
|
-
| Alert / callout | `CalloutCard` |
|
|
18
|
-
| Multi-section panel | `WidgetCard` |
|
|
19
|
-
|
|
20
|
-
## Component Data Contract
|
|
21
|
-
|
|
22
|
-
Common card props: `title`, `subtitle`, `actions`, `loading`, `error`, `emptyMessage`, `...cardProps`.
|
|
23
|
-
|
|
24
|
-
Item shape (StatusCard, ListCard, ItemList): `{ id?, title, description?, status?, timestamp?, value?, unit?, avatar? }`. `title` is primary; `name` is alias.
|
|
25
|
-
|
|
26
|
-
Never use hardcoded `indigo-*` — use `brand-*` classes.
|
|
27
|
-
|
|
28
|
-
## Metric Formatting
|
|
29
|
-
|
|
30
|
-
Use `fmtK()` helper — never manually append K/M/B:
|
|
31
|
-
|
|
32
|
-
```js
|
|
33
|
-
function fmtK(n) {
|
|
34
|
-
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
35
|
-
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
36
|
-
return n.toLocaleString();
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Brand & Accent Colors
|
|
41
|
-
|
|
42
|
-
Defined in `global.css` (`--color-brand-*` and `--color-engine-*`), available as Tailwind classes.
|
|
43
|
-
|
|
44
|
-
- `engine-*` for the Engine brand: `engine-teal`, `engine-coral`, `engine-orange`, `engine-savings`, `engine-text`, `engine-muted`, `engine-label`, `engine-border`, `engine-bg`
|
|
45
|
-
- `brand-*` for the legacy command center palette (indigo-based)
|
|
46
|
-
- Tailwind built-in colors for status (green/amber/red). Never use brand for status.
|
|
47
|
-
|
|
48
|
-
## Semantic Colors
|
|
49
|
-
|
|
50
|
-
- `"default"` — neutral | `"primary"` — brand | `"success"` — healthy | `"warning"` — attention | `"danger"` — critical
|
|
51
|
-
|
|
52
|
-
`changeType` reflects good/bad, not direction. Revenue down = `"negative"`. Incidents down = `"positive"`.
|
|
53
|
-
|
|
54
|
-
## Status Values
|
|
55
|
-
|
|
56
|
-
Canonical: `"operational"`, `"degraded"`, `"outage"`, `"maintenance"`. Always show color + text label.
|
|
57
|
-
|
|
58
|
-
## Dark Mode
|
|
59
|
-
|
|
60
|
-
Every visible element needs both light and dark styles. Library components handle this automatically — which is another reason to use them.
|
|
61
|
-
|
|
62
|
-
For any custom markup you do write:
|
|
63
|
-
|
|
64
|
-
| Role | Light | Dark |
|
|
65
|
-
|------|-------|------|
|
|
66
|
-
| Background | `bg-white` | `dark:bg-slate-900` |
|
|
67
|
-
| Surface | `bg-slate-50` | `dark:bg-slate-950/30` |
|
|
68
|
-
| Border | `border-slate-200` | `dark:border-slate-800` |
|
|
69
|
-
| Text primary | `text-slate-900` | `dark:text-slate-50` |
|
|
70
|
-
| Text secondary | `text-slate-600` | `dark:text-slate-300` |
|
|
71
|
-
| Text muted | `text-slate-500` | `dark:text-slate-400` |
|
|
72
|
-
| Brand tint | `brand-50` | `brand-950` |
|
|
73
|
-
|
|
74
|
-
**Never use `text-black` or `text-white`.** Use the slate scale. Never use `bg-black` for surfaces — use `bg-slate-900` / `dark:bg-slate-950`.
|
|
75
|
-
|
|
76
|
-
## Icons (Command Center)
|
|
77
|
-
|
|
78
|
-
**Heroicons 2** (`@heroicons/react`) exclusively inside command center pages. No Lucide, no Font Awesome, no inline SVGs.
|
|
79
|
-
|
|
80
|
-
- `24/outline` — default | `24/solid` — status/active | `20/solid` — chips/badges
|
|
81
|
-
- Sizes: `h-4 w-4` compact, `h-5 w-5` default, `h-6 w-6` emphasis, `h-8 w-8` hero only
|
|
82
|
-
- Always set a color class. Pass rendered elements: `icon={<UsersIcon className="h-5 w-5 text-brand-500" />}`
|
|
83
|
-
- ❌ Never use inline `<svg>` when a Heroicon exists for the same purpose
|
|
84
|
-
- ❌ Never guess icon names — Heroicons uses specific names that don't always match what you'd expect. Common mistakes: `PlaneIcon` (doesn't exist → use `PaperAirplaneIcon`), `FlightIcon` (doesn't exist → use `PaperAirplaneIcon`), `HotelIcon` (doesn't exist → use `BuildingOfficeIcon`), `MoneyIcon` (doesn't exist → use `BanknotesIcon`), `AlertIcon` (doesn't exist → use `ExclamationTriangleIcon`), `PersonIcon` (doesn't exist → use `UserIcon`)
|
|
85
|
-
|
|
86
|
-
## Fonts
|
|
87
|
-
|
|
88
|
-
Fonts are set in `global.css` via `@theme` block (`--font-sans`, `--font-mono`). No `next/font` in this project (that's the starter). No Google Fonts `<link>` tags.
|
|
89
|
-
|
|
90
|
-
## Filtering & Data Utilities
|
|
91
|
-
|
|
92
|
-
Three layers: `filterUtils.js` (pure) → `usePageFilters` hook (state) → `FilterBar` + components (UI).
|
|
93
|
-
|
|
94
|
-
- Pass `sortedData` (not `filteredData`) to components
|
|
95
|
-
- Disable `TableCard.searchable` when `FilterBar` provides search
|
|
96
|
-
- Declare filter definitions outside component body (or memoize)
|
|
97
|
-
|
|
1
|
+
See SKILL.md — Component Selection, Colors, Dark Mode, Icons sections.
|
|
@@ -1,43 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
`useDataMode()` returns `{ mode, isSample, isLive, toggle, setMode }`. `useDataSource({ sample, live })` picks based on mode.
|
|
4
|
-
|
|
5
|
-
- Every page works in both modes. Default to `"sample"`.
|
|
6
|
-
- Same layout for both modes — only data changes.
|
|
7
|
-
- Sample data: 10–50 items, realistic, deterministic (no `Math.random()`).
|
|
8
|
-
- Define sample data as constants outside the component body.
|
|
9
|
-
|
|
10
|
-
```jsx
|
|
11
|
-
// ✅ Correct: data defined outside, selected by mode
|
|
12
|
-
const SAMPLE_TRIPS = [ ... ];
|
|
13
|
-
|
|
14
|
-
export default function Dashboard() {
|
|
15
|
-
const trips = useDataSource({ sample: SAMPLE_TRIPS, live: liveTrips });
|
|
16
|
-
...
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// ❌ Wrong: hardcoded data inside the component with no mode switching
|
|
20
|
-
export default function Dashboard() {
|
|
21
|
-
const trips = [ ... ]; // no useDataSource, no mode awareness
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Forms & Record Modals
|
|
26
|
-
|
|
27
|
-
Schema-driven: `sections[] → fields[]`. Use `FormModal` (modal) or `FormRenderer` (inline). `useFormState` for state.
|
|
28
|
-
|
|
29
|
-
Define schemas outside component body. Always `required: true` on mandatory fields. Submissions enforce 4s minimum spinner.
|
|
30
|
-
|
|
31
|
-
## AI Chat & Agent
|
|
32
|
-
|
|
33
|
-
Use `ChatBar` (command palette, ⌘K) for dashboards. `ChatPanel` for full-page chat.
|
|
34
|
-
|
|
35
|
-
- Always provide 3–5 `suggestions` for starter prompts
|
|
36
|
-
- Return `components` for structured data — never markdown tables in text
|
|
37
|
-
- Always set height on `ChatPanel`
|
|
38
|
-
- Extract `onSend` handlers to module scope when shared
|
|
39
|
-
|
|
40
|
-
## Tables
|
|
41
|
-
|
|
42
|
-
`searchable` when 10+ rows (unless `FilterBar` handles search). `sortable` on comparable columns. `paginated` when 10+ rows. Use built-in column `type` before custom `render`.
|
|
43
|
-
|
|
1
|
+
See SKILL.md — Data Mode, Forms, AI Chat, Tables sections.
|