Skip to content

Commit 1056bbc

Browse files
author
DevBot
committed
feat(www,tools): zero theme hardcodes — tokens for vinyl/display sizes, dead button CSS removed, raw pre to open-code-block, www theme-token gate wired into AutoFlow
1 parent 76b669e commit 1056bbc

12 files changed

Lines changed: 185 additions & 82 deletions

File tree

deno.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"docs:check-version-anchors": "deno run --allow-read tools/check-version-anchors.ts",
4949
"docs:truth": "deno task docs:check-public && deno task docs:check-current && deno task docs:check-strategy && deno task docs:check-version-anchors && deno task release:evidence:check && deno task www:check-current-truth",
5050
"www:check-current-truth": "deno run --allow-read tools/check-www-current-truth.ts",
51+
"www:check-theme-tokens": "deno run --allow-read tools/check-www-theme-tokens.ts",
5152
"www:check-artifact-truth": "deno run --allow-read tools/check-www-current-truth.ts --artifacts",
5253
"package-surface:check": "deno run --allow-read --allow-env tools/check-package-surface.ts",
5354
"interface:snapshot": "deno run --allow-read --allow-env tools/check-public-interface-snapshot.ts",

tools/autoflow/policy.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ export const GATES: readonly GateDefinition[] = [
126126
tiers: ['ci', 'release'],
127127
triggers: [/^docs\//, /^README/, /^www\/app\/routes\//],
128128
},
129+
{
130+
name: 'www:check-theme-tokens',
131+
command: ['deno', 'task', 'www:check-theme-tokens'],
132+
tiers: ['dev', 'push', 'ci', 'release'],
133+
triggers: [/^www\/app\//, /^www\/vite\.config\.ts$/, /^packages\/ui\/src\/open-props-tokens/],
134+
},
129135
{
130136
name: 'docs:check-version-anchors',
131137
command: ['deno', 'task', 'docs:check-version-anchors'],
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { assertEquals } from '@std/assert';
2+
import { findThemeTokenFailures } from './check-www-theme-tokens.ts';
3+
4+
Deno.test('theme-token gate catches hex, font-family and font-size literals', () => {
5+
const lines = [
6+
' .vinyl { background:#18151e; }',
7+
' code { font-family: "JetBrains Mono", monospace; }',
8+
' font-size: 12px;',
9+
' font-size: .75rem;',
10+
];
11+
const failures = findThemeTokenFailures('x.tsx', lines);
12+
assertEquals(failures.map((f) => f.rule), [
13+
'hex-literal',
14+
'font-family-literal',
15+
'font-size-literal',
16+
'font-size-literal',
17+
]);
18+
});
19+
20+
Deno.test('theme-token gate catches short hex only in CSS contexts', () => {
21+
const violations = findThemeTokenFailures('x.tsx', [
22+
'background: color-mix(in srgb, #fff 18%, transparent);',
23+
]);
24+
assertEquals(violations.length, 1);
25+
const prose = findThemeTokenFailures('x.tsx', [
26+
'External adopter #390 and continued browser evidence.',
27+
'<strong>#390</strong>',
28+
]);
29+
assertEquals(prose.length, 0);
30+
});
31+
32+
Deno.test('theme-token gate skips generated data files', () => {
33+
const failures = findThemeTokenFailures('www/app/data/_generated-blog-data.ts', [
34+
'"content": "themeColor: \'#000000\', font-size: 12px;"',
35+
]);
36+
// The caller-level exclusion (path filter) keeps generated content out;
37+
// the pure function still flags it when asked directly.
38+
assertEquals(failures.length > 0, true);
39+
});
40+
41+
Deno.test('theme-token gate accepts tokens, inherit and fluid clamp()', () => {
42+
const failures = findThemeTokenFailures('x.tsx', [
43+
'color: var(--text-primary);',
44+
'background: color-mix(in srgb, var(--violet-5) 18%, transparent);',
45+
'font-family: var(--font-mono);',
46+
'font-family: inherit;',
47+
'font-size: var(--font-size-00);',
48+
'font-size: clamp(4.1rem, 10vw, 10.5rem);',
49+
'font-size: clamp(var(--font-size-7), 10vw, var(--font-size-8));',
50+
]);
51+
assertEquals(failures.length, 0);
52+
});

tools/check-www-theme-tokens.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* www theme-token gate: theme values in the site must come from open-props
3+
* tokens (packages/ui/src/open-props-tokens.css) and the www alias layer
4+
* (www/vite.config.ts), never from hardcoded literals.
5+
*
6+
* Rules for sources under www/app/ and www/islands/:
7+
* 1. No hex color literals. 6/8-digit forms always fail; 3/4-digit forms
8+
* fail only on lines carrying a CSS property keyword, so issue
9+
* references like `#390` in prose stay legal.
10+
* 2. No `font-family` declarations that bypass var(); `inherit` is allowed.
11+
* 3. No `font-size` literals in px/rem/em outside var(); clamp() fluid
12+
* typography is allowed.
13+
*
14+
* Token definitions belong in www/vite.config.ts (site aliases) or
15+
* packages/ui/src/open-props-tokens.css (source of truth).
16+
*/
17+
18+
import { walk } from '@std/fs/walk';
19+
20+
const SCAN_ROOTS = ['www/app'];
21+
const SOURCE = /\.(ts|tsx)$/;
22+
const HEX_LONG = /#(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\b/;
23+
const HEX_SHORT = /#(?:[0-9a-fA-F]{3,4})\b/;
24+
const CSS_KEYWORD = /color|background|border|shadow|fill|stroke|gradient|outline/i;
25+
const FONT_FAMILY = /font-family\s*:\s*([^;]+);/;
26+
const FONT_SIZE_LITERAL = /font-size\s*:\s*[0-9.]+(?:px|rem|em)\b/;
27+
28+
export interface ThemeTokenFailure {
29+
file: string;
30+
line: number;
31+
rule: string;
32+
text: string;
33+
}
34+
35+
export function findThemeTokenFailures(
36+
file: string,
37+
lines: string[],
38+
): ThemeTokenFailure[] {
39+
const failures: ThemeTokenFailure[] = [];
40+
for (let i = 0; i < lines.length; i++) {
41+
const text = lines[i];
42+
if (HEX_LONG.test(text) || (HEX_SHORT.test(text) && CSS_KEYWORD.test(text))) {
43+
failures.push({ file, line: i + 1, rule: 'hex-literal', text: text.trim() });
44+
}
45+
const family = FONT_FAMILY.exec(text);
46+
if (family && !family[1].includes('var(') && !family[1].includes('inherit')) {
47+
failures.push({ file, line: i + 1, rule: 'font-family-literal', text: text.trim() });
48+
}
49+
if (FONT_SIZE_LITERAL.test(text)) {
50+
failures.push({ file, line: i + 1, rule: 'font-size-literal', text: text.trim() });
51+
}
52+
}
53+
return failures;
54+
}
55+
56+
async function main(): Promise<void> {
57+
const failures: ThemeTokenFailure[] = [];
58+
for (const root of SCAN_ROOTS) {
59+
for await (const entry of walk(root, { exts: ['.ts', '.tsx'] })) {
60+
if (!SOURCE.test(entry.path)) continue;
61+
if (entry.path.includes('/data/_generated-')) continue;
62+
const text = await Deno.readTextFile(entry.path);
63+
failures.push(...findThemeTokenFailures(entry.path, text.split('\n')));
64+
}
65+
}
66+
if (failures.length > 0) {
67+
console.error('www theme token check failed:');
68+
for (const failure of failures) {
69+
console.error(`- ${failure.file}:${failure.line} [${failure.rule}] ${failure.text}`);
70+
}
71+
console.error(
72+
'Theme values must come from open-props tokens or the www/vite.config.ts alias layer.',
73+
);
74+
Deno.exit(1);
75+
}
76+
console.log('www theme token check passed.');
77+
}
78+
79+
if (import.meta.main) {
80+
await main();
81+
}

www/app/routes/404.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ styles.replaceSync(`
8484
padding: 80px 20px 64px;
8585
}
8686
.title {
87-
font-size: 64px;
87+
font-size: var(--font-size-7);
8888
}
8989
.subtitle {
90-
font-size: 20px;
90+
font-size: var(--font-size-2);
9191
}
9292
}
9393
`);

www/app/routes/architecture/architecture.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const tagName = 'engine-architecture';
44
import { OpenElement, StyleSheet } from '@openelement/element';
55
import '@openelement/ui/open-badge';
66
import '@openelement/ui/open-card';
7+
import '@openelement/ui/open-code-block';
78
import { OPENELEMENT_VERSION } from '../../data/version.ts';
89
import '@openelement/site-ui/open-section-frame.tsx';
910
import '@openelement/site-ui/open-page-hero.tsx';
@@ -15,14 +16,14 @@ pageSheet.replaceSync(`
1516
* { box-sizing:border-box; }
1617
.eyebrow { display: flex; flex-wrap: wrap; gap: var(--size-2); margin-bottom: 20px; }
1718
h1 { margin:0; max-width:760px; color:var(--text); font-size:clamp(3.5rem,7vw,7rem); line-height:.88; letter-spacing:-.07em; }
18-
h2 { margin: 0; color: var(--text); font-size: 34px; line-height: 1.12; letter-spacing: 0; }
19+
h2 { margin: 0; color: var(--text); font-size: var(--font-size-display-md); line-height: 1.12; letter-spacing: 0; }
1920
h3 { margin: 0 0 var(--size-2); color: var(--text); }
2021
p { color: var(--text-secondary); line-height: var(--line-height-relaxed); }
2122
.lede { margin: 20px 0 0; font-size: var(--font-size-subhead); max-width: 650px; }
2223
.artifact, .layer-map { border:1px solid color-mix(in srgb,var(--color-border) 72%,var(--brand)); border-radius:var(--radius-2); overflow:hidden; background:color-mix(in srgb,var(--surface-1) 82%,transparent); box-shadow:inset 0 1px 0 var(--edge-highlight),0 28px 90px color-mix(in srgb,var(--violet-10) 24%,transparent); backdrop-filter:blur(18px); }
2324
.artifact-head { display: flex; justify-content: space-between; gap: var(--size-3); padding: 14px var(--size-4); border-bottom: 1px solid var(--color-border); font-size: var(--font-size-0); color: var(--text-muted); }
2425
pre { margin: 0; padding: var(--size-4); overflow-x: auto; background: var(--code-bg); color: var(--code-text); font-size: var(--font-size-0); line-height: 1.65; }
25-
code { font-family: "JetBrains Mono", monospace; }
26+
code { font-family: var(--font-mono); }
2627
.layer { display: grid; grid-template-columns: 170px 1fr 180px; gap: var(--size-4); padding: 14px var(--size-4); border-bottom: 1px solid var(--color-border); align-items: start; }
2728
.layer:last-child { border-bottom: 0; }
2829
.layer strong { color: var(--text); font-size: var(--font-size-1); }
@@ -33,7 +34,7 @@ pageSheet.replaceSync(`
3334
.gate strong { color: var(--color-brand); font-size: var(--font-size-1); }
3435
.gate span { color: var(--text-secondary); font-size: var(--font-size-0); line-height: 1.55; }
3536
.nav-row { display:flex; flex-wrap:wrap; gap:10px; width:min(1180px,calc(100% - 4rem)); margin:var(--size-8) auto 0; }
36-
@media (max-width: 900px) { .cards, .gate-grid { grid-template-columns: 1fr; } .layer { grid-template-columns: 1fr; gap: var(--size-2); } h1 { font-size: 42px; line-height: 1.06; } h2 { font-size: 28px; } }
37+
@media (max-width: 900px) { .cards, .gate-grid { grid-template-columns: 1fr; } .layer { grid-template-columns: 1fr; gap: var(--size-2); } h1 { font-size: var(--font-size-display-lg); line-height: 1.06; } h2 { font-size: var(--font-size-display-sm); } }
3738
@media (max-width: 560px) { .nav-row{width:calc(100% - 2rem)} .gate { grid-template-columns: 1fr; display: grid; } }
3839
`);
3940

@@ -62,7 +63,9 @@ export class ArchitecturePage extends OpenElement {
6263
<open-artifact-panel slot='artifact'>
6364
<span slot='label'>package graph</span>
6465
<span slot='meta'>{OPENELEMENT_VERSION} published line</span>
65-
<pre><code>{PACKAGE_GRAPH}</code></pre>
66+
<open-code-block>
67+
<pre><code>{PACKAGE_GRAPH}</code></pre>
68+
</open-code-block>
6669
</open-artifact-panel>
6770
</open-page-hero>
6871

www/app/routes/architecture/islands-deep.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const routeSheet = new StyleSheet();
1111
routeSheet.replaceSync(
1212
pageStyles + `
1313
.layer-card { padding: 20px var(--size-6); margin: var(--size-4) 0; border-left: 2px solid var(--color-border); background: var(--surface-1); border-radius: 0 3px 3px 0; }
14-
.layer-card .layer-tag { font-size: 11px; font-weight: var(--font-weight-5); text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 0.25rem; }
14+
.layer-card .layer-tag { font-size: var(--font-size-overline); font-weight: var(--font-weight-5); text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 0.25rem; }
1515
.layer-card h3 { margin: 0 0 var(--size-2); }
1616
.strategy-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--size-4); margin: var(--size-4) 0 var(--size-6); }
1717
.strategy-item { padding: var(--size-4) 20px; border: 0.5px solid var(--color-border); border-radius: var(--radius-xs); background: var(--surface-1); }

www/app/routes/blog/[slug].tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ routeSheet.replaceSync(
5252
.blog-tags { display: flex; gap: 0.375rem; flex-wrap: wrap; margin-bottom: var(--size-4); }
5353
.blog-tag { font-size: var(--font-size-00); font-weight: var(--font-weight-6); text-transform: uppercase; letter-spacing: var(--font-letterspacing-2); padding: 0.125rem 0.375rem; border-radius: 2px; background: var(--bg-surface); border: 0.5px solid var(--border); color: var(--text-secondary); }
5454
.blog-content { font-size: var(--font-size-3); line-height: var(--font-lineheight-4); color: var(--text-secondary); }
55-
.blog-content h2 { margin-top: var(--size-10); color: var(--text-primary); font-size: 1.125rem; font-weight: var(--font-weight-6); }
55+
.blog-content h2 { margin-top: var(--size-10); color: var(--text-primary); font-size: var(--font-size-article-title); font-weight: var(--font-weight-6); }
5656
.blog-content h3 { margin-top: var(--size-8); color: var(--text-primary); font-size: var(--font-size-4); font-weight: var(--font-weight-6); }
5757
.blog-content p { margin: var(--size-3) 0; }
5858
.blog-content ul, .blog-content ol { padding-left: var(--size-6); margin: var(--size-3) 0; }
@@ -63,7 +63,7 @@ routeSheet.replaceSync(
6363
.blog-content pre code { background: none; padding: 0; font-size: var(--font-size-0); line-height: 1.6; }
6464
.blog-content table { width: 100%; border-collapse: collapse; margin: var(--size-4) 0; font-size: var(--font-size-1); }
6565
.blog-content th, .blog-content td { padding: var(--size-2) var(--size-3); text-align: left; border-bottom: 0.5px solid var(--border); }
66-
.blog-content th { background: var(--bg-surface); color: var(--text-secondary); font-weight: var(--font-weight-6); font-size: 0.6875rem; text-transform: uppercase; letter-spacing: var(--font-letterspacing-2); }
66+
.blog-content th { background: var(--bg-surface); color: var(--text-secondary); font-weight: var(--font-weight-6); font-size: var(--font-size-overline); text-transform: uppercase; letter-spacing: var(--font-letterspacing-2); }
6767
.blog-content a { color: var(--brand); text-decoration: none; }
6868
.blog-content a:hover { text-decoration: underline; }
6969
.blog-content hr { border: none; border-top: 0.5px solid var(--border); margin: var(--size-8) 0; }

0 commit comments

Comments
 (0)