;
assert>(props: P): P;
}
```
`syncAttrs: true` activa la escritura imperativa por `uix.dom` para partes
cuyos sources ya estan declarados en el runtime. Si un provider sigue
componiendo attrs en render props, no activa `syncAttrs`.
Dos interfaces de opts:
- `ProviderOpts` — `{ id: Active; ref?: State }` — para roots sin DOM
- `WithRefOpts` — `{ id: Active; ref: State }` — para parts con DOM
## 6. Layers
`layers/` contiene solo clases de comportamiento (`.svelte.ts`). Son infraestructura consumida por Providers, nunca directamente por el consumidor.
### Inventario
| Layer | API | Responsabilidad |
| ----------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `Presence` | `new Presence(opts)` | Mount/unmount con animaciones. `isPresent`, `transitionAttrs`, `onComplete`. |
| `FocusScope` | `FocusScope.use(opts)` | Focus trap, loop, auto-focus, restore. Singleton manager con stack. |
| `Dismissal` | `Dismissal.use(opts)` | Escape + click-outside. Registry global. Behaviors: close, ignore, defer. |
| `TextSelection` | `TextSelection.use(opts)` | Previene selection overflow durante drag. |
| `ScrollLock` | `new ScrollLock(initial?, delay?)` | Body scroll lock con refcount. Soporta delay para animaciones. |
| `ResizeObserver$` | `new ResizeObserver$(getter, cb)` | ResizeObserver con lifecycle Svelte. |
| `Floating*` | `FloatingProvider.create()`, `FloatingContent.create(opts)`, etc. | Posicionamiento relativo a anchor via @floating-ui. |
| `Gesture.base` | `Gesture.base(opts)` | Pointer tracking + axis lock + velocity. |
| `Gesture.drag` | `Gesture.drag(opts)` | Base + progress + snap points + dismiss. |
| `Gesture.resize` | `Gesture.resize(opts)` | Base + delta + min/max constraints. |
| `SafePolygon` | `new SafePolygon(opts)` | Hover-gap corridor between trigger↔content. |
`layers/floating/placement.ts` es la fuente unica para `Side`, `Align`,
`Boundary`, `SIDE_OPTIONS` y `ALIGN_OPTIONS`. `floating/types.ts` consume esa
fuente y no importa del runtime `floating.svelte.ts`, evitando ciclos entre
tipos y clases.
### Cobertura P0 actual
Los tests por componente estan creciendo desde las piezas de mayor riesgo. A
2026-05-15 existen tests directos para:
- `$libs/datagrid/table-core.svelte.test.ts` — motor de data grid.
- `$libs/forms/form-core.svelte.test.ts` — motor de formularios + Standard Schema.
- `dialog/dialog-provider.svelte.test.ts` — open/close, fallback target y
eventos morfo.
- `drawer/drawer-provider.svelte.test.ts` — direccion logica, dismiss y modo
persistent.
- `command/command-provider.svelte.test.ts` — filtro, visibilidad, navegación
y selección.
- `combobox/combobox-provider.svelte.test.ts` — selección, `inputValue` y
navegación sin items disabled.
- `select/select-provider.svelte.test.ts` — selección single/multiple y
navegación sin items disabled.
- `popover/popover-provider.svelte.test.ts` — toggle, hover timers y señales
runtime.
- `toast/toaster.svelte.test.ts` — overflow, dismiss/remove y promesas.
- `calendar/calendar-provider.svelte.test.ts` — placeholder, meses, selección
y flags.
- `range-calendar/range-calendar-provider.svelte.test.ts` — placeholder,
orden de endpoints y limites min/max.
- `date-field/date-field-provider.svelte.test.ts` — helpers UI de segmentos,
lectura DOM, navegación por `ActiveDom`, validación y commit segmentado.
- `date-picker/date-picker-provider.svelte.test.ts` — registro runtime y
cierre controlado por `closeOnDateSelect`.
- `date-range-picker/date-range-picker-provider.svelte.test.ts` — registro
runtime y cierre controlado por `closeOnRangeSelect`.
- `time-picker/time-picker-provider.svelte.test.ts` — registro runtime,
valores de placeholder y escritura de sliders al valor compartido.
- `time-range-picker/time-range-picker-provider.svelte.test.ts` — valores por
endpoint, escritura de sliders y cierre al completar rango.
- `time-range-field/time-range-field-provider.svelte.test.ts` — validación de
orden/min/max, validación custom y foco de label via `ActiveDom`.
- `time-field/time-field-provider.svelte.test.ts` — validación, sincronización
de segmentos 12h y commit solo cuando los segmentos renderizados estan
completos.
- `date-range-field/date-range-field-provider.svelte.test.ts` — validación de
orden/min/max, validación custom y foco de label via `ActiveDom`.
- `virtual-list/virtual-list-provider.svelte.test.ts` — cálculo de ventana,
`scrollToIndex` y compensación anti-jump via `ActiveDom`.
- `virtual-grid/virtual-grid-provider.svelte.test.ts` — cálculo de ventana 2D
y `scrollToCell` via `ActiveDom`.
- `number-field/number-field-provider.svelte.test.ts` — parsing localizable,
teclado spinbutton, triggers, props ARIA y scrubber.
- `file-upload/file-upload-provider.svelte.test.ts` — aceptación/rechazo,
dropzone, input oculto, items, progress y acciones remove/clear.
- `color-field/color-field-provider.svelte.test.ts` — commit por segmentos,
edición hex por teclado, selector de formato, input oculto y foco via
`ActiveDom`.
- `color-picker/color-picker-provider.svelte.test.ts` — helpers de canales,
trigger/value/hidden, area 2D, slider de canal y swatches.
- `navigation-menu/navigation-menu-provider.svelte.test.ts` — timers UIX,
enlace item/trigger/content, foco por teclado y props de list/link.
- `tree-grid/tree-grid-provider.svelte.test.ts` — expansión, selección/rango,
filas visibles, navegación y props de row/cell/header/expand trigger.
- `dropdown-menu/dropdown-menu-provider.svelte.test.ts` — trigger open/close,
scoping de items, selección, checkbox/radio groups, submenu y group/separator.
- `context-menu/context-menu-provider.svelte.test.ts` — apertura por
`contextmenu`, anchor virtual, scoping de items, selección, checkbox/radio,
submenu y group/separator.
- `menubar/menubar-provider.svelte.test.ts` — coordinación de menús hermanos,
hover-follow, navegación horizontal y cambio de menú desde content.
- `listbox/listbox-provider.svelte.test.ts` — props ARIA root, selección,
navegación, typeahead, indicador de item y grupos.
- `tree-view/tree-view-provider.svelte.test.ts` — expansión/selección,
navegación root, typeahead, props de ramas/hojas y partes auxiliares.
- `accordion/accordion-provider.svelte.test.ts` — modos single/multiple,
eventos `open/close` por item, navegación de triggers y header/content.
- `checkbox/checkbox-provider.svelte.test.ts` — commits check/uncheck,
indeterminate, hidden input y sincronización con `CheckboxGroup`.
- `radio-group/radio-group-provider.svelte.test.ts` — roving tabindex,
selección síncrona, navegación con auto-select, hidden input y guardas.
- `switch/switch-provider.svelte.test.ts` — `commit-toggle` runtime,
keyboard, hidden input, integración con `FieldProvider` y thumb.
- `toggle/toggle-provider.svelte.test.ts` — `commit-toggle` runtime, attrs DOM
via `ActiveDom`, integración con `FieldProvider` y guardas.
- `toggle-group/toggle-group-provider.svelte.test.ts` — modos single/multiple,
roving tabindex y navegación de items saltando disabled.
- `tabs/tabs-provider.svelte.test.ts` — registros trigger/content, activacion
automatic/manual, navegacion roving, fallback tab stop y partes auxiliares.
- `slider/slider-provider.svelte.test.ts` — contrato provider/range/thumb/tick,
snapping/clamping, drag pointer confirmado y teclado de thumb.
- `progress/progress-provider.svelte.test.ts` y
`meter/meter-provider.svelte.test.ts` — `data-value/data-min/data-max`
emitidos por morfo/runtime, estados derivados e indicador sincronizado.
- `collapsible/collapsible-provider.svelte.test.ts` — refs cruzadas
trigger/content, eventos `expand/collapse` y guardas disabled.
- `clipboard/clipboard-provider.svelte.test.ts` — copia via `uix.clipboard`,
reset con `uix.timers`, labels traducidos/override y errores `onError`.
- `tooltip/tooltip-provider.svelte.test.ts` — delay/close con `uix.timers`,
skip-delay de grupo, focus/blur, disabled y partes trigger/content/arrow.
- `field/field-provider.svelte.test.ts` — wiring de partes/ARIA, input
guardado por disabled/readonly e integración con parent Form.
- `search-field/search-field-provider.svelte.test.ts` — props root/input/clear,
input/submit/clear, foco y OR-merge con Field.
- `pagination/pagination-provider.svelte.test.ts` — rangos/ellipsis, clamp
reactivo, slice, triggers prev/next, item selected y disabled global.
- `breadcrumb/breadcrumb-provider.svelte.test.ts` — label/labelledby,
separator, link current con mirror al item y ellipsis decorativo/interactivo.
- `toolbar/toolbar-provider.svelte.test.ts` — roving focus, focusin, group
single/multiple y separator.
- `stepper/stepper-provider.svelte.test.ts` — conteo/estado de steps,
attrs runtime, selección lineal/no lineal, teclado y prev/next/completed.
- `scroll-area/scroll-area-provider.svelte.test.ts` — medición de overflow,
viewport, visibilidad/timers de scrollbar, track click, thumb y corner.
- `splitter/splitter-provider.svelte.test.ts` — registro de paneles, clamp,
collapse/expand y resize por pointer/teclado con attrs ARIA/data.
- `pin-input/pin-input-provider.svelte.test.ts` — props del input oculto,
merge con Field, patrón/paste, completion y estado de celdas/caret.
- `rating-group/rating-group-provider.svelte.test.ts` — slider attrs,
merge con Field, half hover/click, clearable y teclado LTR/RTL.
- `tag-group/tag-group-provider.svelte.test.ts` — labels/root grid, roving
por DOM renderizado, selección/removal, link y remove button.
- `announce/announce-provider.svelte.test.ts` — alternancia polite/assertive,
timers UIX, regiones ARIA declarativas y uso standalone.
- `feed/feed-provider.svelte.test.ts` — attrs APG feed/article/title,
navegación PageUp/PageDown, threads anidados y sentinel IntersectionObserver.
- `tags-input/tags-input-provider.svelte.test.ts` — add/paste/blur,
navegación de tags, delete/clear triggers y props de input/control.
- `editable/editable-provider.svelte.test.ts` — activación preview/trigger,
foco via `ActiveDom`, commit/cancel por teclado/blur y guards.
- `alert-dialog/alert-dialog-provider.svelte.test.ts` — contratos
action/cancel, labels traducidos/override y cierre delegado a Dialog.
- `carousel/carousel-provider.svelte.test.ts` — detección de slides,
navegación trigger/teclado, props de partes y autoplay via `uix.timers`.
- `grid-list/grid-list-provider.svelte.test.ts` — merge con Field,
selección, navegación/typeahead, celdas focusables y checkbox de fila.
- `link-preview/link-preview-provider.svelte.test.ts` — delays open/close
con `uix.timers`, touch guard, content hover y props Floating/Arrow.
- `table/table-provider.svelte.test.ts` — root/section/header/cell props
desde `$libs/datagrid`, selección de fila y disclosure `RowDetail`.
- `form/form-provider.svelte.test.ts` — submit inválido con foco via
`ActiveDom`, attrs runtime y partes Submit/Reset/ErrorSummary.
- `toast/toast-provider.svelte.test.ts` — viewport hotkey via `ActiveDom`,
runtime por item, ARIA/parts, auto-dismiss y close action.
- `drag-drop/drag-drop-provider.svelte.test.ts` — ruta de teclado
start/nav/drop, filtros `accept`, prevención de drag, cancel y preview.
La guardia `*-provider.svelte.ts` sin test directo devuelve
`NO_MISSING_PROVIDER_TESTS`: todos los providers Soma activos tienen cobertura
directa. `table-core`, `form-core` y el scorer de Command ya no viven dentro
de Soma: se consumen desde `$libs/datagrid`, `$libs/forms` y `$libs/strings`.
### Convencion
- `.use(opts)` → lifecycle auto-gestionado (watch/$effect internos). Constructor privado.
- `new X(opts)` → lifecycle manual. El consumidor controla.
- `.props` → objeto para spread en el Provider.
### Animaciones (Presence)
Lifecycle:
```
OPENING:
open=true → shouldRender=true + data-starting-style
→ next rAF: data-starting-style removed (triggers CSS transition)
→ getAnimations().finished → onComplete(true)
CLOSING:
open=false → data-ending-style (element stays in DOM!)
→ getAnimations().finished
→ shouldRender=false + data-ending-style removed → onComplete(false)
```
- `forceMount` mantiene el elemento en DOM siempre (para transiciones CSS)
- `onComplete` usa `getAnimations()` API, no eventos `transitionend`/`animationend`
- Run ID cancellation previene callbacks stale en toggle rapido
## 7. Soma class (component runtime scope)
Soma reads `ActiveUix` from context and exposes services to components.
Components import Soma internals through relative paths, never from
`$active-app` and never through their own `$soma/*` public alias. Nestable:
child `` overrides parent.
```ts
class Soma {
static create(opts?: SomaOptions): Soma; // factory + context set
static get(): Soma | undefined; // safe read
static require(): Soma; // throws if not found
readonly uix: ActiveUix;
readonly portalTo: string | HTMLElement | undefined;
// Service accessors (delegate to ActiveUix)
get langs(): ActiveLangs;
get nums(): ActiveNumbers | undefined;
get money(): ActiveCurrency | undefined;
get dates(): ActiveDates | undefined;
get units(): ActiveUnits | undefined;
get prefs(): ActiveUixPrefsView;
get logger(): EngineLogger;
}
```
### Service access from components
Components access services through Soma, never through App directly:
```ts
const soma = Soma.get();
soma?.langs.ts('#?common.buttons.close|Close'); // translation via idlangref
soma?.prefs.getDir(); // effective direction from uix.prefs.direction
soma?.money?.format(1099); // currency formatting
soma?.dates?.getDateOrder(); // DMY / MDY / YMD
soma?.dates?.getHourCycle(); // 12 | 24 (numeric — not '12h' / '24h')
soma?.portalTo; // portal target
```
### Date / time types and formatting
Soma imports date-related symbols from `$libs/days`, the canonical date library. Components **never** import from `$lib/util/dates` (legacy) or `@internationalized/date` directly. There is no Soma re-export façade for the date domain.
- Value types: `CalendarDate`, `CalendarDateTime`, `Time`, `ZonedDateTime`
- Types: `DateValue`, `TimeValue`, `DateRange`, `DateMatcher`, `Month`, `WeekStartsOn`, `HourCycle`, `TimeGranularity`, `DateOrder`, `Granularity`, `SegmentPart`, `EditableTimeSegmentPart`, `TimeSegmentObj`, `SegmentValueObj`, `DayPeriod`, …
- Queries: `isSameDay`, `hasTime`, `isZonedDateTime`, `isTimeBefore`, `isTimeAfter`, `today`, `now`, `startOfMonth`, `endOfMonth`, `getLastFirstDayOfWeek`, `getNextLastDayOfWeek`, …
- Operations: `dateValueToDate`, `convertTimeValueToDateValue`, `convertTimeValueToTime`, `toCalendarDate`, `toZoned`, …
- Parsing: `parseDate`, `parseDateTime`, `parseTime`
- Formatting: `DateFormatter`, `getCachedDateFormat`, `getPlaceholder`, `getDefaultDate`, `getDefaultTime`, `inferGranularity`, `inferTimeGranularity`, `getDefaultHourCycle`, `resolveDateOrder(locale)`, `resolveHourCycle(locale)`
- **Segments (dias/segments.ts)**: constants (`DATE_SEGMENT_PARTS`, `EDITABLE_TIME_SEGMENT_PARTS`, …), type guards (`isDateSegmentPart`, `isEditableTimeSegmentPart`, `isDateAndTimeSegmentObj`, …), pure helpers (`initializeSegmentValues`, `initializeTimeSegmentValues`, `getValueFromSegments`, `getTimeValueFromSegments`, `areAllSegmentsFilled`, `createSegmentContent`, `createTimeSegmentContent`, `getOptsByGranularity`, `getOptsByTimeGranularity`).
`HourCycle` is canonically the numeric form `12 | 24` across the whole framework, matching `Intl.DateTimeFormat`'s `hour12` resolved option. String forms like `'12h'`/`'24h'` are legacy and must not appear in new code.
**`soma/datetime/` holds only UI-level helpers** (screen-reader announcer, DOM segment navigation, `SegmentState` shape with `lastKeyZero`/`hasLeftFocus`/`updating`, `isAcceptableSegmentKey` using KEYS, description-element DOM writers). It must not re-export `$libs/days` symbols — consumers import from `$libs/days` directly. Extending `$libs/days` is the default for new date/time helpers; adding to `soma/datetime/` is only correct when the helper is genuinely UI-specific.
### Static method convention (project-wide)
All classes that use Svelte context follow the same pattern:
| Method | Returns | Use when |
| ---------------- | ----------------------- | --------------------------------- |
| `X.create(opts)` | instance | Creating + registering in context |
| `X.get()` | instance or `undefined` | Parent/context is optional |
| `X.require()` | instance (throws) | Parent/context is required |
This applies to `App`, `Soma`, and all state classes that use context. No
standalone functions. No `from()`. No `ctx` exposed.
### Texto funcional
Las traducciones propias del componente se declaran en el morfo:
```ts
export const drawerMorfo = {
name: 'Drawer',
kebab: 'drawer',
translations: {
trigger: { es: 'Abrir cajon', en: 'Open drawer' }
},
parts: [
{
name: 'Trigger',
kebab: 'trigger',
aria: [{ attr: 'aria-label', value: v.translationRef('trigger', 'Open drawer') }]
}
]
} as const satisfies Morfo;
```
Las traducciones compartidas no se duplican por componente:
```ts
value: v.commonRef('buttons.close', 'Close'); // #?common.buttons.close|Close
```
`ActiveUix` conecta el registro de morfos con `ActiveLangs`. Cuando el
provider crea `createSomaRuntime(morfo, sources)` o `soma.runtime(morfo,
sources)`, `registerMorfo(morfo)` registra el contrato `data-*` y publica
`morfo.translations` bajo `components.{kebab}`.
`commonLangs` en `src/uix/langs.ts` aporta los defaults de `common.*`.
`ActiveUix` los registra sin pisar hojas existentes, de modo que el
integrador puede pasar sus propias traducciones y UIX solo completa lo que
falte.
No existe catálogo global por componente. `ActiveUix` conecta el registro de
morfos; cada componente publica sus textos cuando su morfo se registra.
## 8. Sistema reactivo
Capa fina sobre runes de Svelte 5 que permite pasar estado reactivo por referencia entre clases.
- `state(initial)` → `State` (mutable, `.current`)
- `readableActive(() => value)` → `Active` (readonly derived)
- `writableActive(getter, setter)` → `State` (two-way binding)
Los wrappers `.svelte` convierten props normales a `Active`/`State` con estas funciones. Esta conversion es la frontera entre el mundo de props de Svelte y el mundo de clases reactivas de soma.
## 9. Contratos data-\*
Los `data-*` son API publica formal, validados con `assertContract()`.
Convencion:
```
data-dialog → provider (sin -provider, sin -root)
data-dialog-trigger → parte
data-dialog-content → parte
data-state="open|closed" → estado
data-disabled → flag
data-side="top|right|bottom|left" → posicion flotante
data-align="start|center|end" → alineacion
data-starting-style → animacion de entrada (1 frame)
data-ending-style → animacion de salida (persiste)
data-nested → es hijo de otro del mismo tipo
data-nested-open → tiene un hijo abierto
data-dragging → gesture drag activo
data-highlighted → item con virtual focus (aria-activedescendant)
data-resizing → splitter resize activo
```
CSS variables expuestas:
```
--floating-transform-origin
--floating-available-width
--floating-available-height
--floating-anchor-width
--floating-anchor-height
--dialog-depth
--dialog-nested-count
--drawer-progress → 0-1 drag progress
--drawer-offset-x / y → drag offset in px
--toast-swipe-move-x / y → toast swipe offset
```
## 10. IDs
Los IDs se generan con contexto de componente:
```
soma-dialog-c12
soma-dialog-trigger-c13
soma-dialog-content-c14
```
Pattern: `soma-{component}-{part}-{uid}`. Descriptivos e inspeccionables.
## 11. Barrel exports
### Componentes (jerárquico)
```ts
// $soma/components/index.ts
export * as Collapsible from './collapsible';
export * as Dialog from './dialog';
export * as Popover from './popover';
```
Consumo:
```ts
import { Dialog, Popover } from '$soma/components';
Dialog.Provider; // no SomaDialogProvider, no TerraDialogProvider
Dialog.Trigger;
```
### Internal
```ts
import { Portal, Arrow, VisuallyHidden, Soma } from '$soma/components/internal';
```
## 12. Fronteras externas
`soma` distingue entre:
- internos: `layers/`, `reactive/`, `dom/`, `provider/` — helpers propios
- externos: `@floating-ui/dom`, `runed`, `tabbable` — dependencias npm
Si una dependencia tiene API inestable o podria cambiar, se accede a traves de una frontera formal (como `layers/floating/` wrappea @floating-ui). Las dependencias estables (runed, svelte) se importan directamente.
## 13. Estructura del directorio
```
src/uix/soma/
├── SOMA_ARCHITECTURE.md ← this document
├── COMPONENT_GUIDE.md ← step-by-step implementation guide
├── README.md ← API reference
├── runtime.svelte.ts ← SomaRuntime (morfo interpreter)
├── errors.ts ← typed runtime/context errors
├── core/
│ └── soma.svelte.ts ← Soma class (root instance)
├── reactive/ ← reactive system
├── provider/ ← context + opts bridge
├── props/ ← mergeProps, composeHandlers
├── keyboard/ ← KEYS, directional
├── dom/ ← DOM utilities, focus
├── css/ ← styleToString, cssToStyleObj
├── id/ ← createId, useId
├── types/ ← shared types + service interfaces
├── layers/ ← behavior layers (classes only)
│ ├── presence.svelte.ts
│ ├── focus-scope.svelte.ts
│ ├── dismissal.svelte.ts
│ ├── text-selection.svelte.ts
│ ├── scroll-lock.svelte.ts
│ ├── resize-observer.svelte.ts
│ └── floating/
├── datetime/ ← UI-only helpers (announcer, segment DOM nav,
│ segment UI-state shapes, segment-key predicates,
│ description-element writers). NO date math,
│ NO re-exports of days — import `$libs/days`
│ directly.
├── components/
│ ├── internal/ ← Portal, Arrow, VisuallyHidden,
│ ├── {name}/ ← each headless component
│ │ ├── {name}-provider.svelte.ts ← state classes (NOT {name}.svelte.ts)
│ │ ├── types.ts ← public props + canonical field shapes
│ │ ├── langs.ts ← optional idlangref constants for imperative strings
│ │ ├── exports.ts
│ │ ├── index.ts
│ │ └── components/
│ │ ├── {name}.svelte ← root wrapper
│ │ ├── {name}-trigger.svelte
│ │ └── ...
│ └── index.ts ← hierarchical barrel
└── index.ts ← root scope only (`Soma`)
```
### File naming convention
- State class: `{name}-provider.svelte.ts` — NOT `{name}.svelte.ts`
- Avoids Vite module resolution ambiguity with `{name}.svelte` wrapper
- Reflects what's inside: provider/state classes
- Root wrapper: `{name}.svelte` in `components/` subdirectory
- Export name: always `Provider`, never `Root`
## 14. Anti-patterns
Avoid in soma:
- Complex logic inside wrapper `.svelte` — belongs in Provider
- Props drilling when context is the correct pattern
- `data-*` attrs outside of contract
- Inventing part names without checking reference library anatomies (ark-ui, bits-ui, radix-ui)
- Nesting layers as component wrappers in templates
- Inline `z-index: auto` that overrides CSS
- Coupling primitives to app libraries
- Product copy inside the primitive
- Speculative abstractions ("just in case")
- One-line files that only re-export (merge into parent)
- Redundant naming prefixes (SomaDialog, DialogLayerState)
- Dummy refs to satisfy a type — use `ProviderOpts` for no-DOM roots
- **State class file named same as wrapper** — `select.svelte.ts` + `components/select.svelte` causes Vite module duplication. Always use `{name}-provider.svelte.ts`
- **Event handlers not in props** — defining onclick as a class method but not including it in the props derived object
- **getContext in event handlers** — getContext only works during initialization. Capture references in constructor
- **Exporting as Root** — always `Provider`, never `Root`
- **Skipping reference library comparison** — mandatory step, no exceptions
- **Comments in Spanish** — all code comments in English
- **Standalone context functions** — no `createX()`, `getX()`, `useX()` as loose functions. Use `X.create()`, `X.get()`, `X.require()` static methods
- **Importing from `$lib/ext/app`** in components — components access services through `Soma`, never App directly
- **`from()` as factory name** — use `create()` consistently
- **Re-implementing date/time helpers inside soma** — extend `$libs/days` (A23). Importing from `$lib/util/dates` (legacy vendored) or `@internationalized/date` directly is forbidden; use `$libs/days`.
- **Re-export façades over days** — a soma module whose only job is to forward `$libs/days` symbols is dead weight. Consumers import from `$libs/days` directly.
- **UI-level helpers in dias, or date math in `soma/datetime/`** — dias is pure (no DOM, no Svelte, no KEYS); `soma/datetime/` is UI-only (screen-reader announcer, DOM segment navigation, `SegmentState` shapes, KEYS-based predicates). No crossover
- **`readonlySegments` without a concrete value anchor** — A24: warn via `soma?.logger.warn` when `value` is undefined. Range components split into `startReadonlySegments` / `endReadonlySegments` (A25)
- **`keydown.preventDefault()` as the only guard on contenteditable segments** — IME/paste/drop bypass keydown. Always add `onbeforeinput: e => e.preventDefault()` (A26)
- **Time placeholders as `'––'`** — use `createSegmentContent` / `createTimeSegmentContent` from `dias/segments.ts`; time parts render as `hh`/`mm`/`ss` (A28)
- **Pickers that reimplement field/calendar/popover** — compose via shared `writableActive` refs (A27). Only `Provider`, `Trigger`, and the calendar/slider bridge are unique parts
- **Demo pages as galleries of canned snippets** — every soma demo must be an interactive testbed wiring every public prop to a live control, including a Field-integration section (A29)
- **`HourCycle` as `'12h' \| '24h'`** — canonical form is numeric `12 \| 24` (matches `Intl.DateTimeFormat.hour12`). String forms are legacy
## 15. Estado actual y deuda histórica
Soma ha pasado por varias fases. La forma actual (rama `active-uix`,
post-2026-05-08):
- **Provider inheritance dropped** — los providers ya no heredan de un
base abstracto; son clases concretas. La mecanica DOM comun vive en
`SomaRuntime.part(...)`, y los casos que necesitan eventos semanticos
usan el mismo `SomaRuntime` para `trigger`/`keydown`.
- **SomaRuntime cachea** la compilación del morfo (`compileMorfo` por
WeakMap) y registra los `effects` que sincronizan `state → attrs` vía
`dom.apply`.
- **Naming**: `Provider` (nunca `Root`); child providers referencian al
padre como `provider`, nunca `root`. El export de un componente
multi-parte sigue la forma compound `Toggle.Provider + Toggle.Trigger
- ...`.
- **Data-attr naming**: `data-{component}` (provider) y
`data-{component}-{kebab}` (sub-parts). Sin prefijo `data-soma-*`. El
compiler emite estos via `compiled.parts.attrs`.
- **State files**: `{name}-provider.svelte.ts` (explicit, no ambiguity).
- **IDs**: descriptive (`soma-dialog-trigger-c13`).
## 16. Regla de estabilidad
Un componente de soma se considera estable cuando:
- su API publica esta clara y documentada con JSDoc
- sus `data-*` estan registrados y validados con `assertContract`
- el wrapper y el Provider siguen el patron general
- su accesibilidad base esta resuelta (ARIA, roles, keyboard)
- sus props se han comparado con ark-ui, bits-ui y radix-ui
- tiene demo page funcional en `web/routes/uix/components/{componente}/`
- no depende de hacks locales, z-index hardcodeados, ni demo CSS para sostenerse
- compila con 0 errores (`svelte-check`)
## 17. New component checklist
See `COMPONENT_GUIDE.md` for the full step-by-step process (27 general steps + 4 date/time specific, with rules A1–A29). Summary:
```
[ ] 1. Compare with ark-ui, bits-ui, radix-ui — feature table
[ ] 2. Verify membership criteria
[ ] 3. Define parts + attrs + morfo.translations when the component owns text
[ ] 4. Create types.ts (props + canonical field shapes)
[ ] 5. Create langs.ts only for imperative idlangref constants, not as the catalog
[ ] 6. Create {name}-provider.svelte.ts (concrete state classes, no Provider inheritance)
[ ] 7. Create wrapper .svelte files (thin)
[ ] 8. Create exports.ts + index.ts
[ ] 9. Create interactive demo page + link in index (A29)
[ ] 10. README.md with anatomy, ARIA, data-attrs, comparison table
[ ] 11. svelte-check + test in browser
[ ] 12. Date/time components: only consume date/time domain via `$libs/days`,
`onbeforeinput` on contenteditable, readonly-without-value warning,
picker composition pattern (A23–A28)
```