7.1 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.jsdynamicCompileOptions) - 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 (globalization: dates, numbers, locale) |
@/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/config |
src/uix/terra/config/exports.ts |
$terra/external/dates |
src/uix/terra/external/dates/sound.svelte.ts |
$glob |
src/lib/glob |
$lib |
src/lib |
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
.sveltewrappers + logic in*.svelte.tsState 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'sContext) - Props merging:
mergeProps(restProps, state.props)in every wrapper data-*attrs are formal public API, validated viaassertDataContract()againstutils/contracts.tschildsnippet replaces the rendered node;childrenfills the interior
Structure:
[component]/components/— Svelte wrappers[component]/types.ts— public props[component]/*.svelte.ts— State classes[component]/exports.ts+sound.svelte.ts— barrel exportslayers/— 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)config/—TerraConfigfor 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:
- Primitive tokens (
tokens/primitive.css) — absolute scale values, never used directly by components - Semantic tokens (
tokens/semantic.css) — theme-aware, switch via[data-theme] - 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 implementcontract-categorical.css— optional categorical color familiescontract-primitives.css— optional advanced 12-step scalescontracts/components/*.css— per-component public token API
CSS rules:
- Tokens and
data-*selectors must be global CSS (imported.cssfiles), 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-themeattribute on any DOM container (supports nesting) - Variant dimensions are orthogonal:
size→ dimensions,variant→ appearance,state→ transversal overrides
Structure:
components/— styled wrappers over terra primitivestokens/— token implementation (primitive, semantic, per-component)contracts/— public token interfaces for theme authorsthemes/— theme implementations (base light/dark)icons/— icon system (Icon+ per-icon components inicons/lib/)
Internal Libraries (src/lib/)
ling— i18n system (factory:createLing)logr— logging (factory:createLogr, depends onling)glob— globalization: dates, numbers, currency, localeactx— app contextvice— app-level utilities
Important: DateValue and all date utilities come from @/glob/lib, never from @internationalized/date directly. Within terra, use $terra/external/dates.
Test Pages
Component demos live in src/routes/test/. Each terra/air component should have a test page demonstrating its states and variants.
TerraConfig Integration
<TerraConfig {translator} {dateTimeFormatter}>
<Dialog.Root />
</TerraConfig>
Priority chain for resolved props: explicit component prop → TerraConfig value → internal fallback. TerraConfig supports nesting (child overrides parent partially).
Key Conventions
- When creating terra components: follow the checklist in
src/uix/terra/README.md§21 - When reading existing modules: start with
exports.ts→types.ts→ root.svelte→*.svelte.ts→ child wrappers → test page $bindable()without fallback when parent might passundefined; apply defaults via coalescing (seeSVELTE5_BINDABLE_PATTERN.md)- Components that use Portal: consumer must manage
z-indexexplicitly (layers don't impose z-indices)