Mercurial
diff 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 |
line wrap: on
line diff
--- a/design_system/test/design_system_policy_test.js Tue Aug 04 16:49:11 2026 -0700 +++ b/design_system/test/design_system_policy_test.js Wed Aug 05 05:25:40 2026 -0700 @@ -7,7 +7,8 @@ assert.ok(RUNFILES); assert.ok(WORKSPACE); -const sourceRoot = path.join(RUNFILES, WORKSPACE, 'design_system/src'); +const designRoot = path.join(RUNFILES, WORKSPACE, 'design_system'); +const sourceRoot = path.join(designRoot, 'src'); const files = []; function walk(directory) { @@ -19,6 +20,26 @@ } walk(sourceRoot); +function listMarkdown(directory) { + const markdown = []; + for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { + const entryPath = path.join(directory, entry.name); + if (entry.isDirectory()) markdown.push(...listMarkdown(entryPath)); + else if (entry.name.endsWith('.md')) markdown.push(entryPath); + } + return markdown; +} +const documentationFiles = [ + ...fs.readdirSync(designRoot) + .filter(name => name.endsWith('.md')) + .map(name => path.join(designRoot, name)), + ...listMarkdown(path.join(designRoot, 'wiki')), +]; +const namingFiles = [ + ...files, + ...documentationFiles, + path.join(designRoot, 'main.c'), +]; function validateCallableDocumentation( relative, @@ -58,13 +79,18 @@ ].join('|'), 'gi'); const forbiddenGlyph = /[×←→↑↓+◆◇✓✔✕✖★☆⚠]/gu; const pictographic = /\p{Extended_Pictographic}/gu; +const borrowedProductName = + /(?:shadcn|storybook|sonner|radix|tailwind|react)/gi; const failures = []; for (const file of files) { const relative = path.relative(sourceRoot, file); const source = fs.readFileSync(file, 'utf8'); - if (relative !== 'styles/tokens.css') { + if (![ + 'styles/reference.css', + 'styles/tokens.css', + ].includes(relative)) { for (const match of source.matchAll(rawColor)) { failures.push( `${relative}:${source.slice(0, match.index).split('\n').length} ` + @@ -157,7 +183,7 @@ !/(?:collapse|image|radius|spacing|style|width)/.test(property)); if (!colorProperty) continue; const withoutTokens = value.replace( - /var\(\s*--zen-[\w-]+\s*\)/g, + /var\(\s*--(?:zenbu|zen)-[\w-]+\s*\)/g, '', ); const words = withoutTokens.toLowerCase() @@ -168,7 +194,7 @@ )) { failures.push( `${relative}:${css.slice(0, declaration.index).split('\n').length} ` + - `${property} must use a --zen-* token`, + `${property} must use a --zenbu-sys-* token`, ); } } @@ -223,6 +249,54 @@ } } +const cssFiles = files.filter(file => file.endsWith('.css')); +const zenbuDeclarations = new Set(); +for (const file of cssFiles) { + const source = fs.readFileSync(file, 'utf8'); + for (const match of source.matchAll(/(--zenbu-[\w-]+)\s*:/g)) { + zenbuDeclarations.add(match[1]); + } +} +for (const file of cssFiles) { + const relative = path.relative(sourceRoot, file); + const source = fs.readFileSync(file, 'utf8'); + for (const match of source.matchAll(/var\(\s*(--zenbu-[\w-]+)/g)) { + if (!zenbuDeclarations.has(match[1])) { + failures.push(`${relative} references unknown token ${match[1]}`); + } + } + if (![ + 'styles/reference.css', + 'styles/semantic.css', + 'styles/themes.css', + 'styles/tokens.css', + ].includes(relative)) { + for (const match of source.matchAll( + /var\(\s*(--zenbu-ref-color-[\w-]+)/g, + )) { + failures.push( + `${relative} consumes reference color ${match[1]} directly`, + ); + } + } + if (![ + 'styles/themes.css', + 'styles/tokens.css', + ].includes(relative) && /var\(\s*--zen-/.test(source)) { + failures.push(`${relative} consumes deprecated --zen-* tokens`); + } +} + +for (const file of namingFiles) { + const source = fs.readFileSync(file, 'utf8'); + for (const match of source.matchAll(borrowedProductName)) { + failures.push( + `${path.relative(designRoot, file)} uses borrowed product name ` + + JSON.stringify(match[0]), + ); + } +} + for (const file of files.filter(file => file.endsWith('.js') )) {