|
|
# CLAUDE.md
|
|
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
|
|
> **Branch note (`active-uix`):** the legacy layers `src/lib/`, `src/uix/terra/`,
|
|
|
> `src/uix/air/` and the old `src/routes/` test tree were removed during the
|
|
|
> UIX refactor. The new ecosystem lives in `src/arts/`, `src/libs/`, `src/svrs/`,
|
|
|
> and `src/uix/{active-uix,soma,sema,eidos,morfo}`. Demos are being re-authored
|
|
|
> under `web/routes/` from scratch.
|
|
|
|
|
|
## Build / Test Commands
|
|
|
|
|
|
```bash
|
|
|
npm run dev # Start dev server (routes resolve from web/routes)
|
|
|
npm run build # Production build (static via adapter-static)
|
|
|
npm run test # Run all tests once
|
|
|
npm run test:unit # Run tests in watch mode
|
|
|
npx vitest run src/uix/morfo/compile.test.ts # Run a single test file
|
|
|
npx vitest run -t "describe name" # Run tests matching a pattern
|
|
|
npm run check # Type check with svelte-check
|
|
|
npm run lint # Check formatting (Prettier)
|
|
|
npm run format # Auto-format
|
|
|
```
|
|
|
|
|
|
## Code Style
|
|
|
|
|
|
- Tabs for indentation, single quotes, no trailing commas, 100 char print width (Prettier).
|
|
|
- Comments in **English**. (Some legacy Spanish comments remain — don't add new ones.)
|
|
|
- Svelte 5 runes mode is enforced globally (`svelte.config.js` `dynamicCompileOptions`).
|
|
|
- DOM event handlers: lowercase (`onclick`, `onfocus`). User callbacks: camelCase (`onComplete`, `onInteractOutside`).
|
|
|
- State: `$state()` without `#`. Derived: `readonly x = $derived.by(...)`.
|
|
|
|
|
|
## Path Aliases
|
|
|
|
|
|
Aliases are kept in sync between `svelte.config.js` and `vite.config.ts`. Source
|
|
|
of truth is `vite.config.ts` (single `aliases` const reused by both top-level
|
|
|
resolve and the server-test project).
|
|
|
|
|
|
| Alias | Target |
|
|
|
| --- | --- |
|
|
|
| `@` | `src/` |
|
|
|
| `$active-app`, `$adom`, `$auth`, `$bus`, `$cache`, `$connection`, `$format`, `$frontend`, `$http`, `$lang`, `$logger`, `$orca`, `$perm`, `$prefs`, `$session`, `$sium`, `$storage`, `$timer` | `src/arts/{name}` |
|
|
|
| `$libs`, `$locale`, `$reactive` | `src/libs`, `src/libs/locale`, `src/libs/reactive` |
|
|
|
| `$svrs` | `src/svrs/` |
|
|
|
| `$uix`, `$active-uix`, `$soma` | `src/uix`, `src/uix/active-uix`, `src/uix/soma` |
|
|
|
|
|
|
## Vitest Two-Project Structure
|
|
|
|
|
|
`vite.config.ts` defines two test projects:
|
|
|
|
|
|
- **client**: Browser tests via Playwright for `*.svelte.{test,spec}.{js,ts}`.
|
|
|
- **server**: Node environment for `*.{test,spec}.{js,ts}` (excludes svelte tests).
|
|
|
|
|
|
## Architecture
|
|
|
|
|
|
The UIX is layered around a declarative contract (morfo) that the other layers
|
|
|
consume. Each consumer reads the morfo via `compileMorfo(morfo)`, never by
|
|
|
walking the raw declaration.
|
|
|
|
|
|
```
|
|
|
arts/ → runtime artifacts: active-app, adom, perm, prefs, ...
|
|
|
libs/ → pure helpers (zero-dep): reactive, days, dom, locale, ...
|
|
|
svrs/ → server-authoritative engines
|
|
|
|
|
|
uix/morfo → declarative contract: parts, data-attrs, ARIA, keyboard,
|
|
|
events with semantic family/intent. Compiler at compile.ts
|
|
|
emits CompiledMorfo (cached by WeakMap).
|
|
|
uix/sema → semantic engine + channels (visual / sound / vibra).
|
|
|
The visual channel writes data-event* during a hold window.
|
|
|
State attrs (data-state, data-disabled, ...) are NEVER
|
|
|
touched by sema — they belong to the morfo runtime.
|
|
|
uix/soma → headless component layer. Each provider consumes morfo +
|
|
|
sema via `runtime.trigger(eventName)` which sequences:
|
|
|
prewrite → semantic.emit → handler → effect-driven state attrs.
|
|
|
uix/eidos → CSS layer that selects against the data-attrs the morfo
|
|
|
contract promises. lint.ts validates a CSS file against
|
|
|
compileMorfo(morfo).contracts.cssSelectors.
|
|
|
uix/active-uix → composition root. Two boot modes:
|
|
|
- createActiveUix(options) — standalone (owns services)
|
|
|
- attachActiveUix(activeApp) — attach to external app
|
|
|
```
|
|
|
|
|
|
### morfo (`src/uix/morfo/`)
|
|
|
|
|
|
Single source of truth for a component. A `Morfo` declares:
|
|
|
- `parts[]` — kebab + archetype + `data` + `aria` + optional `keyboard`
|
|
|
- `events[]` — name + `target` partRef + `semantic` + optional `prewrite` + `commits`
|
|
|
- `focus` — initial / trap / return / restore (for overlay components)
|
|
|
|
|
|
`compileMorfo(morfo)` returns a `CompiledMorfo` with pre-resolved `AttrPlan[]`,
|
|
|
`KeyboardPlan[]`, `ActionPlan[]`, and `contracts.cssSelectors` (the closed set
|
|
|
of selectors eidos is allowed to use). Cached by morfo identity.
|
|
|
|
|
|
### eidos linter
|
|
|
|
|
|
`scripts/eidos-lint.ts <component>` and `scripts/eidos-lint-all.ts` classify every
|
|
|
`[data-*]` selector in eidos CSS as morfo-backed / eidos-only / invalid. Use
|
|
|
this to detect drift between the morfo contract and the eidos rules.
|
|
|
|
|
|
## Key Conventions
|
|
|
|
|
|
- **Never modify the morfo declaration files** without updating the eidos
|
|
|
selectors and provider sources that depend on them.
|
|
|
- **Read before acting.** When told to read a file, read it. Don't paraphrase.
|
|
|
- **Verify before reporting done.** Run `npm run check` and the relevant
|
|
|
vitest scope. UI claims need a browser check; if you can't run a browser, say so.
|
|
|
- **No backward-compat shims** when relocating code. Update consumers and delete
|
|
|
the old path; don't leave a re-export.
|
|
|
- **No re-export façades** between layers (e.g. soma must not re-export dias —
|
|
|
consume `$libs/days` directly).
|
|
|
- **Provider naming**: child providers reference the parent as `provider`, never
|
|
|
`root`. The exported component is `<Xxx.Provider>`. `createAttrs` still uses
|
|
|
the part kebab `'provider'` internally.
|
|
|
- **Data-attr naming**: `data-{component}` (provider) and `data-{component}-{kebab}`
|
|
|
(sub-parts). Never `data-soma-*`. The compiler emits these via
|
|
|
`compiled.parts.attrs`; runtime queries match exactly.
|
|
|
|
|
|
## Critical Rules
|
|
|
|
|
|
- **NEVER delete files without explicit instruction** ("delete", "remove",
|
|
|
"borra", "elimina"). If ambiguous, ASK first.
|
|
|
- **NEVER create fake translators / mocked services** in tests for components.
|
|
|
Use real instances composed via `createActiveUix` or `attachActiveUix`.
|
|
|
- **NEVER skip hooks (`--no-verify`) or bypass signing** unless explicitly asked.
|
|
|
If a hook fails, fix the root cause.
|
|
|
- **`$reactive` survives, `$lib` does not.** The `$reactive` alias points to the
|
|
|
current `src/libs/reactive`; the singular `$lib` alias was removed in the
|
|
|
cleanup phase and any code that resurfaces it is a regression.
|
|
|
|
|
|
## Reference Documents
|
|
|
|
|
|
Architecture overview and motivations: [`src/uix/active_architecture.md`](src/uix/active_architecture.md).
|
|
|
|
|
|
Per-layer references:
|
|
|
- [`src/uix/morfo/README.md`](src/uix/morfo/README.md)
|
|
|
- [`src/uix/sema/README.md`](src/uix/sema/README.md)
|
|
|
- [`src/uix/soma/SOMA_ARCHITECTURE.md`](src/uix/soma/SOMA_ARCHITECTURE.md) +
|
|
|
[`src/uix/soma/COMPONENT_GUIDE.md`](src/uix/soma/COMPONENT_GUIDE.md)
|
|
|
- [`src/uix/eidos/README.md`](src/uix/eidos/README.md) +
|
|
|
[`src/uix/eidos/components/README.md`](src/uix/eidos/components/README.md)
|
|
|
|
|
|
The canonical guide for sema vocabulary, morfo event shape, color
|
|
|
tokens, persistence and a11ySemantic lives in
|
|
|
[`src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md`](src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md).
|
|
|
That document is authoritative — when in doubt about families, intents,
|
|
|
verbs, color subsets per component, or holds, that's the source.
|
|
|
|
|
|
## Session hand-off — 2026-05-08 (post-canon alignment)
|
|
|
|
|
|
The codebase has been aligned to the canonical guide in three swept
|
|
|
phases:
|
|
|
|
|
|
- **Sema verbs** (`src/uix/sema/verbs.ts`): restructured to
|
|
|
family-keyed `Record`. `select`/`toggle`/`acknowledge` moved to
|
|
|
`commit`; `edit` removed from `handle` (now expressed as
|
|
|
`shift.enter-mode`). New verbs added per the canon (tap, focus,
|
|
|
release, complete, restore, remind, route, step, syncing, etc.).
|
|
|
`validateEventName` recognises both `{verb}-{variant}` and
|
|
|
`{family}-{verb}` shapes.
|
|
|
- **Morfo event shape** (`src/uix/morfo/types.ts`): `target` moved
|
|
|
inside `semantic`; added optional `verb` (advisory, validated against
|
|
|
`SEMA_VERBS[family]`) and `sequence: 'pre' | 'coincident' | 'post'`.
|
|
|
All 22 events across toggle/toast/popover/drawer/dialog refactored
|
|
|
to the new shape.
|
|
|
- **Color tokens** (`src/uix/eidos/themes/base/{light,dark}.css`):
|
|
|
doctrinal 8 tokens — `primary, secondary, neutral, affirm, fulfill,
|
|
|
risk, threat, loss`. Renamed `success → fulfill`, `warning → risk`,
|
|
|
`danger → threat` (same hex). New primitives for `secondary`
|
|
|
(slate-blue), `affirm` (teal-mint, low activation), `loss` (deep
|
|
|
violet-grave, posterior). `info` palette deleted entirely. Legacy
|
|
|
aliases in `_static.css` deleted (clean cut, no transition).
|
|
|
29 consumers (recipe CSS + tokens) migrated to doctrinal names.
|
|
|
|
|
|
Earlier in the session:
|
|
|
|
|
|
- **Toggle is the eidos pilot** (`src/uix/eidos/components/toggle/`):
|
|
|
recipe + Svelte wrapper + types + index + README. The pattern is
|
|
|
documented in `src/uix/eidos/components/README.md`.
|
|
|
- **Eidos has a shared lib** at `src/uix/eidos/lib/types.ts`. First
|
|
|
type is `Size` (`'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'full'`).
|
|
|
Components narrow with `Extract<Size, 'sm' | 'md' | 'lg'>` per the
|
|
|
per-component-subset doctrine.
|
|
|
- **No `Eidos` prefix on types**. The path
|
|
|
`$uix/eidos/components/{x}` already conveys layer. Public types are
|
|
|
`ToggleProps`, `ToggleVariant`, `ToggleSize`.
|
|
|
- **API doctrine**: soma stays compound (`Toggle.Provider`); eidos
|
|
|
exports both `default` and `Provider` for single-part components so
|
|
|
`<Toggle>` works for the ergonomic case while `<Toggle.Provider>`
|
|
|
stays available.
|
|
|
- **SoundChannel eager-init landed** in `src/uix/sema/chans/sound.ts`
|
|
|
to fix the autoplay race: the AudioContext is created + resumed
|
|
|
synchronously on the first user gesture (capture-phase listener on
|
|
|
`document`).
|
|
|
- **Demo page (`web/routes/toggle/+page.svelte`)** has the live
|
|
|
preview rendered ALWAYS above the tablist (so Sema-tab Play buttons
|
|
|
can fire on the real toggle) and the motion preview amplifies scale
|
|
|
×8 visually only — doctrinal values stay in the `<dl>`.
|
|
|
|
|
|
Next concrete steps:
|
|
|
|
|
|
1. **Migrate `switch` to the eidos wrapper pattern.** Toggle is the
|
|
|
template. Switch already has the soma-side updated; needs
|
|
|
`eidos/components/switch/` subdirectory with `switch.css` recipe
|
|
|
(move from flat `components/switch.css`), `switch.svelte` wrapper,
|
|
|
`types.ts`, `index.ts`, `README.md`. Then a demo page at
|
|
|
`web/routes/switch/+page.svelte` mirroring toggle's tab structure.
|
|
|
After switch: `collapsible → dialog → drawer → popover → toast →
|
|
|
avatar`.
|
|
|
2. **Persistence + holds-by-intent** (guide §6) — defer until the
|
|
|
first concrete `signal.warn` / `signal.alert` consumer appears.
|
|
|
Today every signal is `transient` with a numeric hold. Need to add
|
|
|
`persistence: 'transient' | 'untilAction' | 'untilFix' | 'stateBound'`
|
|
|
to `SemanticSignal` + the holds-by-intent table per the canon.
|
|
|
3. **a11ySemantic per event** (guide §9) — defer until there's a
|
|
|
reduced-motion runtime helper + live-region helper to consume the
|
|
|
contract.
|
|
|
4. **Polymorphic events** (guide §5.3, `allowedFamilies` +
|
|
|
`defaultSemantic`) — defer; no current component needs polymorphism.
|
|
|
5. The docs site's topbar prefs (sound mute toggle, theme switcher)
|
|
|
are wired up but the sound mute does NOT yet drive
|
|
|
`SoundChannel.masterGain`. Wiring belongs on layout.svelte's `$effect`.
|
|
|
|
|
|
Baseline `npm run check` shows 39 pre-existing errors in test files
|
|
|
unrelated to this work. Tests are 217/217 green across uix.
|
|
|
|
|
|
---
|
|
|
|
|
|
# Behavioral Guidelines
|
|
|
|
|
|
These reduce common LLM coding mistakes. They bias toward caution over speed —
|
|
|
for trivial tasks, use judgment.
|
|
|
|
|
|
## 1. Think before coding
|
|
|
|
|
|
Don't assume. Don't hide confusion. Surface tradeoffs.
|
|
|
|
|
|
- State assumptions explicitly. If uncertain, ask.
|
|
|
- If multiple interpretations exist, present them — don't pick silently.
|
|
|
- If a simpler approach exists, say so. Push back when warranted.
|
|
|
- If something is unclear, stop. Name what's confusing. Ask.
|
|
|
|
|
|
## 2. Simplicity first
|
|
|
|
|
|
Minimum code that solves the problem. Nothing speculative.
|
|
|
|
|
|
- No features beyond what was asked.
|
|
|
- No abstractions for single-use code.
|
|
|
- No "flexibility" or "configurability" that wasn't requested.
|
|
|
- No error handling for impossible scenarios.
|
|
|
- If you write 200 lines and it could be 50, rewrite it.
|
|
|
|
|
|
## 3. Surgical changes
|
|
|
|
|
|
Touch only what you must. Clean up only your own mess.
|
|
|
|
|
|
- Don't "improve" adjacent code, comments, or formatting.
|
|
|
- Don't refactor things that aren't broken.
|
|
|
- Match existing style, even if you'd do it differently.
|
|
|
- If you notice unrelated dead code, mention it — don't delete it.
|
|
|
|
|
|
When your changes create orphans, remove the imports / variables / functions
|
|
|
your changes made unused. Don't remove pre-existing dead code unless asked.
|
|
|
|
|
|
## 4. Goal-driven execution
|
|
|
|
|
|
Define success criteria. Loop until verified.
|
|
|
|
|
|
- "Add validation" → "Write tests for invalid inputs, then make them pass."
|
|
|
- "Fix the bug" → "Write a test that reproduces it, then make it pass."
|
|
|
- "Refactor X" → "Ensure tests pass before and after."
|
|
|
|
|
|
Strong success criteria let you loop independently. Weak criteria require
|
|
|
constant clarification.
|