@primer/primitives 11.4.0 → 11.4.1-rc.14fb4bb1

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 (165) hide show
  1. package/DESIGN_TOKENS_GUIDE.md +185 -0
  2. package/DESIGN_TOKENS_SPEC.md +565 -0
  3. package/dist/build/formats/jsonFigma.js +8 -1
  4. package/dist/build/formats/markdownLlmGuidelines.d.ts +7 -6
  5. package/dist/build/formats/markdownLlmGuidelines.js +1034 -60
  6. package/dist/build/schemas/borderToken.d.ts +61 -5
  7. package/dist/build/schemas/borderToken.js +2 -1
  8. package/dist/build/schemas/colorToken.d.ts +639 -30
  9. package/dist/build/schemas/colorToken.js +3 -2
  10. package/dist/build/schemas/colorW3cValue.d.ts +28 -0
  11. package/dist/build/schemas/colorW3cValue.js +42 -0
  12. package/dist/build/schemas/cubicBezierToken.d.ts +1 -1
  13. package/dist/build/schemas/dimensionToken.d.ts +9 -2
  14. package/dist/build/schemas/dimensionValue.d.ts +12 -1
  15. package/dist/build/schemas/dimensionValue.js +10 -13
  16. package/dist/build/schemas/durationToken.d.ts +8 -2
  17. package/dist/build/schemas/durationValue.d.ts +11 -1
  18. package/dist/build/schemas/durationValue.js +13 -3
  19. package/dist/build/schemas/fontFamilyToken.d.ts +1 -1
  20. package/dist/build/schemas/fontWeightToken.d.ts +1 -1
  21. package/dist/build/schemas/gradientToken.d.ts +23 -2
  22. package/dist/build/schemas/gradientToken.js +2 -1
  23. package/dist/build/schemas/numberToken.d.ts +1 -1
  24. package/dist/build/schemas/shadowToken.d.ts +1751 -127
  25. package/dist/build/schemas/shadowToken.js +8 -2
  26. package/dist/build/schemas/stringToken.d.ts +1 -1
  27. package/dist/build/schemas/stringToken.js +1 -1
  28. package/dist/build/schemas/tokenType.d.ts +1 -1
  29. package/dist/build/schemas/transitionToken.d.ts +15 -3
  30. package/dist/build/schemas/typographyToken.d.ts +19 -5
  31. package/dist/build/schemas/typographyToken.js +1 -1
  32. package/dist/build/schemas/validTokenType.d.ts +1 -1
  33. package/dist/build/schemas/validTokenType.js +1 -1
  34. package/dist/build/schemas/viewportRangeToken.d.ts +1 -1
  35. package/dist/build/transformers/borderToCss.js +19 -1
  36. package/dist/build/transformers/colorAlphaToCss.js +6 -3
  37. package/dist/build/transformers/colorToHex.js +5 -2
  38. package/dist/build/transformers/colorToRgbAlpha.js +5 -2
  39. package/dist/build/transformers/colorToRgbaFloat.js +5 -0
  40. package/dist/build/transformers/dimensionToPixelUnitless.d.ts +3 -2
  41. package/dist/build/transformers/dimensionToPixelUnitless.js +22 -26
  42. package/dist/build/transformers/dimensionToRem.d.ts +2 -1
  43. package/dist/build/transformers/dimensionToRem.js +21 -22
  44. package/dist/build/transformers/dimensionToRemPxArray.d.ts +2 -1
  45. package/dist/build/transformers/dimensionToRemPxArray.js +21 -22
  46. package/dist/build/transformers/durationToCss.d.ts +2 -1
  47. package/dist/build/transformers/durationToCss.js +18 -11
  48. package/dist/build/transformers/gradientToCss.js +2 -1
  49. package/dist/build/transformers/shadowToCss.js +15 -1
  50. package/dist/build/transformers/utilities/normalizeColorValue.d.ts +23 -0
  51. package/dist/build/transformers/utilities/normalizeColorValue.js +74 -0
  52. package/dist/build/transformers/utilities/parseDimension.d.ts +12 -0
  53. package/dist/build/transformers/utilities/parseDimension.js +31 -0
  54. package/dist/build/types/borderTokenValue.d.ts +5 -2
  55. package/dist/build/types/dimensionTokenValue.d.ts +9 -0
  56. package/dist/build/types/shadowTokenValue.d.ts +8 -5
  57. package/dist/css/functional/themes/dark-colorblind-high-contrast.css +32 -28
  58. package/dist/css/functional/themes/dark-colorblind.css +32 -28
  59. package/dist/css/functional/themes/dark-dimmed-high-contrast.css +32 -28
  60. package/dist/css/functional/themes/dark-dimmed.css +32 -28
  61. package/dist/css/functional/themes/dark-high-contrast.css +32 -28
  62. package/dist/css/functional/themes/dark-tritanopia-high-contrast.css +32 -28
  63. package/dist/css/functional/themes/dark-tritanopia.css +32 -28
  64. package/dist/css/functional/themes/dark.css +32 -28
  65. package/dist/css/functional/themes/light-colorblind-high-contrast.css +32 -28
  66. package/dist/css/functional/themes/light-colorblind.css +32 -28
  67. package/dist/css/functional/themes/light-high-contrast.css +32 -28
  68. package/dist/css/functional/themes/light-tritanopia-high-contrast.css +32 -28
  69. package/dist/css/functional/themes/light-tritanopia.css +32 -28
  70. package/dist/css/functional/themes/light.css +32 -28
  71. package/dist/css/primitives.css +4 -0
  72. package/dist/docs/base/motion/motion.json +96 -24
  73. package/dist/docs/base/size/size.json +76 -19
  74. package/dist/docs/base/typography/typography.json +24 -6
  75. package/dist/docs/functional/size/border.json +26 -11
  76. package/dist/docs/functional/size/breakpoints.json +24 -6
  77. package/dist/docs/functional/size/radius.json +16 -4
  78. package/dist/docs/functional/size/size.json +60 -15
  79. package/dist/docs/functional/themes/dark-colorblind-high-contrast.json +2629 -346
  80. package/dist/docs/functional/themes/dark-colorblind.json +2629 -346
  81. package/dist/docs/functional/themes/dark-dimmed-high-contrast.json +2629 -346
  82. package/dist/docs/functional/themes/dark-dimmed.json +2629 -346
  83. package/dist/docs/functional/themes/dark-high-contrast.json +2629 -346
  84. package/dist/docs/functional/themes/dark-tritanopia-high-contrast.json +2629 -346
  85. package/dist/docs/functional/themes/dark-tritanopia.json +2629 -346
  86. package/dist/docs/functional/themes/dark.json +2629 -346
  87. package/dist/docs/functional/themes/light-colorblind-high-contrast.json +2635 -352
  88. package/dist/docs/functional/themes/light-colorblind.json +2632 -349
  89. package/dist/docs/functional/themes/light-high-contrast.json +2635 -352
  90. package/dist/docs/functional/themes/light-tritanopia-high-contrast.json +2635 -352
  91. package/dist/docs/functional/themes/light-tritanopia.json +2632 -349
  92. package/dist/docs/functional/themes/light.json +2632 -349
  93. package/dist/docs/functional/typography/typography.json +8 -2
  94. package/dist/fallbacks/base/motion/motion.json +48 -12
  95. package/dist/figma/themes/light-colorblind.json +4 -4
  96. package/dist/figma/themes/light-high-contrast.json +4 -4
  97. package/dist/figma/themes/light-tritanopia.json +4 -4
  98. package/dist/figma/themes/light.json +4 -4
  99. package/dist/internalCss/dark-colorblind-high-contrast.css +28 -28
  100. package/dist/internalCss/dark-colorblind.css +28 -28
  101. package/dist/internalCss/dark-dimmed-high-contrast.css +28 -28
  102. package/dist/internalCss/dark-dimmed.css +28 -28
  103. package/dist/internalCss/dark-high-contrast.css +28 -28
  104. package/dist/internalCss/dark-tritanopia-high-contrast.css +28 -28
  105. package/dist/internalCss/dark-tritanopia.css +28 -28
  106. package/dist/internalCss/dark.css +28 -28
  107. package/dist/internalCss/light-colorblind-high-contrast.css +28 -28
  108. package/dist/internalCss/light-colorblind.css +28 -28
  109. package/dist/internalCss/light-high-contrast.css +28 -28
  110. package/dist/internalCss/light-tritanopia-high-contrast.css +28 -28
  111. package/dist/internalCss/light-tritanopia.css +28 -28
  112. package/dist/internalCss/light.css +28 -28
  113. package/dist/styleLint/base/motion/motion.json +96 -24
  114. package/dist/styleLint/base/size/size.json +76 -19
  115. package/dist/styleLint/base/typography/typography.json +30 -12
  116. package/dist/styleLint/functional/size/border.json +27 -12
  117. package/dist/styleLint/functional/size/breakpoints.json +24 -6
  118. package/dist/styleLint/functional/size/radius.json +17 -5
  119. package/dist/styleLint/functional/size/size-coarse.json +3 -3
  120. package/dist/styleLint/functional/size/size-fine.json +3 -3
  121. package/dist/styleLint/functional/size/size.json +111 -66
  122. package/dist/styleLint/functional/themes/dark-colorblind-high-contrast.json +2757 -366
  123. package/dist/styleLint/functional/themes/dark-colorblind.json +2757 -366
  124. package/dist/styleLint/functional/themes/dark-dimmed-high-contrast.json +2757 -366
  125. package/dist/styleLint/functional/themes/dark-dimmed.json +2757 -366
  126. package/dist/styleLint/functional/themes/dark-high-contrast.json +2757 -366
  127. package/dist/styleLint/functional/themes/dark-tritanopia-high-contrast.json +2757 -366
  128. package/dist/styleLint/functional/themes/dark-tritanopia.json +2757 -366
  129. package/dist/styleLint/functional/themes/dark.json +2757 -366
  130. package/dist/styleLint/functional/themes/light-colorblind-high-contrast.json +2763 -372
  131. package/dist/styleLint/functional/themes/light-colorblind.json +2760 -369
  132. package/dist/styleLint/functional/themes/light-high-contrast.json +2763 -372
  133. package/dist/styleLint/functional/themes/light-tritanopia-high-contrast.json +2763 -372
  134. package/dist/styleLint/functional/themes/light-tritanopia.json +2760 -369
  135. package/dist/styleLint/functional/themes/light.json +2760 -369
  136. package/dist/styleLint/functional/typography/typography.json +28 -22
  137. package/package.json +9 -7
  138. package/src/tokens/base/motion/timing.json5 +12 -12
  139. package/src/tokens/base/size/size.json5 +194 -194
  140. package/src/tokens/base/typography/typography.json5 +6 -6
  141. package/src/tokens/component/avatar.json5 +72 -44
  142. package/src/tokens/component/button.json5 +1545 -1193
  143. package/src/tokens/functional/border/border.json5 +4 -1
  144. package/src/tokens/functional/color/bgColor.json5 +8 -0
  145. package/src/tokens/functional/color/display.json5 +7 -0
  146. package/src/tokens/functional/color/fgColor.json5 +8 -0
  147. package/src/tokens/functional/color/syntax.json5 +14 -0
  148. package/src/tokens/functional/shadow/shadow.json5 +1254 -163
  149. package/src/tokens/functional/size/border.json5 +8 -8
  150. package/src/tokens/functional/size/breakpoints.json5 +6 -6
  151. package/src/tokens/functional/size/radius.json5 +4 -4
  152. package/src/tokens/functional/size/size.json5 +15 -15
  153. package/src/tokens/functional/typography/typography.json5 +8 -4
  154. package/dist/build/parsers/index.d.ts +0 -1
  155. package/dist/build/parsers/index.js +0 -1
  156. package/dist/build/parsers/w3cJsonParser.d.ts +0 -6
  157. package/dist/build/parsers/w3cJsonParser.js +0 -25
  158. package/dist/removed/testing.json5 +0 -4
  159. package/guidelines/color.llm.md +0 -16
  160. package/guidelines/guidelines.llm.md +0 -34
  161. package/guidelines/motion.llm.md +0 -41
  162. package/guidelines/spacing.llm.md +0 -20
  163. package/guidelines/typography.llm.md +0 -14
  164. package/src/tokens/removed/testing.json5 +0 -4
  165. package/token-guidelines.llm.md +0 -695
@@ -8,12 +8,613 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { sortByName } from 'style-dictionary/utils';
11
+ // Semantic sets that should be grouped into tables
12
+ const SEMANTIC_SETS = [
13
+ 'accent',
14
+ 'danger',
15
+ 'success',
16
+ 'attention',
17
+ 'severe',
18
+ 'open',
19
+ 'closed',
20
+ 'done',
21
+ 'neutral',
22
+ 'sponsors',
23
+ 'upsell',
24
+ ];
25
+ // Categories that can be merged with wildcard patterns
26
+ const MERGEABLE_CATEGORIES = ['bgColor', 'borderColor', 'fgColor'];
27
+ // Global semantic key definitions - these explain what each semantic color means
28
+ // so we don't repeat this info in every table row
29
+ const SEMANTIC_KEY = {
30
+ danger: {
31
+ meaning: 'Errors, destructive actions, critical warnings',
32
+ usage: 'delete buttons, error messages, validation errors',
33
+ textPairing: 'fg.danger (muted bg) or fg.onEmphasis (emphasis bg)',
34
+ },
35
+ success: {
36
+ meaning: 'Positive states, confirmations, completed actions',
37
+ usage: 'merge buttons, success messages, confirmations',
38
+ textPairing: 'fg.success (muted bg) or fg.onEmphasis (emphasis bg)',
39
+ },
40
+ attention: {
41
+ meaning: 'Warnings, caution states requiring user awareness',
42
+ usage: 'warning banners, caution labels, pending states',
43
+ textPairing: 'fg.attention (muted bg) or fg.default (emphasis bg, due to yellow contrast)',
44
+ },
45
+ severe: {
46
+ meaning: 'High-priority warnings, more urgent than attention',
47
+ usage: 'urgent messages, escalations, high-priority indicators',
48
+ textPairing: 'fg.severe (muted bg) or fg.onEmphasis (emphasis bg)',
49
+ },
50
+ accent: {
51
+ meaning: 'Selected, focused, or highlighted interactive elements',
52
+ usage: 'active states, selected rows, focus indicators',
53
+ textPairing: 'fg.accent (muted bg) or fg.onEmphasis (emphasis bg)',
54
+ },
55
+ neutral: {
56
+ meaning: 'Non-semantic, secondary UI elements',
57
+ usage: 'secondary buttons, tags, labels without status meaning',
58
+ textPairing: 'fg.default (muted bg) or fg.onEmphasis (emphasis bg)',
59
+ },
60
+ open: {
61
+ meaning: 'Open/active state indicators (GitHub issues, PRs)',
62
+ usage: 'open issues, open PRs, active discussions',
63
+ textPairing: 'fg.open (muted bg) or fg.onEmphasis (emphasis bg)',
64
+ },
65
+ closed: {
66
+ meaning: 'Closed/declined state indicators (GitHub issues, PRs)',
67
+ usage: 'closed issues, closed PRs, declined items',
68
+ textPairing: 'fg.closed (muted bg) or fg.onEmphasis (emphasis bg)',
69
+ },
70
+ done: {
71
+ meaning: 'Completed/merged state indicators',
72
+ usage: 'merged PRs, completed tasks, finished items',
73
+ textPairing: 'fg.done (muted bg) or fg.onEmphasis (emphasis bg)',
74
+ },
75
+ sponsors: {
76
+ meaning: 'GitHub Sponsors content only',
77
+ usage: 'sponsor buttons, funding prompts, sponsor cards',
78
+ textPairing: 'fg.sponsors (muted bg) or fg.onEmphasis (emphasis bg)',
79
+ },
80
+ upsell: {
81
+ meaning: 'Upgrade prompts, premium features, promotional content',
82
+ usage: 'upgrade buttons, premium badges, promotional banners',
83
+ textPairing: 'fg.upsell (muted bg) or fg.onEmphasis (emphasis bg)',
84
+ },
85
+ };
86
+ // Typography role groupings for better LLM comprehension
87
+ const TYPOGRAPHY_ROLES = [
88
+ {
89
+ role: 'Headings',
90
+ description: 'Title and display text styles for headings and hero sections.',
91
+ patterns: ['text-title-', 'text-display-', 'text-subtitle-'],
92
+ },
93
+ {
94
+ role: 'Body',
95
+ description: 'Body text and caption styles for content and UI labels.',
96
+ patterns: ['text-body-', 'text-caption-'],
97
+ },
98
+ {
99
+ role: 'Code',
100
+ description: 'Monospace text styles for code blocks and inline code.',
101
+ patterns: ['text-code'],
102
+ },
103
+ ];
104
+ // Size scales for pattern-based compression
105
+ const SIZE_SCALES = ['xsmall', 'small', 'medium', 'large', 'xlarge'];
106
+ const DENSITY_SCALES = ['condensed', 'normal', 'spacious'];
107
+ // Categories that use pattern-based compression for size tokens
108
+ const PATTERN_COMPRESSED_CATEGORIES = {
109
+ control: {
110
+ scaleNote: 'Use xsmall/small for dense layouts, medium for default UI, large/xlarge for prominent CTAs.',
111
+ sizeScale: SIZE_SCALES,
112
+ densityScale: DENSITY_SCALES,
113
+ stateGroups: ['checked', 'transparent'],
114
+ },
115
+ controlStack: {
116
+ scaleNote: 'Match gap size to control size. Use condensed for tight groupings, spacious for separated actions.',
117
+ sizeScale: ['small', 'medium', 'large'],
118
+ densityScale: ['condensed', 'spacious'],
119
+ },
120
+ overlay: {
121
+ scaleNote: 'Use xsmall/small for menus and tooltips, medium for dialogs, large/xlarge for complex modals or sheets.',
122
+ sizeScale: ['xsmall', 'small', 'medium', 'large', 'xlarge'],
123
+ densityScale: ['condensed', 'normal'],
124
+ },
125
+ spinner: {
126
+ scaleNote: 'Use small for inline loading, medium for buttons/cards, large for full-page states.',
127
+ sizeScale: ['small', 'medium', 'large'],
128
+ },
129
+ stack: {
130
+ scaleNote: 'Use condensed for dense lists, normal for standard layouts, spacious for prominent sections.',
131
+ densityScale: DENSITY_SCALES,
132
+ },
133
+ };
134
+ /**
135
+ * Outputs pattern-compressed tokens for a category
136
+ */
137
+ function outputPatternCompressedCategory(category, tokens, config, lines) {
138
+ var _a;
139
+ const tokenNames = tokens.map(t => t.name);
140
+ // Output scale note
141
+ lines.push(`**Scale:** ${config.scaleNote}`);
142
+ lines.push('');
143
+ // Categorize tokens
144
+ const sizeTokens = [];
145
+ const stateTokens = new Map();
146
+ const otherTokens = [];
147
+ for (const name of tokenNames) {
148
+ const rest = name.replace(`${category}-`, '');
149
+ // Check if it's a state token (checked, transparent)
150
+ let isStateToken = false;
151
+ if (config.stateGroups) {
152
+ for (const state of config.stateGroups) {
153
+ if (rest.startsWith(`${state}-`)) {
154
+ if (!stateTokens.has(state)) {
155
+ stateTokens.set(state, []);
156
+ }
157
+ stateTokens.get(state).push(rest.replace(`${state}-`, ''));
158
+ isStateToken = true;
159
+ break;
160
+ }
161
+ }
162
+ }
163
+ if (isStateToken)
164
+ continue;
165
+ // Check if it's a size token (exact match on part, not substring)
166
+ const parts = rest.split('-');
167
+ const hasSizeScale = (_a = config.sizeScale) === null || _a === void 0 ? void 0 : _a.some(s => parts.includes(s));
168
+ if (hasSizeScale) {
169
+ sizeTokens.push(name);
170
+ }
171
+ else {
172
+ otherTokens.push(name);
173
+ }
174
+ }
175
+ // Output size patterns
176
+ if (sizeTokens.length > 0 && config.sizeScale) {
177
+ // Group by property - handle both prefix-size and size-suffix patterns
178
+ // e.g., control-medium-gap (size first) vs overlay-width-medium (size last)
179
+ const byPattern = new Map();
180
+ for (const name of sizeTokens) {
181
+ const rest = name.replace(`${category}-`, '');
182
+ const parts = rest.split('-');
183
+ // Find exact size match in parts
184
+ for (const size of config.sizeScale) {
185
+ const sizeIdx = parts.indexOf(size);
186
+ if (sizeIdx >= 0) {
187
+ const prefix = parts.slice(0, sizeIdx).join('-');
188
+ const suffix = parts.slice(sizeIdx + 1).join('-');
189
+ // Create pattern key with placeholder for size
190
+ let patternKey;
191
+ if (prefix && suffix) {
192
+ patternKey = `${prefix}-[size]-${suffix}`;
193
+ }
194
+ else if (prefix) {
195
+ patternKey = `${prefix}-[size]`;
196
+ }
197
+ else if (suffix) {
198
+ patternKey = `[size]-${suffix}`;
199
+ }
200
+ else {
201
+ patternKey = '[size]';
202
+ }
203
+ if (!byPattern.has(patternKey)) {
204
+ byPattern.set(patternKey, new Set());
205
+ }
206
+ byPattern.get(patternKey).add(size);
207
+ break;
208
+ }
209
+ }
210
+ }
211
+ // Group patterns by their size sets for compact output
212
+ const bySizeSet = new Map();
213
+ for (const [pattern, sizes] of byPattern) {
214
+ const sizeKey = [...sizes].sort((a, b) => config.sizeScale.indexOf(a) - config.sizeScale.indexOf(b)).join(',');
215
+ if (!bySizeSet.has(sizeKey)) {
216
+ bySizeSet.set(sizeKey, []);
217
+ }
218
+ bySizeSet.get(sizeKey).push(pattern);
219
+ }
220
+ lines.push('**Size patterns:**');
221
+ for (const [sizeKey, patterns] of bySizeSet) {
222
+ const sizes = sizeKey.split(',');
223
+ const sizeNotation = sizes.length > 1 ? `[${sizes.join(', ')}]` : sizes[0];
224
+ // Replace [size] placeholder with actual size notation
225
+ const formattedPatterns = patterns.map(p => p.replace('[size]', sizeNotation)).sort();
226
+ // Try to merge patterns with same size but different properties
227
+ if (formattedPatterns.length > 1) {
228
+ // Check if they share a common structure
229
+ const suffixes = formattedPatterns.map(p => {
230
+ const escapedSizeNotation = sizeNotation.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
231
+ const match = p.match(new RegExp(`${escapedSizeNotation}-(.+)$`));
232
+ return match ? match[1] : null;
233
+ });
234
+ if (suffixes.every(s => s !== null)) {
235
+ const uniqueSuffixes = [...new Set(suffixes)].sort();
236
+ if (uniqueSuffixes.length > 1) {
237
+ lines.push(`- \`${category}-${sizeNotation}-[${uniqueSuffixes.join(', ')}]\``);
238
+ }
239
+ else {
240
+ lines.push(`- \`${category}-${sizeNotation}-${uniqueSuffixes[0]}\``);
241
+ }
242
+ }
243
+ else {
244
+ // Output each pattern separately
245
+ for (const pattern of formattedPatterns) {
246
+ lines.push(`- \`${category}-${pattern}\``);
247
+ }
248
+ }
249
+ }
250
+ else {
251
+ lines.push(`- \`${category}-${formattedPatterns[0]}\``);
252
+ }
253
+ }
254
+ lines.push('');
255
+ }
256
+ // Output state tokens
257
+ if (stateTokens.size > 0) {
258
+ lines.push('**State variants:**');
259
+ for (const [state, suffixes] of stateTokens) {
260
+ const sortedSuffixes = [...new Set(suffixes)].sort();
261
+ const suffixNotation = sortedSuffixes.length > 1 ? `[${sortedSuffixes.join(', ')}]` : sortedSuffixes[0];
262
+ lines.push(`- \`${category}-${state}-${suffixNotation}\``);
263
+ }
264
+ lines.push('');
265
+ }
266
+ // Output other tokens (colors, misc)
267
+ if (otherTokens.length > 0) {
268
+ // Group by type (bgColor, fgColor, etc.)
269
+ const byType = new Map();
270
+ for (const name of otherTokens) {
271
+ const rest = name.replace(`${category}-`, '');
272
+ const parts = rest.split('-');
273
+ // Find the type (bgColor, fgColor, borderColor, etc.)
274
+ let type = parts[0];
275
+ let variant = parts.slice(1).join('-') || 'default';
276
+ // Handle compound types like minTarget
277
+ if (parts.length >= 2 && !['bgColor', 'fgColor', 'borderColor', 'iconColor'].includes(type)) {
278
+ type = parts.slice(0, -1).join('-');
279
+ variant = parts[parts.length - 1];
280
+ }
281
+ if (!byType.has(type)) {
282
+ byType.set(type, []);
283
+ }
284
+ byType.get(type).push(variant);
285
+ }
286
+ lines.push('**Other tokens:**');
287
+ for (const [type, variants] of [...byType.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
288
+ const sortedVariants = [...new Set(variants)].sort();
289
+ if (sortedVariants.length > 1) {
290
+ lines.push(`- \`${category}-${type}-[${sortedVariants.join(', ')}]\``);
291
+ }
292
+ else {
293
+ lines.push(`- \`${category}-${type}-${sortedVariants[0]}\``);
294
+ }
295
+ }
296
+ lines.push('');
297
+ }
298
+ }
299
+ /**
300
+ * Outputs pattern-compressed display color tokens
301
+ * Extracts unique colors and properties to show as display-[color]-[property] pattern
302
+ */
303
+ function outputDisplayColorPattern(tokens, lines) {
304
+ const tokenNames = tokens.map(t => t.name);
305
+ // Extract unique colors and properties
306
+ const colors = new Set();
307
+ const properties = new Set();
308
+ const hasScale = new Set();
309
+ for (const name of tokenNames) {
310
+ // Parse: display-{color}-{property} or display-{color}-scale-{n}
311
+ const parts = name.replace('display-', '').split('-');
312
+ if (parts.length < 2)
313
+ continue;
314
+ const color = parts[0];
315
+ colors.add(color);
316
+ // Check for scale tokens (display-blue-scale-0)
317
+ if (parts[1] === 'scale' && parts.length >= 3) {
318
+ hasScale.add(parts[2]);
319
+ }
320
+ else {
321
+ // Property tokens (display-blue-bgColor-emphasis, display-blue-fgColor)
322
+ const property = parts.slice(1).join('-');
323
+ properties.add(property);
324
+ }
325
+ }
326
+ // Sort colors alphabetically
327
+ const sortedColors = [...colors].sort();
328
+ const sortedProperties = [...properties].sort((a, b) => {
329
+ // Sort by type first (bgColor, borderColor, fgColor)
330
+ const typeOrder = ['bgColor-emphasis', 'bgColor-muted', 'borderColor-emphasis', 'borderColor-muted', 'fgColor'];
331
+ const aIdx = typeOrder.indexOf(a);
332
+ const bIdx = typeOrder.indexOf(b);
333
+ if (aIdx !== -1 && bIdx !== -1)
334
+ return aIdx - bIdx;
335
+ if (aIdx !== -1)
336
+ return -1;
337
+ if (bIdx !== -1)
338
+ return 1;
339
+ return a.localeCompare(b);
340
+ });
341
+ // Output pattern with variables
342
+ lines.push('**Pattern:** `display-[color]-[property]`');
343
+ lines.push('');
344
+ lines.push('**Variables:**');
345
+ lines.push(`- **color:** ${sortedColors.join(' | ')}`);
346
+ lines.push(`- **property:** ${sortedProperties.join(' | ')}`);
347
+ lines.push('');
348
+ // Output scale pattern if present
349
+ if (hasScale.size > 0) {
350
+ const sortedScales = [...hasScale].sort((a, b) => parseInt(a) - parseInt(b));
351
+ lines.push('**Scale pattern:** `display-[color]-scale-[n]`');
352
+ lines.push(`- **n:** ${sortedScales.join(' | ')}`);
353
+ lines.push('');
354
+ lines.push('*Scale 0-2: lighter (backgrounds), 3-5: mid-tones, 6-9: darker (foregrounds/borders)*');
355
+ lines.push('');
356
+ }
357
+ }
11
358
  /**
12
- * Creates a unique key for grouping tokens with identical guidelines
359
+ * Outputs pattern-compressed data visualization tokens
360
+ * Extracts unique colors and variants to show as data-[color]-color-[variant] pattern
361
+ */
362
+ function outputDataVisColorPattern(tokens, lines) {
363
+ const tokenNames = tokens.map(t => t.name);
364
+ // Extract unique colors and variants
365
+ const colors = new Set();
366
+ const variants = new Set();
367
+ for (const name of tokenNames) {
368
+ // Parse: data-{color}-color-{variant}
369
+ const parts = name.replace('data-', '').split('-');
370
+ if (parts.length < 3)
371
+ continue;
372
+ const color = parts[0];
373
+ colors.add(color);
374
+ // Variant is the last part (emphasis, muted)
375
+ // Format: color-color-variant -> parts[1] is 'color', parts[2] is variant
376
+ if (parts[1] === 'color' && parts.length >= 3) {
377
+ variants.add(parts[2]);
378
+ }
379
+ }
380
+ // Sort colors alphabetically
381
+ const sortedColors = [...colors].sort();
382
+ const sortedVariants = [...variants].sort((a, b) => {
383
+ // emphasis first, then muted
384
+ const order = ['emphasis', 'muted'];
385
+ return order.indexOf(a) - order.indexOf(b);
386
+ });
387
+ // Output pattern with variables
388
+ lines.push('**Pattern:** `data-[color]-color-[variant]`');
389
+ lines.push('');
390
+ lines.push('**Variables:**');
391
+ lines.push(`- **color:** ${sortedColors.join(' | ')}`);
392
+ lines.push(`- **variant:** ${sortedVariants.join(' | ')}`);
393
+ lines.push('');
394
+ // Add usage guidance
395
+ lines.push('**Variant usage:**');
396
+ lines.push('- **emphasis:** Lines, bars, borders, data points');
397
+ lines.push('- **muted:** Area fills, backgrounds, subtle regions');
398
+ lines.push('');
399
+ lines.push('*Pair emphasis with muted variants of the same color for cohesive chart styling.*');
400
+ lines.push('');
401
+ }
402
+ /**
403
+ * Outputs pattern-compressed ANSI color tokens
404
+ * Extracts unique colors and variants (default/bright) to show as color-ansi-[color]-[variant] pattern
405
+ */
406
+ function outputAnsiColorPattern(tokens, lines) {
407
+ const tokenNames = tokens.map(t => t.name);
408
+ // Extract unique colors and variants
409
+ const colors = new Set();
410
+ const hasVariants = new Set();
411
+ for (const name of tokenNames) {
412
+ // Parse: color-ansi-{color} or color-ansi-{color}-bright
413
+ const parts = name.replace('color-ansi-', '').split('-');
414
+ if (parts.length < 1)
415
+ continue;
416
+ const color = parts[0];
417
+ colors.add(color);
418
+ // Check for bright variant
419
+ if (parts.length > 1 && parts[1] === 'bright') {
420
+ hasVariants.add('bright');
421
+ }
422
+ else {
423
+ hasVariants.add('default');
424
+ }
425
+ }
426
+ // Sort colors in ANSI order
427
+ const ansiOrder = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'gray'];
428
+ const sortedColors = [...colors].sort((a, b) => {
429
+ const aIdx = ansiOrder.indexOf(a);
430
+ const bIdx = ansiOrder.indexOf(b);
431
+ if (aIdx !== -1 && bIdx !== -1)
432
+ return aIdx - bIdx;
433
+ if (aIdx !== -1)
434
+ return -1;
435
+ if (bIdx !== -1)
436
+ return 1;
437
+ return a.localeCompare(b);
438
+ });
439
+ // Output pattern with variables
440
+ lines.push('**Pattern:** `color-ansi-[color]` or `color-ansi-[color]-bright`');
441
+ lines.push('');
442
+ lines.push('**Variables:**');
443
+ lines.push(`- **color:** ${sortedColors.join(' | ')}`);
444
+ if (hasVariants.has('bright')) {
445
+ lines.push('- **variant:** default (no suffix) | bright');
446
+ }
447
+ lines.push('');
448
+ }
449
+ /**
450
+ * Outputs pattern-compressed prettylights syntax tokens using prefix grouping
451
+ * Groups tokens by their primary element category
452
+ */
453
+ function outputPrettylightsPattern(tokens, lines) {
454
+ const tokenNames = tokens.map(t => t.name);
455
+ // Common single-word elements (no sub-variants)
456
+ const simpleElements = [];
457
+ // Grouped elements with sub-variants (markup-*, brackethighlighter-*, etc.)
458
+ const groupedElements = new Map();
459
+ // Elements with bg/text variants - track the base name and which suffixes exist
460
+ const bgTextVariants = new Map();
461
+ for (const name of tokenNames) {
462
+ // Parse: color-prettylights-syntax-{element} or color-prettylights-syntax-{group}-{sub}
463
+ const element = name.replace('color-prettylights-syntax-', '');
464
+ const parts = element.split('-');
465
+ if (parts.length === 1) {
466
+ // Simple element: comment, constant, entity, keyword, string, variable
467
+ simpleElements.push(parts[0]);
468
+ }
469
+ else {
470
+ const group = parts[0];
471
+ const rest = parts.slice(1).join('-');
472
+ // Check for bg/text suffix patterns (e.g., markup-changed-bg, markup-changed-text)
473
+ if (rest.endsWith('-bg') || rest.endsWith('-text')) {
474
+ const baseName = rest.replace(/-bg$/, '').replace(/-text$/, '');
475
+ const suffix = rest.endsWith('-bg') ? 'bg' : 'text';
476
+ const key = `${group}:${baseName}`; // Use colon to separate group from base
477
+ if (!bgTextVariants.has(key)) {
478
+ bgTextVariants.set(key, new Set());
479
+ }
480
+ bgTextVariants.get(key).add(suffix);
481
+ }
482
+ else {
483
+ // Regular grouped element
484
+ if (!groupedElements.has(group)) {
485
+ groupedElements.set(group, new Set());
486
+ }
487
+ groupedElements.get(group).add(rest);
488
+ }
489
+ }
490
+ }
491
+ // Output pattern
492
+ lines.push('**Pattern:** `color-prettylights-syntax-[element]`');
493
+ lines.push('');
494
+ // Output simple elements
495
+ if (simpleElements.length > 0) {
496
+ const sorted = [...new Set(simpleElements)].sort();
497
+ lines.push(`**Core elements:** ${sorted.join(', ')}`);
498
+ }
499
+ // Merge bg/text variants into grouped elements for cleaner output
500
+ // Group bg/text variants by their prefix group
501
+ const bgTextByGroup = new Map();
502
+ for (const [key] of bgTextVariants) {
503
+ const [group, baseName] = key.split(':');
504
+ if (!bgTextByGroup.has(group)) {
505
+ bgTextByGroup.set(group, []);
506
+ }
507
+ bgTextByGroup.get(group).push(baseName);
508
+ }
509
+ // Output grouped elements
510
+ if (groupedElements.size > 0 || bgTextByGroup.size > 0) {
511
+ lines.push('');
512
+ lines.push('**Compound elements:**');
513
+ // Get all groups (both regular and bg/text)
514
+ const allGroups = new Set([...groupedElements.keys(), ...bgTextByGroup.keys()]);
515
+ for (const group of [...allGroups].sort()) {
516
+ const regularSubs = groupedElements.get(group) || new Set();
517
+ const bgTextBases = bgTextByGroup.get(group) || [];
518
+ // Combine regular subs
519
+ if (regularSubs.size > 0) {
520
+ const uniqueSubs = [...regularSubs].sort();
521
+ lines.push(`- \`${group}-[${uniqueSubs.join(', ')}]\``);
522
+ }
523
+ // Output bg/text variants separately for clarity
524
+ if (bgTextBases.length > 0) {
525
+ const uniqueBases = [...new Set(bgTextBases)].sort();
526
+ lines.push(`- \`${group}-[${uniqueBases.join(', ')}]-[bg, text]\``);
527
+ }
528
+ }
529
+ }
530
+ lines.push('');
531
+ }
532
+ // Human-readable category names and descriptions
533
+ const CATEGORY_INFO = {
534
+ bgColor: {
535
+ name: 'Background Colors',
536
+ description: 'Background color tokens for surfaces, containers, and UI elements.',
537
+ },
538
+ borderColor: {
539
+ name: 'Border Colors',
540
+ description: 'Border color tokens for boundaries, dividers, and outlines.',
541
+ },
542
+ fgColor: {
543
+ name: 'Foreground Colors',
544
+ description: 'Text and icon color tokens.',
545
+ },
546
+ borderRadius: { name: 'Border Radius', description: 'Corner radius tokens for rounded elements.' },
547
+ borderWidth: { name: 'Border Width', description: 'Border thickness tokens.' },
548
+ border: { name: 'Border', description: 'Composite border tokens combining color, width, and style.' },
549
+ control: { name: 'Controls', description: 'Tokens for interactive controls like buttons, inputs, and selects.' },
550
+ controlKnob: {
551
+ name: 'Control Knob',
552
+ description: 'Tokens for toggle switch knobs (the circular handle that moves along the track).',
553
+ },
554
+ controlStack: {
555
+ name: 'Control Stack',
556
+ description: 'Gap tokens for groups of controls arranged in a row or column.',
557
+ },
558
+ controlTrack: {
559
+ name: 'Control Track',
560
+ description: 'Tokens for toggle switch tracks (the background rail that the knob slides along).',
561
+ },
562
+ data: {
563
+ name: 'Data Visualization',
564
+ description: 'Color tokens for charts, graphs, and diagrams. Use emphasis variants for lines/bars, muted variants for fills.',
565
+ },
566
+ display: {
567
+ name: 'Display Colors',
568
+ description: 'Decorative colors for categorization without semantic meaning. Use for labels, tags, avatars, and user-assigned colors. Do NOT use for success/error/warning—use semantic colors instead.',
569
+ },
570
+ easing: { name: 'Easing', description: 'Animation easing function tokens.' },
571
+ focus: { name: 'Focus', description: 'Focus ring and outline tokens for keyboard navigation accessibility.' },
572
+ fontStack: { name: 'Font Stacks', description: 'Font family tokens.' },
573
+ outline: { name: 'Outline', description: 'Outline tokens for focus indicators.' },
574
+ overlay: { name: 'Overlay', description: 'Tokens for modals, dialogs, popovers, and dropdown menus.' },
575
+ selection: { name: 'Selection', description: 'Tokens for text selection highlights.' },
576
+ shadow: { name: 'Shadow', description: 'Box shadow tokens for elevation and depth.' },
577
+ spinner: { name: 'Spinner', description: 'Loading spinner size and stroke tokens.' },
578
+ stack: { name: 'Stack', description: 'Spacing tokens for Stack layout components.' },
579
+ text: { name: 'Typography', description: 'Text style shorthand tokens for consistent typography across the UI.' },
580
+ };
581
+ /**
582
+ * Densifies description by removing filler words
583
+ */
584
+ function densifyDescription(description) {
585
+ return description
586
+ .replace(/^Use this for\s+/i, 'For ')
587
+ .replace(/^This is used for\s+/i, 'For ')
588
+ .replace(/^Used for\s+/i, 'For ')
589
+ .replace(/^Use for\s+/i, 'For ')
590
+ .replace(/^This is\s+/i, '')
591
+ .replace(/^This\s+/i, '');
592
+ }
593
+ /**
594
+ * Shortens rules by applying key shorthands
595
+ */
596
+ function shortenRules(rules) {
597
+ return rules
598
+ .replace(/Pair with\s+/gi, 'Pair -> ')
599
+ .replace(/\bfgColor\./g, 'fg.')
600
+ .replace(/\bbgColor\./g, 'bg.')
601
+ .replace(/\bborderColor\./g, 'border.');
602
+ }
603
+ /**
604
+ * Limits usage to max 3 most relevant items
605
+ */
606
+ function limitUsage(usage) {
607
+ if (usage.length <= 3)
608
+ return usage;
609
+ return usage.slice(0, 3);
610
+ }
611
+ /**
612
+ * Creates a unique key for grouping tokens with identical guidelines within a category
13
613
  */
14
614
  function createGuidelineKey(guideline) {
15
615
  var _a;
16
616
  return JSON.stringify({
617
+ category: guideline.category,
17
618
  description: guideline.description || '',
18
619
  usage: ((_a = guideline.usage) === null || _a === void 0 ? void 0 : _a.sort()) || [],
19
620
  rules: guideline.rules || '',
@@ -21,8 +622,6 @@ function createGuidelineKey(guideline) {
21
622
  }
22
623
  /**
23
624
  * Extracts category from token name
24
- * - For "base-*" tokens, uses second word (e.g., "base-easing-ease" -> "easing")
25
- * - Otherwise uses first word (e.g., "bgColor-danger-emphasis" -> "bgColor")
26
625
  */
27
626
  function extractCategory(tokenName) {
28
627
  const parts = tokenName.split('-');
@@ -31,22 +630,49 @@ function extractCategory(tokenName) {
31
630
  }
32
631
  return parts[0] || 'other';
33
632
  }
633
+ /**
634
+ * Extracts semantic subcategory from token name (e.g., "bgColor-danger-emphasis" -> "danger")
635
+ */
636
+ function extractSemanticSubcategory(tokenName) {
637
+ const parts = tokenName.split('-');
638
+ if (parts.length >= 2) {
639
+ const subcat = parts[1];
640
+ if (SEMANTIC_SETS.includes(subcat)) {
641
+ return subcat;
642
+ }
643
+ }
644
+ return null;
645
+ }
646
+ /**
647
+ * Extracts variant from token name (e.g., "bgColor-danger-emphasis" -> "emphasis")
648
+ */
649
+ function extractVariant(tokenName) {
650
+ const parts = tokenName.split('-');
651
+ if (parts.length >= 3) {
652
+ return parts.slice(2).join('-');
653
+ }
654
+ return null;
655
+ }
34
656
  /**
35
657
  * Formats category name for display
36
658
  */
37
659
  function formatCategoryName(category) {
38
- const categoryMap = {
39
- bgColor: 'background color',
40
- fgColor: 'text and foreground color',
41
- };
42
- if (categoryMap[category]) {
43
- return categoryMap[category];
44
- }
45
- // Capitalize first letter
660
+ if (category in CATEGORY_INFO) {
661
+ return CATEGORY_INFO[category].name;
662
+ }
46
663
  return category.charAt(0).toUpperCase() + category.slice(1);
47
664
  }
48
665
  /**
49
- * Creates a key for grouping by usage and rules only (not description)
666
+ * Gets category description if available
667
+ */
668
+ function getCategoryDescription(category) {
669
+ if (category in CATEGORY_INFO) {
670
+ return CATEGORY_INFO[category].description;
671
+ }
672
+ return null;
673
+ }
674
+ /**
675
+ * Creates a key for grouping by usage and rules only
50
676
  */
51
677
  function createUsageRulesKey(guideline) {
52
678
  var _a;
@@ -55,19 +681,31 @@ function createUsageRulesKey(guideline) {
55
681
  rules: guideline.rules || '',
56
682
  });
57
683
  }
684
+ /**
685
+ * Creates a key for cross-category pattern matching
686
+ */
687
+ function createPatternKey(description, rules) {
688
+ const normalizedDesc = description
689
+ .replace(/background/gi, 'COLOR_TYPE')
690
+ .replace(/border/gi, 'COLOR_TYPE')
691
+ .replace(/text/gi, 'COLOR_TYPE')
692
+ .replace(/foreground/gi, 'COLOR_TYPE');
693
+ const normalizedRules = rules
694
+ .replace(/bgColor/g, 'COLOR_TOKEN')
695
+ .replace(/borderColor/g, 'COLOR_TOKEN')
696
+ .replace(/fgColor/g, 'COLOR_TOKEN');
697
+ return JSON.stringify({ description: normalizedDesc, rules: normalizedRules });
698
+ }
58
699
  /**
59
700
  * Extracts a subcategory name from token names for headings
60
- * e.g., "border-accent-emphasis" -> "accent"
61
701
  */
62
702
  function extractSubcategory(tokenNames) {
63
703
  if (tokenNames.length < 2)
64
704
  return null;
65
- // Get the second part of each token name
66
705
  const subcategories = tokenNames.map(name => {
67
706
  const parts = name.split('-');
68
707
  return parts[1] || null;
69
708
  });
70
- // Check if all tokens share the same subcategory
71
709
  const uniqueSubcats = [...new Set(subcategories.filter(Boolean))];
72
710
  if (uniqueSubcats.length === 1) {
73
711
  return uniqueSubcats[0];
@@ -75,12 +713,50 @@ function extractSubcategory(tokenNames) {
75
713
  return null;
76
714
  }
77
715
  /**
78
- * @description Outputs a markdown file with LLM token guidelines, extracting
79
- * description from $description and usage/rules from $extensions['org.primer.llm'].
80
- * Tokens with identical guidelines (from group-level inheritance) are consolidated
81
- * into a single entry listing all token names.
82
- * @param FormatFnArguments
83
- * @returns formatted markdown `string`
716
+ * Outputs typography tokens grouped by role (Headings, Body, Code)
717
+ */
718
+ function outputTypographyByRole(tokens, lines) {
719
+ var _a;
720
+ for (const { role, description, patterns } of TYPOGRAPHY_ROLES) {
721
+ const roleTokens = tokens.filter(t => patterns.some(p => t.name.startsWith(p)));
722
+ if (roleTokens.length === 0)
723
+ continue;
724
+ lines.push(`### ${role}`);
725
+ lines.push('');
726
+ lines.push(description);
727
+ lines.push('');
728
+ // Create table for role tokens
729
+ const headers = ['Token', 'Description', 'U:', 'R:'];
730
+ lines.push(`| ${headers.join(' | ')} |`);
731
+ lines.push(`|${headers.map(() => '---').join('|')}|`);
732
+ for (const token of roleTokens.sort((a, b) => a.name.localeCompare(b.name))) {
733
+ const cells = [
734
+ `**${token.name}**`,
735
+ escapeTableCell(token.description || '-'),
736
+ ((_a = token.usage) === null || _a === void 0 ? void 0 : _a.join(', ')) || '-',
737
+ escapeTableCell(token.rules || '-'),
738
+ ];
739
+ lines.push(`| ${cells.join(' | ')} |`);
740
+ }
741
+ lines.push('');
742
+ }
743
+ }
744
+ /**
745
+ * Escapes pipe characters for markdown tables
746
+ */
747
+ function escapeTableCell(text) {
748
+ // First escape backslashes, then escape pipe characters, to avoid
749
+ // existing backslashes altering how pipes are interpreted.
750
+ return text.replace(/\\/g, '\\\\').replace(/\|/g, '\\|');
751
+ }
752
+ /**
753
+ * @description Outputs a hyper-optimized markdown file with LLM token guidelines.
754
+ * Optimizations:
755
+ * - Bracket notation for tokens with identical guidelines
756
+ * - Global category rules extracted to paragraph
757
+ * - Shortened keys (U:, R:, Pair ->)
758
+ * - Max 3 usage items
759
+ * - No boilerplate
84
760
  */
85
761
  export const markdownLlmGuidelines = (_a) => __awaiter(void 0, [_a], void 0, function* ({ dictionary }) {
86
762
  var _b;
@@ -95,13 +771,13 @@ export const markdownLlmGuidelines = (_a) => __awaiter(void 0, [_a], void 0, fun
95
771
  category: extractCategory(token.name),
96
772
  };
97
773
  if (token.$description && typeof token.$description === 'string') {
98
- guideline.description = token.$description;
774
+ guideline.description = densifyDescription(token.$description);
99
775
  }
100
776
  if (llmExt.usage && Array.isArray(llmExt.usage)) {
101
- guideline.usage = llmExt.usage;
777
+ guideline.usage = limitUsage(llmExt.usage);
102
778
  }
103
779
  if (llmExt.rules && typeof llmExt.rules === 'string') {
104
- guideline.rules = llmExt.rules;
780
+ guideline.rules = shortenRules(llmExt.rules);
105
781
  }
106
782
  guidelines.push(guideline);
107
783
  }
@@ -113,82 +789,380 @@ export const markdownLlmGuidelines = (_a) => __awaiter(void 0, [_a], void 0, fun
113
789
  }
114
790
  grouped[guideline.category].push(guideline);
115
791
  }
116
- // Build markdown output
117
- const lines = ['# Token Guidelines', ''];
792
+ const lines = [
793
+ '# Primer Design Token Guidelines',
794
+ '',
795
+ '> Metadata: This file is a Dictionary of tokens. For usage rules, contrast requirements, and motion logic, refer to DESIGN_TOKENS_GUIDE.md.',
796
+ '',
797
+ 'Reference for using GitHub Primer design tokens.',
798
+ '',
799
+ '## Legend',
800
+ '',
801
+ '- **U:** Use cases',
802
+ '- **R:** Token-specific rules (see Semantic Key for general meaning)',
803
+ '- **emphasis** variant: Strong/prominent version, use `fg.onEmphasis` for text',
804
+ '- **muted** variant: Subtle version, use matching `fg.*` color for text',
805
+ '- **[a, b]** Bracket notation groups related tokens',
806
+ '',
807
+ '## Semantic Key',
808
+ '',
809
+ 'These semantic meanings apply across all token types (bgColor, borderColor, fgColor, border).',
810
+ '',
811
+ '| Semantic | Meaning | Example Usage | Text Pairing |',
812
+ '|---|---|---|---|',
813
+ ];
814
+ // Output semantic key table
815
+ for (const [key, info] of Object.entries(SEMANTIC_KEY)) {
816
+ lines.push(`| **${key}** | ${info.meaning} | ${info.usage} | ${info.textPairing} |`);
817
+ }
818
+ lines.push('');
819
+ // Collect semantic tokens across mergeable categories for cross-category patterns
820
+ const semanticTokensByPattern = new Map();
821
+ for (const category of MERGEABLE_CATEGORIES) {
822
+ if (!(category in grouped))
823
+ continue;
824
+ for (const guideline of grouped[category]) {
825
+ const subcategory = extractSemanticSubcategory(guideline.name);
826
+ const variant = extractVariant(guideline.name);
827
+ if (subcategory && variant) {
828
+ const patternKey = createPatternKey(guideline.description || '', guideline.rules || '');
829
+ const key = `${subcategory}-${variant}-${patternKey}`;
830
+ if (!semanticTokensByPattern.has(key)) {
831
+ semanticTokensByPattern.set(key, []);
832
+ }
833
+ semanticTokensByPattern.get(key).push({ subcategory, variant, categories: [category], guideline });
834
+ }
835
+ }
836
+ }
837
+ // Find patterns spanning multiple categories (must have entries from at least 2 different categories)
838
+ const mergedEntries = [];
839
+ const mergedTokens = new Set();
840
+ for (const [, entries] of semanticTokensByPattern) {
841
+ // Get unique categories in this pattern
842
+ const uniqueCategories = new Set(entries.map(e => e.categories[0]));
843
+ if (uniqueCategories.size > 1) {
844
+ // Only merge if pattern spans multiple categories
845
+ mergedEntries.push({
846
+ subcategory: entries[0].subcategory,
847
+ variant: entries[0].variant,
848
+ guideline: entries[0].guideline,
849
+ });
850
+ for (const e of entries) {
851
+ mergedTokens.add(`${e.categories[0]}-${e.subcategory}-${e.variant}`);
852
+ }
853
+ }
854
+ }
855
+ // Output compact semantic color reference
856
+ // Group by variant (emphasis/muted) and list all applicable semantics
857
+ if (mergedEntries.length > 0) {
858
+ lines.push('## Semantic Colors');
859
+ lines.push('');
860
+ lines.push('Apply to `bgColor-*`, `borderColor-*`, `fgColor-*`, and `border-*` tokens.');
861
+ lines.push('Refer to Semantic Key above for meaning and text pairings.');
862
+ lines.push('');
863
+ // Group entries by variant
864
+ const byVariant = new Map();
865
+ for (const entry of mergedEntries) {
866
+ if (!byVariant.has(entry.variant)) {
867
+ byVariant.set(entry.variant, []);
868
+ }
869
+ byVariant.get(entry.variant).push(entry.subcategory);
870
+ }
871
+ // Output as compact list
872
+ lines.push('| Pattern | Semantics |');
873
+ lines.push('|---|---|');
874
+ for (const [variant, semantics] of byVariant) {
875
+ const sortedSemantics = [...new Set(semantics)].sort();
876
+ lines.push(`| **\\*-[${sortedSemantics.join(', ')}]-${variant}** | See Semantic Key |`);
877
+ }
878
+ lines.push('');
879
+ // Add special notes for tokens with unique constraints
880
+ lines.push('**Special cases:**');
881
+ lines.push('- `*-attention-emphasis`: Use `fg.default` for text (yellow has poor contrast)');
882
+ lines.push('- `*-sponsors-*`: GitHub Sponsors only, not for general pink UI');
883
+ lines.push('- `*-upsell-*`: Promotional content only, not for regular features');
884
+ lines.push('- `*-open/*-closed/*-done`: GitHub issue/PR states specifically');
885
+ lines.push('');
886
+ }
118
887
  for (const category of Object.keys(grouped).sort()) {
119
- lines.push(`## ${formatCategoryName(category)}`, '');
120
888
  const categoryGuidelines = grouped[category];
121
- // Check if all tokens in category share the same usage/rules AND there are multiple tokens
122
- const usageRulesKeys = new Set(categoryGuidelines.map(createUsageRulesKey));
123
- const sharedUsageRules = usageRulesKeys.size === 1 && categoryGuidelines.length > 1;
124
- // If shared, output usage/rules once at category level
125
- if (sharedUsageRules) {
126
- const first = categoryGuidelines[0];
127
- if (first.usage && first.usage.length > 0) {
128
- lines.push(`**Usage:** ${first.usage.join(', ')}`);
129
- }
130
- if (first.rules) {
131
- lines.push(`**Rules:** ${first.rules}`);
889
+ // Separate semantic and non-semantic tokens
890
+ const semanticTokens = [];
891
+ const nonSemanticTokens = [];
892
+ for (const guideline of categoryGuidelines) {
893
+ if (mergedTokens.has(guideline.name))
894
+ continue;
895
+ const subcategory = extractSemanticSubcategory(guideline.name);
896
+ if (subcategory) {
897
+ semanticTokens.push(guideline);
898
+ }
899
+ else {
900
+ nonSemanticTokens.push(guideline);
901
+ }
902
+ }
903
+ if (semanticTokens.length === 0 && nonSemanticTokens.length === 0)
904
+ continue;
905
+ lines.push(`## ${formatCategoryName(category)}`);
906
+ // Special handling for typography - group by role
907
+ if (category === 'text') {
908
+ const categoryDesc = getCategoryDescription(category);
909
+ if (categoryDesc) {
910
+ lines.push('');
911
+ lines.push(categoryDesc);
132
912
  }
133
913
  lines.push('');
914
+ outputTypographyByRole(nonSemanticTokens, lines);
915
+ continue;
134
916
  }
135
- // Group tokens with identical guidelines (description + usage + rules)
136
- const consolidatedGroups = new Map();
137
- for (const guideline of categoryGuidelines) {
917
+ // Special handling for data visualization colors - use pattern compression
918
+ if (category === 'data') {
919
+ const categoryDesc = getCategoryDescription(category);
920
+ if (categoryDesc) {
921
+ lines.push('');
922
+ lines.push(categoryDesc);
923
+ }
924
+ lines.push('');
925
+ // Get usage and rules from the first token with LLM metadata
926
+ const firstWithMeta = nonSemanticTokens.find(t => t.usage || t.rules);
927
+ if (firstWithMeta) {
928
+ if (firstWithMeta.usage && firstWithMeta.usage.length > 0) {
929
+ lines.push(`**U:** ${firstWithMeta.usage.join(', ')}`);
930
+ }
931
+ if (firstWithMeta.rules) {
932
+ lines.push(`**R:** ${firstWithMeta.rules}`);
933
+ }
934
+ lines.push('');
935
+ }
936
+ outputDataVisColorPattern(nonSemanticTokens, lines);
937
+ continue;
938
+ }
939
+ // Special handling for display colors - use pattern compression
940
+ if (category === 'display') {
941
+ const categoryDesc = getCategoryDescription(category);
942
+ if (categoryDesc) {
943
+ lines.push('');
944
+ lines.push(categoryDesc);
945
+ }
946
+ lines.push('');
947
+ // Get usage and rules from the first token with LLM metadata
948
+ const firstWithMeta = nonSemanticTokens.find(t => t.usage || t.rules);
949
+ if (firstWithMeta) {
950
+ if (firstWithMeta.usage && firstWithMeta.usage.length > 0) {
951
+ lines.push(`**U:** ${firstWithMeta.usage.join(', ')}`);
952
+ }
953
+ if (firstWithMeta.rules) {
954
+ lines.push(`**R:** ${firstWithMeta.rules}`);
955
+ }
956
+ lines.push('');
957
+ }
958
+ outputDisplayColorPattern(nonSemanticTokens, lines);
959
+ continue;
960
+ }
961
+ // Special handling for color category (ansi, prettylights subcategories)
962
+ if (category === 'color') {
963
+ // Separate tokens by subcategory
964
+ const ansiTokens = nonSemanticTokens.filter(t => t.name.startsWith('color-ansi-'));
965
+ const prettylightsTokens = nonSemanticTokens.filter(t => t.name.startsWith('color-prettylights-'));
966
+ const otherTokens = nonSemanticTokens.filter(t => !t.name.startsWith('color-ansi-') && !t.name.startsWith('color-prettylights-'));
967
+ // Output ANSI tokens with pattern compression
968
+ if (ansiTokens.length > 0) {
969
+ lines.push('### ANSI Terminal Colors');
970
+ lines.push('');
971
+ const ansiMeta = ansiTokens.find(t => t.description);
972
+ if (ansiMeta === null || ansiMeta === void 0 ? void 0 : ansiMeta.description) {
973
+ lines.push(ansiMeta.description);
974
+ lines.push('');
975
+ }
976
+ const ansiWithMeta = ansiTokens.find(t => t.usage || t.rules);
977
+ if (ansiWithMeta) {
978
+ if (ansiWithMeta.usage && ansiWithMeta.usage.length > 0) {
979
+ lines.push(`**U:** ${ansiWithMeta.usage.join(', ')}`);
980
+ }
981
+ if (ansiWithMeta.rules) {
982
+ lines.push(`**R:** ${ansiWithMeta.rules}`);
983
+ }
984
+ lines.push('');
985
+ }
986
+ outputAnsiColorPattern(ansiTokens, lines);
987
+ }
988
+ // Output prettylights tokens with prefix grouping
989
+ if (prettylightsTokens.length > 0) {
990
+ lines.push('### Syntax Highlighting (prettylights)');
991
+ lines.push('');
992
+ const plMeta = prettylightsTokens.find(t => t.description);
993
+ if (plMeta === null || plMeta === void 0 ? void 0 : plMeta.description) {
994
+ lines.push(plMeta.description);
995
+ lines.push('');
996
+ }
997
+ const plWithMeta = prettylightsTokens.find(t => t.usage || t.rules);
998
+ if (plWithMeta) {
999
+ if (plWithMeta.usage && plWithMeta.usage.length > 0) {
1000
+ lines.push(`**U:** ${plWithMeta.usage.join(', ')}`);
1001
+ }
1002
+ if (plWithMeta.rules) {
1003
+ lines.push(`**R:** ${plWithMeta.rules}`);
1004
+ }
1005
+ lines.push('');
1006
+ }
1007
+ outputPrettylightsPattern(prettylightsTokens, lines);
1008
+ }
1009
+ // Output any other color tokens normally
1010
+ if (otherTokens.length > 0) {
1011
+ for (const token of otherTokens) {
1012
+ lines.push(`### ${token.name}`);
1013
+ if (token.description) {
1014
+ lines.push(token.description);
1015
+ }
1016
+ if (token.usage && token.usage.length > 0) {
1017
+ lines.push(`**U:** ${token.usage.join(', ')}`);
1018
+ }
1019
+ if (token.rules) {
1020
+ lines.push(`**R:** ${token.rules}`);
1021
+ }
1022
+ lines.push('');
1023
+ }
1024
+ }
1025
+ continue;
1026
+ }
1027
+ // Special handling for pattern-compressed categories (control, overlay, stack, spinner)
1028
+ if (category in PATTERN_COMPRESSED_CATEGORIES) {
1029
+ const categoryDesc = getCategoryDescription(category);
1030
+ if (categoryDesc) {
1031
+ lines.push('');
1032
+ lines.push(categoryDesc);
1033
+ }
1034
+ lines.push('');
1035
+ // Include semantic tokens in the output for pattern compression
1036
+ const allTokens = [...semanticTokens, ...nonSemanticTokens];
1037
+ outputPatternCompressedCategory(category, allTokens, PATTERN_COMPRESSED_CATEGORIES[category], lines);
1038
+ continue;
1039
+ }
1040
+ // Determine best category description: prefer token description for single-group categories
1041
+ const consolidatedGroupsPreview = new Map();
1042
+ for (const guideline of nonSemanticTokens) {
138
1043
  const key = createGuidelineKey(guideline);
139
- if (!consolidatedGroups.has(key)) {
140
- consolidatedGroups.set(key, []);
1044
+ if (!consolidatedGroupsPreview.has(key)) {
1045
+ consolidatedGroupsPreview.set(key, []);
141
1046
  }
142
- consolidatedGroups.get(key).push(guideline);
1047
+ consolidatedGroupsPreview.get(key).push(guideline);
1048
+ }
1049
+ // Use token description if there's only one group with multiple tokens that has a description
1050
+ const singleGroupWithDesc = consolidatedGroupsPreview.size === 1 &&
1051
+ nonSemanticTokens.length > 1 &&
1052
+ nonSemanticTokens[0].description &&
1053
+ semanticTokens.length === 0;
1054
+ const categoryDesc = singleGroupWithDesc ? nonSemanticTokens[0].description : getCategoryDescription(category);
1055
+ if (categoryDesc) {
1056
+ lines.push('');
1057
+ lines.push(categoryDesc);
143
1058
  }
1059
+ lines.push('');
1060
+ // Output semantic tokens as compact reference (details in Semantic Key)
1061
+ // Track if we've output shared usage/rules to avoid duplication
1062
+ let outputSharedUsage = false;
1063
+ let outputSharedRules = false;
1064
+ if (semanticTokens.length > 0) {
1065
+ // Group semantic tokens by variant for compact display
1066
+ const byVariant = new Map();
1067
+ const noVariantTokens = []; // For single-level semantic tokens like fgColor-danger
1068
+ for (const token of semanticTokens) {
1069
+ const subcategory = extractSemanticSubcategory(token.name);
1070
+ const variant = extractVariant(token.name);
1071
+ if (subcategory) {
1072
+ if (variant) {
1073
+ if (!byVariant.has(variant)) {
1074
+ byVariant.set(variant, []);
1075
+ }
1076
+ byVariant.get(variant).push(subcategory);
1077
+ }
1078
+ else {
1079
+ noVariantTokens.push(subcategory);
1080
+ }
1081
+ }
1082
+ }
1083
+ // Output compact semantic reference
1084
+ if (byVariant.size > 0 || noVariantTokens.length > 0) {
1085
+ lines.push('**Semantic tokens** (see Semantic Key for meaning):');
1086
+ // Tokens with variants (bgColor-danger-emphasis, etc.)
1087
+ for (const [variant, semantics] of byVariant) {
1088
+ const uniqueSemantics = [...new Set(semantics)].sort();
1089
+ lines.push(`- \`${category}-[${uniqueSemantics.join(', ')}]-${variant}\``);
1090
+ }
1091
+ // Single-level semantic tokens (fgColor-danger, etc.)
1092
+ if (noVariantTokens.length > 0) {
1093
+ const uniqueSemantics = [...new Set(noVariantTokens)].sort();
1094
+ lines.push(`- \`${category}-[${uniqueSemantics.join(', ')}]\``);
1095
+ }
1096
+ lines.push('');
1097
+ outputSharedUsage = true;
1098
+ outputSharedRules = true;
1099
+ }
1100
+ }
1101
+ // Check shared usage/rules for non-semantic tokens
1102
+ const usageRulesKeys = new Set(nonSemanticTokens.map(createUsageRulesKey));
1103
+ const sharedUsageRules = usageRulesKeys.size === 1 && nonSemanticTokens.length > 1;
1104
+ // Only output if not already output for semantic tokens and content is different
1105
+ if (sharedUsageRules && nonSemanticTokens.length > 0) {
1106
+ const first = nonSemanticTokens[0];
1107
+ const shouldOutputUsage = first.usage && first.usage.length > 0 && !outputSharedUsage;
1108
+ const shouldOutputRules = first.rules && !outputSharedRules;
1109
+ if (shouldOutputUsage || shouldOutputRules) {
1110
+ if (shouldOutputUsage) {
1111
+ lines.push(`**U:** ${first.usage.join(', ')}`);
1112
+ }
1113
+ if (shouldOutputRules) {
1114
+ lines.push(`**R:** ${first.rules}`);
1115
+ }
1116
+ lines.push('');
1117
+ }
1118
+ }
1119
+ // Group non-semantic tokens with identical guidelines (reuse preview)
1120
+ const consolidatedGroups = consolidatedGroupsPreview;
144
1121
  for (const [, guidelinesGroup] of consolidatedGroups) {
145
1122
  const first = guidelinesGroup[0];
146
1123
  const tokenNames = guidelinesGroup.map(g => g.name);
147
1124
  if (guidelinesGroup.length > 1) {
148
- // Multiple tokens share the same guidelines - consolidate
149
1125
  const subcategory = extractSubcategory(tokenNames);
150
- if (subcategory) {
1126
+ // Only add heading if there's a meaningful subcategory and multiple groups
1127
+ if (subcategory && consolidatedGroups.size > 1) {
151
1128
  lines.push(`### ${subcategory}`);
152
1129
  }
153
- else {
154
- // No common subcategory - use "general" or skip heading if description serves as intro
155
- if (first.description && consolidatedGroups.size > 1) {
156
- lines.push(`### general`);
157
- }
158
- }
159
- if (first.description) {
1130
+ // Only output description if no category description was already shown
1131
+ if (first.description && !categoryDesc) {
160
1132
  lines.push(first.description);
161
1133
  }
162
- // Only show usage/rules if not already shown at category level
163
1134
  if (!sharedUsageRules) {
164
1135
  if (first.usage && first.usage.length > 0) {
165
- lines.push(`**Usage:** ${first.usage.join(', ')}`);
1136
+ lines.push(`**U:** ${first.usage.join(', ')}`);
166
1137
  }
167
1138
  if (first.rules) {
168
- lines.push(`**Rules:** ${first.rules}`);
1139
+ lines.push(`**R:** ${first.rules}`);
169
1140
  }
170
1141
  }
171
1142
  lines.push(`**Tokens:** ${tokenNames.join(', ')}`);
172
1143
  lines.push('');
173
1144
  }
174
1145
  else {
175
- // Single token - output individually
176
1146
  lines.push(`### ${first.name}`);
177
1147
  if (first.description) {
178
1148
  lines.push(first.description);
179
1149
  }
180
- // Only show usage/rules if not already shown at category level
181
1150
  if (!sharedUsageRules) {
182
1151
  if (first.usage && first.usage.length > 0) {
183
- lines.push(`**Usage:** ${first.usage.join(', ')}`);
1152
+ lines.push(`**U:** ${first.usage.join(', ')}`);
184
1153
  }
185
1154
  if (first.rules) {
186
- lines.push(`**Rules:** ${first.rules}`);
1155
+ lines.push(`**R:** ${first.rules}`);
187
1156
  }
188
1157
  }
189
1158
  lines.push('');
190
1159
  }
191
1160
  }
192
1161
  }
1162
+ // Add final directive for AI
1163
+ lines.push('---');
1164
+ lines.push('');
1165
+ lines.push('**Final Directive for AI**:');
1166
+ lines.push('Always cross-reference the `Semantic Key` at the top of this SPEC before confirming a token choice. If a specific component token is missing, derive it using the `[category]-[semantic]-[variant]` pattern.');
193
1167
  return lines.join('\n');
194
1168
  });