@discourser/design-system 0.22.2 → 0.22.4

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 (75) hide show
  1. package/dist/figma-codex.json +2 -2
  2. package/docs/CSS_USAGE.md +235 -0
  3. package/docs/FIGMA_MAKE_SETUP.md +339 -0
  4. package/docs/GUIDELINES_REVIEW.md +728 -0
  5. package/docs/MAINTAINER_CHECKLIST.md +265 -0
  6. package/docs/TESTING_QUICK_REFERENCE.md +159 -0
  7. package/docs/TESTING_TOKENS.md +340 -0
  8. package/docs/active-stories/README.md +29 -0
  9. package/docs/active-stories/STORY-006a-figma-translation-foundations.md +324 -0
  10. package/docs/active-stories/STORY-006b-figma-translation-components.md +201 -0
  11. package/docs/active-stories/STORY-006c-figma-translation-layout-extension.md +258 -0
  12. package/docs/active-stories/STORY-008-kai-sidecar-fragments.md +137 -0
  13. package/docs/active-stories/STORY-011-verify-translation-docs.md +182 -0
  14. package/docs/archive/ARCHITECTURE-discourser-design-system.md +448 -0
  15. package/docs/claude-feed-back/ARCHITECTURE_DIAGRAM.md +243 -0
  16. package/docs/claude-feed-back/STYLING_VERIFICATION.md +89 -0
  17. package/docs/claude-feed-back/TEST_RESULTS.md +182 -0
  18. package/docs/context-share/ELEVATION_FIX_PLAN.md +903 -0
  19. package/docs/context-share/STORY-001-VALIDATION-PASSED.md +192 -0
  20. package/docs/context-share/STORY-002-IMPLEMENTATION-COMPLETE.md +161 -0
  21. package/docs/context-share/STORYBOOK_MCP_STRATEGY.md +867 -0
  22. package/docs/context-share/TESTING_GAPS_FILLED.md +353 -0
  23. package/docs/context-share/TOKEN_TESTING_SUMMARY.md +388 -0
  24. package/docs/context-share/code-connect-prompt.md +90 -0
  25. package/docs/context-share/dds-autonomous-pipeline.md +765 -0
  26. package/docs/context-share/fix-checkbox-radio-tokens.md +145 -0
  27. package/docs/context-share/icon-component-prompt.md +154 -0
  28. package/docs/context-share/icons/Audience.svg +3 -0
  29. package/docs/context-share/icons/AudioSpeaker.svg +3 -0
  30. package/docs/context-share/icons/BookmarkPlus.svg +3 -0
  31. package/docs/context-share/icons/ClipBoard.svg +8 -0
  32. package/docs/context-share/icons/DiscourserLogo.svg +4 -0
  33. package/docs/context-share/icons/ExitStudio.svg +4 -0
  34. package/docs/context-share/icons/Microphone.svg +5 -0
  35. package/docs/context-share/icons/NotebookPen.svg +3 -0
  36. package/docs/context-share/icons/PausePlay.svg +5 -0
  37. package/docs/context-share/icons/Play.svg +4 -0
  38. package/docs/context-share/icons/Record.svg +6 -0
  39. package/docs/context-share/icons/RepeatQuestion.svg +3 -0
  40. package/docs/context-share/icons/ScrollText.svg +3 -0
  41. package/docs/context-share/icons/Sparkles.svg +3 -0
  42. package/docs/context-share/icons/Speech.svg +3 -0
  43. package/docs/context-share/icons/StopPlay.svg +4 -0
  44. package/docs/context-share/icons/Timer.svg +3 -0
  45. package/docs/context-share/icons/UserProfile.svg +3 -0
  46. package/docs/context-share/m3-token-pipeline-audit.md +125 -0
  47. package/docs/context-share/storybook-mcp-kai-agent-revised-summary.md +211 -0
  48. package/docs/discourser-design-system-prd.md +3698 -0
  49. package/docs/figma-captures/01-typography.png +0 -0
  50. package/docs/figma-captures/02-button-iconbutton.png +0 -0
  51. package/docs/figma-captures/03-form-inputs.png +0 -0
  52. package/docs/figma-captures/04-form-controls.png +0 -0
  53. package/docs/figma-captures/05-data-display.png +0 -0
  54. package/docs/figma-captures/06-feedback.png +0 -0
  55. package/docs/figma-captures/07-overlays.png +0 -0
  56. package/docs/figma-captures/08-navigation-layout.png +0 -0
  57. package/docs/figma-captures/09-custom-components.png +0 -0
  58. package/docs/figma-captures/10-scenario-queue.png +0 -0
  59. package/docs/figma-captures/11-icon-library.png +0 -0
  60. package/docs/figma-make-docs/01-understanding-templates.md +235 -0
  61. package/docs/figma-make-docs/02-prerequisites.md +266 -0
  62. package/docs/figma-make-docs/03-creating-template.md +306 -0
  63. package/docs/figma-make-docs/04-adding-guidelines.md +448 -0
  64. package/docs/figma-make-docs/05-example-starter-code.md +590 -0
  65. package/docs/figma-make-docs/06-publishing-template.md +417 -0
  66. package/docs/figma-make-docs/07-maintenance.md +536 -0
  67. package/docs/figma-make-docs/08-faq.md +490 -0
  68. package/docs/figma-make-docs/README.md +95 -0
  69. package/docs/material-theme.json +418 -0
  70. package/docs/plans/2026-03-12-figma-token-export-rewrite.md +504 -0
  71. package/docs/plans/2026-03-12-step7-panda-token-resolution-design.md +119 -0
  72. package/docs/plans/2026-03-12-step7-panda-token-resolution.md +993 -0
  73. package/docs/token-name-mapping.json +850 -0
  74. package/docs/token-name-mapping.md +251 -0
  75. package/package.json +3 -2
@@ -0,0 +1,504 @@
1
+ # Figma Token Export Rewrite Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Rewrite `scripts/export-figma-tokens.ts` to generate three DTCG-compatible output files from `material3Language` with zero hardcoded values.
6
+
7
+ **Architecture:** Single script with pure helper functions. Each output file is built by a dedicated builder function that reads exclusively from `material3Language`. The semantic key → Figma path mapping is handled by a single `semanticKeyToFigmaPath()` pure function. All three files are written to `dist/`, and `dist/figma-variables.json` is also copied to `tokens/tokens.json`.
8
+
9
+ **Tech Stack:** TypeScript (ESM), tsx runner, Node.js `fs`/`path`, no new dependencies.
10
+
11
+ ---
12
+
13
+ ### Task 1: Add `figma:export` script to package.json
14
+
15
+ **Files:**
16
+
17
+ - Modify: `package.json`
18
+
19
+ **Step 1: Add the script entry**
20
+
21
+ In `package.json`, find the `"scripts"` block. Add after the last existing script entry:
22
+
23
+ ```json
24
+ "figma:export": "tsx scripts/export-figma-tokens.ts"
25
+ ```
26
+
27
+ **Step 2: Verify**
28
+
29
+ ```bash
30
+ cd /Users/willstreeter/WebstormProjects/vibe-coding/shifu-project/Discourser-Design-System
31
+ cat package.json | grep figma:export
32
+ ```
33
+
34
+ Expected output: `"figma:export": "tsx scripts/export-figma-tokens.ts"`
35
+
36
+ **Step 3: Commit**
37
+
38
+ ```bash
39
+ git add package.json
40
+ git commit -m "chore: add figma:export script to package.json"
41
+ ```
42
+
43
+ ---
44
+
45
+ ### Task 2: Rewrite scripts/export-figma-tokens.ts
46
+
47
+ **Files:**
48
+
49
+ - Modify: `scripts/export-figma-tokens.ts` (complete rewrite)
50
+
51
+ **Step 1: Replace the entire file with this implementation**
52
+
53
+ ```typescript
54
+ /**
55
+ * Export Design System Tokens for Figma
56
+ *
57
+ * Generates three DTCG-compatible output files from material3Language.
58
+ * Zero hardcoded values — all data sourced programmatically.
59
+ *
60
+ * Usage: pnpm figma:export
61
+ */
62
+
63
+ import { material3Language } from '../src/languages/material3.language';
64
+ import * as fs from 'fs';
65
+ import * as path from 'path';
66
+ import { fileURLToPath } from 'url';
67
+
68
+ const __filename = fileURLToPath(import.meta.url);
69
+ const __dirname = path.dirname(__filename);
70
+
71
+ const packageJsonPath = path.join(__dirname, '..', 'package.json');
72
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Helper: strip px suffix and convert to number
76
+ // ---------------------------------------------------------------------------
77
+
78
+ function px(value: string): number {
79
+ return parseFloat(value.replace('px', ''));
80
+ }
81
+
82
+ function ms(value: string): number {
83
+ return parseFloat(value.replace('ms', ''));
84
+ }
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // Helper: camelCase semantic key → Figma path (/ separator)
88
+ // ---------------------------------------------------------------------------
89
+
90
+ function semanticKeyToFigmaPath(key: string): string {
91
+ // Surface container variants
92
+ if (key === 'surfaceContainer') return 'surface/container';
93
+ if (key === 'surfaceContainerLow') return 'surface/container/low';
94
+ if (key === 'surfaceContainerLowest') return 'surface/container/lowest';
95
+ if (key === 'surfaceContainerHigh') return 'surface/container/high';
96
+ if (key === 'surfaceContainerHighest') return 'surface/container/highest';
97
+
98
+ // surfaceVariant / onSurfaceVariant
99
+ if (key === 'surfaceVariant') return 'surface/variant';
100
+ if (key === 'onSurfaceVariant') return 'onSurface/variant';
101
+
102
+ // outlineVariant
103
+ if (key === 'outlineVariant') return 'outline/variant';
104
+
105
+ // inverse*
106
+ if (key === 'inverseSurface') return 'inverse/surface';
107
+ if (key === 'inverseOnSurface') return 'inverse/onSurface';
108
+ if (key === 'inversePrimary') return 'inverse/primary';
109
+
110
+ // on*Container (e.g. onPrimaryContainer → onPrimary/container)
111
+ const onContainerMatch = key.match(/^(on[A-Z][a-z]+)Container$/);
112
+ if (onContainerMatch) {
113
+ return `${onContainerMatch[1]}/container`;
114
+ }
115
+
116
+ // *Container (e.g. primaryContainer → primary/container)
117
+ const containerMatch = key.match(/^([a-z][a-zA-Z]+)Container$/);
118
+ if (containerMatch) {
119
+ return `${containerMatch[1]}/container`;
120
+ }
121
+
122
+ // All others pass through as-is
123
+ return key;
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Builder 1: Primitives collection — tonal palettes
128
+ // ---------------------------------------------------------------------------
129
+
130
+ type DtcgColorEntry = {
131
+ $type: 'color';
132
+ $value: Record<string, string>;
133
+ };
134
+
135
+ type DtcgNumberEntry = {
136
+ $type: 'number';
137
+ $value: Record<string, number>;
138
+ };
139
+
140
+ function buildPrimitives(): Record<string, DtcgColorEntry> {
141
+ const result: Record<string, DtcgColorEntry> = {};
142
+ const lang = material3Language;
143
+
144
+ for (const [paletteName, tones] of Object.entries(lang.colors)) {
145
+ for (const [tone, hex] of Object.entries(tones)) {
146
+ const key = `${paletteName}/${tone}`;
147
+ result[key] = {
148
+ $type: 'color',
149
+ $value: { Value: String(hex) },
150
+ };
151
+ }
152
+ }
153
+
154
+ return result;
155
+ }
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // Builder 2: Semantic collection — light + dark modes
159
+ // ---------------------------------------------------------------------------
160
+
161
+ function buildSemantic(): Record<string, DtcgColorEntry> {
162
+ const result: Record<string, DtcgColorEntry> = {};
163
+ const lang = material3Language;
164
+
165
+ // Iterate light keys (semantic and semanticDark must have identical key sets)
166
+ for (const [key, lightValue] of Object.entries(lang.semantic)) {
167
+ const darkValue = (lang.semanticDark as Record<string, string>)[key];
168
+ const figmaPath = semanticKeyToFigmaPath(key);
169
+
170
+ result[figmaPath] = {
171
+ $type: 'color',
172
+ $value: {
173
+ Light: lightValue,
174
+ Dark: darkValue,
175
+ },
176
+ };
177
+ }
178
+
179
+ return result;
180
+ }
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // Builder 3: Spacing & Shape collection — numeric tokens
184
+ // ---------------------------------------------------------------------------
185
+
186
+ function buildSpacingAndShape(): Record<string, DtcgNumberEntry> {
187
+ const result: Record<string, DtcgNumberEntry> = {};
188
+ const lang = material3Language;
189
+
190
+ // Spacing
191
+ for (const [key, value] of Object.entries(lang.spacing)) {
192
+ result[`spacing/${key}`] = {
193
+ $type: 'number',
194
+ $value: { Value: px(value) },
195
+ };
196
+ }
197
+
198
+ // Radii
199
+ for (const [key, value] of Object.entries(lang.shape.radii)) {
200
+ result[`radii/${key}`] = {
201
+ $type: 'number',
202
+ $value: { Value: px(value) },
203
+ };
204
+ }
205
+
206
+ // Border widths
207
+ for (const [key, value] of Object.entries(lang.border.widths)) {
208
+ result[`border/${key}`] = {
209
+ $type: 'number',
210
+ $value: { Value: px(value) },
211
+ };
212
+ }
213
+
214
+ // Durations
215
+ for (const [key, value] of Object.entries(lang.motion.durations)) {
216
+ result[`duration/${key}`] = {
217
+ $type: 'number',
218
+ $value: { Value: ms(value) },
219
+ };
220
+ }
221
+
222
+ return result;
223
+ }
224
+
225
+ // ---------------------------------------------------------------------------
226
+ // Builder 4: Effect styles — elevation
227
+ // ---------------------------------------------------------------------------
228
+
229
+ type ElevationEntry = {
230
+ value: string;
231
+ description: string;
232
+ };
233
+
234
+ const elevationDescriptions: Record<string, string> = {
235
+ level0: 'No elevation — flat surfaces',
236
+ level1: 'Cards at rest, contained buttons (semantic: xs)',
237
+ level2: 'Cards on hover, raised buttons (semantic: sm)',
238
+ level3: 'Dialogs, dropdowns, popovers (semantic: md)',
239
+ level4: 'Navigation drawers, modal sheets (semantic: lg)',
240
+ level5: 'FABs, tooltips, snackbars (semantic: xl)',
241
+ };
242
+
243
+ function buildEffectStyles(): { elevation: Record<string, ElevationEntry> } {
244
+ const elevation: Record<string, ElevationEntry> = {};
245
+ const lang = material3Language;
246
+
247
+ for (const [key, value] of Object.entries(lang.elevation.levels)) {
248
+ elevation[key] = {
249
+ value,
250
+ description: elevationDescriptions[key] ?? key,
251
+ };
252
+ }
253
+
254
+ return { elevation };
255
+ }
256
+
257
+ // ---------------------------------------------------------------------------
258
+ // Builder 5: Text styles — typography
259
+ // ---------------------------------------------------------------------------
260
+
261
+ type TextStyleEntry = {
262
+ fontFamily: string;
263
+ fontSize: number;
264
+ fontWeight: number;
265
+ lineHeight: number;
266
+ letterSpacing: number;
267
+ figmaTextStyle: string;
268
+ };
269
+
270
+ function buildTextStyles(): Record<string, TextStyleEntry> {
271
+ const result: Record<string, TextStyleEntry> = {};
272
+ const lang = material3Language;
273
+
274
+ for (const [key, style] of Object.entries(lang.typography.scale)) {
275
+ const fontKey = style.fontFamily as 'display' | 'body' | 'mono';
276
+ const fontFamilyString = lang.typography.fonts[fontKey];
277
+ // Extract first font name before comma, strip quotes
278
+ const fontFamily = fontFamilyString
279
+ .split(',')[0]
280
+ .replace(/['"]/g, '')
281
+ .trim();
282
+
283
+ result[key] = {
284
+ fontFamily,
285
+ fontSize: px(style.fontSize),
286
+ fontWeight: Number(style.fontWeight),
287
+ lineHeight: px(style.lineHeight),
288
+ letterSpacing: px(style.letterSpacing),
289
+ figmaTextStyle: key,
290
+ };
291
+ }
292
+
293
+ return result;
294
+ }
295
+
296
+ // ---------------------------------------------------------------------------
297
+ // Main: assemble, write files, print summary
298
+ // ---------------------------------------------------------------------------
299
+
300
+ function ensureDir(filePath: string): void {
301
+ const dir = path.dirname(filePath);
302
+ if (!fs.existsSync(dir)) {
303
+ fs.mkdirSync(dir, { recursive: true });
304
+ }
305
+ }
306
+
307
+ function writeJson(filePath: string, data: unknown): void {
308
+ ensureDir(filePath);
309
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
310
+ }
311
+
312
+ const distDir = path.join(__dirname, '..', 'dist');
313
+
314
+ // --- figma-variables.json ---
315
+ const primitives = buildPrimitives();
316
+ const semantic = buildSemantic();
317
+ const spacingAndShape = buildSpacingAndShape();
318
+
319
+ const figmaVariables = {
320
+ $metadata: {
321
+ version: packageJson.version ?? '0.0.0',
322
+ generated: new Date().toISOString(),
323
+ },
324
+ Primitives: primitives,
325
+ Semantic: semantic,
326
+ 'Spacing & Shape': spacingAndShape,
327
+ };
328
+
329
+ const figmaVariablesPath = path.join(distDir, 'figma-variables.json');
330
+ writeJson(figmaVariablesPath, figmaVariables);
331
+
332
+ // --- figma-effect-styles.json ---
333
+ const effectStyles = buildEffectStyles();
334
+ const effectStylesPath = path.join(distDir, 'figma-effect-styles.json');
335
+ writeJson(effectStylesPath, effectStyles);
336
+
337
+ // --- figma-text-styles.json ---
338
+ const textStyles = buildTextStyles();
339
+ const textStylesPath = path.join(distDir, 'figma-text-styles.json');
340
+ writeJson(textStylesPath, textStyles);
341
+
342
+ // --- tokens/tokens.json (copy of figma-variables.json) ---
343
+ const tokensPath = path.join(__dirname, '..', 'tokens', 'tokens.json');
344
+ writeJson(tokensPath, figmaVariables);
345
+
346
+ // --- Summary ---
347
+ console.log(
348
+ `✅ dist/figma-variables.json — ${Object.keys(primitives).length} Primitives, ${Object.keys(semantic).length} Semantic, ${Object.keys(spacingAndShape).length} Spacing & Shape tokens`,
349
+ );
350
+ console.log(
351
+ `✅ dist/figma-effect-styles.json — ${Object.keys(effectStyles.elevation).length} elevation levels`,
352
+ );
353
+ console.log(
354
+ `✅ dist/figma-text-styles.json — ${Object.keys(textStyles).length} text styles`,
355
+ );
356
+ console.log(`✅ tokens/tokens.json updated`);
357
+ ```
358
+
359
+ **Step 2: Run the export script to verify it executes clean**
360
+
361
+ ```bash
362
+ cd /Users/willstreeter/WebstormProjects/vibe-coding/shifu-project/Discourser-Design-System
363
+ pnpm figma:export
364
+ ```
365
+
366
+ Expected output (exact numbers):
367
+
368
+ ```
369
+ ✅ dist/figma-variables.json — 78 Primitives, 31 Semantic, 24 Spacing & Shape tokens
370
+ ✅ dist/figma-effect-styles.json — 6 elevation levels
371
+ ✅ dist/figma-text-styles.json — 15 text styles
372
+ ✅ tokens/tokens.json updated
373
+ ```
374
+
375
+ **Step 3: Spot-check the output files**
376
+
377
+ ```bash
378
+ # Verify Primitives structure
379
+ node -e "const f=JSON.parse(require('fs').readFileSync('dist/figma-variables.json','utf8')); console.log(Object.keys(f.Primitives).slice(0,3)); console.log(f.Primitives['primary/0'])"
380
+ ```
381
+
382
+ Expected:
383
+
384
+ ```
385
+ [ 'primary/0', 'primary/10', 'primary/20' ]
386
+ { '$type': 'color', '$value': { Value: '#000000' } }
387
+ ```
388
+
389
+ ```bash
390
+ # Verify Semantic has Light/Dark modes (no dark-* prefix keys)
391
+ node -e "const f=JSON.parse(require('fs').readFileSync('dist/figma-variables.json','utf8')); const s=f.Semantic; console.log(s['primary']); console.log(s['primary/container']); console.log(s['surface/container/lowest'])"
392
+ ```
393
+
394
+ Expected:
395
+
396
+ ```
397
+ { '$type': 'color', '$value': { Light: '#4C662B', Dark: '#B1D18A' } }
398
+ { '$type': 'color', '$value': { Light: '#CDEDA3', Dark: '#354E16' } }
399
+ { '$type': 'color', '$value': { Light: '#FFFFFF', Dark: '#0C0F09' } }
400
+ ```
401
+
402
+ ```bash
403
+ # Verify numeric tokens are numbers not strings
404
+ node -e "const f=JSON.parse(require('fs').readFileSync('dist/figma-variables.json','utf8')); const s=f['Spacing & Shape']; console.log(s['spacing/md']); console.log(s['radii/extraSmall']); console.log(s['border/thin']); console.log(s['duration/normal'])"
405
+ ```
406
+
407
+ Expected:
408
+
409
+ ```
410
+ { '$type': 'number', '$value': { Value: 16 } }
411
+ { '$type': 'number', '$value': { Value: 4 } }
412
+ { '$type': 'number', '$value': { Value: 1 } }
413
+ { '$type': 'number', '$value': { Value: 200 } }
414
+ ```
415
+
416
+ ```bash
417
+ # Verify text styles have numeric values and correct font family
418
+ node -e "const f=JSON.parse(require('fs').readFileSync('dist/figma-text-styles.json','utf8')); console.log(f['displayLarge']); console.log(f['bodyMedium'])"
419
+ ```
420
+
421
+ Expected:
422
+
423
+ ```
424
+ {
425
+ fontFamily: 'Fraunces',
426
+ fontSize: 57,
427
+ fontWeight: 400,
428
+ lineHeight: 64,
429
+ letterSpacing: -0.25,
430
+ figmaTextStyle: 'displayLarge'
431
+ }
432
+ {
433
+ fontFamily: 'Poppins',
434
+ fontSize: 14,
435
+ fontWeight: 400,
436
+ lineHeight: 20,
437
+ letterSpacing: 0.25,
438
+ figmaTextStyle: 'bodyMedium'
439
+ }
440
+ ```
441
+
442
+ ```bash
443
+ # Verify effect styles
444
+ node -e "const f=JSON.parse(require('fs').readFileSync('dist/figma-effect-styles.json','utf8')); console.log(Object.keys(f.elevation)); console.log(f.elevation.level1)"
445
+ ```
446
+
447
+ Expected:
448
+
449
+ ```
450
+ [ 'level0', 'level1', 'level2', 'level3', 'level4', 'level5' ]
451
+ {
452
+ value: '0px 1px 2px rgba(0, 0, 0, 0.3), 0px 1px 3px 1px rgba(0, 0, 0, 0.15)',
453
+ description: 'Cards at rest, contained buttons (semantic: xs)'
454
+ }
455
+ ```
456
+
457
+ ```bash
458
+ # Verify tokens/tokens.json was updated
459
+ node -e "const f=JSON.parse(require('fs').readFileSync('tokens/tokens.json','utf8')); console.log(Object.keys(f))"
460
+ ```
461
+
462
+ Expected:
463
+
464
+ ```
465
+ [ '$metadata', 'Primitives', 'Semantic', 'Spacing & Shape' ]
466
+ ```
467
+
468
+ **Step 4: Run typecheck**
469
+
470
+ ```bash
471
+ pnpm tsc --noEmit
472
+ ```
473
+
474
+ Expected: No errors.
475
+
476
+ **Step 5: Run tests to verify no regressions**
477
+
478
+ ```bash
479
+ pnpm test
480
+ ```
481
+
482
+ Expected: All tests pass (306/306).
483
+
484
+ **Step 6: Commit**
485
+
486
+ ```bash
487
+ git add scripts/export-figma-tokens.ts tokens/tokens.json
488
+ git commit -m "feat: rewrite export-figma-tokens to DTCG format with Light/Dark modes"
489
+ ```
490
+
491
+ ---
492
+
493
+ ## Success Criteria
494
+
495
+ - [ ] `pnpm figma:export` runs clean with no errors
496
+ - [ ] `dist/figma-variables.json` exists with `Primitives`, `Semantic`, `Spacing & Shape` collections
497
+ - [ ] Semantic collection has `Light`/`Dark` modes (no `dark-*` prefix keys anywhere)
498
+ - [ ] All numeric tokens (spacing, radii, border, duration) are numbers not strings
499
+ - [ ] `dist/figma-effect-styles.json` exists with 6 elevation levels
500
+ - [ ] `dist/figma-text-styles.json` exists with 15 text styles, numeric values
501
+ - [ ] `tokens/tokens.json` updated to match figma-variables.json content
502
+ - [ ] Zero hardcoded hex values in the script
503
+ - [ ] `pnpm test` passes: 306/306 (no regressions)
504
+ - [ ] `pnpm tsc --noEmit` clean
@@ -0,0 +1,119 @@
1
+ # Step 7 Design: Panda Token Resolution for Foundation Stories
2
+
3
+ **Date:** 2026-03-12
4
+ **Branch context:** fix/remove-m3-labels (or new feature branch)
5
+
6
+ ## Goal
7
+
8
+ Replace raw-value rendering with Panda CSS token resolution in all four foundation MDX stories. A broken token must produce a blank/invisible output rather than silently working.
9
+
10
+ ## Approach: A (Update Components)
11
+
12
+ Move all Panda `css()` lookups inside the display component files in `src/stories/foundations/components/`. The `.mdx` files pass token name strings instead of raw values. This is cleaner than inlining JSX in `.mdx`.
13
+
14
+ ---
15
+
16
+ ## Per-Story Design
17
+
18
+ ### Colors.mdx
19
+
20
+ **TonalSwatch section** → Remove entirely. Replace with a note pointing to the Color Scale story. Rationale: tonal palette tones (0, 10, 20, …, 100) have no Panda token equivalents — they are internal pipeline values. Adding 65 documentation-only tokens would be noise. ColorScale.stories.tsx already covers the usable Radix-scale vocabulary.
21
+
22
+ **SemanticSwatch** → Update `ColorSwatch.tsx`:
23
+
24
+ - Remove `lightValue` / `darkValue` props
25
+ - Add `name` prop (token key string, e.g. `"primary"`, `"onPrimary"`, `"primary.container"`)
26
+ - Pre-declare `semanticBg: Record<string, string>` with ~31 explicit `css({ bg: '...' })` entries
27
+ - Show light/dark side-by-side: light box = plain div with lookup class; dark box = same class inside a `data-theme="dark"` wrapper
28
+ - Labels show token path (e.g. `primary`) and `primary [dark]` — no hex strings
29
+
30
+ `Colors.mdx` changes:
31
+
32
+ - Remove tonal palette grid divs + `TonalSwatch` import usage (keep import if ColorSwatch still exports it, or remove)
33
+ - Replace each `<SemanticSwatch lightValue=... darkValue=...>` with `<SemanticSwatch name="primary" ...>`
34
+ - Keep all prose, headings, and usage guidelines unchanged
35
+
36
+ ### Elevation.mdx / ElevationCard.tsx
37
+
38
+ Shadow tokens `level0`–`level5` exist in Panda as `boxShadow` values.
39
+
40
+ `ElevationCard.tsx` changes:
41
+
42
+ - Pre-declare `elevationClasses: Record<string, string>` with 6 entries: `level0: css({ boxShadow: 'level0' })` … `level5: css({ boxShadow: 'level5' })`
43
+ - Remove `shadow` prop; use `level` prop as lookup key
44
+ - The displayed shadow string becomes static text (hardcoded in the mdx reference table)
45
+
46
+ `ElevationGrid.tsx`:
47
+
48
+ - Remove `elevations: Record<string, string>` prop
49
+ - Render a fixed array `['level0', 'level1', 'level2', 'level3', 'level4', 'level5']`
50
+
51
+ `Elevation.mdx` changes:
52
+
53
+ - `<ElevationGrid />` — no prop needed
54
+ - "Visual Comparison" inline divs use pre-declared elevation classes (pre-declared in the mdx or extracted to a helper)
55
+
56
+ ### Spacing.mdx / SpacingBox.tsx
57
+
58
+ Spacing tokens `none`, `xxs`, `xs`, `sm`, `md`, `lg`, `xl`, `xxl`, `xxxl` exist in Panda.
59
+
60
+ `SpacingBox.tsx` changes:
61
+
62
+ - Pre-declare `spacingWidthClasses: Record<string, string>` with 9 entries: `none: css({ width: 'none' })` … `xxxl: css({ width: 'xxxl' })`
63
+ - Width bar uses `className={spacingWidthClasses[name]}` instead of `style={{ width: value }}`
64
+ - Keep `value` prop for the px label display
65
+
66
+ `Spacing.mdx` changes:
67
+
68
+ - `<SpacingBox>` calls keep `name` and `value` props (value is for px label only)
69
+ - "Visual Examples" section uses static px strings (e.g. `gap: '8px'`) instead of `material3Language.spacing.sm`
70
+
71
+ ### Typography.mdx / TypeSpecimen.tsx
72
+
73
+ Text styles exist: `displayLarge`, `displayMedium`, `displaySmall`, `headlineLarge`, `headlineMedium`, `headlineSmall`, `titleLarge`, `titleMedium`, `titleSmall`, `bodyLarge`, `bodyMedium`, `bodySmall`, `labelLarge`, `labelMedium`, `labelSmall` (15 total).
74
+
75
+ `TypeSpecimen.tsx` changes:
76
+
77
+ - Add `styleName` prop (e.g. `'displayLarge'`)
78
+ - Pre-declare `textStyleClasses: Record<string, string>` with 15 explicit `css({ textStyle: '...' })` entries
79
+ - Specimen text uses `className={textStyleClasses[styleName]}` instead of inline `specimenStyle`
80
+ - Keep all raw-value props (`fontSize`, `lineHeight`, etc.) — used only for the spec table below each specimen
81
+
82
+ `Typography.mdx` changes:
83
+
84
+ - Each `<TypeSpecimen>` gets `styleName="displayLarge"` (etc.) added
85
+ - Font Families section: replace `fontFamily: material3Language.typography.fonts.display` with static string `'"Fraunces", Georgia, "Times New Roman", serif'` etc.
86
+
87
+ ---
88
+
89
+ ## Implementation Rules
90
+
91
+ - All `css()` calls use string literals — no dynamic template strings or variables
92
+ - Use a pre-declared lookup object (same pattern as `ColorScale.stories.tsx` `scaleBg` / `semanticClasses`)
93
+ - `material3Language` must not be used to set any rendered color, shadow, width, or textStyle
94
+ - `material3Language` may still be referenced for static label/description text only
95
+
96
+ ---
97
+
98
+ ## Test Requirement
99
+
100
+ After first story is refactored (Colors.mdx / SemanticSwatch):
101
+
102
+ 1. In `semantic-tokens.ts`, temporarily rename `primary` → `primary_BROKEN`
103
+ 2. Storybook: confirm primary color swatches go blank/invisible
104
+ 3. Revert rename
105
+ 4. Confirm swatches return to normal
106
+ Document in commit message.
107
+
108
+ ---
109
+
110
+ ## Success Criteria
111
+
112
+ - [x] All four .mdx stories render via Panda CSS token variables, not raw values
113
+ - [x] No css() calls use dynamic string interpolation
114
+ - [x] material3Language is NOT used to set any rendered color, shadow, size, or textStyle
115
+ - [x] Test-and-revert confirms visual failure on token breakage
116
+ - [x] pnpm test passes: 306/306
117
+ - [x] pnpm tsc --noEmit clean
118
+ - [x] Storybook renders all stories without console errors
119
+ - [x] Sequence status doc updated: Step 7 complete