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. |
| `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.) |
## Codegen — what is generated vs authored
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.) |