# 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 ` 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 ``. `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 doctrinal API conventions (intent ↔ color resolution, per-component subset, soma compound vs eidos flat, sound eager-init, etc.) live in [`src/docs/sema-implementation-guide.md`](src/docs/sema-implementation-guide.md) **Parte IV — Convenciones del API**. That section is authoritative for any new component or migration. Read it before designing a wrapper, declaring intents, or amplifying motion in a docs preview. ## Session hand-off — 2026-05-08 Where we left off: - **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`. Use it as the template to migrate the next component. - **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` 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 `` works for the ergonomic case while `` 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 `
`. Next concrete steps: 1. Migrate the next eidos component from flat CSS to the subdirectory wrapper pattern. Candidates in priority order: `switch` → `collapsible` → `dialog` → `drawer` → `popover` → `toast` → `avatar`. Each will need its own demo page under `web/routes/{name}/+page.svelte` mirroring toggle's tab structure. 2. 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`. 3. Theme global token rename — long-deferred. The 8 doctrinal tokens (`primary, secondary, neutral, affirm, fulfill, risk, threat, loss`) are referenced by the `data-color` recipes via fallback aliases (`success → fulfill`, `warning → risk`, `danger → threat`). When the theme files in `src/uix/eidos/themes/base/{light,dark}.css` adopt the doctrinal names directly, drop the fallback aliases in the per-component recipes. 4. `EidosToggleProps` was renamed; grep for any stale `Eidos*Props` reference before adding new ones — there should be none. Baseline `npm run check` shows 39 pre-existing errors in test files unrelated to this work. Sema tests are green (53/53). --- # 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.