Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ badges/*.map

# Legacy build output dir (build script still emits here; real artifacts live in badges/)
dist/

# full-api demo validator scratch
demos/.validate/
3 changes: 3 additions & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ exclude:
- tests
- dist
- analysis
- demos/generate.mjs
- demos/validate.mjs
- demos/.validate
- bundle.config.json
- commitlint.config.js
- playwright.config.js
Expand Down
2 changes: 1 addition & 1 deletion badges/badge-optimal.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"label": "optimal",
"message": "37.0 kB gzip",
"message": "17.6 kB gzip",
"color": "brightgreen",
"namedLogo": "css3"
}
1,382 changes: 1,382 additions & 0 deletions demos/full-api-demo-with-overrides.html

Large diffs are not rendered by default.

1,381 changes: 1,381 additions & 0 deletions demos/full-api-demo.html

Large diffs are not rendered by default.

707 changes: 707 additions & 0 deletions demos/generate.mjs

Large diffs are not rendered by default.

361 changes: 361 additions & 0 deletions demos/ultimate-override.css

Large diffs are not rendered by default.

102 changes: 102 additions & 0 deletions demos/validate.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Validates the generated demo in a real browser against the locally built
// optimal bundle. Asserts: no console errors, all class + token tiles present,
// JS populates live token values, the dark-mode toggle restyles the page, and
// the runtime override toggle perturbs computed styles.
import { chromium } from '@playwright/test';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const CDN = 'https://cdn.jsdelivr.net/gh/codeslash-dev/SLASHED@dist/slashed.optimal.css';
const LOCAL = path.join(ROOT, 'badges/slashed.optimal.css');
const TMP = path.join(ROOT, 'demos/.validate');
fs.mkdirSync(TMP, { recursive: true });
const DEMOS = path.join(ROOT, 'demos');
// make the runtime-injected override reachable from the temp copy
fs.copyFileSync(path.join(DEMOS, 'ultimate-override.css'), path.join(TMP, 'ultimate-override.css'));

function localCopy(srcName, outName) {
let html = fs.readFileSync(path.join(DEMOS, srcName), 'utf8');
html = html.replaceAll(CDN, 'file://' + LOCAL);
const out = path.join(TMP, outName);
fs.writeFileSync(out, html);
return 'file://' + out;
}
const baseUrl = localCopy('full-api-demo.html', 'base.html');
const expectClasses = (fs.readFileSync(path.join(DEMOS, 'full-api-demo.html'), 'utf8').match(/class="tile"/g) || []).length;
const expectTokens = (fs.readFileSync(path.join(DEMOS, 'full-api-demo.html'), 'utf8').match(/data-token="/g) || []).length;

const browser = await chromium.launch({ executablePath: '/opt/pw-browsers/chromium' });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== demos/validate.mjs ==\n'
wc -l demos/validate.mjs
sed -n '1,220p' demos/validate.mjs

printf '\n== package files mentioning Playwright or validate ==\n'
rg -n "playwright|validate\.mjs|pw-browsers|executablePath|chromium.launch" package.json . -g 'package.json' -g '*.md' -g '*.mjs' -g '*.js'

Repository: codeslash-dev/SLASHED

Length of output: 9133


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== playwright.config.js ==\n'
wc -l playwright.config.js
sed -n '1,220p' playwright.config.js

printf '\n== search for browser path/env assumptions ==\n'
rg -n "pw-browsers|PLAYWRIGHT_BROWSERS_PATH|PLAYWRIGHT_CHROMIUM_EXECUTABLE|executablePath|launchOptions|channel:" . -g '*.js' -g '*.mjs' -g '*.json' -g '*.md'

Repository: codeslash-dev/SLASHED

Length of output: 1626


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== references to demos/validate.mjs ==\n'
rg -n "demos/validate\.mjs|validate\.mjs" . -g '*.yml' -g '*.yaml' -g '*.md' -g '*.json' -g '*.js' -g '*.mjs'

printf '\n== CI/workflow files ==\n'
fd -a -t f '.github' . | sed -n '1,80p'
find .github -maxdepth 3 -type f \( -name '*.yml' -o -name '*.yaml' \) -print 2>/dev/null | sort

Repository: codeslash-dev/SLASHED

Length of output: 199


Avoid hardcoding the Chromium binary path. This pins the validator to one container layout and can make it fail before any checks run on local machines or CI that use Playwright’s managed browser. Let Playwright resolve the browser by default, with an env override only when a custom binary is needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@demos/validate.mjs` at line 29, The Chromium launch in validate.mjs is
hardcoded to a container-specific executable path, which breaks portability.
Update the chromium.launch usage in the validate script to let Playwright
resolve its managed browser by default, and add an environment-based override
only if a custom binary path is explicitly provided. Keep the change localized
to the browser startup logic so the rest of the validation flow stays unchanged.

const page = await browser.newPage({ viewport: { width: 1280, height: 1600 } });
const errors = [];
page.on('console', (m) => { if (m.type() === 'error') errors.push(m.text()); });
page.on('pageerror', (e) => errors.push(String(e)));
await page.goto(baseUrl, { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.waitForTimeout(800);

const fails = [];
const snap = () => page.evaluate(() => {
const cs = getComputedStyle(document.documentElement);
const body = getComputedStyle(document.body);
const vals = [...document.querySelectorAll('.ttile__val')].map((o) => o.textContent.trim());
return {
classTiles: document.querySelectorAll('.tile').length,
tokenTiles: document.querySelectorAll('[data-token]').length,
filledVals: vals.filter((v) => v && v !== '…' && v !== '(empty)').length,
theme: document.documentElement.getAttribute('data-theme'),
bg: body.backgroundColor,
text: body.color,
primary: cs.getPropertyValue('--sf-color-primary').trim(),
spaceM: cs.getPropertyValue('--sf-space-m').trim(),
fxSections: ['functions', 'reference'].filter((id) => document.getElementById(id)).length,
};
});

const click = (sel) => page.evaluate((s) => document.querySelector(s).click(), sel);

const light = await snap();
await page.screenshot({ path: path.join(TMP, 'light.png') });

// dark mode
await click('[data-act="theme:dark"]');
await page.waitForTimeout(300);
const dark = await snap();
await page.screenshot({ path: path.join(TMP, 'dark.png') });

// runtime override toggle (back in light)
await click('[data-act="theme:light"]');
await click('#ovBtn');
await page.waitForTimeout(300);
const overridden = await snap();
await page.screenshot({ path: path.join(TMP, 'override.png') });

// section screenshots for the report
await click('#ovBtn'); // override off
await click('[data-act="theme:light"]');
await page.waitForTimeout(200);
for (const id of ['functions', 'reference']) {
const el = await page.$('#' + id);
if (el) await el.screenshot({ path: path.join(TMP, `sec-${id}.png`) }).catch(() => {});
}
await browser.close();

console.log('expected class tiles:', expectClasses, '| token tiles:', expectTokens);
console.log('LIGHT :', JSON.stringify(light));
console.log('DARK :', JSON.stringify(dark));
console.log('OVERRIDE:', JSON.stringify(overridden));
console.log('console errors:', errors.length);
errors.slice(0, 10).forEach((e) => console.log(' ⚠', e));

if (light.classTiles !== expectClasses) fails.push(`class tiles ${light.classTiles} != ${expectClasses}`);
if (light.tokenTiles !== expectTokens) fails.push(`token tiles ${light.tokenTiles} != ${expectTokens}`);
if (light.filledVals < expectTokens * 0.6) fails.push(`only ${light.filledVals}/${expectTokens} token values populated by JS`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Tighten the token-value coverage assertion.

The generator/runtime contract is “every token gets a live computed value”, but this check still passes when 40% of the reference is blank. That means a large regression in refresh() or token rendering would not fail the build.

Proposed fix
-if (light.filledVals < expectTokens * 0.6) fails.push(`only ${light.filledVals}/${expectTokens} token values populated by JS`);
+if (light.filledVals !== expectTokens) fails.push(`token values populated ${light.filledVals}/${expectTokens}`);

If a small set of tokens is intentionally empty, encode that as an explicit allowlist instead of a percentage heuristic.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (light.filledVals < expectTokens * 0.6) fails.push(`only ${light.filledVals}/${expectTokens} token values populated by JS`);
if (light.filledVals !== expectTokens) fails.push(`token values populated ${light.filledVals}/${expectTokens}`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@demos/validate.mjs` at line 92, The token-value coverage check in the
validation script is too permissive because it allows a large portion of missing
values to pass. Tighten the assertion in the token coverage logic around the
filledVals versus expectTokens check so the generator/runtime contract fails
when tokens are not populated, and if any tokens are intentionally empty, handle
them with an explicit allowlist instead of the current percentage threshold.

if (light.fxSections !== 2) fails.push('functions/reference sections missing');
if (dark.theme !== 'dark') fails.push('dark toggle did not set data-theme=dark');
if (dark.bg === light.bg) fails.push(`dark mode did not change body background (still ${light.bg})`);
if (overridden.primary === light.primary) fails.push('override toggle did not change --sf-color-primary');
if (overridden.spaceM === light.spaceM) fails.push('override toggle did not change --sf-space-m');
if (errors.length) fails.push(`${errors.length} console error(s)`);

if (fails.length) { console.log('\n❌ FAIL:'); fails.forEach((f) => console.log(' ', f)); process.exit(1); }
console.log(`\n✅ PASS — ${expectClasses} classes + ${expectTokens} tokens rendered; JS fills live values; dark mode restyles; override toggles live; 0 console errors.`);
3 changes: 3 additions & 0 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ title: Home
<div class="hero-actions">
<a class="btn-primary" href="/configurator/">Open Configurator →</a>
<a class="btn-secondary" href="/docs/demo.html">View Demo</a>
<a class="btn-secondary" href="/demos/full-api-demo.html">Full API Demo</a>
<a class="btn-secondary" href="https://github.com/codeslash-dev/SLASHED">GitHub</a>
</div>
</div>
Expand Down Expand Up @@ -224,6 +225,8 @@ title: Home
<li><a href="/docs/api-index">API Index <span>machine-readable catalogue</span></a></li>
<li><a href="/docs/source-comment-policy">Comment Policy <span>contributor conventions</span></a></li>
<li><a href="/docs/demo.html">Demo <span>full component showcase</span></a></li>
<li><a href="/demos/full-api-demo.html">Full API Demo <span>every class &amp; token, live · dark mode</span></a></li>
<li><a href="/demos/full-api-demo-with-overrides.html">Full API Demo — overridden <span>ultimate-override.css applied</span></a></li>
<li><a href="/docs/test-coverage.html">Test Coverage <span>visual regression suite</span></a></li>
</ul>
</div>
Expand Down
Loading