|
|
# 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`, `$agent`, `$auth`, `$bus`, `$cache`, `$clipboard`, `$color`, `$connection`, `$ethereal`, `$format`, `$http`, `$langs`, `$logger`, `$motion`, `$orca`, `$perf`, `$perm`, `$prefs`, `$scene`, `$session`, `$sium`, `$storage`, `$timer` | `src/arts/{name}` |
|
|
|
| `$libs`, `$locale`, `$reactive` | `src/libs`, `src/libs/locale`, `src/libs/reactive` |
|
|
|
| `$packs` | `src/packs` (tier de packs — `docs/architecture/packs.md`) |
|
|
|
| `$svrs` | `src/svrs/` |
|
|
|
| `$uix`, `$active-uix`, `$soma` | `src/uix`, `src/uix/active-uix`, `src/uix/soma` |
|
|
|
| `$blocks` | `src/uix/blocks` (tier de blocks — `docs/architecture/blocks.md`) |
|
|
|
|
|
|
## 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
|
|
|
uix/blocks → blocks TIER (not a layer): named compositions of canon
|
|
|
components performing a page function (site-header, hero,
|
|
|
app-shell…). No morfo, no audit row; B contract guarded by
|
|
|
`npm run blocks:check`. Doctrine: docs/architecture/blocks.md
|
|
|
```
|
|
|
|
|
|
**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.
|