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

16 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Build/Test Commands

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

<!-- 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.