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

292 lines
16 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
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build/Test Commands
```bash
npm run dev # Start dev server
npm run build # Production build (static site via adapter-static)
npm run test # Run all tests once
npm run test:unit # Run tests in watch mode
npx vitest run src/lib/ling/test/ling.test.ts # Run single test file
npx vitest run -t "describe name" # Run tests matching pattern
npm run check # Type check with svelte-check
npm run lint # Check formatting with Prettier
npm run format # Auto-format with Prettier
```
## Code Style
- Tabs for indentation, single quotes, no trailing commas, 100 char print width (Prettier)
- Spanish comments in code are acceptable
- Svelte 5 runes mode enforced globally (`svelte.config.js` `dynamicCompileOptions`)
- DOM event handlers: lowercase (`onclick`, `onfocus`). User callbacks: camelCase (`onComplete`, `onInteractOutside`)
- State fields: `$state()` without `#`. Derived: `readonly x = $derived.by(...)`
- Boolean attrs: always use helpers (`boolToTrueOrUndef`, `boolToEmptyStrOrUndef`, `boolToStr`) — never ternaries
## Path Aliases
Aliases must be synced in BOTH `svelte.config.js` AND `vite.config.ts`:
| Alias | Target |
| ----------------------- | ------------------------------------------------------ |
| `@/` | `src/` |
| `@/ling` | `src/lib/ling` (i18n) |
| `@/logr` | `src/lib/logr` (logging) |
| `@/glob` | `src/lib/glob` (legacy; do not use for new code) |
| `@/actx` | `src/lib/actx` (app context) |
| `@/uiux` | `src/lib/uiux` |
| `$uix` | `src/uix` (UI layers) |
| `$terra` | `src/uix/terra` (headless primitives) |
| `$terra/utils` | `src/uix/terra/utils/sound.svelte.ts` |
| `$terra/system` | `src/uix/terra/system/exports.ts` |
| `$terra/external/dates` | `src/uix/terra/external/dates/sound.svelte.ts` |
| `$lib` | `src/lib` |
| `$lib/util/dias` | `src/lib/util/dias` (date/time library — canonical) |
## 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: Three-Layer UI System
```
terra → headless primitives: behavior, accessibility, data-* contracts, context
air → visual layer on top of terra: tokens, themes, recipes, styled components
glob → domain logic: dates, numbers, currency, units, locale
```
The dependency flows one way: `air` → `terra`. Terra never imports from air.
### terra (`src/uix/terra/`)
Headless primitive library. Each component follows `Element.SubElement` pattern (`Dialog.Root`, `Dialog.Trigger`, `Dialog.Content`).
**Key patterns:**
- Thin `.svelte` wrappers + logic in `*.svelte.ts` State classes
- State classes use `static create()` for context registration, `createComposed()` for internal composition without context
- Reactive system: `Active<T>`, `State<T>`, `readableActive()`, `writableActive()` — values travel as boxes with `.current`
- Context via `createTerraContext()` (wraps runed's `Context`)
- Props merging: `mergeProps(restProps, state.props)` in every wrapper
- `data-*` attrs are formal public API, validated via `assertDataContract()` against `utils/contracts.ts`
- `child` snippet replaces the rendered node; `children` fills the interior
**Structure:**
- `[component]/components/` — Svelte wrappers
- `[component]/types.ts` — public props
- `[component]/*.svelte.ts` — State classes
- `[component]/exports.ts` + `sound.svelte.ts` — barrel exports
- `layers/` — shared infra (portal, floating, presence, focus-scope, dismissible, escape, scroll)
- `dom/` — browser-dependent utils (focus, roving focus, arrow navigation)
- `utils/` — pure helpers (state system, attrs, merge-props, events, keyboard, css, datetime)
- `system/` — `TerraConfig` for cross-cutting services (translator, formatters, logger, dir, portalTo)
- `external/dates/` — adapter boundary for date library
**Import rules within terra:** use `$terra/utils` or relative paths internally, never `$uix/terra`.
### air (`src/uix/air/`)
Visual layer built on terra. Consumes terra's public anatomy and `data-*` attrs — never internal classes or incidental DOM structure.
**Three-level token system:**
1. **Primitive tokens** (`tokens/primitive.css`) — absolute scale values, never used directly by components
2. **Semantic tokens** (`tokens/semantic.css`) — theme-aware, switch via `[data-theme]`
3. **Component tokens** (`tokens/components/*.css`) — scoped per component (`--air-button-*`), the only level component CSS writes
**Token naming:**
- Public: `--air-{component}-*` (stable, overridable)
- Private: `--_air-{component}-*` (implementation detail, may change)
**Contracts** (`contracts/`):
- `contract.css` — minimum semantic tokens any theme must implement
- `contract-categorical.css` — optional categorical color families
- `contract-primitives.css` — optional advanced 12-step scales
- `contracts/components/*.css` — per-component public token API
**CSS rules:**
- Tokens and `data-*` selectors must be global CSS (imported `.css` files), not `<style scoped>`
- Svelte scoped styles only for properties that don't depend on `data-*` or terra internals
- Components respond to terra states purely via `data-*` selectors: `[data-disabled]`, `[data-state='open']`, etc.
- Theme activation via `data-theme` attribute on any DOM container (supports nesting)
- Variant dimensions are orthogonal: `size` → dimensions, `variant` → appearance, `state` → transversal overrides
**Structure:**
- `components/` — styled wrappers over terra primitives
- `tokens/` — token implementation (primitive, semantic, per-component)
- `contracts/` — public token interfaces for theme authors
- `themes/` — theme implementations (base light/dark)
- `icons/` — icon system (`Icon` + per-icon components in `icons/lib/`)
### Internal Libraries (`src/lib/`)
- `ling` — i18n system (factory: `createLing`)
- `logr` — logging (factory: `createLogr`, depends on `ling`)
- `ext/` — App-layer reactive services: `app`, `dates`, `lang`, `money`, `nums`, `presentation`, `units`
- `util/dias` — canonical date/time library. Single public door at `$lib/util/dias`. `_vendor/` holds the internal implementation (Adobe Apache 2.0 vendored code) and MUST NOT be imported by consumers
- `glob` — legacy (pre-App-layer); do not use for new code
- `actx` — app context
- `vice` — app-level utilities
**Dates — canonical layout:**
- Value types (`CalendarDate`, `CalendarDateTime`, `Time`, `ZonedDateTime`), 9 calendars, queries, operations, parsing: all re-exported from `$lib/util/dias`
- All date-related `Intl` (`DateFormatter`, cache, `resolveDateOrder`, `resolveHourCycle`, placeholders, defaults) lives in `$lib/util/dias/format.ts`
- `ext/dates` is a thin reactive wrapper for App-level preferences (dateOrder, hourCycle) that delegates formatting to `dias`
- `HourCycle` is canonically numeric — `12 | 24` — matching `Intl.DateTimeFormat`'s `hour12` resolved option. Never `'12h' | '24h'`
- Soma consumes via `$soma/external/dates`; terra via `$terra/external/dates`. Both re-export from `dias`. Components never import `dias` directly
**Number formatting:** use `createNumr` from the App layer, NEVER raw `Intl.NumberFormat`.
### soma (`src/uix/soma/`)
Headless component library. Follows terra's architecture exactly — same patterns, same conventions. Uses `$soma` alias.
**Key patterns (must match terra):**
- Child providers reference parent as `provider`, NEVER `root`
- **The root export is always `Provider`, never `Root`**. Consumer writes `<Dialog.Provider>`, `<Calendar.Provider>`. `createAttrs` still uses the part name `'root'` internally — that is the DOM attribute part, not the export
- **Data-attr naming is canonical**: `data-{component}` (root) and `data-{component}-{part}` (sub-parts), emitted by `createAttrs`. **Never** `data-soma-*`. QuerySelectors, CSS selectors, README tables, and inline strings must all match exactly what `createAttrs` writes on the DOM — the contract validator does NOT check this (only enum values)
- `onChange` callbacks go in `writableActive` setter in the `.svelte` wrapper, NOT in provider Opts
- Translations: idlangref constants (`#?components.xxx.yyy|Fallback`) defined in the component's `langs.ts`. Root-level entries live in `src/uix/soma/core/langs.ts` under `components.{kebab-name}`. Providers call `soma?.langs.ts(IDLANGREF)` — no `resolveSomaTranslationPath` wrapper
- Props defined ONCE in `types.ts` (consumer API). Provider Opts only has reactive wrappers + internal fields
- `Soma.get()` returns the Soma config from context. `Soma.require()` throws when missing
- Test pages use shared layout at `src/routes/test/soma/+layout.svelte` with real `createLangs()` + `createPresentation()` + `App.create()`
**Structure:** mirrors terra exactly:
- `[component]/components/` — Svelte wrappers
- `[component]/types.ts` — public props with JSDoc
- `[component]/*-provider.svelte.ts` — Provider classes
- `[component]/exports.ts` + `index.ts` — barrel exports
- `core/` — Soma class, translator adapter, prop-resolvers, formatters
- `provider/` — base Provider class, context utility
- `layers/` — shared infra (floating, presence, focus-scope, dismissal, scroll-lock)
**Import rules within soma:** use `$soma/` alias paths, never relative paths across component boundaries.
### Test Pages
Component demos live in `src/routes/test/`. Each terra/air/soma component should have a test page demonstrating its states and variants.
Soma test pages share a layout at `src/routes/test/soma/+layout.svelte` that provides:
- Real ling instance with soma translations via `extendSomaTranslationModule`
- `createSomaTranslator` adapter connecting to ling
- Locale/dir switcher in a sticky bar
- `<Soma>` wrapper for all children
Test pages should NOT create their own `<Soma>` or translator — use the layout's.
## TerraConfig / Soma Integration
```svelte
<!-- Terra -->
<TerraConfig {translator} {dateTimeFormatter}>
<Dialog.Root />
</TerraConfig>
<!-- Soma -->
<Soma {translator} {presentation}>
<Dialog.Provider />
</Soma>
```
Priority chain for resolved props: explicit component prop → config context value → internal fallback. Both support nesting (child overrides parent partially).
## Key Conventions
- When creating terra components: follow the checklist in `src/uix/terra/README.md` §21
- When creating soma components: follow the full checklist in `src/uix/soma/COMPONENT_GUIDE.md` (rules A1–A37, 40 checklist items). Walk every item explicitly before reporting done — this is mandatory, not optional. Compare features against ark-ui, bits-ui, radix-ui, react-aria and document the gap table in the component's README. Run `npm run perm:check` (permutation runner) before shipping; instrument the demo with `data-perm-step="N"` annotations for every distinct state transition.
- When reading existing modules: start with `exports.ts` → `types.ts` → root `.svelte` → `*-provider.svelte.ts` → child wrappers → test page → README
- `$bindable()` without fallback when parent might pass `undefined`; apply defaults via coalescing (see `src/uix/terra/README.md` §13.1)
- Components that use Portal: consumer must manage `z-index` explicitly (layers don't impose z-indices)
- Every soma component ships with its own `README.md` following the format of `dialog/README.md` — anatomy, parts, props, ARIA, data-attrs, keyboard, comparison table, example
## Critical Rules
- **NEVER modify terra source code.** Terra is the reference architecture. Soma copies its patterns.
- **NEVER delete files without explicit instruction** ("delete", "remove", "borra", "elimina"). If ambiguous, ASK first. Reading costs nothing, deleting can be irreversible.
- **NEVER create fake translators** in test pages. Use real ling instances with `createSomaTranslator` + `extendSomaTranslationModule`.
- **READ before acting.** When told to read a file, read it. Don't interpret "léete" as "eléte".
- **Verify before reporting done.** Run `npm run check`, test the UI in the browser. `npm run smoke` and `npx tsx scripts/morfo-check.ts` must both be green — smoke proves 200 OK, morfo-check proves the DOM matches the morfo contract and catches `effect_update_depth_exceeded` (it shows up as "Execution context was destroyed" on the affected route — see COMPONENT_GUIDE A35).
- Soma audits: the two most recent are `src/uix/soma/soma-audit-2026-04-20.md` and `soma-audit-2026-04-21.md`. All their findings are resolved. Older audits (`AUDIT_1.md`, `codex_audit.md`) are historical. Read the latest audit before making architectural changes.
- Morfo layer: part naming is `kebab: 'provider'` for the root part (never `'root'`). `createAttrs` still emits `data-{component}` for it. See `src/uix/morfo/README.md`.
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 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.
---
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.

Powered by TurnKey Linux.