docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
SOMA_ARCHITECTURE.md:
- §3.4: Soma.getOr() → Soma.get(), removed Soma.translate()
- §3.6: translations via langs.ts() with idlangref, not Soma.translate()
- §6: added Gesture.base/drag/resize + SafePolygon to layers table
- §7: updated service access examples to use idlangref
- §7: rewritten Texto funcional section with langs.ts pattern
- §9: added data-dragging, data-highlighted, data-resizing, --drawer-* vars
- §13: added core/langs.ts, langs.ts in component structure
- §17: checklist synced with COMPONENT_GUIDE (10 steps)
COMPONENT_GUIDE.md:
- A15: exception for pure drag handles (immediate capture)
- A16: added focus return note for non-modal
- A17: virtual focus vs DOM focus strategy
- A18: registry pattern over DOM queries
- A19: SafePolygon for hover-gap components
- A20: exit animation via Presence (dismiss flow)
- A21: contract case normalization
- A22: Dismissal isValidEvent for complex widgets
README.md:
- SafePolygon added to layers table
- Patterns section: registry, virtual focus, exit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
# Soma Architecture
Documento de referencia arquitectonica para `src/uix/soma` .
`soma` es la capa de primitives headless del sistema. Reemplaza a `terra` con un rediseño que elimina el boilerplate, resuelve los problemas de dispersion, y establece patrones mas claros para el desarrollador de componentes.
## 1. Proposito
`soma` existe para dar una base comun sobre la que construir interfaces complejas sin repetir la misma logica de:
- contexto compartido
- control de foco
- teclado y puntero
- `aria-*`
- `data-*`
- sincronizacion de estado
- integracion con servicios transversales
- animaciones de entrada/salida
- posicionamiento flotante
Su objetivo no es ser una capa visual ni de producto. `soma` define primitives reutilizables y predecibles; la capa visual decide el look and feel.
## 2. Arquitectura de 3 capas
```
soma → headless: behavior, accesibilidad, data-* contracts, context, servicios
capa visual → apariencia: tokens, CSS, temas, motion, sound, semanticas
app → producto: composicion final, contenido, logica de negocio
```
Cada capa tiene responsabilidades estrictas:
### soma aporta
- comportamiento (keyboard, focus, dismiss, scroll lock)
- accesibilidad (ARIA, roles, live regions)
- contratos `data-*` estables y validados
- contexto y composicion de partes
- servicios de runtime (translator, format, logger)
- sistema de animaciones (presence, data-starting/ending-style, onComplete)
- posicionamiento flotante (@floating-ui)
### La capa visual aporta
- apariencia (tokens, colores, tipografia, spacing)
- tono visual (temas light/dark, variantes)
- decisiones de diseno opinionated (sizes, recipes)
- motion y sound semanticos
- responsive design
### La capa visual NUNCA
- importa state classes internas de soma
- depende de estructura DOM incidental
- accede a propiedades privadas
- duplica behavior que soma ya resuelve
- usa `data-*` fuera de los contratos publicados
La frontera es los `data-*` attrs y las CSS variables que soma expone.
## 3. Principios de diseno
### 3.1 El desarrollador no necesita conocer los internos
Los layers, el sistema reactivo, el floating engine — son implementacion interna. El desarrollador de componentes interactua con:
- `Provider` base class
- `Soma` class para servicios
- Barrel imports jerárquicos (`import { Dialog } from '$soma/components'`)
### 3.2 Un patron, no tres
Todo componente sigue el mismo patron:
1. State class extiende `Provider`
2. Wrapper `.svelte` fino convierte props → Active/State
3. Props derivados via `$derived.by` + `assertProps`
4. Contexto para comunicacion padre-hijo
No hay excepciones: components sin DOM usan `ProviderOpts` (ref opcional), components con DOM usan `WithRefOpts` .
### 3.3 Layers como behaviors, no como wrappers
Los layers se instancian en el constructor del Provider y exponen `.props` para merge. No hay nesting de componentes wrapper en template.
```ts
// Correcto: behaviors integrados
readonly focusScope = FocusScope.use({...});
readonly dismissal = Dismissal.use({...});
readonly props = $derived.by(() => ({
...this.baseProps,
...this.focusScope.props,
...this.dismissal.props,
}));
```
```svelte
<!-- Incorrecto: nesting de wrappers (patron terra) -->
< ScrollLock >
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
< FocusScope >
< DismissibleLayer >
{content}
< / DismissibleLayer >
< / FocusScope >
< / ScrollLock >
```
### 3.4 Soma es una clase, no una configuracion
`Soma` es la identidad runtime del framework. No es un archivo de configuracion — es el objeto raiz que provee servicios via context.
```ts
// En un Provider:
docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
SOMA_ARCHITECTURE.md:
- §3.4: Soma.getOr() → Soma.get(), removed Soma.translate()
- §3.6: translations via langs.ts() with idlangref, not Soma.translate()
- §6: added Gesture.base/drag/resize + SafePolygon to layers table
- §7: updated service access examples to use idlangref
- §7: rewritten Texto funcional section with langs.ts pattern
- §9: added data-dragging, data-highlighted, data-resizing, --drawer-* vars
- §13: added core/langs.ts, langs.ts in component structure
- §17: checklist synced with COMPONENT_GUIDE (10 steps)
COMPONENT_GUIDE.md:
- A15: exception for pure drag handles (immediate capture)
- A16: added focus return note for non-modal
- A17: virtual focus vs DOM focus strategy
- A18: registry pattern over DOM queries
- A19: SafePolygon for hover-gap components
- A20: exit animation via Presence (dismiss flow)
- A21: contract case normalization
- A22: Dismissal isValidEvent for complex widgets
README.md:
- SafePolygon added to layers table
- Patterns section: registry, virtual focus, exit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
readonly soma = Soma.get();
const dir = this.soma?.presentation.getDir();
```
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
### 3.5 Los data-\* son contrato publico
Los `data-*` attrs son la frontera entre soma y la capa visual. Cambiarlos es breaking change.
soma: dias date library, Calendar + RangeCalendar, Command fixes, doc updates
**Date library reorganisation** (no new third-party dependency)
- New `src/lib/util/dias/` with `_vendor/` (Adobe Apache 2.0 vendored code,
ATTRIBUTION.md preserved) and a public facade split into `types`,
`values`, `calendars`, `queries`, `operations`, `parse`, `format`,
`index`. Consumers import only from `$lib/util/dias`.
- All date-related `Intl` consolidated in `dias/format.ts` —
`DateFormatter`, cache, `resolveDateOrder`, `resolveHourCycle`,
placeholders, defaults.
- `ext/dates` now a thin reactive wrapper delegating to `dias`.
- Aligned App-layer `HourCycle` to canonical numeric form `12 | 24`
(matches `Intl.DateTimeFormat`'s `hour12`). Removed string form
`'12h' | '24h'` from `ext/app/types`, `ext/dates`,
`ext/presentation/locale-defaults`, and their tests.
- Repointed every consumer: `soma/external/dates`,
`terra/external/dates`, eight `routes/test/*` pages.
- Legacy `src/lib/util/dates/` left untouched by request.
**Tier 3 components**
- `soma/components/calendar/` — 14 parts, single + multiple selection,
`MonthSelect`/`YearSelect`, ISO/simple week numbers,
`isDateHoliday` matcher, `data-weekend` flag, navigation live-
region, RTL flip, full ARIA, 27-item checklist verified.
- `soma/components/range-calendar/` — 14 parts, range selection with
live hover preview (novel vs ark/bits/radix/react-aria), `minDays`
/ `maxDays` / `allowSingleDay`, third-click deselects only the
clicked endpoint.
- Test pages at `/test/soma/calendar` and `/test/soma/range-calendar`
with seven demos each. Links added to the soma index under a new
"Dates" section.
- READMEs with anatomy, parts, props, ARIA, keyboard, data-attrs, and
a competitive comparison table.
**Command fixes**
- Resolved O(N^2) reactive cascade: introduced
`visibleSet: $derived<Set<string>>` on the root provider;
per-item `isVisible` is now an O(1) `has()` lookup instead of
calling back into the provider, which was re-running all items'
derivations on every `itemsVersion` bump.
- Added `dir` prop with fallback to `soma.presentation.getDir()`;
horizontal arrows flip in RTL via `getDirectionalKeys`; root
receives `dir` attribute.
**Doc updates (session outcomes)**
- `CLAUDE.md`: replaced obsolete `@/glob/lib` date references with
`$lib/util/dias`; added canonical date-layer section; reinforced
`Provider` export rule and `data-{component}[-{part}]` naming
(never `data-soma-*`); clarified translation convention.
- `src/uix/soma/COMPONENT_GUIDE.md`: new checklist items 26
(component README required) and 27 (grep for data-attr
consistency); new Common Mistakes 11 (`data-soma-*` prefix), 12
(wrong date module), 13 (string `HourCycle`).
- `src/uix/soma/SOMA_ARCHITECTURE.md`: clarified data-attr naming
(canonical emitter is `createAttrs`; validator checks enum
values only, not names); new section covering date types and
formatting through `$soma/external/dates` → `$lib/util/dias`;
documented numeric `HourCycle`.
**Infrastructure**
- `PrimitiveHeaderAttributes` and `PrimitiveSelectAttributes` added
to `$soma/types`.
- `getLastFirstDayOfWeek`, `getNextLastDayOfWeek` re-exported from
`$soma/external/dates`.
- `calendar.*` and `range-calendar.*` translation blocks added to
`$soma/core/langs.ts`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
Convencion (obligatoria, sin excepciones):
soma: dias date library, Calendar + RangeCalendar, Command fixes, doc updates
**Date library reorganisation** (no new third-party dependency)
- New `src/lib/util/dias/` with `_vendor/` (Adobe Apache 2.0 vendored code,
ATTRIBUTION.md preserved) and a public facade split into `types`,
`values`, `calendars`, `queries`, `operations`, `parse`, `format`,
`index`. Consumers import only from `$lib/util/dias`.
- All date-related `Intl` consolidated in `dias/format.ts` —
`DateFormatter`, cache, `resolveDateOrder`, `resolveHourCycle`,
placeholders, defaults.
- `ext/dates` now a thin reactive wrapper delegating to `dias`.
- Aligned App-layer `HourCycle` to canonical numeric form `12 | 24`
(matches `Intl.DateTimeFormat`'s `hour12`). Removed string form
`'12h' | '24h'` from `ext/app/types`, `ext/dates`,
`ext/presentation/locale-defaults`, and their tests.
- Repointed every consumer: `soma/external/dates`,
`terra/external/dates`, eight `routes/test/*` pages.
- Legacy `src/lib/util/dates/` left untouched by request.
**Tier 3 components**
- `soma/components/calendar/` — 14 parts, single + multiple selection,
`MonthSelect`/`YearSelect`, ISO/simple week numbers,
`isDateHoliday` matcher, `data-weekend` flag, navigation live-
region, RTL flip, full ARIA, 27-item checklist verified.
- `soma/components/range-calendar/` — 14 parts, range selection with
live hover preview (novel vs ark/bits/radix/react-aria), `minDays`
/ `maxDays` / `allowSingleDay`, third-click deselects only the
clicked endpoint.
- Test pages at `/test/soma/calendar` and `/test/soma/range-calendar`
with seven demos each. Links added to the soma index under a new
"Dates" section.
- READMEs with anatomy, parts, props, ARIA, keyboard, data-attrs, and
a competitive comparison table.
**Command fixes**
- Resolved O(N^2) reactive cascade: introduced
`visibleSet: $derived<Set<string>>` on the root provider;
per-item `isVisible` is now an O(1) `has()` lookup instead of
calling back into the provider, which was re-running all items'
derivations on every `itemsVersion` bump.
- Added `dir` prop with fallback to `soma.presentation.getDir()`;
horizontal arrows flip in RTL via `getDirectionalKeys`; root
receives `dir` attribute.
**Doc updates (session outcomes)**
- `CLAUDE.md`: replaced obsolete `@/glob/lib` date references with
`$lib/util/dias`; added canonical date-layer section; reinforced
`Provider` export rule and `data-{component}[-{part}]` naming
(never `data-soma-*`); clarified translation convention.
- `src/uix/soma/COMPONENT_GUIDE.md`: new checklist items 26
(component README required) and 27 (grep for data-attr
consistency); new Common Mistakes 11 (`data-soma-*` prefix), 12
(wrong date module), 13 (string `HourCycle`).
- `src/uix/soma/SOMA_ARCHITECTURE.md`: clarified data-attr naming
(canonical emitter is `createAttrs`; validator checks enum
values only, not names); new section covering date types and
formatting through `$soma/external/dates` → `$lib/util/dias`;
documented numeric `HourCycle`.
**Infrastructure**
- `PrimitiveHeaderAttributes` and `PrimitiveSelectAttributes` added
to `$soma/types`.
- `getLastFirstDayOfWeek`, `getNextLastDayOfWeek` re-exported from
`$soma/external/dates`.
- `calendar.*` and `range-calendar.*` translation blocks added to
`$soma/core/langs.ts`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
- provider: `data-{component}` (no `data-{component}-provider` , **no `data-soma-*`** )
- parte: `data-{component}-{part}`
- estado: `data-state` , `data-disabled` , `data-side` , `data-align` , `data-orientation`
- animacion: `data-starting-style` , `data-ending-style`
- nesting: `data-nested` , `data-nested-open`
soma: dias date library, Calendar + RangeCalendar, Command fixes, doc updates
**Date library reorganisation** (no new third-party dependency)
- New `src/lib/util/dias/` with `_vendor/` (Adobe Apache 2.0 vendored code,
ATTRIBUTION.md preserved) and a public facade split into `types`,
`values`, `calendars`, `queries`, `operations`, `parse`, `format`,
`index`. Consumers import only from `$lib/util/dias`.
- All date-related `Intl` consolidated in `dias/format.ts` —
`DateFormatter`, cache, `resolveDateOrder`, `resolveHourCycle`,
placeholders, defaults.
- `ext/dates` now a thin reactive wrapper delegating to `dias`.
- Aligned App-layer `HourCycle` to canonical numeric form `12 | 24`
(matches `Intl.DateTimeFormat`'s `hour12`). Removed string form
`'12h' | '24h'` from `ext/app/types`, `ext/dates`,
`ext/presentation/locale-defaults`, and their tests.
- Repointed every consumer: `soma/external/dates`,
`terra/external/dates`, eight `routes/test/*` pages.
- Legacy `src/lib/util/dates/` left untouched by request.
**Tier 3 components**
- `soma/components/calendar/` — 14 parts, single + multiple selection,
`MonthSelect`/`YearSelect`, ISO/simple week numbers,
`isDateHoliday` matcher, `data-weekend` flag, navigation live-
region, RTL flip, full ARIA, 27-item checklist verified.
- `soma/components/range-calendar/` — 14 parts, range selection with
live hover preview (novel vs ark/bits/radix/react-aria), `minDays`
/ `maxDays` / `allowSingleDay`, third-click deselects only the
clicked endpoint.
- Test pages at `/test/soma/calendar` and `/test/soma/range-calendar`
with seven demos each. Links added to the soma index under a new
"Dates" section.
- READMEs with anatomy, parts, props, ARIA, keyboard, data-attrs, and
a competitive comparison table.
**Command fixes**
- Resolved O(N^2) reactive cascade: introduced
`visibleSet: $derived<Set<string>>` on the root provider;
per-item `isVisible` is now an O(1) `has()` lookup instead of
calling back into the provider, which was re-running all items'
derivations on every `itemsVersion` bump.
- Added `dir` prop with fallback to `soma.presentation.getDir()`;
horizontal arrows flip in RTL via `getDirectionalKeys`; root
receives `dir` attribute.
**Doc updates (session outcomes)**
- `CLAUDE.md`: replaced obsolete `@/glob/lib` date references with
`$lib/util/dias`; added canonical date-layer section; reinforced
`Provider` export rule and `data-{component}[-{part}]` naming
(never `data-soma-*`); clarified translation convention.
- `src/uix/soma/COMPONENT_GUIDE.md`: new checklist items 26
(component README required) and 27 (grep for data-attr
consistency); new Common Mistakes 11 (`data-soma-*` prefix), 12
(wrong date module), 13 (string `HourCycle`).
- `src/uix/soma/SOMA_ARCHITECTURE.md`: clarified data-attr naming
(canonical emitter is `createAttrs`; validator checks enum
values only, not names); new section covering date types and
formatting through `$soma/external/dates` → `$lib/util/dias`;
documented numeric `HourCycle`.
**Infrastructure**
- `PrimitiveHeaderAttributes` and `PrimitiveSelectAttributes` added
to `$soma/types`.
- `getLastFirstDayOfWeek`, `getNextLastDayOfWeek` re-exported from
`$soma/external/dates`.
- `calendar.*` and `range-calendar.*` translation blocks added to
`$soma/core/langs.ts`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
Los nombres los emite exclusivamente `createAttrs({ component, parts })` . Cualquier `querySelector` , selector CSS, cadena en README o snippet debe coincidir exactamente con lo que `createAttrs` escribe en el DOM. El validador de contratos (`assertContract`) solo verifica valores enumerados, no nombres ni presencia — la consistencia de nombres es responsabilidad del autor del componente (checklist item 27).
### 3.6 La accesibilidad base no se delega
soma resuelve ARIA por defecto. El consumidor no necesita añadir `role` , `aria-modal` , `aria-expanded` , `aria-controls` , etc. — el Provider los genera.
docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
SOMA_ARCHITECTURE.md:
- §3.4: Soma.getOr() → Soma.get(), removed Soma.translate()
- §3.6: translations via langs.ts() with idlangref, not Soma.translate()
- §6: added Gesture.base/drag/resize + SafePolygon to layers table
- §7: updated service access examples to use idlangref
- §7: rewritten Texto funcional section with langs.ts pattern
- §9: added data-dragging, data-highlighted, data-resizing, --drawer-* vars
- §13: added core/langs.ts, langs.ts in component structure
- §17: checklist synced with COMPONENT_GUIDE (10 steps)
COMPONENT_GUIDE.md:
- A15: exception for pure drag handles (immediate capture)
- A16: added focus return note for non-modal
- A17: virtual focus vs DOM focus strategy
- A18: registry pattern over DOM queries
- A19: SafePolygon for hover-gap components
- A20: exit animation via Presence (dismiss flow)
- A21: contract case normalization
- A22: Dismissal isValidEvent for complex widgets
README.md:
- SafePolygon added to layers table
- Patterns section: registry, virtual focus, exit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
Texto funcional (close, cancel, etc.) se resuelve via `langs.ts()` con idlangref: `#?common.buttons.close|Close` . Cada componente define sus constantes en `langs.ts` . Las traducciones common viven en la raiz de langs, las de componente bajo `components.*` .
### 3.7 Props documentadas obligatoriamente
Todas las props de todos los componentes llevan JSDoc en `types.ts` . Cada prop: descripcion, `@default` , notas de comportamiento.
### 3.8 Comparacion con referencias
Cada componente se compara con ark-ui, bits-ui y radix-ui antes de implementar. Se documentan las props que otros tienen y soma no, con justificacion.
## 4. Modelo de componente
La forma base de soma es `Componente.Parte` :
```ts
import { Dialog } from '$soma/components';
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
Dialog.Provider; // root — crea contexto
Dialog.Trigger; // accion — abre/cierra
Dialog.Content; // contenido — layers integrados
Dialog.Overlay; // fondo — presence
Dialog.Title; // metadata ARIA
Dialog.Description; // metadata ARIA
Dialog.Close; // accion — cierra
```
### Provider (root)
Crea el estado central, lo registra en context, gestiona presence para content y overlay. Puede o no renderizar DOM:
- **Con DOM** (Collapsible, Accordion): extiende `Provider<WithRefOpts>` , renderiza `<div>`
- **Sin DOM** (Dialog, Popover): extiende `Provider<ProviderOpts>` , solo renderiza children
### Subcomponentes
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
Leen estado del root via `.require()` . No reimplementan logica — derivan props, ARIA, data-\*, events del estado del padre.
### Portal
Componente interno (`components/internal/portal.svelte`). Renderiza hijos en otro nodo DOM. El contexto de Svelte se preserva.
## 5. Provider base class
```ts
abstract class Provider< S extends ProviderOpts > {
readonly opts: S;
readonly attachment: RefAttachment | undefined;
protected constructor(opts, component, part, partAttr, ctx?, onRefChange?);
protected get baseProps(): { id, [partAttr], ...attachment };
protected assertProps< P > (props: P): P; // valida data-* contract
}
```
Dos interfaces de opts:
- `ProviderOpts` — `{ id: Active<string>; ref?: State<HTMLElement | null> }` — para roots sin DOM
- `WithRefOpts` — `{ id: Active<string>; ref: State<HTMLElement | null> }` — 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
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
| 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. |
docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
SOMA_ARCHITECTURE.md:
- §3.4: Soma.getOr() → Soma.get(), removed Soma.translate()
- §3.6: translations via langs.ts() with idlangref, not Soma.translate()
- §6: added Gesture.base/drag/resize + SafePolygon to layers table
- §7: updated service access examples to use idlangref
- §7: rewritten Texto funcional section with langs.ts pattern
- §9: added data-dragging, data-highlighted, data-resizing, --drawer-* vars
- §13: added core/langs.ts, langs.ts in component structure
- §17: checklist synced with COMPONENT_GUIDE (10 steps)
COMPONENT_GUIDE.md:
- A15: exception for pure drag handles (immediate capture)
- A16: added focus return note for non-modal
- A17: virtual focus vs DOM focus strategy
- A18: registry pattern over DOM queries
- A19: SafePolygon for hover-gap components
- A20: exit animation via Presence (dismiss flow)
- A21: contract case normalization
- A22: Dismissal isValidEvent for complex widgets
README.md:
- SafePolygon added to layers table
- Patterns section: registry, virtual focus, exit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
| `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. |
### 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
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
## 7. Soma class (component runtime scope)
Soma reads App from context and exposes services to components. Components import from `$soma` , never from `$lib/ext/app` . Nestable: child `<Soma portalTo="#modals">` overrides parent.
```ts
class Soma {
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
static create(opts?: SomaOptions): Soma; // factory + context set
static get(): Soma | undefined; // safe read
static require(): Soma; // throws if not found
readonly app: App;
readonly portalTo: string | HTMLElement | undefined;
// Service accessors (delegate to App)
get langs(): AppLangs;
get nums(): AppNums | undefined;
get money(): AppMoney | undefined;
get dates(): AppDates | undefined;
get units(): AppUnits | undefined;
get presentation(): AppPresentation;
get logger(): AppLogger;
}
```
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
### Service access from components
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
Components access services through Soma, never through App directly:
```ts
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
const soma = Soma.get();
docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
SOMA_ARCHITECTURE.md:
- §3.4: Soma.getOr() → Soma.get(), removed Soma.translate()
- §3.6: translations via langs.ts() with idlangref, not Soma.translate()
- §6: added Gesture.base/drag/resize + SafePolygon to layers table
- §7: updated service access examples to use idlangref
- §7: rewritten Texto funcional section with langs.ts pattern
- §9: added data-dragging, data-highlighted, data-resizing, --drawer-* vars
- §13: added core/langs.ts, langs.ts in component structure
- §17: checklist synced with COMPONENT_GUIDE (10 steps)
COMPONENT_GUIDE.md:
- A15: exception for pure drag handles (immediate capture)
- A16: added focus return note for non-modal
- A17: virtual focus vs DOM focus strategy
- A18: registry pattern over DOM queries
- A19: SafePolygon for hover-gap components
- A20: exit animation via Presence (dismiss flow)
- A21: contract case normalization
- A22: Dismissal isValidEvent for complex widgets
README.md:
- SafePolygon added to layers table
- Patterns section: registry, virtual focus, exit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
soma?.langs.ts('#?common.buttons.close|Close'); // translation via idlangref
soma?.presentation.getDir(); // direction
soma?.money?.format(1099); // currency formatting
soma: dias date library, Calendar + RangeCalendar, Command fixes, doc updates
**Date library reorganisation** (no new third-party dependency)
- New `src/lib/util/dias/` with `_vendor/` (Adobe Apache 2.0 vendored code,
ATTRIBUTION.md preserved) and a public facade split into `types`,
`values`, `calendars`, `queries`, `operations`, `parse`, `format`,
`index`. Consumers import only from `$lib/util/dias`.
- All date-related `Intl` consolidated in `dias/format.ts` —
`DateFormatter`, cache, `resolveDateOrder`, `resolveHourCycle`,
placeholders, defaults.
- `ext/dates` now a thin reactive wrapper delegating to `dias`.
- Aligned App-layer `HourCycle` to canonical numeric form `12 | 24`
(matches `Intl.DateTimeFormat`'s `hour12`). Removed string form
`'12h' | '24h'` from `ext/app/types`, `ext/dates`,
`ext/presentation/locale-defaults`, and their tests.
- Repointed every consumer: `soma/external/dates`,
`terra/external/dates`, eight `routes/test/*` pages.
- Legacy `src/lib/util/dates/` left untouched by request.
**Tier 3 components**
- `soma/components/calendar/` — 14 parts, single + multiple selection,
`MonthSelect`/`YearSelect`, ISO/simple week numbers,
`isDateHoliday` matcher, `data-weekend` flag, navigation live-
region, RTL flip, full ARIA, 27-item checklist verified.
- `soma/components/range-calendar/` — 14 parts, range selection with
live hover preview (novel vs ark/bits/radix/react-aria), `minDays`
/ `maxDays` / `allowSingleDay`, third-click deselects only the
clicked endpoint.
- Test pages at `/test/soma/calendar` and `/test/soma/range-calendar`
with seven demos each. Links added to the soma index under a new
"Dates" section.
- READMEs with anatomy, parts, props, ARIA, keyboard, data-attrs, and
a competitive comparison table.
**Command fixes**
- Resolved O(N^2) reactive cascade: introduced
`visibleSet: $derived<Set<string>>` on the root provider;
per-item `isVisible` is now an O(1) `has()` lookup instead of
calling back into the provider, which was re-running all items'
derivations on every `itemsVersion` bump.
- Added `dir` prop with fallback to `soma.presentation.getDir()`;
horizontal arrows flip in RTL via `getDirectionalKeys`; root
receives `dir` attribute.
**Doc updates (session outcomes)**
- `CLAUDE.md`: replaced obsolete `@/glob/lib` date references with
`$lib/util/dias`; added canonical date-layer section; reinforced
`Provider` export rule and `data-{component}[-{part}]` naming
(never `data-soma-*`); clarified translation convention.
- `src/uix/soma/COMPONENT_GUIDE.md`: new checklist items 26
(component README required) and 27 (grep for data-attr
consistency); new Common Mistakes 11 (`data-soma-*` prefix), 12
(wrong date module), 13 (string `HourCycle`).
- `src/uix/soma/SOMA_ARCHITECTURE.md`: clarified data-attr naming
(canonical emitter is `createAttrs`; validator checks enum
values only, not names); new section covering date types and
formatting through `$soma/external/dates` → `$lib/util/dias`;
documented numeric `HourCycle`.
**Infrastructure**
- `PrimitiveHeaderAttributes` and `PrimitiveSelectAttributes` added
to `$soma/types`.
- `getLastFirstDayOfWeek`, `getNextLastDayOfWeek` re-exported from
`$soma/external/dates`.
- `calendar.*` and `range-calendar.*` translation blocks added to
`$soma/core/langs.ts`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
soma?.dates?.getDateOrder(); // DMY / MDY / YMD
soma?.dates?.getHourCycle(); // 12 | 24 (numeric — not '12h' / '24h')
docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
SOMA_ARCHITECTURE.md:
- §3.4: Soma.getOr() → Soma.get(), removed Soma.translate()
- §3.6: translations via langs.ts() with idlangref, not Soma.translate()
- §6: added Gesture.base/drag/resize + SafePolygon to layers table
- §7: updated service access examples to use idlangref
- §7: rewritten Texto funcional section with langs.ts pattern
- §9: added data-dragging, data-highlighted, data-resizing, --drawer-* vars
- §13: added core/langs.ts, langs.ts in component structure
- §17: checklist synced with COMPONENT_GUIDE (10 steps)
COMPONENT_GUIDE.md:
- A15: exception for pure drag handles (immediate capture)
- A16: added focus return note for non-modal
- A17: virtual focus vs DOM focus strategy
- A18: registry pattern over DOM queries
- A19: SafePolygon for hover-gap components
- A20: exit animation via Presence (dismiss flow)
- A21: contract case normalization
- A22: Dismissal isValidEvent for complex widgets
README.md:
- SafePolygon added to layers table
- Patterns section: registry, virtual focus, exit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
soma?.portalTo; // portal target
```
soma: dias date library, Calendar + RangeCalendar, Command fixes, doc updates
**Date library reorganisation** (no new third-party dependency)
- New `src/lib/util/dias/` with `_vendor/` (Adobe Apache 2.0 vendored code,
ATTRIBUTION.md preserved) and a public facade split into `types`,
`values`, `calendars`, `queries`, `operations`, `parse`, `format`,
`index`. Consumers import only from `$lib/util/dias`.
- All date-related `Intl` consolidated in `dias/format.ts` —
`DateFormatter`, cache, `resolveDateOrder`, `resolveHourCycle`,
placeholders, defaults.
- `ext/dates` now a thin reactive wrapper delegating to `dias`.
- Aligned App-layer `HourCycle` to canonical numeric form `12 | 24`
(matches `Intl.DateTimeFormat`'s `hour12`). Removed string form
`'12h' | '24h'` from `ext/app/types`, `ext/dates`,
`ext/presentation/locale-defaults`, and their tests.
- Repointed every consumer: `soma/external/dates`,
`terra/external/dates`, eight `routes/test/*` pages.
- Legacy `src/lib/util/dates/` left untouched by request.
**Tier 3 components**
- `soma/components/calendar/` — 14 parts, single + multiple selection,
`MonthSelect`/`YearSelect`, ISO/simple week numbers,
`isDateHoliday` matcher, `data-weekend` flag, navigation live-
region, RTL flip, full ARIA, 27-item checklist verified.
- `soma/components/range-calendar/` — 14 parts, range selection with
live hover preview (novel vs ark/bits/radix/react-aria), `minDays`
/ `maxDays` / `allowSingleDay`, third-click deselects only the
clicked endpoint.
- Test pages at `/test/soma/calendar` and `/test/soma/range-calendar`
with seven demos each. Links added to the soma index under a new
"Dates" section.
- READMEs with anatomy, parts, props, ARIA, keyboard, data-attrs, and
a competitive comparison table.
**Command fixes**
- Resolved O(N^2) reactive cascade: introduced
`visibleSet: $derived<Set<string>>` on the root provider;
per-item `isVisible` is now an O(1) `has()` lookup instead of
calling back into the provider, which was re-running all items'
derivations on every `itemsVersion` bump.
- Added `dir` prop with fallback to `soma.presentation.getDir()`;
horizontal arrows flip in RTL via `getDirectionalKeys`; root
receives `dir` attribute.
**Doc updates (session outcomes)**
- `CLAUDE.md`: replaced obsolete `@/glob/lib` date references with
`$lib/util/dias`; added canonical date-layer section; reinforced
`Provider` export rule and `data-{component}[-{part}]` naming
(never `data-soma-*`); clarified translation convention.
- `src/uix/soma/COMPONENT_GUIDE.md`: new checklist items 26
(component README required) and 27 (grep for data-attr
consistency); new Common Mistakes 11 (`data-soma-*` prefix), 12
(wrong date module), 13 (string `HourCycle`).
- `src/uix/soma/SOMA_ARCHITECTURE.md`: clarified data-attr naming
(canonical emitter is `createAttrs`; validator checks enum
values only, not names); new section covering date types and
formatting through `$soma/external/dates` → `$lib/util/dias`;
documented numeric `HourCycle`.
**Infrastructure**
- `PrimitiveHeaderAttributes` and `PrimitiveSelectAttributes` added
to `$soma/types`.
- `getLastFirstDayOfWeek`, `getNextLastDayOfWeek` re-exported from
`$soma/external/dates`.
- `calendar.*` and `range-calendar.*` translation blocks added to
`$soma/core/langs.ts`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
### Date / time types and formatting
Soma imports all date-related symbols through a single boundary: `$soma/external/dates` . That module re-exports from `$lib/util/dias` , the canonical date library. Components **never** import from `$lib/util/dates` (legacy) or `@internationalized/date` directly.
- Value types: `CalendarDate` , `CalendarDateTime` , `Time` , `ZonedDateTime`
- Types: `DateValue` , `DateRange` , `DateMatcher` , `Month` , `WeekStartsOn` , `HourCycle` , `DateOrder` , `Granularity`
- Operations: `today` , `now` , `startOfMonth` , `endOfMonth` , `isSameDay` , `getDayOfWeek` , `getLastFirstDayOfWeek` , `getNextLastDayOfWeek` , etc.
- Parsing: `parseDate` , `parseDateTime` , `parseTime`
- Formatting: `DateFormatter` , `getCachedDateFormat` , `resolveDateOrder(locale)` , `resolveHourCycle(locale)`
`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: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
### 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 `Provider` subclasses. No standalone functions. No `from()` . No `ctx` exposed.
### Texto funcional
docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
SOMA_ARCHITECTURE.md:
- §3.4: Soma.getOr() → Soma.get(), removed Soma.translate()
- §3.6: translations via langs.ts() with idlangref, not Soma.translate()
- §6: added Gesture.base/drag/resize + SafePolygon to layers table
- §7: updated service access examples to use idlangref
- §7: rewritten Texto funcional section with langs.ts pattern
- §9: added data-dragging, data-highlighted, data-resizing, --drawer-* vars
- §13: added core/langs.ts, langs.ts in component structure
- §17: checklist synced with COMPONENT_GUIDE (10 steps)
COMPONENT_GUIDE.md:
- A15: exception for pure drag handles (immediate capture)
- A16: added focus return note for non-modal
- A17: virtual focus vs DOM focus strategy
- A18: registry pattern over DOM queries
- A19: SafePolygon for hover-gap components
- A20: exit animation via Presence (dismiss flow)
- A21: contract case normalization
- A22: Dismissal isValidEvent for complex widgets
README.md:
- SafePolygon added to layers table
- Patterns section: registry, virtual focus, exit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
Traducciones funcionales (close, cancel, etc.) se resuelven via `langs.ts()` con idlangref:
```ts
// drawer/langs.ts
export const DRAWER_LANGS = {
TRIGGER: '#?components.drawer.trigger|Open drawer',
CLOSE: '#?common.buttons.close|Close',
} as const;
// En el provider:
'aria-label': this.provider.soma?.langs.ts(DRAWER_LANGS.CLOSE),
```
Common keys (`close`, `open` , `cancel` , etc.) en `common.buttons.*` . Component-specific en `components.{name}.*` . soma exporta `componentLangs` desde `core/langs.ts` — el consumidor lo importa y extiende. soma no inyecta traducciones.
## 8. Sistema reactivo
Capa fina sobre runes de Svelte 5 que permite pasar estado reactivo por referencia entre clases.
- `state<T>(initial)` → `State<T>` (mutable, `.current` )
- `readableActive(() => value)` → `Active<T>` (readonly derived)
- `writableActive(getter, setter)` → `State<T>` (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.
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
## 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
docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
SOMA_ARCHITECTURE.md:
- §3.4: Soma.getOr() → Soma.get(), removed Soma.translate()
- §3.6: translations via langs.ts() with idlangref, not Soma.translate()
- §6: added Gesture.base/drag/resize + SafePolygon to layers table
- §7: updated service access examples to use idlangref
- §7: rewritten Texto funcional section with langs.ts pattern
- §9: added data-dragging, data-highlighted, data-resizing, --drawer-* vars
- §13: added core/langs.ts, langs.ts in component structure
- §17: checklist synced with COMPONENT_GUIDE (10 steps)
COMPONENT_GUIDE.md:
- A15: exception for pure drag handles (immediate capture)
- A16: added focus return note for non-modal
- A17: virtual focus vs DOM focus strategy
- A18: registry pattern over DOM queries
- A19: SafePolygon for hover-gap components
- A20: exit animation via Presence (dismiss flow)
- A21: contract case normalization
- A22: Dismissal isValidEvent for complex widgets
README.md:
- SafePolygon added to layers table
- Patterns section: registry, virtual focus, exit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
data-dragging → gesture drag activo
data-highlighted → item con virtual focus (aria-activedescendant)
data-resizing → splitter resize activo
```
CSS variables expuestas:
```
--soma-floating-transform-origin
--soma-floating-available-width
--soma-floating-available-height
--soma-floating-anchor-width
--soma-floating-anchor-height
--soma-dialog-depth
--soma-dialog-nested-count
docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
SOMA_ARCHITECTURE.md:
- §3.4: Soma.getOr() → Soma.get(), removed Soma.translate()
- §3.6: translations via langs.ts() with idlangref, not Soma.translate()
- §6: added Gesture.base/drag/resize + SafePolygon to layers table
- §7: updated service access examples to use idlangref
- §7: rewritten Texto funcional section with langs.ts pattern
- §9: added data-dragging, data-highlighted, data-resizing, --drawer-* vars
- §13: added core/langs.ts, langs.ts in component structure
- §17: checklist synced with COMPONENT_GUIDE (10 steps)
COMPONENT_GUIDE.md:
- A15: exception for pure drag handles (immediate capture)
- A16: added focus return note for non-modal
- A17: virtual focus vs DOM focus strategy
- A18: registry pattern over DOM queries
- A19: SafePolygon for hover-gap components
- A20: exit animation via Presence (dismiss flow)
- A21: contract case normalization
- A22: Dismissal isValidEvent for complex widgets
README.md:
- SafePolygon added to layers table
- Patterns section: registry, virtual focus, exit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
--drawer-progress → 0-1 drag progress
--drawer-offset-x / y → drag offset in px
--soma-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';
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
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: 12 components, architecture docs, naming fixes, form tier complete
Components (12):
- Accordion (keyboard nav, single/multiple, collapsible, loop)
- Checkbox (indeterminate, group, readonly)
- Collapsible
- Combobox (input + floating listbox, consumer-side filtering)
- Dialog (variant dialog/alertdialog, modal, nested, animations, forceMount)
- DropdownMenu (15 parts, submenus, typeahead, checkbox/radio items)
- Popover (floating, openOnHover, modal, custom anchor)
- RadioGroup (roving tabindex, orientation, readonly)
- Select (single/multiple, typeahead, scroll-into-view, hidden select)
- Slider (drag, keyboard, single/range, ticks, vertical)
- Switch (hidden input, data-checked)
- Tooltip (skip-delay group, 3-state, hover/focus)
Architecture:
- State class files renamed: {name}.svelte.ts → {name}-provider.svelte.ts
(avoids Vite module resolution ambiguity with .svelte wrappers)
- Export name standardized: always Provider, never Root
- COMPONENT_GUIDE.md: step-by-step implementation guide with checklist
- SOMA_ARCHITECTURE.md: updated structure, anti-patterns, improvements table
- README.md: updated naming, file structure, inventory
- context.ts: error message uses Provider not Root
- Demo index page at /test/soma/
Bug fixes:
- DropdownMenu trigger missing onclick/onkeydown in props
- DropdownMenu interactOutsideBehavior always 'close' (not modal-dependent)
- Checkbox getContext in event handler → captured in constructor
- Select context not found → was Vite cache issue (not naming)
- ScrollLock unlock path
- Floating wrapper z-index: auto override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
├── SOMA_ARCHITECTURE.md ← this document
├── COMPONENT_GUIDE.md ← step-by-step implementation guide
├── README.md ← API reference
├── core/
docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
SOMA_ARCHITECTURE.md:
- §3.4: Soma.getOr() → Soma.get(), removed Soma.translate()
- §3.6: translations via langs.ts() with idlangref, not Soma.translate()
- §6: added Gesture.base/drag/resize + SafePolygon to layers table
- §7: updated service access examples to use idlangref
- §7: rewritten Texto funcional section with langs.ts pattern
- §9: added data-dragging, data-highlighted, data-resizing, --drawer-* vars
- §13: added core/langs.ts, langs.ts in component structure
- §17: checklist synced with COMPONENT_GUIDE (10 steps)
COMPONENT_GUIDE.md:
- A15: exception for pure drag handles (immediate capture)
- A16: added focus return note for non-modal
- A17: virtual focus vs DOM focus strategy
- A18: registry pattern over DOM queries
- A19: SafePolygon for hover-gap components
- A20: exit animation via Presence (dismiss flow)
- A21: contract case normalization
- A22: Dismissal isValidEvent for complex widgets
README.md:
- SafePolygon added to layers table
- Patterns section: registry, virtual focus, exit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
│ ├── soma.svelte.ts ← Soma class (root instance)
│ └── langs.ts ← componentLangs (default translations)
soma: 12 components, architecture docs, naming fixes, form tier complete
Components (12):
- Accordion (keyboard nav, single/multiple, collapsible, loop)
- Checkbox (indeterminate, group, readonly)
- Collapsible
- Combobox (input + floating listbox, consumer-side filtering)
- Dialog (variant dialog/alertdialog, modal, nested, animations, forceMount)
- DropdownMenu (15 parts, submenus, typeahead, checkbox/radio items)
- Popover (floating, openOnHover, modal, custom anchor)
- RadioGroup (roving tabindex, orientation, readonly)
- Select (single/multiple, typeahead, scroll-into-view, hidden select)
- Slider (drag, keyboard, single/range, ticks, vertical)
- Switch (hidden input, data-checked)
- Tooltip (skip-delay group, 3-state, hover/focus)
Architecture:
- State class files renamed: {name}.svelte.ts → {name}-provider.svelte.ts
(avoids Vite module resolution ambiguity with .svelte wrappers)
- Export name standardized: always Provider, never Root
- COMPONENT_GUIDE.md: step-by-step implementation guide with checklist
- SOMA_ARCHITECTURE.md: updated structure, anti-patterns, improvements table
- README.md: updated naming, file structure, inventory
- context.ts: error message uses Provider not Root
- Demo index page at /test/soma/
Bug fixes:
- DropdownMenu trigger missing onclick/onkeydown in props
- DropdownMenu interactOutsideBehavior always 'close' (not modal-dependent)
- Checkbox getContext in event handler → captured in constructor
- Select context not found → was Vite cache issue (not naming)
- ScrollLock unlock path
- Floating wrapper z-index: auto override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
├── reactive/ ← reactive system
├── provider/ ← Provider base class + context
├── props/ ← mergeProps, composeHandlers
├── attrs/ ← createAttrs, contracts, helpers
├── keyboard/ ← KEYS, directional
├── dom/ ← DOM utilities, focus
├── events/ ← addEventListener
├── css/ ← styleToString, cssToStyleObj
├── id/ ← createId, useId
├── types/ ← shared types + service interfaces
soma: 12 components, architecture docs, naming fixes, form tier complete
Components (12):
- Accordion (keyboard nav, single/multiple, collapsible, loop)
- Checkbox (indeterminate, group, readonly)
- Collapsible
- Combobox (input + floating listbox, consumer-side filtering)
- Dialog (variant dialog/alertdialog, modal, nested, animations, forceMount)
- DropdownMenu (15 parts, submenus, typeahead, checkbox/radio items)
- Popover (floating, openOnHover, modal, custom anchor)
- RadioGroup (roving tabindex, orientation, readonly)
- Select (single/multiple, typeahead, scroll-into-view, hidden select)
- Slider (drag, keyboard, single/range, ticks, vertical)
- Switch (hidden input, data-checked)
- Tooltip (skip-delay group, 3-state, hover/focus)
Architecture:
- State class files renamed: {name}.svelte.ts → {name}-provider.svelte.ts
(avoids Vite module resolution ambiguity with .svelte wrappers)
- Export name standardized: always Provider, never Root
- COMPONENT_GUIDE.md: step-by-step implementation guide with checklist
- SOMA_ARCHITECTURE.md: updated structure, anti-patterns, improvements table
- README.md: updated naming, file structure, inventory
- context.ts: error message uses Provider not Root
- Demo index page at /test/soma/
Bug fixes:
- DropdownMenu trigger missing onclick/onkeydown in props
- DropdownMenu interactOutsideBehavior always 'close' (not modal-dependent)
- Checkbox getContext in event handler → captured in constructor
- Select context not found → was Vite cache issue (not naming)
- ScrollLock unlock path
- Floating wrapper z-index: auto override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
├── 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/
soma: 12 components, architecture docs, naming fixes, form tier complete
Components (12):
- Accordion (keyboard nav, single/multiple, collapsible, loop)
- Checkbox (indeterminate, group, readonly)
- Collapsible
- Combobox (input + floating listbox, consumer-side filtering)
- Dialog (variant dialog/alertdialog, modal, nested, animations, forceMount)
- DropdownMenu (15 parts, submenus, typeahead, checkbox/radio items)
- Popover (floating, openOnHover, modal, custom anchor)
- RadioGroup (roving tabindex, orientation, readonly)
- Select (single/multiple, typeahead, scroll-into-view, hidden select)
- Slider (drag, keyboard, single/range, ticks, vertical)
- Switch (hidden input, data-checked)
- Tooltip (skip-delay group, 3-state, hover/focus)
Architecture:
- State class files renamed: {name}.svelte.ts → {name}-provider.svelte.ts
(avoids Vite module resolution ambiguity with .svelte wrappers)
- Export name standardized: always Provider, never Root
- COMPONENT_GUIDE.md: step-by-step implementation guide with checklist
- SOMA_ARCHITECTURE.md: updated structure, anti-patterns, improvements table
- README.md: updated naming, file structure, inventory
- context.ts: error message uses Provider not Root
- Demo index page at /test/soma/
Bug fixes:
- DropdownMenu trigger missing onclick/onkeydown in props
- DropdownMenu interactOutsideBehavior always 'close' (not modal-dependent)
- Checkbox getContext in event handler → captured in constructor
- Select context not found → was Vite cache issue (not naming)
- ScrollLock unlock path
- Floating wrapper z-index: auto override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
├── external/
│ └── dates/ ← boundary with date library
├── components/
soma: 12 components, architecture docs, naming fixes, form tier complete
Components (12):
- Accordion (keyboard nav, single/multiple, collapsible, loop)
- Checkbox (indeterminate, group, readonly)
- Collapsible
- Combobox (input + floating listbox, consumer-side filtering)
- Dialog (variant dialog/alertdialog, modal, nested, animations, forceMount)
- DropdownMenu (15 parts, submenus, typeahead, checkbox/radio items)
- Popover (floating, openOnHover, modal, custom anchor)
- RadioGroup (roving tabindex, orientation, readonly)
- Select (single/multiple, typeahead, scroll-into-view, hidden select)
- Slider (drag, keyboard, single/range, ticks, vertical)
- Switch (hidden input, data-checked)
- Tooltip (skip-delay group, 3-state, hover/focus)
Architecture:
- State class files renamed: {name}.svelte.ts → {name}-provider.svelte.ts
(avoids Vite module resolution ambiguity with .svelte wrappers)
- Export name standardized: always Provider, never Root
- COMPONENT_GUIDE.md: step-by-step implementation guide with checklist
- SOMA_ARCHITECTURE.md: updated structure, anti-patterns, improvements table
- README.md: updated naming, file structure, inventory
- context.ts: error message uses Provider not Root
- Demo index page at /test/soma/
Bug fixes:
- DropdownMenu trigger missing onclick/onkeydown in props
- DropdownMenu interactOutsideBehavior always 'close' (not modal-dependent)
- Checkbox getContext in event handler → captured in constructor
- Select context not found → was Vite cache issue (not naming)
- ScrollLock unlock path
- Floating wrapper z-index: auto override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
│ ├── internal/ ← Portal, Arrow, VisuallyHidden, < Soma >
│ ├── {name}/ ← each headless component
│ │ ├── {name}-provider.svelte.ts ← state classes (NOT {name}.svelte.ts)
docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
SOMA_ARCHITECTURE.md:
- §3.4: Soma.getOr() → Soma.get(), removed Soma.translate()
- §3.6: translations via langs.ts() with idlangref, not Soma.translate()
- §6: added Gesture.base/drag/resize + SafePolygon to layers table
- §7: updated service access examples to use idlangref
- §7: rewritten Texto funcional section with langs.ts pattern
- §9: added data-dragging, data-highlighted, data-resizing, --drawer-* vars
- §13: added core/langs.ts, langs.ts in component structure
- §17: checklist synced with COMPONENT_GUIDE (10 steps)
COMPONENT_GUIDE.md:
- A15: exception for pure drag handles (immediate capture)
- A16: added focus return note for non-modal
- A17: virtual focus vs DOM focus strategy
- A18: registry pattern over DOM queries
- A19: SafePolygon for hover-gap components
- A20: exit animation via Presence (dismiss flow)
- A21: contract case normalization
- A22: Dismissal isValidEvent for complex widgets
README.md:
- SafePolygon added to layers table
- Patterns section: registry, virtual focus, exit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
│ │ ├── types.ts ← public props + canonical field shapes
│ │ ├── langs.ts ← idlangref constants (if has translations)
soma: 12 components, architecture docs, naming fixes, form tier complete
Components (12):
- Accordion (keyboard nav, single/multiple, collapsible, loop)
- Checkbox (indeterminate, group, readonly)
- Collapsible
- Combobox (input + floating listbox, consumer-side filtering)
- Dialog (variant dialog/alertdialog, modal, nested, animations, forceMount)
- DropdownMenu (15 parts, submenus, typeahead, checkbox/radio items)
- Popover (floating, openOnHover, modal, custom anchor)
- RadioGroup (roving tabindex, orientation, readonly)
- Select (single/multiple, typeahead, scroll-into-view, hidden select)
- Slider (drag, keyboard, single/range, ticks, vertical)
- Switch (hidden input, data-checked)
- Tooltip (skip-delay group, 3-state, hover/focus)
Architecture:
- State class files renamed: {name}.svelte.ts → {name}-provider.svelte.ts
(avoids Vite module resolution ambiguity with .svelte wrappers)
- Export name standardized: always Provider, never Root
- COMPONENT_GUIDE.md: step-by-step implementation guide with checklist
- SOMA_ARCHITECTURE.md: updated structure, anti-patterns, improvements table
- README.md: updated naming, file structure, inventory
- context.ts: error message uses Provider not Root
- Demo index page at /test/soma/
Bug fixes:
- DropdownMenu trigger missing onclick/onkeydown in props
- DropdownMenu interactOutsideBehavior always 'close' (not modal-dependent)
- Checkbox getContext in event handler → captured in constructor
- Select context not found → was Vite cache issue (not naming)
- ScrollLock unlock path
- Floating wrapper z-index: auto override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
│ │ ├── exports.ts
│ │ ├── index.ts
│ │ └── components/
│ │ ├── {name}.svelte ← root wrapper
│ │ ├── {name}-trigger.svelte
│ │ └── ...
│ └── index.ts ← hierarchical barrel
└── index.ts ← main barrel
```
soma: 12 components, architecture docs, naming fixes, form tier complete
Components (12):
- Accordion (keyboard nav, single/multiple, collapsible, loop)
- Checkbox (indeterminate, group, readonly)
- Collapsible
- Combobox (input + floating listbox, consumer-side filtering)
- Dialog (variant dialog/alertdialog, modal, nested, animations, forceMount)
- DropdownMenu (15 parts, submenus, typeahead, checkbox/radio items)
- Popover (floating, openOnHover, modal, custom anchor)
- RadioGroup (roving tabindex, orientation, readonly)
- Select (single/multiple, typeahead, scroll-into-view, hidden select)
- Slider (drag, keyboard, single/range, ticks, vertical)
- Switch (hidden input, data-checked)
- Tooltip (skip-delay group, 3-state, hover/focus)
Architecture:
- State class files renamed: {name}.svelte.ts → {name}-provider.svelte.ts
(avoids Vite module resolution ambiguity with .svelte wrappers)
- Export name standardized: always Provider, never Root
- COMPONENT_GUIDE.md: step-by-step implementation guide with checklist
- SOMA_ARCHITECTURE.md: updated structure, anti-patterns, improvements table
- README.md: updated naming, file structure, inventory
- context.ts: error message uses Provider not Root
- Demo index page at /test/soma/
Bug fixes:
- DropdownMenu trigger missing onclick/onkeydown in props
- DropdownMenu interactOutsideBehavior always 'close' (not modal-dependent)
- Checkbox getContext in event handler → captured in constructor
- Select context not found → was Vite cache issue (not naming)
- ScrollLock unlock path
- Floating wrapper z-index: auto override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
### 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 subclasses
- 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
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
- **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
soma: 12 components, architecture docs, naming fixes, form tier complete
Components (12):
- Accordion (keyboard nav, single/multiple, collapsible, loop)
- Checkbox (indeterminate, group, readonly)
- Collapsible
- Combobox (input + floating listbox, consumer-side filtering)
- Dialog (variant dialog/alertdialog, modal, nested, animations, forceMount)
- DropdownMenu (15 parts, submenus, typeahead, checkbox/radio items)
- Popover (floating, openOnHover, modal, custom anchor)
- RadioGroup (roving tabindex, orientation, readonly)
- Select (single/multiple, typeahead, scroll-into-view, hidden select)
- Slider (drag, keyboard, single/range, ticks, vertical)
- Switch (hidden input, data-checked)
- Tooltip (skip-delay group, 3-state, hover/focus)
Architecture:
- State class files renamed: {name}.svelte.ts → {name}-provider.svelte.ts
(avoids Vite module resolution ambiguity with .svelte wrappers)
- Export name standardized: always Provider, never Root
- COMPONENT_GUIDE.md: step-by-step implementation guide with checklist
- SOMA_ARCHITECTURE.md: updated structure, anti-patterns, improvements table
- README.md: updated naming, file structure, inventory
- context.ts: error message uses Provider not Root
- Demo index page at /test/soma/
Bug fixes:
- DropdownMenu trigger missing onclick/onkeydown in props
- DropdownMenu interactOutsideBehavior always 'close' (not modal-dependent)
- Checkbox getContext in event handler → captured in constructor
- Select context not found → was Vite cache issue (not naming)
- ScrollLock unlock path
- Floating wrapper z-index: auto override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
## 15. Improvements over terra
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
| Aspect | Terra | Soma |
| ------------- | -------------------------------- | ---------------------------------------------------- |
| Layers | 5-level template nesting | Behaviors integrated in Provider |
| Config | `config/` with loose functions | `Soma` class with services |
| Resolvers | 6 specific functions | 1 generic `Soma.resolve()` |
| Animations | Basic animationend detection | `getAnimations()` API + `data-starting/ending-style` |
| Naming | Redundant prefixes (TerraDialog) | Hierarchical barrel (`Dialog.Provider`) |
| Files | 28 files in 10 dirs (layers) | 13 files in 2 dirs |
| Provider | No base class | `Provider` abstract with baseProps, assertProps |
| Types | Scattered | `ProviderOpts` vs `WithRefOpts` |
| IDs | Generic (`soma-c12`) | Descriptive (`soma-dialog-trigger-c13`) |
| Format | 4 loose props in presentation | 1 namespace `SomaFormat` |
| State files | `{name}.svelte.ts` (ambiguous) | `{name}-provider.svelte.ts` (explicit) |
| Exports | Mixed Root/Provider | Always Provider |
| Documentation | README only | Architecture + Component Guide + JSDoc |
## 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 test page funcional en `/test/soma/[componente]`
- no depende de hacks locales, z-index hardcodeados, ni demo CSS para sostenerse
- compila con 0 errores (`svelte-check`)
soma: 12 components, architecture docs, naming fixes, form tier complete
Components (12):
- Accordion (keyboard nav, single/multiple, collapsible, loop)
- Checkbox (indeterminate, group, readonly)
- Collapsible
- Combobox (input + floating listbox, consumer-side filtering)
- Dialog (variant dialog/alertdialog, modal, nested, animations, forceMount)
- DropdownMenu (15 parts, submenus, typeahead, checkbox/radio items)
- Popover (floating, openOnHover, modal, custom anchor)
- RadioGroup (roving tabindex, orientation, readonly)
- Select (single/multiple, typeahead, scroll-into-view, hidden select)
- Slider (drag, keyboard, single/range, ticks, vertical)
- Switch (hidden input, data-checked)
- Tooltip (skip-delay group, 3-state, hover/focus)
Architecture:
- State class files renamed: {name}.svelte.ts → {name}-provider.svelte.ts
(avoids Vite module resolution ambiguity with .svelte wrappers)
- Export name standardized: always Provider, never Root
- COMPONENT_GUIDE.md: step-by-step implementation guide with checklist
- SOMA_ARCHITECTURE.md: updated structure, anti-patterns, improvements table
- README.md: updated naming, file structure, inventory
- context.ts: error message uses Provider not Root
- Demo index page at /test/soma/
Bug fixes:
- DropdownMenu trigger missing onclick/onkeydown in props
- DropdownMenu interactOutsideBehavior always 'close' (not modal-dependent)
- Checkbox getContext in event handler → captured in constructor
- Select context not found → was Vite cache issue (not naming)
- ScrollLock unlock path
- Floating wrapper z-index: auto override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
## 17. New component checklist
docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
SOMA_ARCHITECTURE.md:
- §3.4: Soma.getOr() → Soma.get(), removed Soma.translate()
- §3.6: translations via langs.ts() with idlangref, not Soma.translate()
- §6: added Gesture.base/drag/resize + SafePolygon to layers table
- §7: updated service access examples to use idlangref
- §7: rewritten Texto funcional section with langs.ts pattern
- §9: added data-dragging, data-highlighted, data-resizing, --drawer-* vars
- §13: added core/langs.ts, langs.ts in component structure
- §17: checklist synced with COMPONENT_GUIDE (10 steps)
COMPONENT_GUIDE.md:
- A15: exception for pure drag handles (immediate capture)
- A16: added focus return note for non-modal
- A17: virtual focus vs DOM focus strategy
- A18: registry pattern over DOM queries
- A19: SafePolygon for hover-gap components
- A20: exit animation via Presence (dismiss flow)
- A21: contract case normalization
- A22: Dismissal isValidEvent for complex widgets
README.md:
- SafePolygon added to layers table
- Patterns section: registry, virtual focus, exit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
See `COMPONENT_GUIDE.md` for the full step-by-step process (25 steps with A1-A22 rules). Summary:
```
docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
SOMA_ARCHITECTURE.md:
- §3.4: Soma.getOr() → Soma.get(), removed Soma.translate()
- §3.6: translations via langs.ts() with idlangref, not Soma.translate()
- §6: added Gesture.base/drag/resize + SafePolygon to layers table
- §7: updated service access examples to use idlangref
- §7: rewritten Texto funcional section with langs.ts pattern
- §9: added data-dragging, data-highlighted, data-resizing, --drawer-* vars
- §13: added core/langs.ts, langs.ts in component structure
- §17: checklist synced with COMPONENT_GUIDE (10 steps)
COMPONENT_GUIDE.md:
- A15: exception for pure drag handles (immediate capture)
- A16: added focus return note for non-modal
- A17: virtual focus vs DOM focus strategy
- A18: registry pattern over DOM queries
- A19: SafePolygon for hover-gap components
- A20: exit animation via Presence (dismiss flow)
- A21: contract case normalization
- A22: Dismissal isValidEvent for complex widgets
README.md:
- SafePolygon added to layers table
- Patterns section: registry, virtual focus, exit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
[ ] 1. Compare with ark-ui, bits-ui, radix-ui — feature table
[ ] 2. Verify membership criteria
[ ] 3. Define parts + attrs + contract (registerContract)
[ ] 4. Create types.ts (props + canonical field shapes)
[ ] 5. Create langs.ts (idlangref constants, if has translations)
[ ] 6. Create {name}-provider.svelte.ts (Provider subclasses)
[ ] 7. Create wrapper .svelte files (thin)
[ ] 8. Create exports.ts + index.ts
[ ] 9. Create test page + link in index
[ ] 10. svelte-check + test in browser
```