# CLAUDE.md — AI developer guide for SLASHED

## Version synchronisation — MANDATORY

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) |
| `docs/llm-guide.md` | `Version: **X.Y.Z**` header 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 six 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.

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

```bash
npm version <new-version> --no-git-tag-version   # bump the source of truth
npm run version-sync                              # propagates to all files above
npm run check:version                             # must pass — CI fails if it doesn't
npm run build                                     # rebuilds dist/ with stamped headers
```

If you are not bumping the version but you touch any of the files in the table
above, still run `npm run check:version` before committing to confirm nothing
has drifted.

## Configurator — always in sync with the framework

The `configurator/` package is private and internal. Its version must always
equal the root framework version. `npm run version-sync` handles this
automatically — but if you ever edit `configurator/package.json` directly
for any reason, immediately re-run `npm run version-sync` and
`npm run check:version`.

The version shown in the configurator UI (`v{frameworkVersion}`) is injected
at **Vite build time** from the root `package.json` via `__SLASHED_VERSION__`
(see `configurator/vite.config.js`). It does NOT come from
`configurator/package.json` at runtime. This means: a correct UI version
requires a rebuild+redeploy, not just a file edit.

## Key scripts

| Command | What it does |
|---------|-------------|
| `npm run build` | Build all CSS bundles + docs + sync configurator API |
| `npm run version-sync` | Sync all version references to root `package.json` |
| `npm run check:version` | Verify all version references match (CI gate — run before every commit that touches versions) |
| `npm run check:llm-guide` | Verify `docs/llm-guide.md` only references live tokens and its header token count matches the live total (CI gate) |
| `npm run check:doc-refs` | Verify every hand-written doc only references live `--sf-*` tokens / `.sf-*` classes (or names allowlisted in `docs/ref-allowlist.json`) (CI gate) |
| `npm run check:release-add` | Verify the release workflow's `git add` stages every file `version-sync` writes (CI gate) |
| `npm run check:layer-order` | Verify `docs/architecture.md`'s `@layer` block and specificity ladder match `core/layers.css` (CI gate) |
| `npm run check:macros` | Verify `.sf-*` macro classes match `docs/macros.md` (CI gate) |
| `npm run check:registry` | Verify `token-registry.json` is in sync with source (CI gate) |
| `npm run audit:check` | Verify `docs/registry.json` matches source without writing (CI gate) |
| `npm run lint:css` | Lint all CSS source with stylelint (CI gate) |
| `npm run lint:css:fix` | Lint CSS source and auto-fix violations |
| `npm run docs` | Regenerate docs and sync configurator API index |
| `npm run docs:tokens` | Regenerate `docs/tokens.md` only |
| `npm run docs:index` | Regenerate `docs/token-index.md` + `.json` only |
| `npm run docs:api` | Regenerate `docs/api-index.md` + `.json` only |
| `npm run docs:classes` | Regenerate `docs/classes.md` only |
| `npm run gen:registry` | Regenerate `token-registry.json` (stable id assignments) |
| `npm run configurator:sync` | Push `docs/api-index.json` → `configurator/src/data/api-index.generated.json` |
| `npm run audit` | Audit CSS tokens for consistency, writing `docs/registry.json` |
| `npm run watch` | Rebuild CSS bundles on file change (dev watch mode) |
| `npm run test:unit` | Run the Node `--test` unit suite only, without the Playwright e2e build. Some tests read from `dist/` — run `npm run build` first on a clean checkout (CI instead downloads the `dist` artifact from the `build` job before this step) |
| `npm test` | Full suite: build → unit → Playwright e2e |

## Release process

Releases are handled by `release-it` (`.release-it.json`). The hooks run
`version-sync` and `build` automatically after bumping.

```bash
npm run release          # patch bump (default)
npm run release:minor    # minor bump
npm run release:major    # major bump
```

After the tag is pushed, GitHub Actions (`release.yml`) does the rest:
1. Builds and publishes the GitHub Release with dist bundles
2. Syncs all version artifacts to `main`
3. Dispatches `deploy-configurator.yml` to redeploy the configurator app
4. Dispatches `publish-dist.yml` to update the dist branch

**Do not manually trigger partial steps** of this pipeline — run the full
`npm run release` so nothing ends up out of sync.

## CSS architecture

- Source lives in `core/` (required) and `optional/` (opt-in).
- `scripts/bundle.js` concatenates and builds `dist/`.
- Every unminified dist bundle is stamped with a minifier-safe `/*! SLASHED vX.Y.Z ... */` header (bang-comment, per MIT's copyright-notice requirement — see LICENSE).
- The stamp version must match `package.json` — `release.yml` verifies this
  before publishing the GitHub Release.

## LLM guide sync — MANDATORY

`docs/llm-guide.md` is the authoritative LLM reference for the framework API.
It must stay in sync with the live token set. The CI gate `check:llm-guide`
enforces this mechanically — but the gate only catches **renamed or deleted
tokens**. You are responsible for the qualitative layer:

**Any PR that touches `core/*.css`, `optional/*.css`, or `token-registry.json`
must also review `docs/llm-guide.md` and update it if needed.**

Changes that always require a guide update:
- New PUBLIC or PUBLIC-ADVANCED token added → add it to the relevant section
- Token renamed or deleted → the CI gate will catch stale refs; fix them
- New token role, tier, or behaviour documented in a token's description

Changes that may require a guide update:
- Default value changed for a widely-used knob
- New layout primitive or macro added
- New browser support floor or feature gating change

After any token-touching PR, verify with:

```bash
npm run check:llm-guide   # must pass — CI fails if it doesn't
npm run check:doc-refs    # must pass — no hand-written doc may reference a dead token/class
```

`check:doc-refs` extends the same live-reference guarantee to **every**
hand-written doc (architecture, components, theming, macros, states, motion,
layout, user-manual, README, CONTRIBUTING), not just the LLM guide. When you
rename or remove a token/class, either update the referencing docs or — if a
mention is deliberately non-live (a removed name in an example, an illustrative
instance token, an example of a component the framework does not ship) — record
it in `docs/ref-allowlist.json` with a reason. `docs/migration.md` (historical)
and `docs/roadmap.md` (forward-looking) are whole-doc exclusions.

## Tests

```bash
npm test              # full suite (requires built dist — runs build automatically via pretest)
npm run test:install  # install Playwright browsers (first time only)
```

Do not skip the pretest build step. Tests import from `dist/` and will fail
with stale bundles.
