npm run lint # prettier --check · npm run format to fix
```
For a component you also run the contract validators (below). A change is not
done until `check` is clean and the relevant validators pass.
## Tests — two projects
`vite.config.ts` defines **two vitest projects** (see CLAUDE.md → "Vitest
Two-Project Structure"):
- **client** — browser tests via Playwright, for `*.svelte.{test,spec}.{js,ts}`.
This is where component providers are exercised with real runes + DOM.
- **server** — Node environment for `*.{test,spec}.{js,ts}` (excludes the svelte
tests). Pure engines, libs, and server logic.
```bash
npm run test:unit # watch mode
npm run test # one run
npx vitest run src/uix/morfo/compile.test.ts # a single file
npx vitest run -t "describe name" # by test name
```
Component providers carry their own `{name}-provider.svelte.test.ts` under the
client project; the reusable engines they consume live in `$libs/datagrid`,
`$libs/forms`, `$libs/strings` and are tested there, not inside soma. See
[`SOMA_ARCHITECTURE.md`](../src/uix/soma/SOMA_ARCHITECTURE.md) §6 for the
per-component coverage map.
## Validation — what each script catches
These catch classes of bug that `check` (types) and an HTTP 200 (SSR) miss.
| Command | Catches |
| --- | --- |
| `npm run morfo:check` | the **real DOM vs the morfo contract** — navigates each demo and validates emitted `data-*` against the declaration. |
| `npm run morfo:vocabulary` | **canonical-verb / vocabulary drift** in morfo events (e.g. `open\|closed`, declared verbs not in `SEMA_VERBS`). Hard-fails CI on declared-verb drift. |
| `npm run component:audit` | the **acceptance matrix** of [`COMPONENT_COMPLETION_CHECKLIST.md`](../src/uix/COMPONENT_COMPLETION_CHECKLIST.md) — per-rule severity/applicability across all four layers + recipe + demo. |
| `npm run perm:check` | **state-transition** bugs — cycles a component through its declared states (via `data-perm-step` annotations) and re-validates morfo after each. Catches reactivity loops and transition-time drift `morfo:check` can't. |
| `npm run smoke` | **runtime / hydration** errors — walks every `+page.svelte` with Playwright and surfaces `pageerror`, `console.error`, missing translation keys, `Context "X" not found`, and `__uix_lang_missing__` markers. Needs `npm run dev` running. HTTP 200 is SSR only; smoke exercises client hydration. |
| `npm run docs:check` | **doc-corpus drift** — copied vocabulary counts vs the source consts, phantom fields (the legacy morfo text field; rejected API shapes), dependency claims vs `package.json`, the variant-vocab mirror in `component-audit.ts`, checklist↔audit rule-ID sync, and relative links (warn severity). Guards the "link the canon, never copy it" law of [`docs/authoring.md`](./authoring.md). |
| `scripts/eidos-lint.ts` · `eidos-lint-all.ts` | classifies every `[data-*]` selector in eidos CSS as **morfo-backed / eidos-only / invalid** — drift between the morfo contract and the CSS. (Opt-in safety net; the architectural defense is the typed `semaSelector` builder — see the "Eidos drift defense" rule in CLAUDE.md.) |
| `src/uix/contracts.test.ts` (vitest, via `npm run test`) | **catalogue invariants** that keep declarations / recipes coherent as the framework grows. Each fails on drift naming the offender, and excludes the active-dev-track set so it stays green for the maintained catalogue: **VG-8** every morfo is `as const satisfies Morfo`, never `: Morfo` (a `: Morfo` annotation widens the literal so the schema can't check it); **SYS-1 scope-drift** a component shipping an `eidos/components/{c}/` recipe declares `'eidos'` in `scope`; **A31** no per-item membership predicate (`isSelected` / `isItemPressed` / …) doing `.current.includes` (O(N²) — lift a `Set`, use `.has()`); **A30**`inputId` registered with the parent Field in the constructor, not wrapped in a `$effect`; **THEME-SYS-1** overlay z-index references the named `--z-index-overlay-*` scale, never a raw integer. |
Some surfaces are produced from a source of truth, not hand-maintained. Don't
edit the output; edit the source and regenerate.
| Command | Generates from |
| --- | --- |
| `npm run generate:eidos-css` | the eidos foundation/recipe CSS, from `EidosConfig.recipes`. (`generated/base.css` is output — regenerate, don't hand-edit.) |