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.
291 lines
15 KiB
291 lines
15 KiB
# 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 27-item checklist in `src/uix/soma/COMPONENT_GUIDE.md`. 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
|
|
- 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.
|
|
- Soma audit: `src/uix/soma/AUDIT.md` — 4 pending issues (F3, F12, F13, F14). Read it before making architectural changes.
|
|
|
|
|
|
|
|
|
|
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.
|