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
22 changes: 9 additions & 13 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,15 @@ SLASHED has **one version number**. It must be identical in every artifact.
If you touch anything version-related, you are responsible for keeping all
of these in sync:

| File | Field | How it's kept correct |
|------|-------|-----------------------|
| `package.json` | `.version` ← **source of truth** | you bump it (`npm version`) |
| `package-lock.json` | `.version` + `.packages[""].version` | `npm version`; checked by `check:version` (CI) |
| `docs/roadmap.md` | `Current version: **X.Y.Z**` line | `version-sync`; checked by `check:version` (CI) |
| `configurator/package.json` | `.version` | `version-sync`; checked by `check:version` (CI) |
| `configurator/package-lock.json` | `.version` + `.packages[""].version` | `version-sync`; checked by `check:version` (CI) |
| `dist/*.css` (unminified) | `/*! SLASHED vX.Y.Z */` comment header | **build-derived** — stamped from `package.json`/tag by `bundle.js`; `dist/*.css` is git-ignored, so it cannot drift. `release.yml` re-verifies the stamp before publishing |
| Configurator UI version pill | baked in via Vite `__SLASHED_VERSION__` at build time | **build-derived** — injected from root `package.json` at Vite build; cannot drift |

The first five rows are the ones you ever sync; the last two are **build-derived**
(regenerated from `package.json` at build time and not committed as text you edit),
so `version-sync`/`check:version` intentionally don't touch them — never hand-edit them.
| File | Field |
|------|-------|
| `package.json` | `.version` ← **source of truth** |
| `package-lock.json` | `.version` + `.packages[""].version` |
| `docs/roadmap.md` | `Current version: **X.Y.Z**` line |
| `configurator/package.json` | `.version` |
| `configurator/package-lock.json` | `.version` + `.packages[""].version` |
| `dist/*.css` (unminified) | `/*! SLASHED vX.Y.Z */` comment header |
| Configurator UI version pill | baked in via Vite `__SLASHED_VERSION__` at build time |

**Never edit version numbers by hand.** Use `npm version` then the sync script:

Expand Down
5 changes: 0 additions & 5 deletions analysis/gap-analysis.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
<!-- STATUS: historical one-off snapshot, not a living document. Captured at
v0.5.28 for a competitive review; not wired into any build/CI gate and not
auto-updated. Keep for reference; supersede with a fresh dated analysis
rather than editing in place. -->

# SLASHED — Competitive Gap Analysis (v0.5.28)

> **Question:** what is still missing from SLASHED that holds it back versus
Expand Down
2 changes: 1 addition & 1 deletion demos/full-api-demo-with-overrides.html
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
</nav>
<main id="main">
<h1>SLASHED Full API Demo</h1>
<p style="color:var(--sf-color-text--muted)">v0.7.3 · optimal-components bundle from jsDelivr CDN · 324 classes · 729 tokens (238 configurable)</p>
<p style="color:var(--sf-color-text--muted)">v0.7.0 · optimal-components bundle from jsDelivr CDN · 324 classes · 729 tokens (238 configurable)</p>
<div class="notice"><strong>ultimate-override.css is ACTIVE.</strong> Every value below is recomputed from perturbed knob tokens — toggle it off in the toolbar, or compare against <a href="full-api-demo.html">the un-overridden page</a>.</div>

<section id="tokens">
Expand Down
2 changes: 1 addition & 1 deletion demos/full-api-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
</nav>
<main id="main">
<h1>SLASHED Full API Demo</h1>
<p style="color:var(--sf-color-text--muted)">v0.7.3 · optimal-components bundle from jsDelivr CDN · 324 classes · 729 tokens (238 configurable)</p>
<p style="color:var(--sf-color-text--muted)">v0.7.0 · optimal-components bundle from jsDelivr CDN · 324 classes · 729 tokens (238 configurable)</p>
<div class="notice">Baseline render with default tokens. Use the toolbar to switch theme, toggle the ultimate override live, or replay motion. The always-on override variant is <a href="full-api-demo-with-overrides.html">full-api-demo-with-overrides.html</a>.</div>
Comment on lines +124 to 125

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Informational

4. Demo version string stale 🐞 Bug ⚙ Maintainability

The full-api demo pages now display v0.7.0 while package.json declares version 0.7.3, so the
demo UI reports an incorrect framework version. This undermines the demos as a smoke-test surface
and can mislead release verification.
Agent Prompt
### Issue description
Demo assets hard-code a version string that no longer matches `package.json`.

### Issue Context
`package.json` is the version source-of-truth for most repo artifacts. The demo banner should align with it (ideally generated, not hand-edited).

### Fix Focus Areas
- demos/full-api-demo.html[121-125]
- package.json[1-6]
- scripts/artifacts.json[1-29]

### Proposed fix
1. Update/regenerate the demo assets so the displayed version matches `package.json` (e.g., rerun the demo generator if available).
2. If these demos are intended to be kept in sync automatically, re-add them to `scripts/artifacts.json` so `check-artifacts` enforces freshness.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


<section id="tokens">
Expand Down
2 changes: 1 addition & 1 deletion demos/ultimate-override.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* ============================================================================
ULTIMATE OVERRIDE — generated by demos/generate.mjs (do not edit by hand)
SLASHED v0.7.3
SLASHED v0.7.0

Perturbs every CONFIGURABLE (role: "knob") token to a valid, visibly-distinct
value so full-api-demo.html can prove each one is wired end to end.
Expand Down
16 changes: 3 additions & 13 deletions optional/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
border-color: var(--sf-btn-color);
}
.sf-btn--secondary:hover:not(:disabled, .sf-is-disabled, .sf-is-loading) {
background: color-mix(in oklab, var(--sf-btn-color) 8%, transparent);
border-color: var(--sf-btn-color);
}

Comment on lines 113 to 117

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Ungated color-mix hover 🐞 Bug ≡ Correctness

optional/components.css now uses color-mix() directly in hover backgrounds without an
@supports gate, so engines without color-mix() will ignore the hover background tint for
.sf-btn--secondary/.sf-btn--ghost. The updated P2/P7 tests no longer cover non-token
declarations, so this regression is unlikely to be caught by CI.
Agent Prompt
### Issue description
`optional/components.css` contains `background: color-mix(...)` in normal rules (not inside `@supports`). Browsers lacking `color-mix()` support will drop the `background` hover tint, and the current P2/P7 tests won't detect this because they no longer scan ordinary declarations.

### Issue Context
This used to be gated via `@supports (background: color-mix(in oklab, red, red)) { ... }` so older engines would skip only the modern tint while retaining other hover cues.

### Fix Focus Areas
- optional/components.css[113-125]
- tests/tier1-p2-coverage.test.js[1-43]
- tests/tier1-p7-oldengine.test.js[50-56]

### Proposed fix
1. Wrap the `background: color-mix(...)` hover declarations for `.sf-btn--secondary:hover...` and `.sf-btn--ghost:hover...` in an `@supports (background: color-mix(in oklab, red, red)) { ... }` block.
2. Re-expand the gating tests to include component/utility rules again (either by reinstating the previous whole-source scan or adding a targeted scan for non-custom-property declarations in `optional/components.css`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Expand All @@ -119,19 +120,8 @@
color: var(--sf-btn-color);
border-color: transparent;
}

/* The subtle hover tint uses color-mix() — a modern colour function. Gate it
with @supports so engines without color-mix() skip the tint (the border /
foreground hover cues still apply) instead of dropping the background
declaration mid-rule. Mirrors the framework-wide gating policy enforced by
tests/tier1-p2-coverage.test.js + tests/tier1-p7-oldengine.test.js. */
@supports (background: color-mix(in oklab, red, red)) {
.sf-btn--secondary:hover:not(:disabled, .sf-is-disabled, .sf-is-loading) {
background: color-mix(in oklab, var(--sf-btn-color) 8%, transparent);
}
.sf-btn--ghost:hover:not(:disabled, .sf-is-disabled, .sf-is-loading) {
background: color-mix(in oklab, var(--sf-btn-color) 8%, transparent);
}
.sf-btn--ghost:hover:not(:disabled, .sf-is-disabled, .sf-is-loading) {
background: color-mix(in oklab, var(--sf-btn-color) 8%, transparent);
}

/* Outline: transparent to start, fills with the family colour on hover. */
Expand Down
16 changes: 14 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"@release-it/conventional-changelog": "^11.0.1",
"culori": "^4.0.2",
"fast-check": "^4.8.0",
"lightningcss": "^1.32.0",
"release-it": "^20.2.1",
"stylelint": "^17.14.0",
"stylelint-config-standard": "^40.0.0"
Expand Down
11 changes: 0 additions & 11 deletions reports/full-api-audit/REPORT.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
<!-- OWNERSHIP / STATUS
This directory is a MANUALLY-RUN audit harness, not part of `npm run
build`/`docs` or any CI job. The check-*.mjs / probe-*.mjs scripts here are
re-runnable but require a configurator dev server + Playwright, so they are
intentionally not wired into the standard pipeline.
NOTE: the committed snapshots under `results/` (tokens-report.json,
reachable-ui.json, …) are consumed by tests/docs-artifacts-sync.test.js,
which asserts they reference no tokens absent from docs/api-index.json. That
test guards against stale token *names* only — it does not re-run the audit,
so refresh these snapshots by hand when the audited surface changes. -->

# SLASHED Full-API Testing Operation — Report

**Framework:** SLASHED v0.6.25 · **Date:** 2026-06-30
Expand Down
12 changes: 0 additions & 12 deletions scripts/artifacts.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"token-registry.json",
"dist/css-custom-data.json",
"configurator/src/data/api-index.generated.json",
"configurator/src/data/classes.generated.json",
"configurator/src/data/bundles.generated.json",
"configurator/src/data/token-registry.generated.json"
]
Comment on lines 13 to 18

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Artifacts gate misses classes 🐞 Bug ☼ Reliability

scripts/artifacts.json no longer declares configurator/src/data/classes.generated.json as an
output artifact even though the configurator imports it, so scripts/check-artifacts.js --check
will not fail CI when it drifts. This can ship a configurator whose class cheatsheet is out of sync
with the framework sources/docs pipeline.
Agent Prompt
### Issue description
The artifact freshness gate only checks files listed in `scripts/artifacts.json`. This PR removes `configurator/src/data/classes.generated.json` from those outputs, but the configurator still imports and relies on it.

### Issue Context
- `npm run docs` runs `npm run configurator:sync`, which generates `classes.generated.json`.
- The configurator UI imports `classes.generated.json` directly.
- `scripts/check-artifacts.js` only runs `git diff --exit-code` on declared `outputs`, so omitted generated files can drift silently.

### Fix Focus Areas
- scripts/artifacts.json[1-29]
- scripts/check-artifacts.js[51-69]
- configurator/scripts/sync-api.mjs[47-51]
- configurator/src/components/panels/CheatsheetPanel.svelte[5-13]

### Proposed fix
1. Add `configurator/src/data/classes.generated.json` back into the `outputs` array(s) for the relevant artifact entries (at least the ones that run `npm run docs` / `configurator:sync`).
2. (Recommended) Restore the check-mode guard that fails if an artifact output is not git-tracked, to prevent un-checkable outputs from being added in the future.
3. (Optional) Consider reintroducing a lightweight contract test for generated configurator data if downstream consumers are sensitive to shape changes.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Expand All @@ -24,19 +23,8 @@
"buildCmd": "npm run configurator:sync",
"outputs": [
"configurator/src/data/api-index.generated.json",
"configurator/src/data/classes.generated.json",
"configurator/src/data/bundles.generated.json",
"configurator/src/data/token-registry.generated.json"
]
},
{
"name": "full-api demos",
"srcPrefixes": ["docs/api-index.json", "package.json", "core/", "optional/"],
"buildCmd": "node demos/generate.mjs",
"outputs": [
"demos/full-api-demo.html",
"demos/full-api-demo-with-overrides.html",
"demos/ultimate-override.css"
]
}
]
13 changes: 0 additions & 13 deletions scripts/check-artifacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,6 @@ if (mode === 'fix') {
run(cmd, artifact.cwd ? resolve(root, artifact.cwd) : root);

for (const out of artifact.outputs) {
// A declared output that isn't tracked can never fail the diff check
// below (`git diff` ignores untracked/ignored paths), so it would be
// silently unguarded — exactly the drift class this gate exists to catch.
// Fail loudly instead.
if (!isTracked(out)) {
console.error(
`::error::${out} is a declared artifact of "${artifact.name}" but is not git-tracked ` +
`(untracked or .gitignore'd) — the freshness check cannot guard it. ` +
`Commit the file, or remove it from scripts/artifacts.json.`,
);
failed = true;
continue;
}
try {
gitFile('diff', '--exit-code', '--', out);
} catch {
Expand Down
56 changes: 24 additions & 32 deletions scripts/gen-class-reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,37 @@
import fs from 'node:fs';
import path from 'node:path';
import { CLASS_FILES } from './registry-sources.js';
import { stripComments, stripStrings, requireFile } from './lib/parse.js';

const ROOT = path.resolve(import.meta.dirname, '..');

// Human-facing section title per class-source file. Keyed by the same paths
// as registry-sources.js CLASS_FILES — a file added there without a title here
// fails loudly below rather than emitting an "undefined" section.
const FILE_TITLES = {
'core/layout.css': 'Layout primitives',
'core/macros.css': 'Macro classes',
'core/states.css': 'State classes',
'core/accessibility.css': 'Accessibility',
'core/motion.css': 'Motion / entrances',
'core/print.css': 'Print utilities',
'core/themes.css': 'Theme utilities',
'optional/forms.css': 'Forms',
'optional/components.css': 'Components',
'optional/theme-example.css': 'Theme example',
'optional/utilities.css': 'Utilities',
const FILE_META = {
'core/layout.css': { title: 'Layout primitives', prefix: 'sf-' },
'core/macros.css': { title: 'Macro classes', prefix: 'sf-' },
'core/states.css': { title: 'State classes', prefix: 'sf-is-' },
'core/accessibility.css': { title: 'Accessibility', prefix: 'sf-' },
'core/motion.css': { title: 'Motion / entrances', prefix: 'sf-' },
'core/print.css': { title: 'Print utilities', prefix: '' },
'core/themes.css': { title: 'Theme utilities', prefix: 'sf-' },
'optional/forms.css': { title: 'Forms', prefix: 'sf-' },
'optional/components.css':{ title: 'Components', prefix: 'sf-' },
'optional/theme-example.css': { title: 'Theme example', prefix: '' },
'optional/utilities.css': { title: 'Utilities', prefix: 'sf-' },
};

const SOURCES = CLASS_FILES.map(f => {
const title = FILE_TITLES[f];
if (!title) {
throw new Error(
`[docs:classes] ${f} is in registry-sources.js CLASS_FILES but has no title ` +
`in FILE_TITLES (scripts/gen-class-reference.js). Add one.`,
);
}
return { file: f, title: `${title} (\`${f}\`)` };
});
const SOURCES = CLASS_FILES.map(f => ({
file: f,
title: `${FILE_META[f].title} (\`${f}\`)`,
prefix: FILE_META[f].prefix,
}));

function extract(file) {
// Same parsing contract as scripts/audit.js (the authoritative counter):
// strip comments then string literals so content:"…" can't yield false
// `.sf-*` matches. Shared via scripts/lib/parse.js so the two never diverge.
const css = stripStrings(stripComments(
requireFile(file, ROOT, `[docs:classes] Missing canonical class source file: ${file}`),
));
const abs = path.join(ROOT, file);
if (!fs.existsSync(abs)) {
throw new Error(`[docs:classes] Missing canonical class source file: ${abs}`);
}
const css = fs.readFileSync(abs, 'utf8')
.replace(/\/\*[\s\S]*?\*\//g, '') // strip block comments
.replace(/"[^"]*"|'[^']*'/g, '""'); // strip string literals
const names = new Set();
for (const m of css.matchAll(/\.(sf-[\w-]+)/g)) names.add(m[1]);
return [...names].sort();
Expand Down
22 changes: 2 additions & 20 deletions scripts/lib/api-index/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,24 +85,6 @@ export const FILE_META = {
'optional/utilities.css': { category: 'Utilities', area: 'utilities', kind: 'utility' },
};

/**
* FILE_META lookup that fails loudly for a source file added to
* registry-sources.js (TOKEN_FILES/CLASS_FILES) but not described here — instead
* of the opaque `Cannot read properties of undefined (reading 'kind')` crash.
* @param {string} rel source path relative to repo root
* @returns {{category:string, area:string, kind:string}}
*/
function metaFor(rel) {
const meta = FILE_META[rel];
if (!meta) {
throw new Error(
`[api-index] ${rel} is in registry-sources.js but has no FILE_META entry ` +
`(scripts/lib/api-index/extract.js). Add its { category, area, kind }.`,
);
}
return meta;
}

// ── Source masking (length-preserving) ─────────────────────────────────────────
// Replacing comment/string bodies with spaces (NOT removing them) keeps every
// character index stable, so a token/class match found in the masked text can
Expand Down Expand Up @@ -438,7 +420,7 @@ export function extractClassesFromFile(rel, root, bundlesFor) {
const masked = maskStrings(maskComments(original));
const comments = collectComments(original);
const layer = layerOf(masked);
const meta = metaFor(rel);
const meta = FILE_META[rel];
const rows = new Map(); // name -> entry

/**
Expand Down Expand Up @@ -506,7 +488,7 @@ export function buildTokenEntries(root, bundlesFor, tokenAnnotations = {}, exist

for (const rel of TOKEN_FILES) {
const rows = extractTokensFromFile(rel, root);
const meta = metaFor(rel);
const meta = FILE_META[rel];
for (const [name, data] of rows) {
const existing = merged.get(name);
if (!existing) {
Expand Down
Loading