@discourser/design-system 0.25.3 → 0.27.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.
- package/README.md +76 -73
- package/dist/{chunk-ZPECW4N2.js → chunk-4XOWPACJ.js} +257 -105
- package/dist/chunk-4XOWPACJ.js.map +1 -0
- package/dist/{chunk-QNCZYFUJ.cjs → chunk-AZ6QU2L2.cjs} +257 -105
- package/dist/chunk-AZ6QU2L2.cjs.map +1 -0
- package/dist/{chunk-TBLDQATQ.cjs → chunk-EBDNCZF6.cjs} +94 -54
- package/dist/chunk-EBDNCZF6.cjs.map +1 -0
- package/dist/{chunk-UHSL4N44.js → chunk-MAVUSE4F.js} +94 -55
- package/dist/chunk-MAVUSE4F.js.map +1 -0
- package/dist/components/Checkbox.d.ts +1 -1
- package/dist/components/Icons/LeftArrowIcon.d.ts +6 -0
- package/dist/components/Icons/LeftArrowIcon.d.ts.map +1 -0
- package/dist/components/Icons/RightArrowIcon.d.ts.map +1 -1
- package/dist/components/Icons/index.d.ts +1 -0
- package/dist/components/Icons/index.d.ts.map +1 -1
- package/dist/components/index.cjs +79 -75
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/contracts/design-language.contract.d.ts +52 -18
- package/dist/contracts/design-language.contract.d.ts.map +1 -1
- package/dist/figma-codex.json +2 -2
- package/dist/index.cjs +83 -79
- package/dist/index.js +2 -2
- package/dist/languages/material3.language.d.ts.map +1 -1
- package/dist/languages/transform.d.ts +5 -5
- package/dist/languages/transform.d.ts.map +1 -1
- package/dist/preset/index.cjs +2 -2
- package/dist/preset/index.js +1 -1
- package/docs/component-catalog.md +469 -0
- package/docs/superpowers/plans/2026-04-03-component-catalog-pipeline.md +667 -0
- package/docs/token-name-mapping.json +614 -42
- package/docs/token-name-mapping.md +117 -29
- package/package.json +3 -2
- package/src/components/Icons/LeftArrowIcon.tsx +28 -0
- package/src/components/Icons/RightArrowIcon.tsx +7 -2
- package/src/components/Icons/index.ts +1 -0
- package/src/components/__tests__/AbsoluteCenter.test.tsx +31 -0
- package/src/components/__tests__/Divider.test.tsx +38 -0
- package/src/components/__tests__/Group.test.tsx +34 -0
- package/src/components/__tests__/Icon.test.tsx +31 -0
- package/src/components/__tests__/SettingsPopover.test.tsx +39 -0
- package/src/components/__tests__/StudioControls.test.tsx +59 -0
- package/src/components/__tests__/Toaster.test.tsx +24 -0
- package/src/components/index.ts +1 -0
- package/src/contracts/design-language.contract.ts +69 -20
- package/src/languages/material3.language.ts +249 -80
- package/src/languages/transform.ts +45 -48
- package/src/preset/__tests__/translation-token-accuracy.test.ts +13 -0
- package/src/stories/foundations/Colors.mdx +9 -1
- package/src/stories/foundations/Elevation.mdx +23 -17
- package/src/stories/foundations/TokenReference.stories.tsx +970 -0
- package/src/stories/foundations/TonalPaletteDerivation.stories.tsx +782 -0
- package/src/stories/foundations/Typography.mdx +125 -25
- package/dist/chunk-QNCZYFUJ.cjs.map +0 -1
- package/dist/chunk-TBLDQATQ.cjs.map +0 -1
- package/dist/chunk-UHSL4N44.js.map +0 -1
- package/dist/chunk-ZPECW4N2.js.map +0 -1
- package/docs/context-share/ELEVATION_FIX_PLAN.md +0 -903
- package/docs/context-share/fix-checkbox-radio-tokens.md +0 -145
- package/docs/context-share/icon-component-prompt.md +0 -154
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
# Component Catalog Generation Pipeline — Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Build a fully automated pipeline that generates `docs/component-catalog.md` from the live component exports and catalog story, and validates catalog coverage at build time.
|
|
6
|
+
|
|
7
|
+
**Architecture:** A new `scripts/generate-component-catalog.ts` script parses `src/components/index.ts` and `stories/ComponentCatalog.stories.tsx` to generate a structured markdown catalog. `scripts/validate-exports.ts` gains a second validation phase that warns when exported components lack catalog story entries. Both scripts are wired into the `build` npm sequence.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Node.js ESM, `tsx`, TypeScript, `node:fs`, `node:path` — same stack as `validate-exports.ts`. No new dependencies.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Important Design Decision: Phase 2 Exit Behavior
|
|
14
|
+
|
|
15
|
+
The spec states Phase 2 (catalog coverage check) should use `process.exit(1)` when components are missing. However, 6 components are currently missing from `ComponentCatalog.stories.tsx` (see baseline table below). Making this fatal would block the build before the catalog is complete.
|
|
16
|
+
|
|
17
|
+
**Decision:** Phase 2 is **non-fatal (warning only)** — uses `console.warn` + `YELLOW` color, does not contribute to `process.exit(1)`. The mechanism is tested by the mutation test below. Phase 2 should be tightened to fatal once all components have catalog entries (a follow-up task). This is a known, deliberate divergence from the spec's literal text, motivated by the spec's own Phase 2 instruction: "If any stub warnings appear, list which components are missing catalog entries and **flag them for follow-up**."
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Phase 1 Coverage Baseline
|
|
22
|
+
|
|
23
|
+
**The component name parser in this pipeline extracts component identity from the _source path_ of each export statement, not from individual symbol names.** This means `AddScenarioDialog` (exported as a named symbol from `./ScenarioQueue`) is invisible to the parser — the parser sees `ScenarioQueue` (already in the catalog). Only the 6 components below will appear as stubs.
|
|
24
|
+
|
|
25
|
+
| Component | In index.ts | In Catalog Story | Will Appear as Stub |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| Button | ✅ | ✅ | — |
|
|
28
|
+
| ButtonGroup | ✅ | ✅ | — |
|
|
29
|
+
| IconButton | ✅ | ✅ | — |
|
|
30
|
+
| Input | ✅ | ✅ | — |
|
|
31
|
+
| InputAddon | ✅ | ✅ | — |
|
|
32
|
+
| InputGroup | ✅ | ✅ | — |
|
|
33
|
+
| Textarea | ✅ | ✅ | — |
|
|
34
|
+
| Header | ✅ | ✅ | — |
|
|
35
|
+
| **Divider** | ✅ | ❌ | ✅ |
|
|
36
|
+
| Badge | ✅ | ✅ | — |
|
|
37
|
+
| Spinner | ✅ | ✅ | — |
|
|
38
|
+
| Toaster | ✅ | ✅ | — |
|
|
39
|
+
| Card | ✅ | ✅ | — |
|
|
40
|
+
| Dialog | ✅ | ✅ | — |
|
|
41
|
+
| Switch | ✅ | ✅ | — |
|
|
42
|
+
| Accordion | ✅ | ✅ | — |
|
|
43
|
+
| Drawer | ✅ | ✅ | — |
|
|
44
|
+
| Tabs | ✅ | ✅ | — |
|
|
45
|
+
| Checkbox | ✅ | ✅ | — |
|
|
46
|
+
| RadioGroup | ✅ | ✅ | — |
|
|
47
|
+
| Select | ✅ | ✅ | — |
|
|
48
|
+
| Slider | ✅ | ✅ | — |
|
|
49
|
+
| Avatar | ✅ | ✅ | — |
|
|
50
|
+
| Progress | ✅ | ✅ | — |
|
|
51
|
+
| Skeleton | ✅ | ✅ | — |
|
|
52
|
+
| Popover | ✅ | ✅ | — |
|
|
53
|
+
| Tooltip | ✅ | ✅ | — |
|
|
54
|
+
| CloseButton | ✅ | ✅ (as `CloseButtonNS`) | — |
|
|
55
|
+
| **Icon** | ✅ | ❌ | ✅ |
|
|
56
|
+
| **AbsoluteCenter** | ✅ | ❌ | ✅ |
|
|
57
|
+
| **Group** | ✅ | ❌ | ✅ |
|
|
58
|
+
| Breadcrumb | ✅ | ✅ | — |
|
|
59
|
+
| ContentCard | ✅ | ✅ | — |
|
|
60
|
+
| Stepper | ✅ | ✅ | — |
|
|
61
|
+
| NavigationMenu | ✅ | ✅ | — |
|
|
62
|
+
| **SettingsPopover** | ✅ | ❌ | ✅ |
|
|
63
|
+
| ScenarioSettings | ✅ | ✅ | — |
|
|
64
|
+
| **StudioControls** | ✅ | ❌ | ✅ |
|
|
65
|
+
| ScenarioQueue | ✅ | ✅ | — |
|
|
66
|
+
| ScenarioCard | ✅ | ✅ | — |
|
|
67
|
+
|
|
68
|
+
**6 stub warnings expected:** AbsoluteCenter, Divider, Group, Icon, SettingsPopover, StudioControls
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## File Map
|
|
73
|
+
|
|
74
|
+
| File | Action | Responsibility |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| `scripts/generate-component-catalog.ts` | **Create** | Parse index.ts + catalog story → write `docs/component-catalog.md` |
|
|
77
|
+
| `scripts/validate-exports.ts` | **Modify** (append) | Add Phase 2: catalog story coverage warning |
|
|
78
|
+
| `package.json` | **Modify** | Add `catalog:generate` script; insert into `build` |
|
|
79
|
+
| `docs/component-catalog.md` | **Generated** | Written by the script — never hand-edited |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Task 1: Create `scripts/generate-component-catalog.ts`
|
|
84
|
+
|
|
85
|
+
**Files:**
|
|
86
|
+
- Create: `scripts/generate-component-catalog.ts`
|
|
87
|
+
|
|
88
|
+
Write the complete file in one step, assembling all sections:
|
|
89
|
+
|
|
90
|
+
- [ ] **Step 1A: Write the complete script**
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
#!/usr/bin/env tsx
|
|
94
|
+
/**
|
|
95
|
+
* generate-component-catalog.ts
|
|
96
|
+
*
|
|
97
|
+
* Reads src/components/index.ts and stories/ComponentCatalog.stories.tsx,
|
|
98
|
+
* then writes a complete docs/component-catalog.md.
|
|
99
|
+
*
|
|
100
|
+
* Parser note: component identity is derived from the *source path* of each
|
|
101
|
+
* export statement, not from individual symbol names. Named symbols exported
|
|
102
|
+
* from a compound module (e.g. AddScenarioDialog from ./ScenarioQueue) are
|
|
103
|
+
* not individually tracked — only the module-level name (ScenarioQueue) is.
|
|
104
|
+
*
|
|
105
|
+
* Run manually: pnpm catalog:generate
|
|
106
|
+
* Run in build: included after exports:validate in pnpm build
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
import fs from 'node:fs';
|
|
110
|
+
import path from 'node:path';
|
|
111
|
+
import { fileURLToPath } from 'node:url';
|
|
112
|
+
|
|
113
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
114
|
+
const root = path.resolve(__dirname, '..');
|
|
115
|
+
|
|
116
|
+
// ── Color constants (match validate-exports.ts) ───────────────────────────────
|
|
117
|
+
const RED = '\x1b[31m';
|
|
118
|
+
const YELLOW = '\x1b[33m';
|
|
119
|
+
const GREEN = '\x1b[32m';
|
|
120
|
+
const RESET = '\x1b[0m';
|
|
121
|
+
|
|
122
|
+
// ── 1. Parse src/components/index.ts ─────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
type ComponentEntry = { name: string; type: 'Simple' | 'Compound' };
|
|
125
|
+
|
|
126
|
+
function parseComponentIndex(): ComponentEntry[] {
|
|
127
|
+
const indexPath = path.join(root, 'src/components/index.ts');
|
|
128
|
+
const source = fs.readFileSync(indexPath, 'utf8');
|
|
129
|
+
const entries: ComponentEntry[] = [];
|
|
130
|
+
|
|
131
|
+
// export * as Dialog from './Dialog' → Compound
|
|
132
|
+
const namespaceRe = /export\s+\*\s+as\s+(\w+)\s+from\s+['"][^'"]+['"]/g;
|
|
133
|
+
for (const [, name] of source.matchAll(namespaceRe)) {
|
|
134
|
+
entries.push({ name, type: 'Compound' });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// export { Button, ... } from './Button' → Simple
|
|
138
|
+
// Captures only the first path segment — Icons/ entries are skipped.
|
|
139
|
+
// Individual symbols from compound modules (e.g. AddScenarioDialog from
|
|
140
|
+
// ./ScenarioQueue) are not separately tracked; only the module name is.
|
|
141
|
+
const namedRe = /export\s+\{[^}]+\}\s+from\s+['"]\.\/([^/'"]+)/g;
|
|
142
|
+
for (const [, srcPath] of source.matchAll(namedRe)) {
|
|
143
|
+
if (srcPath === 'Icons') continue;
|
|
144
|
+
const normalized =
|
|
145
|
+
srcPath.charAt(0).toUpperCase() + srcPath.slice(1);
|
|
146
|
+
if (!entries.some((e) => e.name === normalized)) {
|
|
147
|
+
entries.push({ name: normalized, type: 'Simple' });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── 2. Parse stories/ComponentCatalog.stories.tsx ────────────────────────────
|
|
155
|
+
|
|
156
|
+
function parseCatalogImports(): Set<string> {
|
|
157
|
+
const storyPath = path.join(root, 'stories/ComponentCatalog.stories.tsx');
|
|
158
|
+
const source = fs.readFileSync(storyPath, 'utf8');
|
|
159
|
+
|
|
160
|
+
// Find the import { ... } from '../src' block
|
|
161
|
+
const importBlockRe = /import\s+\{([^}]+)\}\s+from\s+['"]\.\.\/src['"]/s;
|
|
162
|
+
const match = source.match(importBlockRe);
|
|
163
|
+
if (!match) return new Set();
|
|
164
|
+
|
|
165
|
+
const importedNames = new Set<string>();
|
|
166
|
+
for (const token of match[1].split(',')) {
|
|
167
|
+
const trimmed = token.trim();
|
|
168
|
+
if (!trimmed || trimmed.startsWith('type ')) continue;
|
|
169
|
+
// Handle "CloseButton as CloseButtonNS" → extract "CloseButton"
|
|
170
|
+
const baseName = trimmed.split(/\s+as\s+/)[0].trim();
|
|
171
|
+
if (baseName) importedNames.add(baseName);
|
|
172
|
+
}
|
|
173
|
+
return importedNames;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── 3. Extract JSX usage examples ────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
function extractUsageExample(
|
|
179
|
+
componentName: string,
|
|
180
|
+
source: string,
|
|
181
|
+
): string | null {
|
|
182
|
+
// Find first self-closing or opening JSX tag for this component.
|
|
183
|
+
// Handles namespace components like <Card.Root ...> via <Card.*
|
|
184
|
+
const tagRe = new RegExp(
|
|
185
|
+
`<${componentName}(?:\\.[A-Z]\\w*)?[\\s\\S]{0,200}?(?:/>|>)`,
|
|
186
|
+
);
|
|
187
|
+
const match = source.match(tagRe);
|
|
188
|
+
if (!match) return null;
|
|
189
|
+
|
|
190
|
+
// Collapse whitespace to a single representative line
|
|
191
|
+
return match[0]
|
|
192
|
+
.replace(/\s+/g, ' ')
|
|
193
|
+
.replace(/\{ /g, '{')
|
|
194
|
+
.replace(/ \}/g, '}')
|
|
195
|
+
.trim();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── 4. Generate docs/component-catalog.md ────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
function generateCatalog(): void {
|
|
201
|
+
const components = parseComponentIndex();
|
|
202
|
+
const catalogImports = parseCatalogImports();
|
|
203
|
+
|
|
204
|
+
const storyPath = path.join(root, 'stories/ComponentCatalog.stories.tsx');
|
|
205
|
+
const storySource = fs.readFileSync(storyPath, 'utf8');
|
|
206
|
+
|
|
207
|
+
const pkgPath = path.join(root, 'package.json');
|
|
208
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as {
|
|
209
|
+
version?: string;
|
|
210
|
+
};
|
|
211
|
+
const version = pkg.version ?? '0.0.0';
|
|
212
|
+
const now = new Date().toISOString().split('T')[0];
|
|
213
|
+
|
|
214
|
+
const lines: string[] = [];
|
|
215
|
+
|
|
216
|
+
// ── Header ────────────────────────────────────────────────────────────────
|
|
217
|
+
lines.push('# Component Catalog');
|
|
218
|
+
lines.push('');
|
|
219
|
+
lines.push(
|
|
220
|
+
'> **Status:** Generated — auto-produced by `scripts/generate-component-catalog.ts`',
|
|
221
|
+
);
|
|
222
|
+
lines.push(
|
|
223
|
+
'> **Source:** `stories/ComponentCatalog.stories.tsx` + `src/components/index.ts`',
|
|
224
|
+
);
|
|
225
|
+
lines.push(`> **Design System Version:** ${version}`);
|
|
226
|
+
lines.push(`> **Generated:** ${now}`);
|
|
227
|
+
lines.push(
|
|
228
|
+
'> **Do not hand-edit** — this file is overwritten on every build',
|
|
229
|
+
);
|
|
230
|
+
lines.push('');
|
|
231
|
+
lines.push('---');
|
|
232
|
+
lines.push('');
|
|
233
|
+
lines.push('## Overview');
|
|
234
|
+
lines.push('');
|
|
235
|
+
lines.push(`${components.length} components in the Discourser Design System.`);
|
|
236
|
+
lines.push('Run `pnpm catalog:generate` to regenerate after changes.');
|
|
237
|
+
lines.push('');
|
|
238
|
+
lines.push('---');
|
|
239
|
+
lines.push('');
|
|
240
|
+
|
|
241
|
+
// ── Component sections ────────────────────────────────────────────────────
|
|
242
|
+
const stubWarnings: string[] = [];
|
|
243
|
+
|
|
244
|
+
for (const { name, type } of components) {
|
|
245
|
+
lines.push(`## ${name}`);
|
|
246
|
+
lines.push('');
|
|
247
|
+
lines.push(`**Type:** ${type}`);
|
|
248
|
+
lines.push(
|
|
249
|
+
`**Import:** \`import { ${name} } from '@discourser/design-system'\``,
|
|
250
|
+
);
|
|
251
|
+
lines.push('');
|
|
252
|
+
|
|
253
|
+
if (!catalogImports.has(name)) {
|
|
254
|
+
lines.push(
|
|
255
|
+
'> ⚠️ No catalog entry found in ComponentCatalog.stories.tsx — add an example to keep this catalog accurate.',
|
|
256
|
+
);
|
|
257
|
+
stubWarnings.push(name);
|
|
258
|
+
} else {
|
|
259
|
+
const example = extractUsageExample(name, storySource);
|
|
260
|
+
if (example) {
|
|
261
|
+
lines.push('**Usage:**');
|
|
262
|
+
lines.push('```tsx');
|
|
263
|
+
lines.push(example);
|
|
264
|
+
lines.push('```');
|
|
265
|
+
} else {
|
|
266
|
+
lines.push('**Usage:** *(see ComponentCatalog.stories.tsx)*');
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
lines.push('');
|
|
271
|
+
lines.push('---');
|
|
272
|
+
lines.push('');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ── Write output ──────────────────────────────────────────────────────────
|
|
276
|
+
const outPath = path.join(root, 'docs/component-catalog.md');
|
|
277
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
278
|
+
fs.writeFileSync(outPath, lines.join('\n'), 'utf8');
|
|
279
|
+
|
|
280
|
+
console.log('');
|
|
281
|
+
console.log(
|
|
282
|
+
`${GREEN}✓ Generated docs/component-catalog.md — ${components.length} components${RESET}`,
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
if (stubWarnings.length > 0) {
|
|
286
|
+
console.log('');
|
|
287
|
+
console.log(
|
|
288
|
+
`${YELLOW} ⚠️ ${stubWarnings.length} components have no catalog entry:${RESET}`,
|
|
289
|
+
);
|
|
290
|
+
for (const name of stubWarnings) {
|
|
291
|
+
console.log(`${YELLOW} • ${name}${RESET}`);
|
|
292
|
+
}
|
|
293
|
+
console.log(
|
|
294
|
+
`${YELLOW} Add these to stories/ComponentCatalog.stories.tsx to complete the catalog.${RESET}`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
console.log('');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
generateCatalog();
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
- [ ] **Step 1B: Run the script and verify it passes**
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
cd /Users/willstreeter/WebstormProjects/vibe-coding/shifu-project/Discourser-Design-System
|
|
308
|
+
pnpm tsx scripts/generate-component-catalog.ts
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Expected output (script exits 0):
|
|
312
|
+
```
|
|
313
|
+
✓ Generated docs/component-catalog.md — N components
|
|
314
|
+
|
|
315
|
+
⚠️ 6 components have no catalog entry:
|
|
316
|
+
• AbsoluteCenter
|
|
317
|
+
• Divider
|
|
318
|
+
• Group
|
|
319
|
+
• Icon
|
|
320
|
+
• SettingsPopover
|
|
321
|
+
• StudioControls
|
|
322
|
+
Add these to stories/ComponentCatalog.stories.tsx to complete the catalog.
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Then verify:
|
|
326
|
+
```bash
|
|
327
|
+
# File exists and is non-empty
|
|
328
|
+
wc -l docs/component-catalog.md
|
|
329
|
+
|
|
330
|
+
# Every component heading appears exactly once (should print nothing)
|
|
331
|
+
grep "^## " docs/component-catalog.md | sort | uniq -d
|
|
332
|
+
|
|
333
|
+
# Version and date headers are present
|
|
334
|
+
head -10 docs/component-catalog.md
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
- [ ] **Step 1C: Commit**
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
git add scripts/generate-component-catalog.ts docs/component-catalog.md
|
|
341
|
+
git commit -m "feat(catalog): add generate-component-catalog.ts script
|
|
342
|
+
|
|
343
|
+
Generates docs/component-catalog.md from src/components/index.ts and
|
|
344
|
+
stories/ComponentCatalog.stories.tsx. Stubs the 6 components missing
|
|
345
|
+
from the catalog story with a warning comment."
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Task 2: Extend `scripts/validate-exports.ts` with catalog coverage check
|
|
351
|
+
|
|
352
|
+
**Files:**
|
|
353
|
+
- Modify: `scripts/validate-exports.ts`
|
|
354
|
+
|
|
355
|
+
**Design:** Phase 2 is **warning-only** (non-fatal) because 6 components currently lack catalog entries. The mutation test below confirms the detection mechanism works. Phase 2 should be tightened to `process.exit(1)` once all components have catalog entries (tracked separately).
|
|
356
|
+
|
|
357
|
+
The existing file has two early exits that must both be removed:
|
|
358
|
+
1. `process.exit(0)` at line ~112 (inside the success branch) — defer so Phase 2 runs
|
|
359
|
+
2. `process.exit(1)` at line ~152 (bottom of file) — replace with `let phase1Failed`
|
|
360
|
+
|
|
361
|
+
### Exact diff for the existing file:
|
|
362
|
+
|
|
363
|
+
- [ ] **Step 2A: Remove the early `process.exit(0)` and defer the bottom `process.exit(1)`**
|
|
364
|
+
|
|
365
|
+
In `scripts/validate-exports.ts`, make these two edits:
|
|
366
|
+
|
|
367
|
+
**Edit 1** — lines 107–113: remove the inner `process.exit(0)`:
|
|
368
|
+
|
|
369
|
+
Replace:
|
|
370
|
+
```typescript
|
|
371
|
+
if (missing.length === 0 && extra.length === 0) {
|
|
372
|
+
console.log(
|
|
373
|
+
`${GREEN}✓ All exported components have matching package.json export entries.${RESET}`,
|
|
374
|
+
);
|
|
375
|
+
console.log('');
|
|
376
|
+
process.exit(0);
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
With:
|
|
381
|
+
```typescript
|
|
382
|
+
if (missing.length === 0 && extra.length === 0) {
|
|
383
|
+
console.log(
|
|
384
|
+
`${GREEN}✓ All exported components have matching package.json export entries.${RESET}`,
|
|
385
|
+
);
|
|
386
|
+
console.log('');
|
|
387
|
+
// Phase 2 runs next — do not exit here
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Edit 2** — lines 150–153: replace the bottom `process.exit(1)` with a flag:
|
|
392
|
+
|
|
393
|
+
Replace:
|
|
394
|
+
```typescript
|
|
395
|
+
// Fail the build only when components are missing from exports
|
|
396
|
+
if (missing.length > 0) {
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
With:
|
|
402
|
+
```typescript
|
|
403
|
+
// Fail the build only when components are missing from exports (Phase 1)
|
|
404
|
+
const phase1Failed = missing.length > 0;
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
- [ ] **Step 2B: Append Phase 2 to the end of `scripts/validate-exports.ts`**
|
|
408
|
+
|
|
409
|
+
Append this block after the last line of the current file (after the `phase1Failed` declaration):
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
// ── Phase 2: Validate ComponentCatalog.stories.tsx coverage ──────────────────
|
|
413
|
+
//
|
|
414
|
+
// Warning-only (non-fatal) while the catalog is being built out. Tighten to
|
|
415
|
+
// process.exit(1) once all exported components have catalog story entries.
|
|
416
|
+
|
|
417
|
+
console.log('');
|
|
418
|
+
console.log(
|
|
419
|
+
'📖 Validating ComponentCatalog.stories.tsx coverage against src/components/index.ts...',
|
|
420
|
+
);
|
|
421
|
+
console.log('');
|
|
422
|
+
|
|
423
|
+
const storyPath = path.join(root, 'stories/ComponentCatalog.stories.tsx');
|
|
424
|
+
const storySource = fs.readFileSync(storyPath, 'utf8');
|
|
425
|
+
|
|
426
|
+
// Parse the import { ... } from '../src' block
|
|
427
|
+
const importBlockRe = /import\s+\{([^}]+)\}\s+from\s+['"]\.\.\/src['"]/s;
|
|
428
|
+
const importMatch = storySource.match(importBlockRe);
|
|
429
|
+
|
|
430
|
+
const catalogImports = new Set<string>();
|
|
431
|
+
if (importMatch) {
|
|
432
|
+
for (const token of importMatch[1].split(',')) {
|
|
433
|
+
const trimmed = token.trim();
|
|
434
|
+
if (!trimmed || trimmed.startsWith('type ')) continue;
|
|
435
|
+
// Handle "CloseButton as CloseButtonNS" → extract "CloseButton"
|
|
436
|
+
const baseName = trimmed.split(/\s+as\s+/)[0].trim();
|
|
437
|
+
if (baseName) catalogImports.add(baseName);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const missingFromCatalog: string[] = [];
|
|
442
|
+
for (const component of exportedComponents) {
|
|
443
|
+
if (!catalogImports.has(component)) {
|
|
444
|
+
missingFromCatalog.push(component);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const extraInCatalog: string[] = [];
|
|
449
|
+
for (const name of catalogImports) {
|
|
450
|
+
// Only check PascalCase names (component names, not lowercase utilities like `toaster`)
|
|
451
|
+
if (name.charAt(0) !== name.charAt(0).toUpperCase()) continue;
|
|
452
|
+
const found = [...exportedComponents].some(
|
|
453
|
+
(c) => c.toLowerCase() === name.toLowerCase(),
|
|
454
|
+
);
|
|
455
|
+
if (!found) extraInCatalog.push(name);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (missingFromCatalog.length === 0) {
|
|
459
|
+
console.log(
|
|
460
|
+
`${GREEN}✓ All exported components are present in ComponentCatalog.stories.tsx.${RESET}`,
|
|
461
|
+
);
|
|
462
|
+
console.log('');
|
|
463
|
+
} else {
|
|
464
|
+
// Warning-only — see comment above. Change to console.error + phase2Failed=true to harden.
|
|
465
|
+
console.warn(
|
|
466
|
+
`${YELLOW}⚠ Components exported from index.ts but not yet in ComponentCatalog.stories.tsx:${RESET}`,
|
|
467
|
+
);
|
|
468
|
+
for (const name of missingFromCatalog.sort()) {
|
|
469
|
+
console.warn(` ${YELLOW}• ${name}${RESET}`);
|
|
470
|
+
}
|
|
471
|
+
console.warn('');
|
|
472
|
+
console.warn(
|
|
473
|
+
`${YELLOW} Add these to stories/ComponentCatalog.stories.tsx to complete the catalog.${RESET}`,
|
|
474
|
+
);
|
|
475
|
+
console.warn('');
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (extraInCatalog.length > 0) {
|
|
479
|
+
console.warn(
|
|
480
|
+
`${YELLOW}⚠ ComponentCatalog.stories.tsx imports components not found in index.ts (may be intentional):${RESET}`,
|
|
481
|
+
);
|
|
482
|
+
for (const name of extraInCatalog.sort()) {
|
|
483
|
+
console.warn(` ${YELLOW}• ${name}${RESET}`);
|
|
484
|
+
}
|
|
485
|
+
console.warn('');
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ── Final exit ────────────────────────────────────────────────────────────────
|
|
489
|
+
if (phase1Failed) {
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
process.exit(0);
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
- [ ] **Step 2C: Run with current state — confirm exits 0, Phase 2 warns about 6 missing**
|
|
497
|
+
|
|
498
|
+
```bash
|
|
499
|
+
pnpm exports:validate
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Expected: exits code 0. Output includes both phases. Phase 2 warns:
|
|
503
|
+
```
|
|
504
|
+
⚠ Components exported from index.ts but not yet in ComponentCatalog.stories.tsx:
|
|
505
|
+
• AbsoluteCenter
|
|
506
|
+
• Divider
|
|
507
|
+
• Group
|
|
508
|
+
• Icon
|
|
509
|
+
• SettingsPopover
|
|
510
|
+
• StudioControls
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
- [ ] **Step 2D: Mutation test — temporarily break one catalog import**
|
|
514
|
+
|
|
515
|
+
In `stories/ComponentCatalog.stories.tsx`, change `NavigationMenu,` to `FakeComponent,` in the import block.
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
pnpm exports:validate
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Expected: exits code 0 (still non-fatal), Phase 2 now also warns about `NavigationMenu` in addition to the 6 known stubs.
|
|
522
|
+
|
|
523
|
+
Restore `FakeComponent` → `NavigationMenu`.
|
|
524
|
+
|
|
525
|
+
```bash
|
|
526
|
+
pnpm exports:validate
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
Expected: exits code 0, back to only the 6 known stubs in the Phase 2 warning.
|
|
530
|
+
|
|
531
|
+
> **Why exit 0 in the mutation test:** Phase 2 is warning-only by design. The test confirms the detection mechanism (missing components are correctly identified and reported) rather than the exit code. When Phase 2 is hardened to fatal in a follow-up task, the mutation test expectation should be updated to exit 1.
|
|
532
|
+
|
|
533
|
+
- [ ] **Step 2E: Commit**
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
git add scripts/validate-exports.ts
|
|
537
|
+
git commit -m "feat(catalog): add catalog coverage check to validate-exports.ts
|
|
538
|
+
|
|
539
|
+
Adds Phase 2 that checks every component exported from src/components/index.ts
|
|
540
|
+
appears in ComponentCatalog.stories.tsx. Non-fatal (warns) while 6 components
|
|
541
|
+
lack catalog entries. Tighten to fatal after catalog is complete."
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
## Task 3: Wire into `package.json`
|
|
547
|
+
|
|
548
|
+
**Files:**
|
|
549
|
+
- Modify: `package.json`
|
|
550
|
+
|
|
551
|
+
Two changes needed (`docs/` is already in the `files` array — it covers all files under `docs/`, including the generated catalog):
|
|
552
|
+
|
|
553
|
+
- [ ] **Step 3A: Add `catalog:generate` script to `package.json`**
|
|
554
|
+
|
|
555
|
+
In the `scripts` object, add this entry (place it near `exports:validate`):
|
|
556
|
+
```json
|
|
557
|
+
"catalog:generate": "tsx scripts/generate-component-catalog.ts",
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
- [ ] **Step 3B: Update the `build` script**
|
|
561
|
+
|
|
562
|
+
Current value:
|
|
563
|
+
```
|
|
564
|
+
"pnpm build:panda && pnpm typecheck && pnpm build:lib && pnpm build:types && pnpm exports:validate && pnpm codex:generate"
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Updated value (insert `catalog:generate` after `exports:validate`, before `codex:generate`):
|
|
568
|
+
```
|
|
569
|
+
"pnpm build:panda && pnpm typecheck && pnpm build:lib && pnpm build:types && pnpm exports:validate && pnpm catalog:generate && pnpm codex:generate"
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
- [ ] **Step 3C: Run the full build and confirm it passes**
|
|
573
|
+
|
|
574
|
+
```bash
|
|
575
|
+
pnpm build
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Expected:
|
|
579
|
+
- Build completes with exit code 0
|
|
580
|
+
- `docs/component-catalog.md` is updated with fresh date/version
|
|
581
|
+
- Build output shows `catalog:generate` running between `exports:validate` and `codex:generate`
|
|
582
|
+
|
|
583
|
+
- [ ] **Step 3D: Confirm catalog file will ship with the package**
|
|
584
|
+
|
|
585
|
+
`docs/` is already in the `files` array (`package.json` line 197: `"docs"`), so `docs/component-catalog.md` is covered without adding a more specific entry. Verify:
|
|
586
|
+
|
|
587
|
+
```bash
|
|
588
|
+
pnpm pack --dry-run 2>&1 | grep component-catalog
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Expected: `docs/component-catalog.md` appears in the output.
|
|
592
|
+
|
|
593
|
+
- [ ] **Step 3E: Commit**
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
git add package.json
|
|
597
|
+
git commit -m "feat(catalog): wire catalog:generate into build pipeline
|
|
598
|
+
|
|
599
|
+
Adds catalog:generate npm script and inserts it between exports:validate
|
|
600
|
+
and codex:generate in the build sequence. The docs/ entry in the files
|
|
601
|
+
array already covers docs/component-catalog.md for package publishing."
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
## Task 4: Final Verification
|
|
607
|
+
|
|
608
|
+
Run each of these in order and confirm all pass:
|
|
609
|
+
|
|
610
|
+
- [ ] **4A:** `pnpm exports:validate` → exits 0; Phase 1 prints success; Phase 2 warns about 6 missing components
|
|
611
|
+
- [ ] **4B:** `pnpm catalog:generate` → exits 0; `docs/component-catalog.md` is updated
|
|
612
|
+
- [ ] **4C:** `pnpm build` → exits 0; full build succeeds end to end
|
|
613
|
+
- [ ] **4D:** Inspect `docs/component-catalog.md`:
|
|
614
|
+
- `grep "^## " docs/component-catalog.md | sort | uniq -d` prints nothing (no duplicate headings)
|
|
615
|
+
- Version header matches `package.json` version (`0.26.0`)
|
|
616
|
+
- Date is today (`2026-04-03`)
|
|
617
|
+
- Exactly 6 stub warnings appear for: AbsoluteCenter, Divider, Group, Icon, SettingsPopover, StudioControls
|
|
618
|
+
|
|
619
|
+
- [ ] **4E: Open a PR**
|
|
620
|
+
|
|
621
|
+
```bash
|
|
622
|
+
# Confirm you are on a feature branch (not dev or main)
|
|
623
|
+
git branch
|
|
624
|
+
|
|
625
|
+
# If all 3 commits are on the current branch:
|
|
626
|
+
git push -u origin HEAD
|
|
627
|
+
|
|
628
|
+
gh pr create --title "feat: component catalog generation pipeline" \
|
|
629
|
+
--body "$(cat <<'EOF'
|
|
630
|
+
## Summary
|
|
631
|
+
|
|
632
|
+
- Adds `scripts/generate-component-catalog.ts` that auto-generates `docs/component-catalog.md` from live exports and the catalog story
|
|
633
|
+
- Extends `scripts/validate-exports.ts` with Phase 2: catalog coverage check (non-fatal warning while 6 components lack catalog entries)
|
|
634
|
+
- Wires `catalog:generate` into the `build` sequence after `exports:validate`
|
|
635
|
+
|
|
636
|
+
## Parser note
|
|
637
|
+
|
|
638
|
+
Component identity is derived from *source path* of export statements, not individual symbol names. Named symbols exported from compound modules (e.g. `AddScenarioDialog` from `./ScenarioQueue`) are not separately tracked — only the module-level name is.
|
|
639
|
+
|
|
640
|
+
## Known stub warnings (expected)
|
|
641
|
+
|
|
642
|
+
6 components are exported but have no entry in `ComponentCatalog.stories.tsx`:
|
|
643
|
+
AbsoluteCenter, Divider, Group, Icon, SettingsPopover, StudioControls
|
|
644
|
+
|
|
645
|
+
These warn at build time. A follow-up task should add them to the catalog story and harden Phase 2 to `process.exit(1)`.
|
|
646
|
+
|
|
647
|
+
## Test plan
|
|
648
|
+
|
|
649
|
+
- [ ] `pnpm exports:validate` exits 0, both phases print, Phase 2 warns about 6 components
|
|
650
|
+
- [ ] `pnpm catalog:generate` exits 0, file non-empty
|
|
651
|
+
- [ ] `pnpm build` exits 0 end to end
|
|
652
|
+
- [ ] `docs/component-catalog.md` has correct version/date, no duplicate headings
|
|
653
|
+
|
|
654
|
+
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
|
655
|
+
EOF
|
|
656
|
+
)"
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
## Constraints
|
|
662
|
+
|
|
663
|
+
- Do **not** modify `stories/ComponentCatalog.stories.tsx` — it is the source of truth, not a target
|
|
664
|
+
- Do **not** modify `src/components/index.ts` — read it, never write it
|
|
665
|
+
- No hard-coded component names in either script
|
|
666
|
+
- Use `tsx`-compatible TypeScript (no transpilation step)
|
|
667
|
+
- Match `validate-exports.ts` code style exactly: same color constants (`RED`, `YELLOW`, `GREEN`, `RESET`), same ESM imports (`node:fs`, `node:path`, `fileURLToPath`), same comment header style
|