You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/CLAUDE.md

351 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# CLAUDE.md
1. Think Before Coding
Don't assume. Don't hide confusion. Surface tradeoffs.
Before implementing:
State your 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.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
3. Surgical Changes
Touch only what you must. Clean up only your own mess.
When editing existing code:
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 imports/variables/functions that YOUR changes made unused.
Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
4. Goal-Driven Execution
Define success criteria. Loop until verified.
Transform tasks into verifiable goals:
"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"
For multi-step tasks, state a brief plan:
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
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`, `$clipboard`, `$color`, `$connection`, `$ethereal`, `$format`, `$http`, `$langs`, `$logger`, `$motion`, `$orca`, `$perf`, `$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, color, motion, perf, 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
```
**Motion** lives in `arts/motion` (`EngineMotion`, alias `$motion`), exposed as
`uix.motion` and consumed by BOTH soma (`Presence.motion` → `motion.run`) and
eidos (CSS generation + preset registration). It's an art — not part of eidos —
so soma can drive JS motion (spring) without a soma→eidos dependency. The DOM
arrives injected via a structural `MotionDom` port (`adom` satisfies it).
Two-moment model + F1–F7: `src/arts/motion/README.md` +
`docs/theming/motion.md`.
### 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.
- **Compose existing components; flag gaps.** When building a component (or its
demo, or any UI), compose the framework's existing soma/eidos components
(`Button`, `Field`, `Popover`, `Icon`, …) — never re-implement a primitive
inline. If a needed component is missing, **flag the gap** so it gets built as
a reusable component, never reinvented ad-hoc. Detail: `docs/guides/component-guide.md` §4.
- **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.
- **CSS token naming (eidos recipes)**: bare-prefixed by component, never
by layer. Public tokens: `--{component}-…` (e.g. `--dialog-content-bg`,
`--toggle-radius-md`). Internal recipe tokens: `--_{component}-…`.
Never `--eidos-…`, `--air-…`, `--terra-…`, `--soma-…`. Migrating
`air-` baselines means stripping the `air-` prefix, not replacing it.
- **DOM helpers**: `$adom` is the single public surface for DOM-related
functionality at component / soma level. It re-exports all of
`$libs/dom` wholesale, plus the reactive `createActiveDom` runtime
and stateful primitives (`BodyScrollLock`, `DOMContext`, `RovingFocusGroup`).
Components and soma NEVER import from `$libs/dom` directly —
consume `$adom`. Only `arts/adom/*` (the implementation), `arts/frontend`
(peer service fallback), and tests of `$libs/dom` may reach into it.
This includes responsive types (`ResponsiveProp<T>`, `Breakpoint`,
`Breakpoints`) — they travel with the runtime, so they live in `$adom`.
- **Iframe / popup correctness**: when you have a `uix.dom`, prefer
`uix.dom.getDocument(node?)` and `uix.dom.getWindow(node?)` over the
bare functions of the same name. The instance methods respect the
active-dom's `targetWindow`; the bare functions fall back to the
global `document` / `window`, which is wrong in iframe / popup /
happy-dom-test contexts.
- **Layout reads run post-layout, never sync-after-write**: layout-forcing reads
(`getBoundingClientRect`, `getComputedStyle`, `offset*`, `scroll*`, `client*`)
flush pending style + layout, so calling one synchronously right after a DOM /
style write forces a reflow mid-turn — the "[Violation] Forced reflow while
executing JavaScript" class of bug. Defer them: `dom.measure(read, node?)`
queues the read in a coalesced animation frame (the sanctioned vehicle), or run
it from a `dom.raf` callback. It's a *timing* rule, not an ownership one — a bare
deferred read complies; only the sync-after-write ordering is forbidden. The
dev-only `uix.perf` detector (opt-in `reflowDetector`) names violators at runtime
via Long Animation Frames. To turn a theme token into a concrete colour, use
`eidos.resolveToken(token)` (config + `$color`) — never a `getComputedStyle` probe.
## 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
**Start here**: [`docs/README.md`](docs/README.md) — the documentation entry point.
It maps the whole corpus (the strata, the reading order, and where every doc
lives) with task-oriented shortcuts ("I want to…"). New sessions and agents
should read it first to orient.
The layer reference lives in the book tree under `docs/` (old paths hold
stubs, so citations keep resolving):
- Architecture overview and motivations: [`docs/architecture/active-architecture.md`](docs/architecture/active-architecture.md)
- Per-layer chapters: [`architecture/morfo.md`](docs/architecture/morfo.md) ·
[`architecture/sema.md`](docs/architecture/sema.md) ·
[`architecture/soma.md`](docs/architecture/soma.md) +
[`architecture/soma-architecture.md`](docs/architecture/soma-architecture.md) ·
[`architecture/eidos.md`](docs/architecture/eidos.md)
- Building a component: [`docs/building-a-component.md`](docs/building-a-component.md)
(the one door) → [`guides/component-guide.md`](docs/guides/component-guide.md)
(build steps) + [`guides/completion-checklist.md`](docs/guides/completion-checklist.md)
(acceptance) + [`src/uix/eidos/components/README.md`](src/uix/eidos/components/README.md)
(the eidos pattern, in-place)
- Theming: [`docs/theming/reference.md`](docs/theming/reference.md) +
[`docs/canon/tsc.md`](docs/canon/tsc.md)
**The semantic vocabulary is [`docs/CANON.md`](docs/CANON.md)** — when in
doubt about families, intents, verbs, channels or holds, that's the source
(anchored to the book + code; deviations tracked in
[`docs/decisions/book-deviations.md`](docs/decisions/book-deviations.md)).
The old Spanish guide (`GUIA_IMPLEMENTACION_SEMAUIX`) is a historical seed,
NOT authoritative — it lives at
[`docs/decisions/guia-semantica-historica.md`](docs/decisions/guia-semantica-historica.md).
## Sema doctrine — operational rules
The doctrine lives in [`docs/CANON.md`](docs/CANON.md) (vocabulary: 8
families, 6 intents, verbs, channels, holds) and
[`docs/architecture/sema.md`](docs/architecture/sema.md) (engine, cascade
1·2·3·4·5a·5b, packs). Never copy their tables here or anywhere — link them.
The rules you must actively obey when touching sema:
- **Typed selector builder is mandatory.** Cascade rules in
`src/uix/sema/components/*.ts` construct their `selector` via
`semaSelector(morfo, partKebab, matchers?)` from `$uix/morfo`. A
hand-written selector string targeting morfo-emitted attrs is an
architecture violation — renames must break at type level, not drift.
- **Intent policy is two-axis** (`intentRequirement` type gate +
`intentGuidance` doctrinal hint), encoded in `SEMA_FAMILY_POLICY`
(`src/uix/sema/types.ts`) — the const IS the source; editing it reshapes
the discriminated unions. `commit` and `signal` require intent.
- **Channels split by owner**: sema executes `sound` + `haptic` only (plus
the `data-event-*` visual stamp during the hold); eidos materializes every
visual channel (motion / presence / depth / shape / color) in CSS. The
engine knows no DOM/CSS. Apps extend `SemaChannelSignatures` via
declaration merging. (Detail: `docs/theming/channels.md` + `docs/CANON.md`.)
- **Cascade rules add character, never the intent's evaluative profile.**
`intent.deltas` (layer 2) owns `pitch` / `gain` / `contour` (and per-intent
haptic `intensity`). A pack (layers 5a/5b) MUST NOT replace those
primitives — to shift one, compose with `{ op: 'add', value: … }`.
- **Per-component packs** live in `src/uix/sema/components/{name}.ts`;
packs compose `SOUND_TUNINGS`, never samples (exception: `signal` family
— [`book-deviations.md`](docs/decisions/book-deviations.md) D.7).
- Sound and haptic default **off** (audible/tactile side effects); haptic
opts in via `new EngineSemantic({ haptic: true })`.
## Eidos drift defense — types over lint
When a layer can consume the morfo via TypeScript (anything in `.ts` /
`.svelte`), it MUST use the typed builder (`semaSelector`); hand-written
morfo-targeting selector strings in TS are an architecture violation, not a
lint warning. Plain `.css` recipes have no builder yet — for those,
`scripts/eidos-lint.ts` is the opt-in safety net (classifies each `[data-*]`
selector as morfo-backed / eidos-only / invalid). The architectural contract
is the morfo declaration. (Detail: [`docs/architecture/eidos.md`](docs/architecture/eidos.md).)
---
# 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.

Powered by TurnKey Linux.