comparison design_system/test/design_system_policy_test.js @ 258:60a876c4587a

[ui] Add semantic primitive ownership Build a layered Zenbu token and sizing system, make authored controls use native-underneath primitives, migrate mrjunejune without imposing visual surfaces, and document/enforce HTML ownership in the catalog and wiki. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Wed, 05 Aug 2026 05:25:40 -0700
parents 2b6e732087ff
children
comparison
equal deleted inserted replaced
257:609d3c6aff4e 258:60a876c4587a
5 const RUNFILES = process.env.JS_BINARY__RUNFILES; 5 const RUNFILES = process.env.JS_BINARY__RUNFILES;
6 const WORKSPACE = process.env.JS_BINARY__WORKSPACE; 6 const WORKSPACE = process.env.JS_BINARY__WORKSPACE;
7 assert.ok(RUNFILES); 7 assert.ok(RUNFILES);
8 assert.ok(WORKSPACE); 8 assert.ok(WORKSPACE);
9 9
10 const sourceRoot = path.join(RUNFILES, WORKSPACE, 'design_system/src'); 10 const designRoot = path.join(RUNFILES, WORKSPACE, 'design_system');
11 const sourceRoot = path.join(designRoot, 'src');
11 const files = []; 12 const files = [];
12 13
13 function walk(directory) { 14 function walk(directory) {
14 for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { 15 for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
15 const entryPath = path.join(directory, entry.name); 16 const entryPath = path.join(directory, entry.name);
17 else if (/\.(?:css|html|js)$/.test(entry.name)) files.push(entryPath); 18 else if (/\.(?:css|html|js)$/.test(entry.name)) files.push(entryPath);
18 } 19 }
19 } 20 }
20 21
21 walk(sourceRoot); 22 walk(sourceRoot);
23 function listMarkdown(directory) {
24 const markdown = [];
25 for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
26 const entryPath = path.join(directory, entry.name);
27 if (entry.isDirectory()) markdown.push(...listMarkdown(entryPath));
28 else if (entry.name.endsWith('.md')) markdown.push(entryPath);
29 }
30 return markdown;
31 }
32 const documentationFiles = [
33 ...fs.readdirSync(designRoot)
34 .filter(name => name.endsWith('.md'))
35 .map(name => path.join(designRoot, name)),
36 ...listMarkdown(path.join(designRoot, 'wiki')),
37 ];
38 const namingFiles = [
39 ...files,
40 ...documentationFiles,
41 path.join(designRoot, 'main.c'),
42 ];
22 43
23 function validateCallableDocumentation( 44 function validateCallableDocumentation(
24 relative, 45 relative,
25 source, 46 source,
26 index, 47 index,
56 '#[0-9a-fA-F]{3,8}\\b', 77 '#[0-9a-fA-F]{3,8}\\b',
57 '\\b(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch)\\s*\\(', 78 '\\b(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch)\\s*\\(',
58 ].join('|'), 'gi'); 79 ].join('|'), 'gi');
59 const forbiddenGlyph = /[×←→↑↓+◆◇✓✔✕✖★☆⚠]/gu; 80 const forbiddenGlyph = /[×←→↑↓+◆◇✓✔✕✖★☆⚠]/gu;
60 const pictographic = /\p{Extended_Pictographic}/gu; 81 const pictographic = /\p{Extended_Pictographic}/gu;
82 const borrowedProductName =
83 /(?:shadcn|storybook|sonner|radix|tailwind|react)/gi;
61 const failures = []; 84 const failures = [];
62 85
63 for (const file of files) { 86 for (const file of files) {
64 const relative = path.relative(sourceRoot, file); 87 const relative = path.relative(sourceRoot, file);
65 const source = fs.readFileSync(file, 'utf8'); 88 const source = fs.readFileSync(file, 'utf8');
66 89
67 if (relative !== 'styles/tokens.css') { 90 if (![
91 'styles/reference.css',
92 'styles/tokens.css',
93 ].includes(relative)) {
68 for (const match of source.matchAll(rawColor)) { 94 for (const match of source.matchAll(rawColor)) {
69 failures.push( 95 failures.push(
70 `${relative}:${source.slice(0, match.index).split('\n').length} ` + 96 `${relative}:${source.slice(0, match.index).split('\n').length} ` +
71 `raw color ${JSON.stringify(match[0])}`, 97 `raw color ${JSON.stringify(match[0])}`,
72 ); 98 );
155 ].includes(property) || 181 ].includes(property) ||
156 (property.startsWith('border') && 182 (property.startsWith('border') &&
157 !/(?:collapse|image|radius|spacing|style|width)/.test(property)); 183 !/(?:collapse|image|radius|spacing|style|width)/.test(property));
158 if (!colorProperty) continue; 184 if (!colorProperty) continue;
159 const withoutTokens = value.replace( 185 const withoutTokens = value.replace(
160 /var\(\s*--zen-[\w-]+\s*\)/g, 186 /var\(\s*--(?:zenbu|zen)-[\w-]+\s*\)/g,
161 '', 187 '',
162 ); 188 );
163 const words = withoutTokens.toLowerCase() 189 const words = withoutTokens.toLowerCase()
164 .match(/[a-z][a-z-]*/g) || []; 190 .match(/[a-z][a-z-]*/g) || [];
165 if (words.some(word => !allowedWords.has(word)) && 191 if (words.some(word => !allowedWords.has(word)) &&
166 !/^\s*(?:0|none|transparent|currentColor|inherit)\s*$/i.test( 192 !/^\s*(?:0|none|transparent|currentColor|inherit)\s*$/i.test(
167 value, 193 value,
168 )) { 194 )) {
169 failures.push( 195 failures.push(
170 `${relative}:${css.slice(0, declaration.index).split('\n').length} ` + 196 `${relative}:${css.slice(0, declaration.index).split('\n').length} ` +
171 `${property} must use a --zen-* token`, 197 `${property} must use a --zenbu-sys-* token`,
172 ); 198 );
173 } 199 }
174 } 200 }
175 } 201 }
176 } 202 }
218 failures.push( 244 failures.push(
219 `${relative} imports external stylesheet ${JSON.stringify(match[1])}`, 245 `${relative} imports external stylesheet ${JSON.stringify(match[1])}`,
220 ); 246 );
221 } 247 }
222 } 248 }
249 }
250 }
251
252 const cssFiles = files.filter(file => file.endsWith('.css'));
253 const zenbuDeclarations = new Set();
254 for (const file of cssFiles) {
255 const source = fs.readFileSync(file, 'utf8');
256 for (const match of source.matchAll(/(--zenbu-[\w-]+)\s*:/g)) {
257 zenbuDeclarations.add(match[1]);
258 }
259 }
260 for (const file of cssFiles) {
261 const relative = path.relative(sourceRoot, file);
262 const source = fs.readFileSync(file, 'utf8');
263 for (const match of source.matchAll(/var\(\s*(--zenbu-[\w-]+)/g)) {
264 if (!zenbuDeclarations.has(match[1])) {
265 failures.push(`${relative} references unknown token ${match[1]}`);
266 }
267 }
268 if (![
269 'styles/reference.css',
270 'styles/semantic.css',
271 'styles/themes.css',
272 'styles/tokens.css',
273 ].includes(relative)) {
274 for (const match of source.matchAll(
275 /var\(\s*(--zenbu-ref-color-[\w-]+)/g,
276 )) {
277 failures.push(
278 `${relative} consumes reference color ${match[1]} directly`,
279 );
280 }
281 }
282 if (![
283 'styles/themes.css',
284 'styles/tokens.css',
285 ].includes(relative) && /var\(\s*--zen-/.test(source)) {
286 failures.push(`${relative} consumes deprecated --zen-* tokens`);
287 }
288 }
289
290 for (const file of namingFiles) {
291 const source = fs.readFileSync(file, 'utf8');
292 for (const match of source.matchAll(borrowedProductName)) {
293 failures.push(
294 `${path.relative(designRoot, file)} uses borrowed product name ` +
295 JSON.stringify(match[0]),
296 );
223 } 297 }
224 } 298 }
225 299
226 for (const file of files.filter(file => 300 for (const file of files.filter(file =>
227 file.endsWith('.js') 301 file.endsWith('.js')