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
# Component Implementation Guide
Step-by-step guide for building soma headless components.
## Before You Start
### 1. Compare with reference libraries
**This step is mandatory. Do not skip it.**
Search ark-ui, bits-ui, and radix-ui for the same component. Create a feature table:
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
| Feature | Radix | Ark | Bits | Soma | Decision |
| ----------- | ----- | --- | ---- | ---- | ------------- |
| (each prop) | ... | ... | ... | ✓/✗ | justification |
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
Document what soma includes and what it skips (with reason).
### 2. Verify membership criteria
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired
Morfo (src/uix/morfo/): machine-readable contract of a component's public
DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across
three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment.
Types, sium-based validator with cross-field invariants, dialog.ts as the
first canonical morfo with data-last-action for Sema-causal exits, 7 passing
unit tests.
createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy
inline signature removed. Dialog provider refactored to derive parts +
data contract from dialogMorfo. The other 65 soma components need porting
(svelte-check lists them) — tracked work.
Tooling:
scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes
scripts/morfo-check.ts — validates emitted DOM vs morfo
scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency
npm run smoke / morfo:check / morfo:vocabulary
WAI-ARIA APG components added (100% APG coverage now):
Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress,
SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel
pattern; hierarchical Table rows deprecated — use TreeGrid instead).
air: Link, Banner.
Sema prep:
src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements.
Morfo already provides everything Sema needs (data-last-action patterns,
transition markers, enumerable state values, cross-component consistency).
Sema implementation deferred.
COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM
topology vs .require() audit, npm run smoke is part of done) + A34 rule
with incident log. WAI-ARIA pattern listed as soma membership criterion.
Pagination translation bug fixed (soma.pagination.page -> idlangref
PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.*
-> components.table.*). Avatar demo now offline-safe (SVG data URL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
The component must meet ALL of these:
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
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired
Morfo (src/uix/morfo/): machine-readable contract of a component's public
DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across
three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment.
Types, sium-based validator with cross-field invariants, dialog.ts as the
first canonical morfo with data-last-action for Sema-causal exits, 7 passing
unit tests.
createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy
inline signature removed. Dialog provider refactored to derive parts +
data contract from dialogMorfo. The other 65 soma components need porting
(svelte-check lists them) — tracked work.
Tooling:
scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes
scripts/morfo-check.ts — validates emitted DOM vs morfo
scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency
npm run smoke / morfo:check / morfo:vocabulary
WAI-ARIA APG components added (100% APG coverage now):
Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress,
SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel
pattern; hierarchical Table rows deprecated — use TreeGrid instead).
air: Link, Banner.
Sema prep:
src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements.
Morfo already provides everything Sema needs (data-last-action patterns,
transition markers, enumerable state values, cross-component consistency).
Sema implementation deferred.
COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM
topology vs .require() audit, npm run smoke is part of done) + A34 rule
with incident log. WAI-ARIA pattern listed as soma membership criterion.
Pagination translation bug fixed (soma.pagination.page -> idlangref
PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.*
-> components.table.*). Avatar demo now offline-safe (SVG data URL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
- **WAI-ARIA pattern or semantic role** — the component implements a pattern from the [ARIA Authoring Practices Guide ](https://www.w3.org/WAI/ARIA/apg/patterns/ ) (Dialog, Combobox, Treegrid, Feed, Tabs, Toolbar, …) OR a canonical ARIA role (`role="status"`, `role="meter"` , `role="progressbar"` , `role="searchbox"` , …). If the browser's native HTML gives you the right role + keyboard model with no extra behavior required (e.g. `<a>` for Link, `<hr>` for Separator, `<img>` for Image), the primitive belongs in **air** , not soma.
- **Composition of parts** — 2+ sub-components communicating via context (Provider + Trigger + Content, Provider + Row + Cell, etc.). A single-DOM wrapper is air-level styling, not headless behavior.
- **Complex behavior** — keyboard navigation, focus management, floating, ARIA relationships, state machines, drag, form integration, or live-region coordination. Adding `role="…"` + `aria-label` to a single element is not enough.
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
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired
Morfo (src/uix/morfo/): machine-readable contract of a component's public
DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across
three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment.
Types, sium-based validator with cross-field invariants, dialog.ts as the
first canonical morfo with data-last-action for Sema-causal exits, 7 passing
unit tests.
createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy
inline signature removed. Dialog provider refactored to derive parts +
data contract from dialogMorfo. The other 65 soma components need porting
(svelte-check lists them) — tracked work.
Tooling:
scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes
scripts/morfo-check.ts — validates emitted DOM vs morfo
scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency
npm run smoke / morfo:check / morfo:vocabulary
WAI-ARIA APG components added (100% APG coverage now):
Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress,
SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel
pattern; hierarchical Table rows deprecated — use TreeGrid instead).
air: Link, Banner.
Sema prep:
src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements.
Morfo already provides everything Sema needs (data-last-action patterns,
transition markers, enumerable state values, cross-component consistency).
Sema implementation deferred.
COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM
topology vs .require() audit, npm run smoke is part of done) + A34 rule
with incident log. WAI-ARIA pattern listed as soma membership criterion.
Pagination translation bug fixed (soma.pagination.page -> idlangref
PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.*
-> components.table.*). Avatar demo now offline-safe (SVG data URL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
If it fails any of these → it's air-native, not soma.
Accepted exceptions (composition criterion waived when WAI-ARIA defines a tight contract):
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired
Morfo (src/uix/morfo/): machine-readable contract of a component's public
DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across
three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment.
Types, sium-based validator with cross-field invariants, dialog.ts as the
first canonical morfo with data-last-action for Sema-causal exits, 7 passing
unit tests.
createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy
inline signature removed. Dialog provider refactored to derive parts +
data contract from dialogMorfo. The other 65 soma components need porting
(svelte-check lists them) — tracked work.
Tooling:
scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes
scripts/morfo-check.ts — validates emitted DOM vs morfo
scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency
npm run smoke / morfo:check / morfo:vocabulary
WAI-ARIA APG components added (100% APG coverage now):
Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress,
SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel
pattern; hierarchical Table rows deprecated — use TreeGrid instead).
air: Link, Banner.
Sema prep:
src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements.
Morfo already provides everything Sema needs (data-last-action patterns,
transition markers, enumerable state values, cross-component consistency).
Sema implementation deferred.
COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM
topology vs .require() audit, npm run smoke is part of done) + A34 rule
with incident log. WAI-ARIA pattern listed as soma membership criterion.
Pagination translation bug fixed (soma.pagination.page -> idlangref
PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.*
-> components.table.*). Avatar demo now offline-safe (SVG data URL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
- `Announce` — a live-region primitive per WAI-ARIA 1.2 live regions; meets complex-behavior via dual-region A/B dispatch + auto-clear + priority routing, even though its surface is a single region per priority.
- `Progress` / `Meter` — canonical single-element roles with computed ARIA values and CSS custom properties for the decorative indicator; shipped with an `Indicator` part so consumers have two slots (the role host and the fill), crossing the composition threshold.
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 Structure
```
components/{name}/
├── {name}-provider.svelte.ts ← ALL state classes (Provider subclasses)
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
├── types.ts ← ALL prop types with JSDoc + canonical field shapes
├── langs.ts ← optional idlangref constants for imperative strings
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 ← barrel (Provider, Trigger, Content, etc.)
├── index.ts ← re-exports from exports.ts
└── components/
├── {name}.svelte ← root wrapper
├── {name}-trigger.svelte ← trigger wrapper
├── {name}-content.svelte ← content wrapper
└── ...
```
### File naming rules
- State class file: `{name}-provider.svelte.ts` (NOT `{name}.svelte.ts` )
- Why: avoids Vite module resolution ambiguity with `{name}.svelte` wrapper
- Contains ALL provider/state classes for the component
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
- Wrapper files: `{name}.svelte` , `{name}-trigger.svelte` , etc.
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
- Types file: `types.ts` — public props + canonical field shapes for Opts
- Langs file: optional `langs.ts` — idlangref constants only when provider code needs imperative refs (see A3)
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
- Barrel: `exports.ts` + `index.ts`
## State Class Pattern ({name}-provider.svelte.ts)
```ts
import { context, type ProviderOpts, type WithRefOpts } from '../../provider';
import { Soma } from '../../core/soma.svelte';
import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
import { createAttrs } from '$uix/morfo';
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
// Parts + data contract live in the component's morfo (see src/uix/morfo/README.md):
import { {name}Morfo } from '../../../morfo/components/{name}';
// Selector helper only. Registration and DOM writes happen through SomaRuntime.part().
const attrs = createAttrs({name}Morfo);
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
// Root provider (with or without DOM)
interface {Name}Opts extends ProviderOpts, ... {} // ProviderOpts if no DOM
interface {Name}Opts extends WithRefOpts, ... {} // WithRefOpts if renders element
export class {Name}Provider {
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
static readonly ctx = context< {Name}Provider>('{Name}');
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 get() { return this.ctx.getOr(undefined) as {Name}Provider | undefined; }
static require() { return this.ctx.get(); }
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
readonly opts: {Name}Opts;
readonly runtime: SomaRuntime;
readonly runtimePart: SomaRuntimePart;
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: {Name}Opts) {
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
return new {Name}Provider(opts);
}
private constructor(opts: {Name}Opts) {
this.opts = opts;
this.runtime = Soma.require().runtime({name}Morfo, {});
this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
context: {Name}Provider.ctx,
syncAttrs: true
});
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
}
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,
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
// component-specific props
} as const),
);
}
// Sub-parts read parent context
export class {Name}TriggerProvider {
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) { return new {Name}TriggerProvider(opts); }
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
readonly opts: {Name}TriggerOpts;
readonly runtimePart: SomaRuntimePart;
readonly provider: {Name}Provider;
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
private constructor(opts) {
this.opts = opts;
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
this.provider = {Name}Provider.require();
this.runtimePart = this.provider.runtime.part('trigger', {
id: opts.id,
ref: opts.ref,
owner: this,
syncAttrs: true
});
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: 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
All classes that use Svelte context follow the same 3-method pattern:
| Method | Behavior | When to use |
| -------------- | ------------------------------- | ------------------------------------------------------- |
| `create(opts)` | Factory + context set | Root wrapper creates the provider |
| `get()` | Returns instance or `undefined` | Optional parent (e.g., Checkbox inside optional Group) |
| `require()` | Throws if not found | Required parent (e.g., Trigger must be inside Provider) |
This applies consistently to:
- **Provider classes**: `XProvider.create()` , `XProvider.get()` , `XProvider.require()`
- **Soma**: `Soma.create()` , `Soma.get()` , `Soma.require()`
- **App**: `App.create()` , `App.get()` , `App.require()`
No standalone functions (`createApp`, `getApp` , `useApp` ). No `from()` . The `ctx` field is `readonly` on the class but not exposed as public API — consumers use the static methods.
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
### Rules
- `getContext()` only works during component initialization (constructor called from script block). NEVER in event handlers, timeouts, or callbacks.
- If a handler needs a context reference, capture it in the constructor.
- Event handlers (onclick, onkeydown) must be included in `props` . Defining them as class methods without spreading them into props means they won't reach the DOM.
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
- Layers (Presence, FocusScope, Dismissal, Gesture, etc.) are instantiated in the constructor and their `.props` are spread into the component's `props` .
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
- For DropdownMenu: `interactOutsideBehavior` defaults to `'close'` (not inherited from `modal` like Dialog).
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
### Types: define once, reference in Opts
Canonical field shapes are defined once in `types.ts` and referenced by provider Opts via `StateProps<>` / `ActiveProps<>` :
```ts
// types.ts
export type DrawerStateFields = { open: boolean; activeSnapPoint: SnapPoint | null; };
export type DrawerActiveFields = { disabled: boolean; modal: boolean; direction: Direction; ... };
// provider
interface DrawerOpts extends ProviderOpts, StateProps< DrawerStateFields > , ActiveProps< DrawerActiveFields > {}
```
Do NOT redeclare field types in both `types.ts` and the Opts interface.
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
## Wrapper Pattern (components/{name}.svelte)
```svelte
< script lang = "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
import { readableActive, writableActive } from '../../reactive';
import { mergeProps } from '../../props';
import { createId } from '../../id';
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
import { {Name}Provider } from '../{name}-provider.svelte';
import type { {Name}Props } from '../types';
const uid = $props.id();
let {
ref = $bindable(null),
id = createId(uid, '{name}'),
// ... props with defaults ...
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
onOpenChange = () => {},
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
children,
child,
...restProps
}: {Name}Props = $props();
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 state = {Name}Provider.create({
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
id: readableActive(() => id),
ref: writableActive(() => ref, (v) => (ref = v)),
// ... wrap each prop ...
});
const mergedProps = $derived(mergeProps(restProps, state.props));
< / script >
{#if child}
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
{@render child({ props: mergedProps })}
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
{:else}
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
< div { . . . mergedProps } >
{@render children?.()}
< / div >
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
{/if}
```
### Rules
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
- Wrappers are thin: props → Active/State → Provider.create() → mergeProps → render
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
- No logic in wrappers. If you're writing more than prop conversion, the logic belongs in the Provider.
- IDs: `createId(uid, '{component}-{part}')` — descriptive and inspectable
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
- Callbacks default to `() => {}` inline — no `noop` import, soma does not depend on `$lib`
- Direction: `soma?.prefs.getDir() ?? 'ltr'` where Soma is available;
this is backed by `uix.prefs.direction`
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 Pattern (exports.ts)
```ts
export { default as Provider } from './components/{name}.svelte'; // ALWAYS "Provider", never "Root"
export { default as Trigger } from './components/{name}-trigger.svelte';
export { default as Content } from './components/{name}-content.svelte';
export type {
{Name}Props as ProviderProps, // ALWAYS "ProviderProps", never "RootProps"
{Name}TriggerProps as TriggerProps,
{Name}ContentProps as ContentProps,
} from './types';
```
## Types Pattern (types.ts)
ALL props documented with JSDoc. No exceptions.
```ts
export type {Name}Props = WithChild< {
/** Unique identifier. Auto-generated if omitted. */
id?: string;
/** Whether open. Bindable. @default false */
open?: boolean;
/** Callback on open change. */
onOpenChange?: OnChangeFn< boolean > ;
/**
* Multi-line for complex behavior.
* Inherits from X when not set.
* @default 'close'
*/
escapeKeydownBehavior?: DismissalBehavior;
}> & Without< PrimitiveDivAttributes , { } > ;
```
### Rules
- One-line JSDoc for simple props
- Multi-line when there's conditional behavior or prop relationships
- `@default` on every prop that has a default in the wrapper
- Callbacks: document what `e.preventDefault()` does if applicable
- `value: string` for required props (no `?` )
## ID Generation
```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
createId(uid, 'dialog'); // → "soma-dialog-c12"
createId(uid, 'dialog-trigger'); // → "soma-dialog-trigger-c13"
createId(uid, 'dialog-content'); // → "soma-dialog-content-c14"
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
```
Pattern: `soma-{component}-{part}-{uid}` . Always descriptive.
## Data Attributes
- Provider: `data-{component}` (no `-provider` suffix)
- Parts: `data-{component}-{part}`
- State: `data-state="open|closed"` , `data-state="checked|unchecked|indeterminate"`
- Flags: `data-disabled` , `data-readonly` , `data-checked`
- Floating: `data-side` , `data-align`
- Animation: `data-starting-style` , `data-ending-style`
- Nesting: `data-nested` , `data-nested-open`
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
- Drag: `data-dragging` (present during active gesture)
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
## Checklist
```
[ ] 1. Compare with ark-ui, bits-ui, radix-ui — feature table with decisions
[ ] 2. Verify membership criteria (composition + complex behavior)
[ ] 3. Define parts: Provider + sub-parts (root part uses 'provider', not 'root' — A2)
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
[ ] 4. Create {name}-provider.svelte.ts with all Provider subclasses
[ ] 5. Use `createSomaRuntime()` / `soma.runtime()` so `registerMorfo()` runs — A1
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
[ ] 6. Create types.ts with JSDoc on ALL props + canonical field shapes
[ ] 7. Add `morfo.translations` for component-owned text; create langs.ts only for imperative constants — A3
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
[ ] 8. Create wrapper .svelte files (thin: props → Active → Provider → mergeProps → render — A11)
[ ] 9. Verify: event handlers included in props (not just class methods)
[ ] 10. Verify: context captured in constructor, not in handlers
[ ] 11. Verify: ARIA relationships complete (aria-controls, aria-labelledby, aria-expanded — A4)
[ ] 12. Verify: accessible name via morfo `translationRef` / `commonRef` / idlangref constant, not hardcoded string — A3
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
[ ] 13. Verify: keyboard navigation respects RTL via getDirectionalKeys() — A12
[ ] 14. Verify: timers/listeners cleaned up in $effect return — A6
[ ] 15. Verify: gesture cleanup on unmount if using Gesture layer — A6, A15
[ ] 16. Verify: .get() for optional parents, .require() for required — A7
[ ] 17. Verify: no visual styles in provider (A8), static methods follow create/get/require pattern
[ ] 18. Verify: roving tabindex has exactly one tabindex=0 item — A14
[ ] 19. Create exports.ts (Provider, not Root)
[ ] 20. Add to components/index.ts barrel
docs: prune obsolete audits + studies, refresh remaining references
Deleted 60 obsolete documents that no longer reflect the active-uix
state:
- 47 per-component audit logs (*-audit.md, audit_N.md, AUDIT.md)
- 5 global soma audits (AUDIT_1, codex_audit, soma-audit-04-20/21,
audit-prompt template)
- 5 closed studies (soma/study, morfo/study, morfo/DESIGN, morfo/
PROVIDER_STUDY_2026-04-23, uix/CONTINUITY_2026-04-24)
- 2 superseded refactor proposals (refactor_claude, refactor_code)
- 1 stale bug log (form/BUG-onchange-onblur-hang — repro path
src/routes/test/sium/kitchen-sink no longer exists)
Updated the remaining operational references to drop dead links and
reflect the post-cleanup architecture:
- src/uix/README.md — rewritten links section, dropped pointers to
terra/air/CONTINUITY/refactor docs, updated adom/dom paths
- src/uix/active_architecture.md — header now dated 2026-05-08, lists
the major shifts since 2026-04-26 (provider inheritance dropped,
eidos in production, doctrina parte IV, sound eager-init); pendings
section reflects the eidos migration roadmap; "see also" purged of
deleted files
- src/uix/morfo/README.md — file tree updated (removed DESIGN/study,
added compile/runtime/PERMUTATION_RUNNER); see-also pruned
- src/uix/sema/README.md — SoundChannel section now describes the
eager-init pattern (constructor-time, not lazy); pointer to
sema-implementation-guide added
- src/uix/soma/README.md — drops terra/air mentions, declares the
actual dependency direction (eidos -> soma, not the reverse)
- src/uix/soma/SOMA_ARCHITECTURE.md — §15 rewritten as "Estado actual
y deuda histórica" reflecting Provider-inheritance-drop; demo path
updated to web/routes/
- src/uix/soma/COMPONENT_GUIDE.md — A23 dates section now points to
$libs/days (no external/dates façade); demo paths swapped from
/test/soma/* to web/routes/
- src/uix/morfo/PERMUTATION_RUNNER.md — paths swapped to web/routes/
with a relocation note
- CLAUDE.md — Refactor Documents section replaced with Reference
Documents pointing to per-layer READMEs
npm run check holds at baseline (39 pre-existing errors, none new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
[ ] 21. Create demo page in web/routes/{name} as an INTERACTIVE TESTBED (A29)
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs
Tier 3 (complete):
DateField, DatePicker, DateRangeField, DateRangePicker
Tier 4 (partial):
TimeField, TimePicker (TimeRangeField pending)
Core:
$lib/util/dias/segments.ts — pure domain primitives for segmented
date/time inputs (constants, guards, initializers, reconciliation,
content generation, Intl opts)
soma/external/dates re-exports everything (no façades inside soma)
soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav,
SegmentState, KEYS predicates, description writers)
Conventions established (A23–A29):
A23 — never re-implement date helpers; extend dias
A24 — readonlySegments without value anchor logs warning
A25 — range components split readonly per-endpoint
A26 — contenteditable segments use onbeforeinput to block IME/paste
A27 — picker composition pattern with shared writableActive refs
A28 — time placeholders render as hh/mm/ss via dias/segments
A29 — demo pages are interactive testbeds, not static galleries
Demo pages: Material Design 3 styled, every public prop wired to a live
control, Field integration + state readout. Day-period uses real radio
group in 12h mode. Auto-anchor on range pickers keeps the end month
visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly.
Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29,
checklist items 28–31, picker composition section, dias boundary,
directory structure note for soma/datetime/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
— every public prop wired to a live control, Field integration section,
state readout. Not a gallery of canned snippets.
docs: prune obsolete audits + studies, refresh remaining references
Deleted 60 obsolete documents that no longer reflect the active-uix
state:
- 47 per-component audit logs (*-audit.md, audit_N.md, AUDIT.md)
- 5 global soma audits (AUDIT_1, codex_audit, soma-audit-04-20/21,
audit-prompt template)
- 5 closed studies (soma/study, morfo/study, morfo/DESIGN, morfo/
PROVIDER_STUDY_2026-04-23, uix/CONTINUITY_2026-04-24)
- 2 superseded refactor proposals (refactor_claude, refactor_code)
- 1 stale bug log (form/BUG-onchange-onblur-hang — repro path
src/routes/test/sium/kitchen-sink no longer exists)
Updated the remaining operational references to drop dead links and
reflect the post-cleanup architecture:
- src/uix/README.md — rewritten links section, dropped pointers to
terra/air/CONTINUITY/refactor docs, updated adom/dom paths
- src/uix/active_architecture.md — header now dated 2026-05-08, lists
the major shifts since 2026-04-26 (provider inheritance dropped,
eidos in production, doctrina parte IV, sound eager-init); pendings
section reflects the eidos migration roadmap; "see also" purged of
deleted files
- src/uix/morfo/README.md — file tree updated (removed DESIGN/study,
added compile/runtime/PERMUTATION_RUNNER); see-also pruned
- src/uix/sema/README.md — SoundChannel section now describes the
eager-init pattern (constructor-time, not lazy); pointer to
sema-implementation-guide added
- src/uix/soma/README.md — drops terra/air mentions, declares the
actual dependency direction (eidos -> soma, not the reverse)
- src/uix/soma/SOMA_ARCHITECTURE.md — §15 rewritten as "Estado actual
y deuda histórica" reflecting Provider-inheritance-drop; demo path
updated to web/routes/
- src/uix/soma/COMPONENT_GUIDE.md — A23 dates section now points to
$libs/days (no external/dates façade); demo paths swapped from
/test/soma/* to web/routes/
- src/uix/morfo/PERMUTATION_RUNNER.md — paths swapped to web/routes/
with a relocation note
- CLAUDE.md — Refactor Documents section replaced with Reference
Documents pointing to per-layer READMEs
npm run check holds at baseline (39 pre-existing errors, none new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
[ ] 22. Add link to web/routes/+layout.svelte sidebar nav
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
[ ] 23. svelte-check: 0 errors
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired
Morfo (src/uix/morfo/): machine-readable contract of a component's public
DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across
three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment.
Types, sium-based validator with cross-field invariants, dialog.ts as the
first canonical morfo with data-last-action for Sema-causal exits, 7 passing
unit tests.
createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy
inline signature removed. Dialog provider refactored to derive parts +
data contract from dialogMorfo. The other 65 soma components need porting
(svelte-check lists them) — tracked work.
Tooling:
scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes
scripts/morfo-check.ts — validates emitted DOM vs morfo
scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency
npm run smoke / morfo:check / morfo:vocabulary
WAI-ARIA APG components added (100% APG coverage now):
Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress,
SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel
pattern; hierarchical Table rows deprecated — use TreeGrid instead).
air: Link, Banner.
Sema prep:
src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements.
Morfo already provides everything Sema needs (data-last-action patterns,
transition markers, enumerable state values, cross-component consistency).
Sema implementation deferred.
COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM
topology vs .require() audit, npm run smoke is part of done) + A34 rule
with incident log. WAI-ARIA pattern listed as soma membership criterion.
Pagination translation bug fixed (soma.pagination.page -> idlangref
PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.*
-> components.table.*). Avatar demo now offline-safe (SVG data URL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
[ ] 24. Run `npm run smoke` — all routes pass, including the new one.
Smoke script catches runtime errors that svelte-check + HTTP 200 miss:
`pageerror` (uncaught throws during hydration), `console.error` ,
translation-key-not-found, and `Context "X" not found` . HTTP 200 alone
is SSR — it does NOT exercise client hydration. Interactively exercise
every control in DevTools afterwards.
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
[ ] 25. Document gaps vs reference libraries
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
[ ] 26. Create README.md in the component folder — anatomy, props, data-attrs,
keyboard, ARIA, and at least one composition example. Follow the
format used by dialog/README.md and accordion/README.md.
[ ] 27. Verify data-attr naming is consistent across code, CSS, and docs.
The morfo compiler emits `data-{component}` for root and
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
`data-{component}-{part}` for sub-parts — NEVER `data-soma-*` .
`createAttrs(morfo)` is only the typed selector helper for those
names; it does not register the contract or write to the DOM.
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
Grep the component folder for `data-soma-` and any other
prefix: if any querySelector, CSS selector, README, or inline
string uses a name that does not match the morfo-generated
attrs, the reference is broken (selectors return null, CSS
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
matches nothing) and the contract registration lies about
what is on the DOM.
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs
Tier 3 (complete):
DateField, DatePicker, DateRangeField, DateRangePicker
Tier 4 (partial):
TimeField, TimePicker (TimeRangeField pending)
Core:
$lib/util/dias/segments.ts — pure domain primitives for segmented
date/time inputs (constants, guards, initializers, reconciliation,
content generation, Intl opts)
soma/external/dates re-exports everything (no façades inside soma)
soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav,
SegmentState, KEYS predicates, description writers)
Conventions established (A23–A29):
A23 — never re-implement date helpers; extend dias
A24 — readonlySegments without value anchor logs warning
A25 — range components split readonly per-endpoint
A26 — contenteditable segments use onbeforeinput to block IME/paste
A27 — picker composition pattern with shared writableActive refs
A28 — time placeholders render as hh/mm/ss via dias/segments
A29 — demo pages are interactive testbeds, not static galleries
Demo pages: Material Design 3 styled, every public prop wired to a live
control, Field integration + state readout. Day-period uses real radio
group in 12h mode. Auto-anchor on range pickers keeps the end month
visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly.
Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29,
checklist items 28–31, picker composition section, dias boundary,
directory structure note for soma/datetime/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
# Date / time specific
[ ] 28. Import date types/utilities from `$libs/days` directly — never from
`$lib/util/dates` (legacy) and never from `@internationalized/date` .
Extend `$libs/days` when a reusable helper is missing; never
re-implement inside soma (A23). `soma/datetime/` only holds UI-level
helpers.
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs
Tier 3 (complete):
DateField, DatePicker, DateRangeField, DateRangePicker
Tier 4 (partial):
TimeField, TimePicker (TimeRangeField pending)
Core:
$lib/util/dias/segments.ts — pure domain primitives for segmented
date/time inputs (constants, guards, initializers, reconciliation,
content generation, Intl opts)
soma/external/dates re-exports everything (no façades inside soma)
soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav,
SegmentState, KEYS predicates, description writers)
Conventions established (A23–A29):
A23 — never re-implement date helpers; extend dias
A24 — readonlySegments without value anchor logs warning
A25 — range components split readonly per-endpoint
A26 — contenteditable segments use onbeforeinput to block IME/paste
A27 — picker composition pattern with shared writableActive refs
A28 — time placeholders render as hh/mm/ss via dias/segments
A29 — demo pages are interactive testbeds, not static galleries
Demo pages: Material Design 3 styled, every public prop wired to a live
control, Field integration + state readout. Day-period uses real radio
group in 12h mode. Auto-anchor on range pickers keeps the end month
visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly.
Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29,
checklist items 28–31, picker composition section, dias boundary,
directory structure note for soma/datetime/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
[ ] 29. Segmented inputs emit `onbeforeinput: e => e.preventDefault()` on the
contenteditable segment (A26). `keydown.preventDefault` does not
stop IME / paste / drop.
[ ] 30. `readonlySegments` in a single-value component warns via
`soma?.logger.warn` when `value` is undefined (A24). Range components
split into `startReadonlySegments` / `endReadonlySegments` (A25).
[ ] 31. Pickers follow the shared-state composition pattern (A27): root
wrapper creates the picker Provider + PopoverProvider + underlying
Field/Calendar/Slider providers pointing at the same `writableActive`
refs. Unique parts only for `Provider` / `Trigger` / calendar-or-slider
bridge; everything else re-exports from the composed components.
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
# Reactivity hazards — mandatory
[ ] 32. Registering a child id with a parent provider's state is a **direct
assignment in the constructor** (A30). Never use `$effect` for this.
`$effect(() => parent.inputId.current = opts.id.current)` creates a
reactive edge child → parent that can loop when any downstream
consumer feeds back. Symptom: the page "freezes" / "blocks" on
mount.
[ ] 33. Per-entity `$derived` MUST NOT call a provider method that reads
global state (value array, items list, version counter) (A31). Lift
the computation to a single `$derived` on the provider; per-entity
derivations compare against the lifted result with O(1) operations.
Symptom: works with 1– 5 items, hangs with 30+.
[ ] 36. Reactive collections: use ** `SvelteMap` / `SvelteSet` ** from
`svelte/reactivity` whenever readers index per-entry (`.get(k)`,
`.has(k)` , iteration, `.size` ) inside `$derived` / `$effect` /
templates (A33). `$state(new Map())` only tracks field reassignment;
`.set(k, v)` on the existing Map silently fails to notify readers.
Symptom: cache updates but derivations that read it never re-run.
[ ] 38. Per-item `$effect` MUST NOT read `opts.ref.current` / tracked inputs
AND write provider state that per-item `props` $derived read back
(A35). The attachment reapply loop triggers
`effect_update_depth_exceeded` . Register in the constructor; put
only the cleanup in `$effect` . If a per-item method walks the full
DOM / item set, wrap the walk in `untrack(...)` so the caller's
`$derived` depends on one reactive field, not every sibling's ref.
Symptom: demo page throws `effect_update_depth_exceeded` on mount;
`morfo:check` reports "Execution context was destroyed" for that
route.
soma/morfo: typed createAttrs + 6 audit batches + form hang fix + A36
## morfo: typed createAttrs
- `types.ts`: all array fields are now `readonly` so morfos can be authored
with `as const satisfies Morfo` without casts.
- `createAttrs` is a generic `<const M extends MorfoShape>` that infers the
emitted attr map from the morfo's literal shape. Typos like `attrs.trigerr`
are now compile errors; autocomplete lists every valid part.
- All 66 morfos migrated: `export const xMorfo = { ... } as const satisfies Morfo`.
- Two latent bugs the new typing caught: `dateRangeFieldAttrs.root` and
`timeRangeFieldAttrs.root` — legacy from the root→provider refactor, fixed
to `.provider`.
- Docs (morfo/README, COMPONENT_GUIDE) mandate `as const satisfies Morfo`;
the older `: Morfo` annotation widens literals and loses autocomplete.
## Audit batches 1–7 (soma-audit-2026-04-20)
Batch 1 — Keyboard a11y (A14/A12):
- Tabs: `isFallbackTabStop(el)` using `untrack` → first trigger gets
`tabindex=0` when no tab matches (WCAG 2.1.1 was failing).
- RatingGroup: `dir` prop + `getDirectionalKeys(dir, 'horizontal')` for RTL.
- Accordion P1-1/P1-6 dismissed: roving/labelledby already correct per APG.
Batch 2 — A33 reactive collections: Select `labelRegistry` and NavigationMenu
`triggerRefs` migrated to `SvelteMap`.
Batch 3 — A31 O(N²) fix: RadioGroup lifts `hasSelection` + `isFallbackTabStop`
to the provider with `untrack`-wrapped DOM walk. Per-item derivations drop
from O(N²) to O(1) per trigger.
Batch 4 — Morfo aria sync: toast / time-field / date-field / tabs /
tag-group / navigation-menu declare every aria the provider emits
(14 entries across 6 files).
Batch 5 — Accessible naming props: Tabs.List, ToggleGroup, TreeView,
NavigationMenu expose `aria-label?` / `aria-labelledby?` with explicit
"required unless labelledby is set" JSDoc.
Batch 6 — A30 cleanup: Checkbox `setLabelId` and 5 trigger-ref providers
(select/tooltip/link-preview/dropdown-menu/context-menu) migrated from
`$effect(() => parent.ref = opts.ref)` to the `Provider` base's
`onRefChange` callback (fires via attachRef inside `untrack`).
Batch 7 — A11 wrapper thinning: stepper/tree-view wrappers removed
`ctx.get()`, providers expose proxy derivations; time/date-range-field
inputs use `untrack(() => …)` for one-shot id registration; menubar
content's 40-line keyboard nav moved to `MenubarMenuProvider.onContentKeydown`;
color-picker area + channel-slider pointer state machines moved to the
providers with `onpointerdown/move/up` emitted via `props`.
form-auto-fields deferred with justification (reflective schema renderer,
not a state-holding wrapper).
## Form hang fix (A36)
`runValidate` entered the `isPromiseLike` branch even for sync schemas
because sium's Standard-Schema adapter declares `validate: async`. The
fallback `return { errors, issues }` read both reactive vars inside the
validation `$effect` while the `.then(...)` microtask wrote them
→ microtask-mediated loop, silent to Svelte's sync effect-depth guard.
Symptom: kitchen-sink hung the tab on mount with onChange / onBlur.
Fix: `return untrack(() => ({ errors, issues }))`. Regression locked by
two new 5s-timeout tests in `form-auto-fields.svelte.test.ts`.
Pattern codified as A36 in COMPONENT_GUIDE; CLAUDE.md count updated to
A1–A36 / 39 items.
## Verification
- `npm run check`: 0 errors in soma/morfo scope (sema demo pre-existing
errors untouched).
- `npx vitest run src/uix/soma src/uix/morfo`: all tests green
(94 + 7 = 101 tests, inc. 2 new form regression tests).
- `npm run smoke`: 65/65 routes.
- `npx tsx scripts/morfo-check.ts`: 66/66 morfos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
[ ] 39. An `$effect` that kicks off an async side-effect (`.then` /
microtask / `setTimeout` ) which eventually WRITES a reactive var
MUST NOT read that same var back — directly or via any helper it
calls — without `untrack` (A36). The write will re-trigger the
effect via the tracked read, spawn another async side-effect, and
keep looping through the microtask queue. Svelte's synchronous
effect-depth guard does not fire; the browser tab simply freezes.
Symptom: `npm run smoke` passes (500 ms settle doesn't catch the
build-up), component demo hangs on mount when reading a derived
whose body triggers the effect. Wrap the fallback read in
`untrack(() => ({ errors, issues }))` or similar.
morfo: add permutation runner (A37) — third validation layer for state transitions
## Why
`morfo:check` validates single-state DOM at page load; `smoke` catches
hydration-time errors. Neither ejercices state TRANSITIONS — and that is
where every reactivity bug we caught this week actually lived:
- toolbar A35 loop (mount-time, not caught by either)
- form A36 microtask loop (mount-time with onChange)
- slider RTL transform (static value, off-by-thumb-width)
All passed the existing CI. All would have failed a "click, re-validate"
pass.
## What
- **`scripts/permutation-check.ts`** — Playwright runner. For each demo,
collects `data-perm-step="N"` elements, executes them in ascending
order (re-discovering after each step so portal-gated controls get
counted), and re-validates the component's morfo + captures any
`pageerror` / `console.error` between steps.
- **`data-perm-*` authoring convention** — step index, human label, mode
(click / focus / type / key), settle delay, skip-validate flag. Demos
opt in incrementally; the runner SKIPs annotation-less pages without
failing.
- **`src/uix/morfo/PERMUTATION_RUNNER.md`** — full design doc covering
what it validates, the annotation convention, example, failure format,
and v2–v4 roadmap (URL-driven states → morfo-inferred cycles →
MutationObserver ordering for Sema).
- **COMPONENT_GUIDE A37 + checklist item 40** — instrumentation is now a
ship-gate rule; doc lists coverage targets (overlays / toggleables /
composite roving / forms / RTL) and explicit skips (alerts, file
pickers).
- **`npm run perm:check`** — pipeline entry, exit codes 0/1/2
parallelling `morfo:check`.
## Instrumented demos (v1 seed)
- dialog — 2 steps: open via trigger → close via Close button. Step 1 is
inside `{#if open}` + Portal; runner's dynamic re-discovery handles it.
- tabs — 3 steps: initial tab 1 → switch to tab 2 → switch to tab 3.
- toolbar — 3 steps: toggle bold / italic / underline GroupItems
(exercises the A35 pattern that previously looped).
Result: 8/8 permutations pass; 63 demos SKIPPED pending instrumentation.
## Verification
- `npm run check`: 0 errors in soma/morfo/scripts scope.
- `npm run smoke`: 65/65 routes.
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 3/3 instrumented demos, 8/8 permutations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
[ ] 40. Instrument the component's demo page with `data-perm-step="N"`
annotations on every interactive control that drives a distinct
state transition (A37). Run `npm run perm:check` before shipping
and confirm every permutation passes — this is the validation
layer that catches reactivity loops (A35 / A36) and transition-
time morfo drift that `morfo:check` misses. At minimum cover:
open / dismiss for overlays, toggle for toggleables, first-to-
second-item for composite roving, empty→invalid→valid for forms.
See `src/uix/morfo/PERMUTATION_RUNNER.md` for the full authoring
convention and the opt-in modifiers (`data-perm-mode`,
`data-perm-settle` , `data-perm-skip-validate` ).
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
# Scope approval — mandatory
[ ] 34. Before declaring the component done, **present the comparison table
to the user in the conversation message** (A32). Not just in the
README — in the reply. Every `❌` and `⚠️` row gets an explicit
decision: (a) implement now, (b) defer to v2 with written
justification and cost estimate in the README, or (c) drop because
it's not a real gap. The user approves scope — the programmer
does not.
[ ] 35. Deferred features land in an ** "Out of scope (v2 roadmap)"** section
in the component's README (A32). Each entry: what it is, the
reference libraries that ship it, why it's deferred, and a cost
estimate. This becomes the PR backlog — no feature dies in a
footnote.
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired
Morfo (src/uix/morfo/): machine-readable contract of a component's public
DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across
three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment.
Types, sium-based validator with cross-field invariants, dialog.ts as the
first canonical morfo with data-last-action for Sema-causal exits, 7 passing
unit tests.
createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy
inline signature removed. Dialog provider refactored to derive parts +
data contract from dialogMorfo. The other 65 soma components need porting
(svelte-check lists them) — tracked work.
Tooling:
scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes
scripts/morfo-check.ts — validates emitted DOM vs morfo
scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency
npm run smoke / morfo:check / morfo:vocabulary
WAI-ARIA APG components added (100% APG coverage now):
Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress,
SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel
pattern; hierarchical Table rows deprecated — use TreeGrid instead).
air: Link, Banner.
Sema prep:
src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements.
Morfo already provides everything Sema needs (data-last-action patterns,
transition markers, enumerable state values, cross-component consistency).
Sema implementation deferred.
COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM
topology vs .require() audit, npm run smoke is part of done) + A34 rule
with incident log. WAI-ARIA pattern listed as soma membership criterion.
Pagination translation bug fixed (soma.pagination.page -> idlangref
PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.*
-> components.table.*). Avatar demo now offline-safe (SVG data URL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
# Translation + topology audits — mandatory (A34)
[ ] 37. **Translation namespace grep.** After touching any lang-related
code in a component or demo, grep the repo for `soma\.` inside
quoted string literals outside `.md` files:
grep -n "['\"]soma\.[a-z-]" src --include=!*.md
soma's translation namespace is ALWAYS `components.{kebab-name}.*`
(or `common.*` for shared strings). Any `langs.t('soma.…')` /
`langs.ts('soma.…')` is a bug and will log `Translation key not
found` at runtime. Component-owned paths should come from
`morfo.translations` + `v.translationRef` ; shared paths should use
`v.commonRef` or an explicit idlangref constant (A3).
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired
Morfo (src/uix/morfo/): machine-readable contract of a component's public
DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across
three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment.
Types, sium-based validator with cross-field invariants, dialog.ts as the
first canonical morfo with data-last-action for Sema-causal exits, 7 passing
unit tests.
createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy
inline signature removed. Dialog provider refactored to derive parts +
data contract from dialogMorfo. The other 65 soma components need porting
(svelte-check lists them) — tracked work.
Tooling:
scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes
scripts/morfo-check.ts — validates emitted DOM vs morfo
scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency
npm run smoke / morfo:check / morfo:vocabulary
WAI-ARIA APG components added (100% APG coverage now):
Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress,
SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel
pattern; hierarchical Table rows deprecated — use TreeGrid instead).
air: Link, Banner.
Sema prep:
src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements.
Morfo already provides everything Sema needs (data-last-action patterns,
transition markers, enumerable state values, cross-component consistency).
Sema implementation deferred.
COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM
topology vs .require() audit, npm run smoke is part of done) + A34 rule
with incident log. WAI-ARIA pattern listed as soma membership criterion.
Pagination translation bug fixed (soma.pagination.page -> idlangref
PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.*
-> components.table.*). Avatar demo now offline-safe (SVG data URL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
[ ] 38. **DOM topology vs `.require()` audit.** For every `X.require()`
call in the provider file, answer: "is the required provider's
component a DOM ancestor of the consumer of my component?". If
the answer is NO, `.require()` WILL throw at runtime — context
only flows to descendants. The classic trap is HTML constraints:
`<tr>` cannot nest `<tr>` , so `Table.RowDetail` (a sibling `<tr>` )
cannot `TableRowProvider.require()` even though it "belongs" to a
row conceptually. Fix by: (a) receive the object via prop, (b) use
`.get()` + fallback, or (c) restructure the DOM. Svelte-check
never catches this — smoke does.
[ ] 39. **Smoke script is part of done.** A component is not done until
`npm run smoke` (with `npm run dev` running) reports PASS for
its new route AND all existing routes. Regressions in unrelated
components caused by translation-table edits, lang-key typos, or
core context changes must be caught here before declaring the
work complete.
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
```
## Common Mistakes
1. **Event handlers not in props** — defining `onclick` as a class method but forgetting to include it in the `props` derived object. The handler exists but never reaches the DOM.
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
2. **getContext in event handler** — calling `ctx.get()` inside onclick/onkeydown. getContext only works during component initialization. Capture the reference in the constructor.
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
3. **Naming Root instead of Provider** — the export name is always `Provider` , never `Root` .
4. **State class file named same as wrapper** — `select.svelte.ts` + `components/select.svelte` can cause Vite module resolution issues. Always use `{name}-provider.svelte.ts` .
5. **Missing readonly prop on form components** — Switch, Checkbox, RadioGroup should have `readonly` alongside `disabled` . readonly prevents interaction but keeps the element focusable.
6. **Comments in Spanish** — all code comments must be in English.
7. **Skipping reference library comparison** — mandatory before implementation. No exceptions.
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
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
8. **Redeclaring field types** — defining prop types in both `types.ts` and the provider Opts interface. Define canonical shapes once in `types.ts` , reference with `StateProps<>` / `ActiveProps<>` .
9. **Hardcoding aria strings** — declare component-owned text in `morfo.translations` and reference it with `v.translationRef` ; use `v.commonRef` / idlangref constants for shared imperative labels. Never inline strings in providers.
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
10. **Gesture capturing child clicks** — `setPointerCapture` must be deferred until moveBuffer is exceeded. Immediate capture on pointerdown steals click events from buttons inside the draggable area.
11. ** `data-soma-*` prefix** — the framework never emits `data-soma-{component}-*` . The morfo compiler produces `data-{component}` for the root part and `data-{component}-{part}` for children; `createAttrs(morfo)` only exposes those names as typed strings for selectors. Writing a querySelector like `[data-soma-calendar-day]` returns `null` silently and the contract validator does NOT catch it (it only checks enum values, not attribute presence). Always grep the component folder for any `data-soma-` reference before completing the work — checklist item 27.
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
12. **Date types from the wrong module** — `DateValue` , `CalendarDate` , `CalendarDateTime` , `Time` , `ZonedDateTime` , `DateRange` , `Month` , etc. come from `$libs/days` . Never import from `$lib/util/dates` (the legacy vendored copy) or directly from `@internationalized/date` .
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
13. **Using `HourCycle` as `'12h' \| '24h'`** — the canonical form is numeric `12 \| 24` , matching `Intl.DateTimeFormat` 's `hour12` resolved option. The App-layer `ext/dates` service, `ext/app/types` , and the `dias` library all share this form. String forms are legacy.
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
## Audit-Derived Rules (mandatory for all components)
These rules were extracted from a full audit of all 25 soma components. Every issue below was found in multiple components. Follow these to avoid repeating them.
### A1. Register the morfo through the runtime
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
Every component MUST register its morfo before it relies on `assertContract`
or morfo-owned translations. The canonical path is to create a runtime:
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
```ts
const runtime = createSomaRuntime({name}Morfo, {
dom: this.soma.dom,
eventEngine: this.soma.events,
states,
props,
parts,
events
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
});
```
or, when the provider has a `Soma` scope:
```ts
const runtime = soma.runtime({name}Morfo, { states, props, parts, events });
```
Both paths call `registerMorfo(morfo)` internally. That compiles the morfo,
registers its `data-*` contract and publishes `morfo.translations` to connected
`ActiveLangs` instances. Only call `registerMorfo(morfo)` manually for a
legacy provider or tool that needs the registry side effect without creating
a runtime.
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
### A2. Root part must use 'provider', not 'root'
The orchestrator / context-creator part uses `kebab: 'provider'` in the morfo. The morfo compiler special-cases `'provider'` to strip the suffix, so the emitted attribute is `data-{component}` (bare, no suffix). Using any other name produces `data-{component}-{name}` .
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
**Naming coherence**: morfo's `name: 'Provider'` field (the consumer-facing export) and `kebab: 'provider'` field (the DOM role) match — one name for the same part across both axes.
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
```ts
// Correct — in the morfo file:
{ name: 'Provider', kebab: 'provider', /* ... */ }
// The provider registers the same kebab through the runtime:
this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
context: DialogProvider.ctx,
syncAttrs: true
});
// runtimePart.props includes data-dialog
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
// Wrong
{ name: 'Provider', kebab: 'root' }
// runtimePart.props would include data-dialog-root instead of data-dialog
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
```
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
### A3. Soma access, translations, and imports
**Soma access:** Declare `readonly soma = Soma.get()` in the root provider when the component needs prefs-derived services or imperative translations. Sub-parts access soma via `this.provider.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
**Translations:** Component-owned text lives in the morfo, not in a per-component catalog file:
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
```ts
export const drawerMorfo = {
name: 'Drawer',
kebab: 'drawer',
translations: {
trigger: { es: 'Abrir cajon', en: 'Open drawer' }
},
parts: [
{
name: 'Trigger',
kebab: 'trigger',
aria: [{ attr: 'aria-label', value: v.translationRef('trigger', 'Open drawer') }]
}
]
} as const satisfies Morfo;
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
```
Shared strings use common refs:
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
```ts
value: v.commonRef('buttons.close', 'Close');
```
`langs.ts` is still allowed, but only as a small constants file when provider
code needs an imperative idlangref:
```ts
// drawer/langs.ts
export const DRAWER_LANGS = {
CLOSE: '#?common.buttons.close|Close'
} as const;
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
// provider
'aria-label': this.provider.soma?.langs.ts(DRAWER_LANGS.CLOSE)
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
```
**Translation namespace structure:**
```
common.buttons.close ← project-wide, shared by soma + eidos + app
common.buttons.open
common.labels.*
components.drawer.trigger ← component-specific
components.dialog.trigger
```
- Common keys live under `common.*` at the lang root — not under soma
- Component keys live under `components.{name}.*`
- `ActiveUix` registers `commonLangs` defaults without overwriting user-provided leaves
- `ActiveUix` registers `morfo.translations` dynamically when `registerMorfo(morfo)` runs through the runtime
- There is no global per-component catalog. Component-owned strings live in `morfo.translations` ; shared strings live in `common.*` .
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
- `langs.ts()` with idlangref for simple strings. `langs.t()` only for interpolated templates (e.g., `Page {{value}}` )
Do NOT:
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
- Create `translate()` helper methods in providers
- Use `?? 'fallback'` — the fallback belongs inside the langref (`#?path|fallback`)
- Hardcode aria strings — use `morfo.translations` + `v.translationRef` , `v.commonRef` , or an explicit idlangref constant
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
- Put common keys (close, open, cancel) under component namespaces — they belong in `common.*`
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
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
**Imports within soma:** Use relative paths, not `$soma/` aliases. Relative paths make the library portable without requiring alias configuration in the consumer's build. soma does not import from `$lib` — trivial utilities (like empty callbacks) are inline (`() => {}`).
```ts
// Inside soma — relative
import { DRAWER_LANGS } from './langs';
import type { DrawerSide } from './types';
import { Presence } from '../../layers/presence.svelte';
// Wrong — alias
import { DRAWER_LANGS } from '$soma/components/drawer/langs';
// Wrong — external dependency
import { noop } from '$lib/util/funcs';
```
Consumer code (layouts, test pages, app) uses `$soma/` alias — that's their build config, not soma's concern.
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
### A4. ARIA relationships are mandatory
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
Every component with trigger→content pattern MUST emit:
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
- **Trigger**: `aria-controls={contentId}` , `aria-expanded`
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
- **Content**: `aria-labelledby={triggerId}` (for dialogs, popovers, selects, drawers)
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
- **Form controls**: `aria-labelledby={labelId}` when a Label part exists
- **Groups**: `aria-label` or `aria-labelledby` on `role="group"` , `role="tablist"` , `role="toolbar"` , `role="radiogroup"` , `role="tree"`
Missing ARIA relationships = the component is broken for screen readers.
### A5. Feature flags are opt-in (`=== true`)
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
Per-item feature flags (Table columns, tree nodes) default to `false` . A feature only activates when the consumer explicitly sets it to `true` .
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
```ts
// Correct — opt-in
return col?.def.enableSorting === true;
// Wrong — opt-out (active by default)
return col?.def.enableSorting !== false;
```
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
Applies to Table: `enableSorting` , `enableFiltering` , `enableResizing` , `enablePinning` , `enableHiding` (exception: `enableHiding` defaults to `true` — documented in ColumnDef JSDoc).
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
### A6. Clean up timers, listeners, and observers
Every `setTimeout` , `setInterval` , listener, `ResizeObserver` , or `MutationObserver` created in a provider MUST have cleanup in `$effect` return or explicit dispose. Uncleaned resources cause memory leaks.
Global or transversal listeners use `this.soma.dom.listen(...)` / `this.provider.soma.dom.listen(...)` . Local Svelte handlers stay in props (`onclick`, `onkeydown` , etc.).
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
```ts
// Correct
$effect(() => {
const timer = setTimeout(fn, delay);
return () => clearTimeout(timer);
});
// Wrong — leak
constructor() {
setTimeout(fn, delay); // never cleared
window.addEventListener('keydown', fn); // bypasses ActiveDom and is never removed
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
}
```
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
### A7. Use `.get()` for optional parents, `.require()` for required
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
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
| Method | Returns | Use when |
| ---------------- | ---------------------------- | --------------------------------- |
| `X.create(opts)` | instance | Creating + registering in context |
| `X.get()` | instance or `undefined` | Parent is optional |
| `X.require()` | instance (throws if missing) | Parent is required |
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
```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
// Tooltip can work without Group — use get()
this.group = TooltipGroupProvider.get();
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
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
// Accordion Item MUST be inside Accordion — use require()
this.provider = AccordionProvider.require();
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
```
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
This convention applies to App, Soma, and all Provider classes. No standalone functions (`createApp`, `getSoma` ). No `from()` .
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
### A8. No visual styles in headless providers
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
Headless providers MUST NOT emit visual CSS properties (`border-radius`, `background` , `color` , `overflow: auto` ). Only functional CSS is allowed:
- `touch-action: none` — required for gesture drag
- `pointer-events: auto` — required for overlays and fixed-position content
- `transition: none` — required during active drag to disable CSS transitions
- `transform: translate3d(...)` — required during active drag for visual feedback
- CSS custom properties (`--drawer-progress`, `--drawer-offset-*` ) — data for the visual layer
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
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
The visual layer (air/eidos) owns appearance. The provider owns behavior.
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
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
### A9. Dismissal behavior for drawers vs dialogs
A drawer is NOT a popover. `interactOutsideBehavior` should be `'ignore'` for drawers:
- **Modal drawer**: overlay `onclick` handles close. Dismissal layer only handles Escape.
- **Non-modal drawer**: background is interactive by definition. Dismissal layer disabled entirely. Escape handled via `onkeydown` on the content element.
- **Non-dismissible**: Dismissal layer fully disabled. Close button uses `forceClose()` (bypasses the `dismissible` guard).
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
### A10. DOM queries in providers must handle dynamism
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
`getItems()` patterns using `querySelectorAll` are fragile — they capture a snapshot, not a live reference. If items are added/removed dynamically, the query must re-run. For nested components (e.g., nested Accordion), filter results to only include elements whose closest root is the current root:
```ts
getTriggers(): HTMLButtonElement[] {
const root = this.opts.ref?.current;
if (!root) return [];
const all = Array.from(root.querySelectorAll< HTMLButtonElement > (selector));
return all.filter((el) => el.closest(`[${attrs.provider}]`) === root);
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
}
```
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
### A11. Wrapper must be thin
Complex logic (effects, DOM manipulation, state machines, event coordination) belongs in the Provider, not the wrapper. The wrapper's job is: destructure props → wrap in Active/State → create Provider → mergeProps → render. If a wrapper has `$effect` blocks, `onMount` , or branching logic, the code belongs in the Provider.
### A12. Keyboard navigation must respect RTL
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 with arrow-key navigation MUST check `dir` and swap left/right keys. Use `getDirectionalKeys(dir, orientation)` .
soma: audit fixes — 84 issues across 25 components, Table complete
Table component:
- Complete state manager: sorting, filtering, pagination, selection,
column pinning (sticky), column sizing, expandable rows (sub-rows +
detail panels), column visibility
- Provider renders <table> with translated aria-label
- Sorting opt-in per column, consumer places sort buttons
- filteredRowCount/totalRowCount for correct pagination count
- Sub-row IDs hierarchical (parentId.subIndex), no collisions
- Production-grade test page (Swiss Industrial design)
- Full README with all features documented
Critical fixes (10):
- Toast: memory leaks (timer + hotkey listener cleanup via $effect)
- Toast: restartTimer() method for promise-based toasts
- ScrollArea: type='always' now works (check order fix)
- ScrollArea: thumb click no longer triggers track jump
- Tooltip: no crash without <Tooltip.Group> (ctx.getOr)
- Combobox: click-outside-to-close fixed (isValidEvent)
- Popover: stray 'n' character removed from popover.svelte
- TreeView: ARIA ownership fixed (role=treeitem on Branch, not Control)
- Splitter: documented required array spread for reactivity
High fixes (14):
- Checkbox: group disabled/name propagation + checked sync via $effect
- Select: aria-labelledby on content, aria-disabled on items
- Stepper: aria-controls + aria-labelledby cross-linking
- TagsInput: highlightPrev returns to input, role on Control/Item
- Editable: Preview role=button + aria-label
- Pagination: translated aria-label on items
- RadioGroup: removed unused Soma import
Medium fixes (60):
- registerContract() added to all 25 components
- Dead config=getSoma() removed from 7 providers
- Dead imports removed (Tooltip, Popover, Slider, Combobox)
- Dialog: aria-modal conditional on modal prop
- Slider: pointer capture release + onpointercancel
- Popover: hover timeout cleanup, aria-modal support
- Tabs: inactive content hidden + aria-hidden + tabindex=-1
- ToggleGroup: aria-orientation + roving tabindex fallback
- Toolbar: data-disabled on Button
- Stepper: Content tabindex
- TreeView: RTL keyboard support (expand/collapse key swap)
- NumberField: Enter key commit, clampOnBlur, allowMouseWheel, scrubber a11y
- Toast: data-state on root, aria-live on viewport
- ScrollArea: scrollbar aria-label + tabindex
- Collapsible: part root (not provider), aria-controls, role=region
- Tooltip: removed dead provider part from attrs
Architecture:
- COMPONENT_GUIDE.md: 14 audit-derived rules (A1-A14)
- Checklist expanded from 15 to 23 steps
- 8 new HTML primitive types (Table, THead, TBody, TFoot, TR, TH, TD, Caption)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
```ts
const { nextKey, prevKey } = getDirectionalKeys(dir, orientation);
```
Do NOT hardcode `KEYS.ARROW_LEFT` / `KEYS.ARROW_RIGHT` for directional navigation.
### A13. Form components need hidden inputs
Components that participate in forms (Checkbox, RadioGroup, Switch, Select, TagsInput, NumberField) should render a hidden `<input>` with the current value and `name` prop. The `name` from a parent Group must propagate to children.
### A14. Roving tabindex: exactly one item gets tabindex=0
In roving tabindex patterns (RadioGroup, Toolbar, Tabs, ToggleGroup), exactly one item must have `tabindex=0` at all times — either the focused/selected item, or the first item when nothing is selected. Never all `-1` (group unreachable) and never multiple `0` (breaks single-tab-stop pattern).
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
### A15. Gesture layer integration
Components with drag behavior (Drawer, Slider, Splitter, ScrollArea, Toast) use the Gesture layer from `layers/gesture/` . Three specializations:
- `Gesture.base()` — pointer tracking + axis lock + velocity (Slider, ScrollArea)
- `Gesture.drag()` — base + progress + snap points + dismiss (Drawer, Toast)
- `Gesture.resize()` — base + delta + min/max constraints (Splitter)
Rules:
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
- `setPointerCapture` deferred until moveBuffer exceeded for containers with child buttons (Drawer, Slider). **Exception** : pure drag handles (Splitter resize trigger) capture immediately — the handle IS the drag target, no children to protect
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
- Gesture `.props` (only `onpointerdown` ) must be spread into the component's `props`
- Gesture cleanup on unmount: `$effect(() => { return () => { this.gesture.cancel(); }; })`
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
- CSS vars (`--drawer-progress`, `--drawer-offset-x/y` ) set by provider for visual layer
- During active drag: `transition: none` + inline `transform` for immediate feedback
- Scroll-drag guard: disable gesture via `enabled` , not by suppressing callback
- The gesture layer measures — the component decides what it means (dismiss, value, resize)
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
### A16. Non-modal overlay components
Components with `modal` prop (Dialog, Drawer) must adjust behavior when `modal=false` :
- **FocusScope**: `trap=false` , `enabled=false` — background must stay interactive
- **ScrollLock**: disabled — background scrolling must work
- **Dismissal**: disabled — no interactOutside, no focusOutside
- **Escape**: handled via `onkeydown` on the content element, not via Dismissal layer
- **Auto-focus**: prevented (`e.preventDefault()` in `onOpenAutoFocus` )
- **Content**: needs `tabindex=-1` to receive keyboard events without focus trap
- **Overlay**: not rendered (consumer should not include `<Overlay>` for non-modal)
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
- **Focus return**: non-modal close must return focus to trigger manually (FocusScope doesn't handle it)
### A17. Focus strategy: virtual vs DOM
Two focus strategies exist. Choose based on component type:
- **`aria-activedescendant` (virtual focus)**: focus stays on trigger/input, items highlighted via CSS `[data-highlighted]` . Used for **Select** , **Combobox** — the trigger owns keyboard, items are options.
- **Roving tabindex (DOM focus)**: items receive real DOM focus. Used for **DropdownMenu** , **RadioGroup** , **Toolbar** , **Tabs** — items are independent interactive elements.
Never mix both in the same component. If the trigger has `aria-activedescendant` , items must NOT call `.focus()` .
### A18. Registry pattern over DOM queries
Prefer registering sub-parts in a Map on mount/unmount over `querySelectorAll` for keyboard navigation:
```ts
// In root provider:
private triggerRegistry = new Map< number , HTMLElement > ();
registerTrigger(index: number, el: HTMLElement) { this.triggerRegistry.set(index, el); }
unregisterTrigger(index: number) { this.triggerRegistry.delete(index); }
getRegisteredTriggers(): HTMLElement[] {
return [...this.triggerRegistry.entries()]
.sort((a, b) => a[0] - b[0])
.map(([, el]) => el)
.filter(el => el.getAttribute('aria-disabled') !== 'true');
}
// In sub-part constructor:
$effect(() => {
const el = opts.ref.current;
if (el) this.provider.registerTrigger(this.index, el);
return () => this.provider.unregisterTrigger(this.index);
});
```
Benefits: no DOM queries, works with portaled/lazy-mounted elements, O(1) lookup.
Same pattern for value-to-label registries (Select, Combobox):
```ts
private labelRegistry = new Map< string , string > ();
registerLabel(value: string, label: string) { this.labelRegistry.set(value, label); }
```
### A19. SafePolygon for hover-gap components
Components where pointer must traverse a gap between trigger and content (DropdownMenu submenus, Tooltip) integrate `SafePolygon` from `layers/floating/safe-polygon` :
```ts
import { SafePolygon } from '../../layers/floating/safe-polygon';
// In the provider that owns both trigger and content refs:
new SafePolygon({
enabled: () => opts.open.current,
triggerNode: () => this.triggerRef.current,
contentNode: () => this.contentRef.current,
onPointerExit: () => this.handleClose(),
buffer: 2,
transitIntentTimeout: 300
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
});
```
SafePolygon calculates a corridor polygon between trigger and content. The pointer can traverse the gap without closing. `onPointerExit` fires only when the pointer leaves the safe zone.
### A20. Exit animation via Presence
Components that dismiss/remove elements (Toast, Drawer) must integrate `Presence` for exit animations:
1. `dismiss()` marks the element as dismissing (state change, not removal)
2. `data-state` transitions from `'open'` to `'closed'`
3. Presence emits `data-ending-style` for CSS exit animation
4. Animation completes → Presence fires `onComplete(false)` → element removed from array
```ts
// In Toaster:
dismiss(id) { this.toasts = this.toasts.map(t => t.id === id ? { ...t, dismissing: true } : t); }
remove(id) { this.toasts = this.toasts.filter(t => t.id !== id); /* + onDismiss callback */ }
// In provider:
this.presence = new Presence({
open: readableActive(() => this.isOpen),
ref: opts.ref,
onComplete: (open) => { if (!open) this.provider.toaster.remove(id); }
});
```
### A21. Contract case normalization
`registerMorfo()` and `assertContract()` normalize names to lowercase. Provider code should use morfo kebabs (`'provider'`, `'trigger'` , `'content'` ) when calling `runtime.part(...)` ; component names in contracts remain normalized internally. No manual case matching needed.
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
### A22. Dismissal isValidEvent for complex widgets
Components with multiple interactive zones (Combobox, Select) must exclude their own elements from interact-outside detection. The `isValidEvent` callback should return `false` for clicks on trigger, input, and content:
```ts
isValidEvent: readableActive(() => (e: PointerEvent | FocusEvent) => {
const target = e.target;
if (!(target instanceof Node)) return true;
if (inputEl?.contains(target)) return false;
if (triggerEl?.contains(target)) return false;
if (contentEl?.contains(target)) return false;
return true;
});
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
```
Without this, clicking scrollbars inside the content, or clicking the trigger to close, triggers interact-outside and causes race conditions.
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs
Tier 3 (complete):
DateField, DatePicker, DateRangeField, DateRangePicker
Tier 4 (partial):
TimeField, TimePicker (TimeRangeField pending)
Core:
$lib/util/dias/segments.ts — pure domain primitives for segmented
date/time inputs (constants, guards, initializers, reconciliation,
content generation, Intl opts)
soma/external/dates re-exports everything (no façades inside soma)
soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav,
SegmentState, KEYS predicates, description writers)
Conventions established (A23–A29):
A23 — never re-implement date helpers; extend dias
A24 — readonlySegments without value anchor logs warning
A25 — range components split readonly per-endpoint
A26 — contenteditable segments use onbeforeinput to block IME/paste
A27 — picker composition pattern with shared writableActive refs
A28 — time placeholders render as hh/mm/ss via dias/segments
A29 — demo pages are interactive testbeds, not static galleries
Demo pages: Material Design 3 styled, every public prop wired to a live
control, Field integration + state readout. Day-period uses real radio
group in 12h mode. Auto-anchor on range pickers keeps the end month
visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly.
Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29,
checklist items 28–31, picker composition section, dias boundary,
directory structure note for soma/datetime/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
### A23. Date/time utilities — never re-implement, extend `dias`
The canonical date library is `$libs/days` . Soma consumes it directly via the alias — **no Soma façade** . Before porting any date helper or writing a new one:
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs
Tier 3 (complete):
DateField, DatePicker, DateRangeField, DateRangePicker
Tier 4 (partial):
TimeField, TimePicker (TimeRangeField pending)
Core:
$lib/util/dias/segments.ts — pure domain primitives for segmented
date/time inputs (constants, guards, initializers, reconciliation,
content generation, Intl opts)
soma/external/dates re-exports everything (no façades inside soma)
soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav,
SegmentState, KEYS predicates, description writers)
Conventions established (A23–A29):
A23 — never re-implement date helpers; extend dias
A24 — readonlySegments without value anchor logs warning
A25 — range components split readonly per-endpoint
A26 — contenteditable segments use onbeforeinput to block IME/paste
A27 — picker composition pattern with shared writableActive refs
A28 — time placeholders render as hh/mm/ss via dias/segments
A29 — demo pages are interactive testbeds, not static galleries
Demo pages: Material Design 3 styled, every public prop wired to a live
control, Field integration + state readout. Day-period uses real radio
group in 12h mode. Auto-anchor on range pickers keeps the end month
visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly.
Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29,
checklist items 28–31, picker composition section, dias boundary,
directory structure note for soma/datetime/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
docs: prune obsolete audits + studies, refresh remaining references
Deleted 60 obsolete documents that no longer reflect the active-uix
state:
- 47 per-component audit logs (*-audit.md, audit_N.md, AUDIT.md)
- 5 global soma audits (AUDIT_1, codex_audit, soma-audit-04-20/21,
audit-prompt template)
- 5 closed studies (soma/study, morfo/study, morfo/DESIGN, morfo/
PROVIDER_STUDY_2026-04-23, uix/CONTINUITY_2026-04-24)
- 2 superseded refactor proposals (refactor_claude, refactor_code)
- 1 stale bug log (form/BUG-onchange-onblur-hang — repro path
src/routes/test/sium/kitchen-sink no longer exists)
Updated the remaining operational references to drop dead links and
reflect the post-cleanup architecture:
- src/uix/README.md — rewritten links section, dropped pointers to
terra/air/CONTINUITY/refactor docs, updated adom/dom paths
- src/uix/active_architecture.md — header now dated 2026-05-08, lists
the major shifts since 2026-04-26 (provider inheritance dropped,
eidos in production, doctrina parte IV, sound eager-init); pendings
section reflects the eidos migration roadmap; "see also" purged of
deleted files
- src/uix/morfo/README.md — file tree updated (removed DESIGN/study,
added compile/runtime/PERMUTATION_RUNNER); see-also pruned
- src/uix/sema/README.md — SoundChannel section now describes the
eager-init pattern (constructor-time, not lazy); pointer to
sema-implementation-guide added
- src/uix/soma/README.md — drops terra/air mentions, declares the
actual dependency direction (eidos -> soma, not the reverse)
- src/uix/soma/SOMA_ARCHITECTURE.md — §15 rewritten as "Estado actual
y deuda histórica" reflecting Provider-inheritance-drop; demo path
updated to web/routes/
- src/uix/soma/COMPONENT_GUIDE.md — A23 dates section now points to
$libs/days (no external/dates façade); demo paths swapped from
/test/soma/* to web/routes/
- src/uix/morfo/PERMUTATION_RUNNER.md — paths swapped to web/routes/
with a relocation note
- CLAUDE.md — Refactor Documents section replaced with Reference
Documents pointing to per-layer READMEs
npm run check holds at baseline (39 pre-existing errors, none new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
1. Read `$libs/days/*.ts` (types, queries, operations, parse, format, segments) fully.
2. If the helper already exists in days → import it via `$libs/days` . Never duplicate.
3. If it is missing **and** reusable outside soma (pure, no DOM, no KEYS/Svelte deps) → add it to days. Don't proxy.
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs
Tier 3 (complete):
DateField, DatePicker, DateRangeField, DateRangePicker
Tier 4 (partial):
TimeField, TimePicker (TimeRangeField pending)
Core:
$lib/util/dias/segments.ts — pure domain primitives for segmented
date/time inputs (constants, guards, initializers, reconciliation,
content generation, Intl opts)
soma/external/dates re-exports everything (no façades inside soma)
soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav,
SegmentState, KEYS predicates, description writers)
Conventions established (A23–A29):
A23 — never re-implement date helpers; extend dias
A24 — readonlySegments without value anchor logs warning
A25 — range components split readonly per-endpoint
A26 — contenteditable segments use onbeforeinput to block IME/paste
A27 — picker composition pattern with shared writableActive refs
A28 — time placeholders render as hh/mm/ss via dias/segments
A29 — demo pages are interactive testbeds, not static galleries
Demo pages: Material Design 3 styled, every public prop wired to a live
control, Field integration + state readout. Day-period uses real radio
group in 12h mode. Auto-anchor on range pickers keeps the end month
visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly.
Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29,
checklist items 28–31, picker composition section, dias boundary,
directory structure note for soma/datetime/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
4. Only when the helper is UI-specific (DOM navigation, KEYS-based predicates, screen-reader announcer, segment UI-state shapes with `hasLeftFocus` /`lastKeyZero`) does it live in `soma/datetime/` .
docs: prune obsolete audits + studies, refresh remaining references
Deleted 60 obsolete documents that no longer reflect the active-uix
state:
- 47 per-component audit logs (*-audit.md, audit_N.md, AUDIT.md)
- 5 global soma audits (AUDIT_1, codex_audit, soma-audit-04-20/21,
audit-prompt template)
- 5 closed studies (soma/study, morfo/study, morfo/DESIGN, morfo/
PROVIDER_STUDY_2026-04-23, uix/CONTINUITY_2026-04-24)
- 2 superseded refactor proposals (refactor_claude, refactor_code)
- 1 stale bug log (form/BUG-onchange-onblur-hang — repro path
src/routes/test/sium/kitchen-sink no longer exists)
Updated the remaining operational references to drop dead links and
reflect the post-cleanup architecture:
- src/uix/README.md — rewritten links section, dropped pointers to
terra/air/CONTINUITY/refactor docs, updated adom/dom paths
- src/uix/active_architecture.md — header now dated 2026-05-08, lists
the major shifts since 2026-04-26 (provider inheritance dropped,
eidos in production, doctrina parte IV, sound eager-init); pendings
section reflects the eidos migration roadmap; "see also" purged of
deleted files
- src/uix/morfo/README.md — file tree updated (removed DESIGN/study,
added compile/runtime/PERMUTATION_RUNNER); see-also pruned
- src/uix/sema/README.md — SoundChannel section now describes the
eager-init pattern (constructor-time, not lazy); pointer to
sema-implementation-guide added
- src/uix/soma/README.md — drops terra/air mentions, declares the
actual dependency direction (eidos -> soma, not the reverse)
- src/uix/soma/SOMA_ARCHITECTURE.md — §15 rewritten as "Estado actual
y deuda histórica" reflecting Provider-inheritance-drop; demo path
updated to web/routes/
- src/uix/soma/COMPONENT_GUIDE.md — A23 dates section now points to
$libs/days (no external/dates façade); demo paths swapped from
/test/soma/* to web/routes/
- src/uix/morfo/PERMUTATION_RUNNER.md — paths swapped to web/routes/
with a relocation note
- CLAUDE.md — Refactor Documents section replaced with Reference
Documents pointing to per-layer READMEs
npm run check holds at baseline (39 pre-existing errors, none new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
**Never** create a soma module whose only job is to re-export days symbols — consumers import from `$libs/days` directly. Dead re-export façades hide the real dependency.
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs
Tier 3 (complete):
DateField, DatePicker, DateRangeField, DateRangePicker
Tier 4 (partial):
TimeField, TimePicker (TimeRangeField pending)
Core:
$lib/util/dias/segments.ts — pure domain primitives for segmented
date/time inputs (constants, guards, initializers, reconciliation,
content generation, Intl opts)
soma/external/dates re-exports everything (no façades inside soma)
soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav,
SegmentState, KEYS predicates, description writers)
Conventions established (A23–A29):
A23 — never re-implement date helpers; extend dias
A24 — readonlySegments without value anchor logs warning
A25 — range components split readonly per-endpoint
A26 — contenteditable segments use onbeforeinput to block IME/paste
A27 — picker composition pattern with shared writableActive refs
A28 — time placeholders render as hh/mm/ss via dias/segments
A29 — demo pages are interactive testbeds, not static galleries
Demo pages: Material Design 3 styled, every public prop wired to a live
control, Field integration + state readout. Day-period uses real radio
group in 12h mode. Auto-anchor on range pickers keeps the end month
visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly.
Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29,
checklist items 28–31, picker composition section, dias boundary,
directory structure note for soma/datetime/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
### A24. Readonly segments without a concrete value must log a warning
`readonlySegments` (or `startReadonlySegments` /`endReadonlySegments` in range components) fixes specific segments so the user cannot change them. The lock needs a concrete anchor:
- **Valid**: `value` is set → locked segments preserve their values from `value` .
- **Invalid**: `value` is `undefined` → the lock falls back to `placeholder` (empty-state display), which is not a commitment. Log a warning:
```ts
this.soma?.logger.warn(
'{Name}Field',
'`readonlySegments` is set but `value` is undefined — lock has no concrete anchor; falling back to placeholder. Supply an initial `value` so the locked segment has a defined meaning.',
{ readonlySegments: [...segs] }
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs
Tier 3 (complete):
DateField, DatePicker, DateRangeField, DateRangePicker
Tier 4 (partial):
TimeField, TimePicker (TimeRangeField pending)
Core:
$lib/util/dias/segments.ts — pure domain primitives for segmented
date/time inputs (constants, guards, initializers, reconciliation,
content generation, Intl opts)
soma/external/dates re-exports everything (no façades inside soma)
soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav,
SegmentState, KEYS predicates, description writers)
Conventions established (A23–A29):
A23 — never re-implement date helpers; extend dias
A24 — readonlySegments without value anchor logs warning
A25 — range components split readonly per-endpoint
A26 — contenteditable segments use onbeforeinput to block IME/paste
A27 — picker composition pattern with shared writableActive refs
A28 — time placeholders render as hh/mm/ss via dias/segments
A29 — demo pages are interactive testbeds, not static galleries
Demo pages: Material Design 3 styled, every public prop wired to a live
control, Field integration + state readout. Day-period uses real radio
group in 12h mode. Auto-anchor on range pickers keeps the end month
visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly.
Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29,
checklist items 28–31, picker composition section, dias boundary,
directory structure note for soma/datetime/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
);
```
Guard against spam: track the last warned segment set and only re-warn when it changes, reset when the config becomes valid.
### A25. Range components split readonly per-endpoint
`DateRangeField` , `DateRangePicker` (and future `TimeRangeField` ) expose two lists:
- `startReadonlySegments?: EditableTimeSegmentPart[] | EditableSegmentPart[]` — locks segments on the start input.
- `endReadonlySegments?: ...` — locks segments on the end input.
A single `readonlySegments` applied symmetrically is wrong because the user may legitimately want one endpoint fixed (e.g., start's year) while the other remains editable.
For range pickers whose calendar is shared between endpoints: the calendar's navigation for a segment is blocked **only** when **both** endpoints have that segment in their readonly list. Blocking when only one side is locked would prevent navigating to pick the other endpoint's value. Per-cell selection constraints are not applied from outside because `RangeCalendar` picks the endpoint based on its own anchor state.
### A26. Block direct contenteditable mutations with `onbeforeinput`
Segmented inputs (DateField, TimeField) use `contenteditable="true"` to get `role="spinbutton"` keyboard behaviour. The contenteditable surface must **never** accept direct mutations — all content is driven by the provider's `segmentValues` :
```ts
readonly sharedSegmentAttrs = {
// …
onbeforeinput: (e: Event) => e.preventDefault()
};
```
`keydown.preventDefault()` alone is not enough: IME/composition paths, paste, drag-and-drop, and mobile autocomplete bypass keydown. `beforeinput` fires before the browser mutates the element and blocks every insertion path in one line.
### A27. Picker composition pattern (shared state)
Pickers (`DatePicker`, `DateRangePicker` , `TimePicker` , future `TimeRangePicker` ) compose `Popover` + one of (`DateField`/`DateRangeField`/`TimeField`) + one of (`Calendar`/`RangeCalendar`/slider group). The picker's own Provider owns the shared reactive state, and the root wrapper creates **three** providers pointing at the same `writableActive` refs:
```ts
// Root wrapper script
const sharedValue = writableActive(/* getter */, /* setter */);
const sharedPlaceholder = writableActive(…);
const sharedOpen = writableActive(…);
{Name}PickerProvider.create({ value: sharedValue, placeholder: sharedPlaceholder, open: sharedOpen, …config });
PopoverProvider.create({ open: sharedOpen, … });
{Base}FieldProvider.create({ value: sharedValue, placeholder: sharedPlaceholder, …config });
// Calendar/RangeCalendar/slider providers are created inside their own wrapper
// (DatePicker.Calendar, TimePicker.HourSlider, …) reading from the picker context.
```
**Parts**: unique wrappers for `Provider` , `Trigger` , and the calendar/slider bridge. Everything else re-exports from the composed components — their native data-attrs (`data-popover-*`, `data-date-field-*` , `data-calendar-*` , `data-slider-*` ) remain authoritative for styling. The picker only adds identity attrs (`data-{picker}-trigger`, `data-{picker}-calendar` ) on the unique wrappers.
**Auto-close / auto-anchor**: the picker's Provider exposes a `handleSelect()` method that the calendar wrapper calls when a selection completes. Range pickers also re-anchor the placeholder so the end month lands in the rightmost visible slot — the user sees their selection, not the month they last scrolled past.
### A28. Time placeholders are `hh`/`mm`/`ss`, not `--`
Dias' `getPlaceholder('hour'|'minute'|'second', …)` returns `'– – '` (two en-dashes). Unreadable in most fonts and not self-describing. `dias/segments.ts` exposes `createSegmentContent` / `createTimeSegmentContent` which use a local `getSegmentPlaceholder` that returns `'hh'` / `'mm'` / `'ss'` for time parts (while delegating to dias for date parts). Time-segmented components automatically benefit — no extra code needed.
### A29. Demo pages are interactive testbeds
docs: prune obsolete audits + studies, refresh remaining references
Deleted 60 obsolete documents that no longer reflect the active-uix
state:
- 47 per-component audit logs (*-audit.md, audit_N.md, AUDIT.md)
- 5 global soma audits (AUDIT_1, codex_audit, soma-audit-04-20/21,
audit-prompt template)
- 5 closed studies (soma/study, morfo/study, morfo/DESIGN, morfo/
PROVIDER_STUDY_2026-04-23, uix/CONTINUITY_2026-04-24)
- 2 superseded refactor proposals (refactor_claude, refactor_code)
- 1 stale bug log (form/BUG-onchange-onblur-hang — repro path
src/routes/test/sium/kitchen-sink no longer exists)
Updated the remaining operational references to drop dead links and
reflect the post-cleanup architecture:
- src/uix/README.md — rewritten links section, dropped pointers to
terra/air/CONTINUITY/refactor docs, updated adom/dom paths
- src/uix/active_architecture.md — header now dated 2026-05-08, lists
the major shifts since 2026-04-26 (provider inheritance dropped,
eidos in production, doctrina parte IV, sound eager-init); pendings
section reflects the eidos migration roadmap; "see also" purged of
deleted files
- src/uix/morfo/README.md — file tree updated (removed DESIGN/study,
added compile/runtime/PERMUTATION_RUNNER); see-also pruned
- src/uix/sema/README.md — SoundChannel section now describes the
eager-init pattern (constructor-time, not lazy); pointer to
sema-implementation-guide added
- src/uix/soma/README.md — drops terra/air mentions, declares the
actual dependency direction (eidos -> soma, not the reverse)
- src/uix/soma/SOMA_ARCHITECTURE.md — §15 rewritten as "Estado actual
y deuda histórica" reflecting Provider-inheritance-drop; demo path
updated to web/routes/
- src/uix/soma/COMPONENT_GUIDE.md — A23 dates section now points to
$libs/days (no external/dates façade); demo paths swapped from
/test/soma/* to web/routes/
- src/uix/morfo/PERMUTATION_RUNNER.md — paths swapped to web/routes/
with a relocation note
- CLAUDE.md — Refactor Documents section replaced with Reference
Documents pointing to per-layer READMEs
npm run check holds at baseline (39 pre-existing errors, none new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
Every soma component demo at `web/routes/{name}` must expose **every public prop** of the Provider as a live control plus a Field-integration section when applicable. Not six static code snippets. Required coverage:
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs
Tier 3 (complete):
DateField, DatePicker, DateRangeField, DateRangePicker
Tier 4 (partial):
TimeField, TimePicker (TimeRangeField pending)
Core:
$lib/util/dias/segments.ts — pure domain primitives for segmented
date/time inputs (constants, guards, initializers, reconciliation,
content generation, Intl opts)
soma/external/dates re-exports everything (no façades inside soma)
soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav,
SegmentState, KEYS predicates, description writers)
Conventions established (A23–A29):
A23 — never re-implement date helpers; extend dias
A24 — readonlySegments without value anchor logs warning
A25 — range components split readonly per-endpoint
A26 — contenteditable segments use onbeforeinput to block IME/paste
A27 — picker composition pattern with shared writableActive refs
A28 — time placeholders render as hh/mm/ss via dias/segments
A29 — demo pages are interactive testbeds, not static galleries
Demo pages: Material Design 3 styled, every public prop wired to a live
control, Field integration + state readout. Day-period uses real radio
group in 12h mode. Auto-anchor on range pickers keeps the end month
visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly.
Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29,
checklist items 28–31, picker composition section, dias boundary,
directory structure note for soma/datetime/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
1. Each boolean → switch/checkbox. Each enum → radio or chip group. Each number → input. Arrays (e.g. `readonlySegments` ) → one toggle per valid value.
2. All format/locale/direction variants switchable (granularity, hourCycle, locale, dateOrder, dir).
3. Field integration section with toggles for parent `Field` 's `disabled` /`readonly`/`required`/`invalid` to verify inheritance.
4. Live state readout — bindable value + placeholder + last `onInvalid` message visible.
5. Edge cases — empty value, readonly-without-value (triggers A24 warning), disabled, required, form submission.
The demo page is how a consumer evaluates the component; a gallery of canned examples does not satisfy that.
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
### A30. Register child ids with direct assignment — never `$effect`
A child provider that publishes its `id` to a parent provider's state (typical for `Field.inputId` , `Dialog.triggerId` , group `labelId` , etc.) assigns **directly in the constructor** . Wrapping the write in `$effect` creates a reactive edge child → parent that can loop when any downstream consumer feeds back into the child's derivations — the page appears "frozen" / "bloqueada" on mount.
```ts
// Wrong — $effect tracks opts.id + writes parent state; any downstream
// chain that reads back into this child loops through Svelte's scheduler.
$effect(() => {
if (this.field) this.field.inputId.current = opts.id.current;
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
});
// Right — one-shot assignment at construction, matching Dialog, Combobox,
// Command, NumberField, DateField, TimeField, ColorField.
if (this.field) this.field.inputId.current = opts.id.current;
```
The rule is specifically for **boilerplate identity writes** (id, labelId, triggerId, contentId, descriptionId). Genuine side effects that must react to dep changes — DOM observers, timers, external subscriptions — still use `$effect` . `ids` almost never change after mount; there's nothing to react to.
**How to recognise a violation:** grep the provider file for `$effect` blocks whose body writes to a parent's `Id.current` / `labelId.current` / similar bookkeeping. Replace with a direct assignment after `Provider.require()` or after the parent reference is captured.
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
Incident: Listbox and PinInput both shipped with `$effect` wrappers for id registration. Listbox froze the page on mount.
### A31. Per-entity `$derived` must NOT read global state through the provider
When each item / row / cell owns a `$derived` that calls a provider method which reads a shared `$state` (selection array, items registry, version counter, expanded map), every mutation of that shared state invalidates **every** entity's derivation — and each re-runs the provider method. Classic O(N²) cascade. Works with 1– 5 items; hangs at 30+.
```ts
// Wrong — O(N²): value change invalidates N isRovingTarget derivations,
// each re-runs a full DOM query + Set construction.
readonly isRovingTarget = $derived.by(() => {
return this.provider.rovingTarget() === this.opts.ref.current;
});
rovingTarget(): HTMLElement | undefined {
const selected = new Set(this.opts.value.current);
return this.getItems().find((el) => selected.has(el.dataset.value)) ?? this.getItems()[0];
}
// Right — O(N): lift the expensive computation to a single $derived on
// the provider. Per-entity derivations only pointer-compare.
// Provider:
readonly rovingTargetEl = $derived.by(() => {
const items = this.getItems();
if (items.length === 0) return undefined;
const selected = new Set(this.opts.value.current);
return items.find((el) => selected.has(el.dataset.value)) ?? items[0];
});
// Item:
readonly isRovingTarget = $derived.by(() => {
return this.opts.ref.current === this.provider.rovingTargetEl;
});
```
**Patterns that commonly hit this:**
- `provider.isVisible(value)` / `provider.isSelected(value)` / `provider.isExpanded(id)` called from N per-item derivations → lift a `visibleSet: Set<string>` / `selectedSet` / `expandedSet` on the provider.
- `provider.getItems()` (DOM query or registry read) called from N per-item derivations → lift `rovingTargetEl` / `firstVisibleIndex` / whatever the real answer is to a single provider derived.
**Recognise it:** works with a handful of entities, freezes with a larger list. `isX` method called from N derivations is the signature. Fix before shipping — do not mask with `untrack` , microtask batching, or version-counter reads.
Incidents: Command component (2026-04-17, external diagnosis required), Listbox rovingTarget (2026-04-18).
### A32. Explicit gap sign-off — the user approves scope, the programmer doesn't
The comparison table (`## Comparison` in every component README) is a **contract** , not a footnote. Before saying "component done":
1. **Fill the table** — every feature that at least one of Radix / Ark / bits / React Aria implements is a row. Mark each cell `✅` / `⚠️` / `❌` — don't omit rows to hide a gap.
2. **Present the table in the conversation** — paste the rows where at least one `⚠️` or `❌` exists (or the full table) into the reply that finalises the component. The user sees the gaps before approving.
3. **Decide each `❌` / `⚠️` explicitly** — for every non-`✅`, the user approves one of:
- **Implement now** — the gap is strategic or blocks a WAI-ARIA / reference expectation. Bring it into scope and finish the component with the feature.
- **Defer to v2** — the gap exists but isn't blocking. Add it to the component's `## Out of scope (v2 roadmap)` section with: what it is, reference libraries that ship it, why deferred, cost estimate in lines. This becomes the PR backlog.
- **Drop** — the feature isn't a real gap for soma (e.g. a competitor's framework-specific quirk, or something the air layer should own). Document the reasoning and remove the row from the table.
4. **No silent gaps** — if a feature appears only as a footnote and nowhere else, that's a failure mode. The reader of the README should see `❌` and know it's a deliberate decision.
**Why this exists:** during the 2026-04-19 session, AlertDialog / Listbox / Carousel / NavigationMenu all shipped with strategic gaps (Escape default, range-select, multi-slide, Viewport, Sub, data-motion, skipDelayDuration) hidden inside comparison tables the user never saw in conversation. AlertDialog in particular inherited Dialog's `escapeKeydownBehavior='ignore'` default — a WAI-ARIA regression disguised as a `⚠️` row. The rule is: if the gap isn't argued explicitly, it doesn't get to ship.
**How to apply:** the checklist items 34– 35 are the mechanism. Item 34 says "present the table in the conversation and get sign-off"; item 35 says "deferred features become their own README section, not a table footnote".
### A33. Reactive collections: `SvelteMap` / `SvelteSet`, not `$state(new Map())`
In Svelte 5 runes, plain `Map` / `Set` are **not** deeply reactive. `$state(new Map())` only tracks **reassignment** of the field — writing to the map via `.set(k, v)` / `.delete(k)` does not notify readers of `.get(k)` , `.has(k)` , `.size` , or iteration.
Use `SvelteMap` / `SvelteSet` from `'svelte/reactivity'` when:
- Readers index **per-entry** (`.get(k)`, `.has(k)` , iteration, `.size` ) inside a `$derived` , `$effect` , or template expression.
- Mutations happen via `.set(k, v)` / `.delete(k)` on the existing collection (the common ergonomic case).
- You want **per-entry invalidation** — changing key `A` shouldn't invalidate readers of key `B` .
```ts
// ❌ Wrong — .set() updates don't propagate to readers of .get() in $derived.
import { ... } from '...';
class ExampleProvider {
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
private cache = $state(new Map< string , number > ());
measure(k: string, v: number) {
this.cache.set(k, v); // silently non-reactive
}
readonly computed = $derived.by(() => this.cache.get('x') ?? 0);
// ^^^^^ never re-runs after .set()
}
// ✅ Right — per-entry reactive, clean .set().
import { SvelteMap } from 'svelte/reactivity';
class ExampleProvider {
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
private cache = new SvelteMap< string , number > ();
measure(k: string, v: number) {
this.cache.set(k, v); // notifies readers of .get(k) etc.
}
readonly computed = $derived.by(() => this.cache.get('x') ?? 0);
// re-runs when .set('x', ...) fires
}
```
**The "reassign-the-whole-Map" workaround** — some code does this to force reactivity with plain `$state(Map)` :
```ts
// Works but fragile:
registerLabel(value: string, label: string) {
const next = new Map(this.labels);
next.set(value, label);
this.labels = next; // field reassignment triggers tracking
}
```
This is O(N) per mutation (copies the whole Map), looks like a bug to future readers ("why clone?"), and breaks silently if anyone refactors to `this.labels.set(...)` direct. `SvelteMap` removes both problems — `.set` is reactive and O(1).
**Detection:** grep for `$state(new Map` / `$state(new Set` in the providers folder. For each hit, audit: are readers using `.get` / `.has` / `.size` / iteration inside `$derived` ? If yes, migrate to `SvelteMap` /`SvelteSet`. The reassignment-clone workaround should be rewritten too.
**Incidents:**
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
- VirtualList dynamic heights (2026-04-19) — `ResizeObserver` wrote sizes to `$state(Map)` cache, `offsets` derived read `.get(key)` and never re-ran. Every row stayed at the 60 px estimate.
- Form `touched` + `registry` Maps — `setFieldTouched` / `registerField` use `.set` direct, but `isTouched` / `isDirty` / `firstInvalidField` derivations read `.values()` / `.keys()` / `.has()` . Same bug, harder to notice because `values` + `errors` state cover most user-visible flows.
- Combobox `labelRegistry` — used the "clone-and-reassign" workaround. Works today but fragile.
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired
Morfo (src/uix/morfo/): machine-readable contract of a component's public
DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across
three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment.
Types, sium-based validator with cross-field invariants, dialog.ts as the
first canonical morfo with data-last-action for Sema-causal exits, 7 passing
unit tests.
createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy
inline signature removed. Dialog provider refactored to derive parts +
data contract from dialogMorfo. The other 65 soma components need porting
(svelte-check lists them) — tracked work.
Tooling:
scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes
scripts/morfo-check.ts — validates emitted DOM vs morfo
scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency
npm run smoke / morfo:check / morfo:vocabulary
WAI-ARIA APG components added (100% APG coverage now):
Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress,
SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel
pattern; hierarchical Table rows deprecated — use TreeGrid instead).
air: Link, Banner.
Sema prep:
src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements.
Morfo already provides everything Sema needs (data-last-action patterns,
transition markers, enumerable state values, cross-component consistency).
Sema implementation deferred.
COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM
topology vs .require() audit, npm run smoke is part of done) + A34 rule
with incident log. WAI-ARIA pattern listed as soma membership criterion.
Pagination translation bug fixed (soma.pagination.page -> idlangref
PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.*
-> components.table.*). Avatar demo now offline-safe (SVG data URL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
### A34. Verification before "done": translation namespace + DOM topology + smoke
Three classes of bug cannot be caught by `svelte-check` or HTTP 200 — they all require either a runtime grep or a real browser. They must be run every time a component, demo, or lang entry is touched.
1. **Translation namespace grep.** soma's namespace is `components.{kebab-name}.*` (or `common.*` for shared strings). Any `soma.…` or other prefix inside a quoted translation path is a bug that logs `[langs] Translation key not found` at runtime. Check with:
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired
Morfo (src/uix/morfo/): machine-readable contract of a component's public
DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across
three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment.
Types, sium-based validator with cross-field invariants, dialog.ts as the
first canonical morfo with data-last-action for Sema-causal exits, 7 passing
unit tests.
createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy
inline signature removed. Dialog provider refactored to derive parts +
data contract from dialogMorfo. The other 65 soma components need porting
(svelte-check lists them) — tracked work.
Tooling:
scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes
scripts/morfo-check.ts — validates emitted DOM vs morfo
scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency
npm run smoke / morfo:check / morfo:vocabulary
WAI-ARIA APG components added (100% APG coverage now):
Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress,
SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel
pattern; hierarchical Table rows deprecated — use TreeGrid instead).
air: Link, Banner.
Sema prep:
src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements.
Morfo already provides everything Sema needs (data-last-action patterns,
transition markers, enumerable state values, cross-component consistency).
Sema implementation deferred.
COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM
topology vs .require() audit, npm run smoke is part of done) + A34 rule
with incident log. WAI-ARIA pattern listed as soma membership criterion.
Pagination translation bug fixed (soma.pagination.page -> idlangref
PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.*
-> components.table.*). Avatar demo now offline-safe (SVG data URL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
```sh
grep -rn "['\"]soma\.[a-z-]" src --include=!*.md
```
Fixes: route component-owned text through `morfo.translations` + `v.translationRef` ; route shared text through `v.commonRef` or an explicit idlangref constant. Don't build translation paths via template strings in demos or providers. If a demo needs a dynamic path helper (like `tt('columns', 'Columns')` ), hard-code the namespace prefix `components.{name}.` correctly.
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired
Morfo (src/uix/morfo/): machine-readable contract of a component's public
DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across
three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment.
Types, sium-based validator with cross-field invariants, dialog.ts as the
first canonical morfo with data-last-action for Sema-causal exits, 7 passing
unit tests.
createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy
inline signature removed. Dialog provider refactored to derive parts +
data contract from dialogMorfo. The other 65 soma components need porting
(svelte-check lists them) — tracked work.
Tooling:
scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes
scripts/morfo-check.ts — validates emitted DOM vs morfo
scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency
npm run smoke / morfo:check / morfo:vocabulary
WAI-ARIA APG components added (100% APG coverage now):
Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress,
SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel
pattern; hierarchical Table rows deprecated — use TreeGrid instead).
air: Link, Banner.
Sema prep:
src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements.
Morfo already provides everything Sema needs (data-last-action patterns,
transition markers, enumerable state values, cross-component consistency).
Sema implementation deferred.
COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM
topology vs .require() audit, npm run smoke is part of done) + A34 rule
with incident log. WAI-ARIA pattern listed as soma membership criterion.
Pagination translation bug fixed (soma.pagination.page -> idlangref
PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.*
-> components.table.*). Avatar demo now offline-safe (SVG data URL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
2. **DOM topology vs `.require()` audit.** Svelte's context (via `getContext` ) flows only to descendants. Every `X.require()` call must be reachable from a descendant of the component that set the context. The trap is HTML: `<tr>` cannot nest `<tr>` , so `Table.RowDetail` (rendered as a sibling `<tr>` of `Table.Row` ) cannot `TableRowProvider.require()` . Use one of:
- Receive the object via a prop (consumer passes `{row}` or similar explicitly). This is consistent with `<Table.Row {row}>` / `<Table.Cell {cell}>` — Table already requires explicit objects.
- Use `.get()` + a fallback for truly optional context (e.g. `FeedProvider.get()` inside `Feed.Sentinel` , which can live outside a Feed).
- Restructure so the child actually lives inside the parent's subtree.
Svelte-check never catches this — the error is thrown on mount. Smoke catches it.
docs: prune obsolete audits + studies, refresh remaining references
Deleted 60 obsolete documents that no longer reflect the active-uix
state:
- 47 per-component audit logs (*-audit.md, audit_N.md, AUDIT.md)
- 5 global soma audits (AUDIT_1, codex_audit, soma-audit-04-20/21,
audit-prompt template)
- 5 closed studies (soma/study, morfo/study, morfo/DESIGN, morfo/
PROVIDER_STUDY_2026-04-23, uix/CONTINUITY_2026-04-24)
- 2 superseded refactor proposals (refactor_claude, refactor_code)
- 1 stale bug log (form/BUG-onchange-onblur-hang — repro path
src/routes/test/sium/kitchen-sink no longer exists)
Updated the remaining operational references to drop dead links and
reflect the post-cleanup architecture:
- src/uix/README.md — rewritten links section, dropped pointers to
terra/air/CONTINUITY/refactor docs, updated adom/dom paths
- src/uix/active_architecture.md — header now dated 2026-05-08, lists
the major shifts since 2026-04-26 (provider inheritance dropped,
eidos in production, doctrina parte IV, sound eager-init); pendings
section reflects the eidos migration roadmap; "see also" purged of
deleted files
- src/uix/morfo/README.md — file tree updated (removed DESIGN/study,
added compile/runtime/PERMUTATION_RUNNER); see-also pruned
- src/uix/sema/README.md — SoundChannel section now describes the
eager-init pattern (constructor-time, not lazy); pointer to
sema-implementation-guide added
- src/uix/soma/README.md — drops terra/air mentions, declares the
actual dependency direction (eidos -> soma, not the reverse)
- src/uix/soma/SOMA_ARCHITECTURE.md — §15 rewritten as "Estado actual
y deuda histórica" reflecting Provider-inheritance-drop; demo path
updated to web/routes/
- src/uix/soma/COMPONENT_GUIDE.md — A23 dates section now points to
$libs/days (no external/dates façade); demo paths swapped from
/test/soma/* to web/routes/
- src/uix/morfo/PERMUTATION_RUNNER.md — paths swapped to web/routes/
with a relocation note
- CLAUDE.md — Refactor Documents section replaced with Reference
Documents pointing to per-layer READMEs
npm run check holds at baseline (39 pre-existing errors, none new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
3. ** `npm run smoke` .** The smoke script (`scripts/smoke-check.mjs`) walks every `web/routes/*` route with Playwright and surfaces:
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired
Morfo (src/uix/morfo/): machine-readable contract of a component's public
DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across
three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment.
Types, sium-based validator with cross-field invariants, dialog.ts as the
first canonical morfo with data-last-action for Sema-causal exits, 7 passing
unit tests.
createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy
inline signature removed. Dialog provider refactored to derive parts +
data contract from dialogMorfo. The other 65 soma components need porting
(svelte-check lists them) — tracked work.
Tooling:
scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes
scripts/morfo-check.ts — validates emitted DOM vs morfo
scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency
npm run smoke / morfo:check / morfo:vocabulary
WAI-ARIA APG components added (100% APG coverage now):
Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress,
SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel
pattern; hierarchical Table rows deprecated — use TreeGrid instead).
air: Link, Banner.
Sema prep:
src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements.
Morfo already provides everything Sema needs (data-last-action patterns,
transition markers, enumerable state values, cross-component consistency).
Sema implementation deferred.
COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM
topology vs .require() audit, npm run smoke is part of done) + A34 rule
with incident log. WAI-ARIA pattern listed as soma membership criterion.
Pagination translation bug fixed (soma.pagination.page -> idlangref
PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.*
-> components.table.*). Avatar demo now offline-safe (SVG data URL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
- `pageerror` (uncaught throw during hydration — e.g. `Context "X" not found` )
- `console.error` (runtime exceptions caught by the framework)
- Translation key missing warnings
- `[soma]` context-not-found warnings
Run it before declaring a component done. Regressions in unrelated components caused by lang-table edits or core changes surface here too. `npm run smoke` requires `npm run dev` running in another terminal and auto-detects the port on 5173– 5180.
**Incidents:**
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired
Morfo (src/uix/morfo/): machine-readable contract of a component's public
DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across
three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment.
Types, sium-based validator with cross-field invariants, dialog.ts as the
first canonical morfo with data-last-action for Sema-causal exits, 7 passing
unit tests.
createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy
inline signature removed. Dialog provider refactored to derive parts +
data contract from dialogMorfo. The other 65 soma components need porting
(svelte-check lists them) — tracked work.
Tooling:
scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes
scripts/morfo-check.ts — validates emitted DOM vs morfo
scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency
npm run smoke / morfo:check / morfo:vocabulary
WAI-ARIA APG components added (100% APG coverage now):
Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress,
SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel
pattern; hierarchical Table rows deprecated — use TreeGrid instead).
air: Link, Banner.
Sema prep:
src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements.
Morfo already provides everything Sema needs (data-last-action patterns,
transition markers, enumerable state values, cross-component consistency).
Sema implementation deferred.
COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM
topology vs .require() audit, npm run smoke is part of done) + A34 rule
with incident log. WAI-ARIA pattern listed as soma membership criterion.
Pagination translation bug fixed (soma.pagination.page -> idlangref
PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.*
-> components.table.*). Avatar demo now offline-safe (SVG data URL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
- Table demo (2026-04-19) — `tt('columns')` built `soma.table.columns` instead of `components.table.columns` . Dozens of `Translation key not found` logs, silent in svelte-check.
- Pagination item `aria-label` (pre-existing) — provider called `langs.t('soma.pagination.page')` directly. Same class of bug; fixed by migrating to an idlangref constant (`PAGINATION_LANGS.PAGE`).
- `Table.RowDetail` (2026-04-19) — first version called `TableRowProvider.require()` . Threw `Context "TableRow" not found` because `<tr>` cannot nest and the Detail is a DOM sibling, not descendant. Fixed by taking `{row}` as prop + deriving the `aria-controls` id deterministically from `row.id` .
### A35. `$effect` reading `ref.current` + writing provider state is a loop trap
A30 forbids using `$effect` for _id registration_ . A35 extends the ban to **any** per-item `$effect` that reads `opts.ref.current` (or similar reactive input) and writes to provider state that the per-item `props` $derived reads back through the attachment system.
**Recognise the shape:**
```ts
// ❌ Wrong — mounts the component and immediately loops.
$effect(() => {
void opts.ref.current; // tracked
void opts.disabled.current; // tracked
this.provider.notifyItemsChanged(); // writes itemsVersion
return () => this.provider.notifyItemsChanged();
});
// Provider:
readonly firstTabStop = $derived.by(() => {
void this.itemsVersion; // tracks the counter
return this.getItems()[0];
});
// Item props:
tabindex: this.provider.isTabStop(this.opts.ref.current) ? 0 : -1
// isTabStop reads firstTabStop → tabindex depends on itemsVersion
```
**Why it loops:** the item `$effect` writes `itemsVersion` → invalidates `firstTabStop` → invalidates every item's `props` $derived → Svelte re-spreads `{...mergedProps}` including the ref attachment → attachment re-runs → `ref.current = node` (same node, but the internal write still notifies tracked subscribers) → item `$effect` re-runs → back to step 1. Svelte terminates with`effect_update_depth_exceeded`.
**Fix:**
- **Don't use `$effect` with reactive deps to notify the provider.** Register in the constructor (A30 pattern) or via explicit method calls from handlers. `$effect` is for the cleanup function only: `$effect(() => () => provider.unregister(...))` .
- When a per-item `$derived` needs to consult the full item set (e.g. "am I the first tab stop?"), wrap the set-walking read in `untrack(...)` so the derivation depends only on the single reactive field it actually cares about (`lastFocusedElement`), not on every sibling's ref or a shared counter.
```ts
// ✅ Right — no counter, no feedback edge.
isTabStop(el: HTMLElement | null): boolean {
if (!el) return false;
if (this.lastFocusedElement) return this.lastFocusedElement === el;
return untrack(() => this.getItems()[0] === el);
}
```
**Recognise it:** demo page freezes or logs `effect_update_depth_exceeded` on mount. The stacktrace names the per-item `$effect` and the provider setter it calls (e.g. `set itemsVersion` ). `npm run smoke` passes (HTTP 200) but `scripts/morfo-check.ts` fails with `page.$$eval: Execution context was destroyed, most likely because of a navigation` — Playwright sees the page's error handler trip and the document effectively dies mid-query.
**Incident:** Toolbar (2026-04-19) — Button / Link / GroupItem each carried a mount `$effect` that called `notifyItemsChanged()` ; `firstTabStop` $derived read `itemsVersion` ; per-item `props` read `firstTabStop` via `isTabStop` . Loop tripped on every page load, hiding behind an `ERROR toolbar ... Execution context destroyed` in `morfo:check` (not obviously a reactivity bug until probed in the browser console). Fix: removed the counter + three effects; `isTabStop` uses `untrack` .
soma/morfo: typed createAttrs + 6 audit batches + form hang fix + A36
## morfo: typed createAttrs
- `types.ts`: all array fields are now `readonly` so morfos can be authored
with `as const satisfies Morfo` without casts.
- `createAttrs` is a generic `<const M extends MorfoShape>` that infers the
emitted attr map from the morfo's literal shape. Typos like `attrs.trigerr`
are now compile errors; autocomplete lists every valid part.
- All 66 morfos migrated: `export const xMorfo = { ... } as const satisfies Morfo`.
- Two latent bugs the new typing caught: `dateRangeFieldAttrs.root` and
`timeRangeFieldAttrs.root` — legacy from the root→provider refactor, fixed
to `.provider`.
- Docs (morfo/README, COMPONENT_GUIDE) mandate `as const satisfies Morfo`;
the older `: Morfo` annotation widens literals and loses autocomplete.
## Audit batches 1–7 (soma-audit-2026-04-20)
Batch 1 — Keyboard a11y (A14/A12):
- Tabs: `isFallbackTabStop(el)` using `untrack` → first trigger gets
`tabindex=0` when no tab matches (WCAG 2.1.1 was failing).
- RatingGroup: `dir` prop + `getDirectionalKeys(dir, 'horizontal')` for RTL.
- Accordion P1-1/P1-6 dismissed: roving/labelledby already correct per APG.
Batch 2 — A33 reactive collections: Select `labelRegistry` and NavigationMenu
`triggerRefs` migrated to `SvelteMap`.
Batch 3 — A31 O(N²) fix: RadioGroup lifts `hasSelection` + `isFallbackTabStop`
to the provider with `untrack`-wrapped DOM walk. Per-item derivations drop
from O(N²) to O(1) per trigger.
Batch 4 — Morfo aria sync: toast / time-field / date-field / tabs /
tag-group / navigation-menu declare every aria the provider emits
(14 entries across 6 files).
Batch 5 — Accessible naming props: Tabs.List, ToggleGroup, TreeView,
NavigationMenu expose `aria-label?` / `aria-labelledby?` with explicit
"required unless labelledby is set" JSDoc.
Batch 6 — A30 cleanup: Checkbox `setLabelId` and 5 trigger-ref providers
(select/tooltip/link-preview/dropdown-menu/context-menu) migrated from
`$effect(() => parent.ref = opts.ref)` to the `Provider` base's
`onRefChange` callback (fires via attachRef inside `untrack`).
Batch 7 — A11 wrapper thinning: stepper/tree-view wrappers removed
`ctx.get()`, providers expose proxy derivations; time/date-range-field
inputs use `untrack(() => …)` for one-shot id registration; menubar
content's 40-line keyboard nav moved to `MenubarMenuProvider.onContentKeydown`;
color-picker area + channel-slider pointer state machines moved to the
providers with `onpointerdown/move/up` emitted via `props`.
form-auto-fields deferred with justification (reflective schema renderer,
not a state-holding wrapper).
## Form hang fix (A36)
`runValidate` entered the `isPromiseLike` branch even for sync schemas
because sium's Standard-Schema adapter declares `validate: async`. The
fallback `return { errors, issues }` read both reactive vars inside the
validation `$effect` while the `.then(...)` microtask wrote them
→ microtask-mediated loop, silent to Svelte's sync effect-depth guard.
Symptom: kitchen-sink hung the tab on mount with onChange / onBlur.
Fix: `return untrack(() => ({ errors, issues }))`. Regression locked by
two new 5s-timeout tests in `form-auto-fields.svelte.test.ts`.
Pattern codified as A36 in COMPONENT_GUIDE; CLAUDE.md count updated to
A1–A36 / 39 items.
## Verification
- `npm run check`: 0 errors in soma/morfo scope (sema demo pre-existing
errors untouched).
- `npx vitest run src/uix/soma src/uix/morfo`: all tests green
(94 + 7 = 101 tests, inc. 2 new form regression tests).
- `npm run smoke`: 65/65 routes.
- `npx tsx scripts/morfo-check.ts`: 66/66 morfos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
### A36. Async side-effect + reactive read-back = microtask-mediated loop
A35 covers **synchronous** `$effect` feedback cycles. A36 covers the
**asynchronous** variant — the one Svelte's effect-depth guard does NOT
catch because each re-entry happens in a separate scheduler tick, mediated
by the microtask queue.
**Shape:**
```ts
// ❌ Wrong — microtask loop.
$effect(() => {
JSON.stringify(values); // tracks values
const res = schema['~standard'].validate(values);
if (isPromiseLike(res)) {
// Fires later in a microtask:
res.then((r) => {
errors = groupIssues(r.issues); // writes errors
issues = groupIssues(r.issues); // writes issues
});
// Synchronous fallback value — reads errors/issues REACTIVELY:
return { errors, issues }; // ← tracks errors + issues
}
return res.issues ? groupIssues(res.issues) : { errors: {}, issues: {} };
soma/morfo: typed createAttrs + 6 audit batches + form hang fix + A36
## morfo: typed createAttrs
- `types.ts`: all array fields are now `readonly` so morfos can be authored
with `as const satisfies Morfo` without casts.
- `createAttrs` is a generic `<const M extends MorfoShape>` that infers the
emitted attr map from the morfo's literal shape. Typos like `attrs.trigerr`
are now compile errors; autocomplete lists every valid part.
- All 66 morfos migrated: `export const xMorfo = { ... } as const satisfies Morfo`.
- Two latent bugs the new typing caught: `dateRangeFieldAttrs.root` and
`timeRangeFieldAttrs.root` — legacy from the root→provider refactor, fixed
to `.provider`.
- Docs (morfo/README, COMPONENT_GUIDE) mandate `as const satisfies Morfo`;
the older `: Morfo` annotation widens literals and loses autocomplete.
## Audit batches 1–7 (soma-audit-2026-04-20)
Batch 1 — Keyboard a11y (A14/A12):
- Tabs: `isFallbackTabStop(el)` using `untrack` → first trigger gets
`tabindex=0` when no tab matches (WCAG 2.1.1 was failing).
- RatingGroup: `dir` prop + `getDirectionalKeys(dir, 'horizontal')` for RTL.
- Accordion P1-1/P1-6 dismissed: roving/labelledby already correct per APG.
Batch 2 — A33 reactive collections: Select `labelRegistry` and NavigationMenu
`triggerRefs` migrated to `SvelteMap`.
Batch 3 — A31 O(N²) fix: RadioGroup lifts `hasSelection` + `isFallbackTabStop`
to the provider with `untrack`-wrapped DOM walk. Per-item derivations drop
from O(N²) to O(1) per trigger.
Batch 4 — Morfo aria sync: toast / time-field / date-field / tabs /
tag-group / navigation-menu declare every aria the provider emits
(14 entries across 6 files).
Batch 5 — Accessible naming props: Tabs.List, ToggleGroup, TreeView,
NavigationMenu expose `aria-label?` / `aria-labelledby?` with explicit
"required unless labelledby is set" JSDoc.
Batch 6 — A30 cleanup: Checkbox `setLabelId` and 5 trigger-ref providers
(select/tooltip/link-preview/dropdown-menu/context-menu) migrated from
`$effect(() => parent.ref = opts.ref)` to the `Provider` base's
`onRefChange` callback (fires via attachRef inside `untrack`).
Batch 7 — A11 wrapper thinning: stepper/tree-view wrappers removed
`ctx.get()`, providers expose proxy derivations; time/date-range-field
inputs use `untrack(() => …)` for one-shot id registration; menubar
content's 40-line keyboard nav moved to `MenubarMenuProvider.onContentKeydown`;
color-picker area + channel-slider pointer state machines moved to the
providers with `onpointerdown/move/up` emitted via `props`.
form-auto-fields deferred with justification (reflective schema renderer,
not a state-holding wrapper).
## Form hang fix (A36)
`runValidate` entered the `isPromiseLike` branch even for sync schemas
because sium's Standard-Schema adapter declares `validate: async`. The
fallback `return { errors, issues }` read both reactive vars inside the
validation `$effect` while the `.then(...)` microtask wrote them
→ microtask-mediated loop, silent to Svelte's sync effect-depth guard.
Symptom: kitchen-sink hung the tab on mount with onChange / onBlur.
Fix: `return untrack(() => ({ errors, issues }))`. Regression locked by
two new 5s-timeout tests in `form-auto-fields.svelte.test.ts`.
Pattern codified as A36 in COMPONENT_GUIDE; CLAUDE.md count updated to
A1–A36 / 39 items.
## Verification
- `npm run check`: 0 errors in soma/morfo scope (sema demo pre-existing
errors untouched).
- `npx vitest run src/uix/soma src/uix/morfo`: all tests green
(94 + 7 = 101 tests, inc. 2 new form regression tests).
- `npm run smoke`: 65/65 routes.
- `npx tsx scripts/morfo-check.ts`: 66/66 morfos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
});
```
**Why it loops:**
1. Effect runs. `JSON.stringify(values)` tracks `values` . The fallback
`return { errors, issues }` tracks `errors` and `issues` as deps.
2. `.then(...)` is scheduled as a microtask.
3. Effect returns.
4. Microtask fires: writes `errors = …` and `issues = …` .
5. Those writes invalidate the effect (it depends on `errors` /`issues`).
6. Effect re-runs. New `.then` scheduled. Goto 4.
Each iteration enqueues another microtask. The microtask queue starves the
event loop — the tab freezes. **No `effect_update_depth_exceeded` fires**
because the guard only counts depth inside a single synchronous tick.
**Fix:** wrap the reactive read in `untrack` so the effect doesn't
subscribe to the state the async callback writes.
```ts
// ✅ Right — `untrack` breaks the feedback edge.
if (isPromiseLike(res)) {
res.then((r) => {
errors = groupIssues(r.issues);
issues = groupIssues(r.issues);
});
return untrack(() => ({ errors, issues }));
soma/morfo: typed createAttrs + 6 audit batches + form hang fix + A36
## morfo: typed createAttrs
- `types.ts`: all array fields are now `readonly` so morfos can be authored
with `as const satisfies Morfo` without casts.
- `createAttrs` is a generic `<const M extends MorfoShape>` that infers the
emitted attr map from the morfo's literal shape. Typos like `attrs.trigerr`
are now compile errors; autocomplete lists every valid part.
- All 66 morfos migrated: `export const xMorfo = { ... } as const satisfies Morfo`.
- Two latent bugs the new typing caught: `dateRangeFieldAttrs.root` and
`timeRangeFieldAttrs.root` — legacy from the root→provider refactor, fixed
to `.provider`.
- Docs (morfo/README, COMPONENT_GUIDE) mandate `as const satisfies Morfo`;
the older `: Morfo` annotation widens literals and loses autocomplete.
## Audit batches 1–7 (soma-audit-2026-04-20)
Batch 1 — Keyboard a11y (A14/A12):
- Tabs: `isFallbackTabStop(el)` using `untrack` → first trigger gets
`tabindex=0` when no tab matches (WCAG 2.1.1 was failing).
- RatingGroup: `dir` prop + `getDirectionalKeys(dir, 'horizontal')` for RTL.
- Accordion P1-1/P1-6 dismissed: roving/labelledby already correct per APG.
Batch 2 — A33 reactive collections: Select `labelRegistry` and NavigationMenu
`triggerRefs` migrated to `SvelteMap`.
Batch 3 — A31 O(N²) fix: RadioGroup lifts `hasSelection` + `isFallbackTabStop`
to the provider with `untrack`-wrapped DOM walk. Per-item derivations drop
from O(N²) to O(1) per trigger.
Batch 4 — Morfo aria sync: toast / time-field / date-field / tabs /
tag-group / navigation-menu declare every aria the provider emits
(14 entries across 6 files).
Batch 5 — Accessible naming props: Tabs.List, ToggleGroup, TreeView,
NavigationMenu expose `aria-label?` / `aria-labelledby?` with explicit
"required unless labelledby is set" JSDoc.
Batch 6 — A30 cleanup: Checkbox `setLabelId` and 5 trigger-ref providers
(select/tooltip/link-preview/dropdown-menu/context-menu) migrated from
`$effect(() => parent.ref = opts.ref)` to the `Provider` base's
`onRefChange` callback (fires via attachRef inside `untrack`).
Batch 7 — A11 wrapper thinning: stepper/tree-view wrappers removed
`ctx.get()`, providers expose proxy derivations; time/date-range-field
inputs use `untrack(() => …)` for one-shot id registration; menubar
content's 40-line keyboard nav moved to `MenubarMenuProvider.onContentKeydown`;
color-picker area + channel-slider pointer state machines moved to the
providers with `onpointerdown/move/up` emitted via `props`.
form-auto-fields deferred with justification (reflective schema renderer,
not a state-holding wrapper).
## Form hang fix (A36)
`runValidate` entered the `isPromiseLike` branch even for sync schemas
because sium's Standard-Schema adapter declares `validate: async`. The
fallback `return { errors, issues }` read both reactive vars inside the
validation `$effect` while the `.then(...)` microtask wrote them
→ microtask-mediated loop, silent to Svelte's sync effect-depth guard.
Symptom: kitchen-sink hung the tab on mount with onChange / onBlur.
Fix: `return untrack(() => ({ errors, issues }))`. Regression locked by
two new 5s-timeout tests in `form-auto-fields.svelte.test.ts`.
Pattern codified as A36 in COMPONENT_GUIDE; CLAUDE.md count updated to
A1–A36 / 39 items.
## Verification
- `npm run check`: 0 errors in soma/morfo scope (sema demo pre-existing
errors untouched).
- `npx vitest run src/uix/soma src/uix/morfo`: all tests green
(94 + 7 = 101 tests, inc. 2 new form regression tests).
- `npm run smoke`: 65/65 routes.
- `npx tsx scripts/morfo-check.ts`: 66/66 morfos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
}
```
**Recognise it:**
- **Browser tab freezes on mount** of a specific component variant. No
Svelte error in the console.
- `npm run smoke` **passes** because its 500 ms post-load settle is
shorter than the microtask storm's ramp-up.
- `npm run morfo:check` may pass too — the DOM exists, validation just
never reaches a steady state.
- Bisect by stripping the effect body to `read + empty-write → add
validate alone → add the real writes back`. The combination where the
soma/morfo: typed createAttrs + 6 audit batches + form hang fix + A36
## morfo: typed createAttrs
- `types.ts`: all array fields are now `readonly` so morfos can be authored
with `as const satisfies Morfo` without casts.
- `createAttrs` is a generic `<const M extends MorfoShape>` that infers the
emitted attr map from the morfo's literal shape. Typos like `attrs.trigerr`
are now compile errors; autocomplete lists every valid part.
- All 66 morfos migrated: `export const xMorfo = { ... } as const satisfies Morfo`.
- Two latent bugs the new typing caught: `dateRangeFieldAttrs.root` and
`timeRangeFieldAttrs.root` — legacy from the root→provider refactor, fixed
to `.provider`.
- Docs (morfo/README, COMPONENT_GUIDE) mandate `as const satisfies Morfo`;
the older `: Morfo` annotation widens literals and loses autocomplete.
## Audit batches 1–7 (soma-audit-2026-04-20)
Batch 1 — Keyboard a11y (A14/A12):
- Tabs: `isFallbackTabStop(el)` using `untrack` → first trigger gets
`tabindex=0` when no tab matches (WCAG 2.1.1 was failing).
- RatingGroup: `dir` prop + `getDirectionalKeys(dir, 'horizontal')` for RTL.
- Accordion P1-1/P1-6 dismissed: roving/labelledby already correct per APG.
Batch 2 — A33 reactive collections: Select `labelRegistry` and NavigationMenu
`triggerRefs` migrated to `SvelteMap`.
Batch 3 — A31 O(N²) fix: RadioGroup lifts `hasSelection` + `isFallbackTabStop`
to the provider with `untrack`-wrapped DOM walk. Per-item derivations drop
from O(N²) to O(1) per trigger.
Batch 4 — Morfo aria sync: toast / time-field / date-field / tabs /
tag-group / navigation-menu declare every aria the provider emits
(14 entries across 6 files).
Batch 5 — Accessible naming props: Tabs.List, ToggleGroup, TreeView,
NavigationMenu expose `aria-label?` / `aria-labelledby?` with explicit
"required unless labelledby is set" JSDoc.
Batch 6 — A30 cleanup: Checkbox `setLabelId` and 5 trigger-ref providers
(select/tooltip/link-preview/dropdown-menu/context-menu) migrated from
`$effect(() => parent.ref = opts.ref)` to the `Provider` base's
`onRefChange` callback (fires via attachRef inside `untrack`).
Batch 7 — A11 wrapper thinning: stepper/tree-view wrappers removed
`ctx.get()`, providers expose proxy derivations; time/date-range-field
inputs use `untrack(() => …)` for one-shot id registration; menubar
content's 40-line keyboard nav moved to `MenubarMenuProvider.onContentKeydown`;
color-picker area + channel-slider pointer state machines moved to the
providers with `onpointerdown/move/up` emitted via `props`.
form-auto-fields deferred with justification (reflective schema renderer,
not a state-holding wrapper).
## Form hang fix (A36)
`runValidate` entered the `isPromiseLike` branch even for sync schemas
because sium's Standard-Schema adapter declares `validate: async`. The
fallback `return { errors, issues }` read both reactive vars inside the
validation `$effect` while the `.then(...)` microtask wrote them
→ microtask-mediated loop, silent to Svelte's sync effect-depth guard.
Symptom: kitchen-sink hung the tab on mount with onChange / onBlur.
Fix: `return untrack(() => ({ errors, issues }))`. Regression locked by
two new 5s-timeout tests in `form-auto-fields.svelte.test.ts`.
Pattern codified as A36 in COMPONENT_GUIDE; CLAUDE.md count updated to
A1–A36 / 39 items.
## Verification
- `npm run check`: 0 errors in soma/morfo scope (sema demo pre-existing
errors untouched).
- `npx vitest run src/uix/soma src/uix/morfo`: all tests green
(94 + 7 = 101 tests, inc. 2 new form regression tests).
- `npm run smoke`: 65/65 routes.
- `npx tsx scripts/morfo-check.ts`: 66/66 morfos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
async helper reads state the effect writes is the trigger.
Why this is especially sneaky with **Standard Schema v1** adapters: some
libraries (sium included) declare their adapter's `validate` as
`async (...)` unconditionally, so the `Promise` branch fires even for
schemas whose underlying validation is synchronous. The soma `Form` has
to live with that until the adapter exposes a sync path — `untrack`
around the fallback is the durable fix.
**Incident:** Form `onChange` / `onBlur` hang (2026-04-21) — kitchen-sink
at `/test/sium/kitchen-sink` froze on mount with a 12-field nested schema
because `runValidate` 's fallback read `errors` /`issues` reactively inside
the validation `$effect` . Fixed in `form-core.svelte.ts` by wrapping the
fallback return in `untrack` . Regression locked by two new tests in
`form-auto-fields.svelte.test.ts` with 5 s vitest timeouts. See
`src/uix/soma/components/form/BUG-onchange-onblur-hang.md` for the full
diagnostic transcript.
morfo: add permutation runner (A37) — third validation layer for state transitions
## Why
`morfo:check` validates single-state DOM at page load; `smoke` catches
hydration-time errors. Neither ejercices state TRANSITIONS — and that is
where every reactivity bug we caught this week actually lived:
- toolbar A35 loop (mount-time, not caught by either)
- form A36 microtask loop (mount-time with onChange)
- slider RTL transform (static value, off-by-thumb-width)
All passed the existing CI. All would have failed a "click, re-validate"
pass.
## What
- **`scripts/permutation-check.ts`** — Playwright runner. For each demo,
collects `data-perm-step="N"` elements, executes them in ascending
order (re-discovering after each step so portal-gated controls get
counted), and re-validates the component's morfo + captures any
`pageerror` / `console.error` between steps.
- **`data-perm-*` authoring convention** — step index, human label, mode
(click / focus / type / key), settle delay, skip-validate flag. Demos
opt in incrementally; the runner SKIPs annotation-less pages without
failing.
- **`src/uix/morfo/PERMUTATION_RUNNER.md`** — full design doc covering
what it validates, the annotation convention, example, failure format,
and v2–v4 roadmap (URL-driven states → morfo-inferred cycles →
MutationObserver ordering for Sema).
- **COMPONENT_GUIDE A37 + checklist item 40** — instrumentation is now a
ship-gate rule; doc lists coverage targets (overlays / toggleables /
composite roving / forms / RTL) and explicit skips (alerts, file
pickers).
- **`npm run perm:check`** — pipeline entry, exit codes 0/1/2
parallelling `morfo:check`.
## Instrumented demos (v1 seed)
- dialog — 2 steps: open via trigger → close via Close button. Step 1 is
inside `{#if open}` + Portal; runner's dynamic re-discovery handles it.
- tabs — 3 steps: initial tab 1 → switch to tab 2 → switch to tab 3.
- toolbar — 3 steps: toggle bold / italic / underline GroupItems
(exercises the A35 pattern that previously looped).
Result: 8/8 permutations pass; 63 demos SKIPPED pending instrumentation.
## Verification
- `npm run check`: 0 errors in soma/morfo/scripts scope.
- `npm run smoke`: 65/65 routes.
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 3/3 instrumented demos, 8/8 permutations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
### A37. Instrument demos with `data-perm-step` for the permutation runner
Single-state validation (`morfo:check`) passes even when a state transition
would loop or emit an undeclared attr. The permutation runner
(`scripts/permutation-check.ts`) cycles components through their declared
state space and re-validates morfo after every transition. This is the
layer that would have caught the toolbar A35 loop, the form A36 microtask
loop, and the slider RTL transform bug the same day they shipped — each of
them passed `morfo:check` but failed the moment state changed.
Demos opt in by tagging interactive controls with `data-perm-step="N"` :
```svelte
<!-- Open → close → re - open cycle -->
< Dialog.Trigger data-perm-step = "0" data-perm-label = "open via trigger" > Open< / Dialog.Trigger >
morfo: add permutation runner (A37) — third validation layer for state transitions
## Why
`morfo:check` validates single-state DOM at page load; `smoke` catches
hydration-time errors. Neither ejercices state TRANSITIONS — and that is
where every reactivity bug we caught this week actually lived:
- toolbar A35 loop (mount-time, not caught by either)
- form A36 microtask loop (mount-time with onChange)
- slider RTL transform (static value, off-by-thumb-width)
All passed the existing CI. All would have failed a "click, re-validate"
pass.
## What
- **`scripts/permutation-check.ts`** — Playwright runner. For each demo,
collects `data-perm-step="N"` elements, executes them in ascending
order (re-discovering after each step so portal-gated controls get
counted), and re-validates the component's morfo + captures any
`pageerror` / `console.error` between steps.
- **`data-perm-*` authoring convention** — step index, human label, mode
(click / focus / type / key), settle delay, skip-validate flag. Demos
opt in incrementally; the runner SKIPs annotation-less pages without
failing.
- **`src/uix/morfo/PERMUTATION_RUNNER.md`** — full design doc covering
what it validates, the annotation convention, example, failure format,
and v2–v4 roadmap (URL-driven states → morfo-inferred cycles →
MutationObserver ordering for Sema).
- **COMPONENT_GUIDE A37 + checklist item 40** — instrumentation is now a
ship-gate rule; doc lists coverage targets (overlays / toggleables /
composite roving / forms / RTL) and explicit skips (alerts, file
pickers).
- **`npm run perm:check`** — pipeline entry, exit codes 0/1/2
parallelling `morfo:check`.
## Instrumented demos (v1 seed)
- dialog — 2 steps: open via trigger → close via Close button. Step 1 is
inside `{#if open}` + Portal; runner's dynamic re-discovery handles it.
- tabs — 3 steps: initial tab 1 → switch to tab 2 → switch to tab 3.
- toolbar — 3 steps: toggle bold / italic / underline GroupItems
(exercises the A35 pattern that previously looped).
Result: 8/8 permutations pass; 63 demos SKIPPED pending instrumentation.
## Verification
- `npm run check`: 0 errors in soma/morfo/scripts scope.
- `npm run smoke`: 65/65 routes.
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 3/3 instrumented demos, 8/8 permutations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
{#if open}
< Dialog.Content >
< Dialog.Close data-perm-step = "1" data-perm-label = "close via Close button" > Close< / Dialog.Close >
< / Dialog.Content >
morfo: add permutation runner (A37) — third validation layer for state transitions
## Why
`morfo:check` validates single-state DOM at page load; `smoke` catches
hydration-time errors. Neither ejercices state TRANSITIONS — and that is
where every reactivity bug we caught this week actually lived:
- toolbar A35 loop (mount-time, not caught by either)
- form A36 microtask loop (mount-time with onChange)
- slider RTL transform (static value, off-by-thumb-width)
All passed the existing CI. All would have failed a "click, re-validate"
pass.
## What
- **`scripts/permutation-check.ts`** — Playwright runner. For each demo,
collects `data-perm-step="N"` elements, executes them in ascending
order (re-discovering after each step so portal-gated controls get
counted), and re-validates the component's morfo + captures any
`pageerror` / `console.error` between steps.
- **`data-perm-*` authoring convention** — step index, human label, mode
(click / focus / type / key), settle delay, skip-validate flag. Demos
opt in incrementally; the runner SKIPs annotation-less pages without
failing.
- **`src/uix/morfo/PERMUTATION_RUNNER.md`** — full design doc covering
what it validates, the annotation convention, example, failure format,
and v2–v4 roadmap (URL-driven states → morfo-inferred cycles →
MutationObserver ordering for Sema).
- **COMPONENT_GUIDE A37 + checklist item 40** — instrumentation is now a
ship-gate rule; doc lists coverage targets (overlays / toggleables /
composite roving / forms / RTL) and explicit skips (alerts, file
pickers).
- **`npm run perm:check`** — pipeline entry, exit codes 0/1/2
parallelling `morfo:check`.
## Instrumented demos (v1 seed)
- dialog — 2 steps: open via trigger → close via Close button. Step 1 is
inside `{#if open}` + Portal; runner's dynamic re-discovery handles it.
- tabs — 3 steps: initial tab 1 → switch to tab 2 → switch to tab 3.
- toolbar — 3 steps: toggle bold / italic / underline GroupItems
(exercises the A35 pattern that previously looped).
Result: 8/8 permutations pass; 63 demos SKIPPED pending instrumentation.
## Verification
- `npm run check`: 0 errors in soma/morfo/scripts scope.
- `npm run smoke`: 65/65 routes.
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 3/3 instrumented demos, 8/8 permutations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
{/if}
```
Extra modifiers:
- `data-perm-mode='key="Escape"'` — dispatch a keydown instead of clicking.
- `data-perm-mode='type="ada@example.com"'` — type a string.
- `data-perm-settle="800"` — longer wait before re-validation (for animations or async validation).
- `data-perm-skip-validate` — click but don't re-validate (intermediate action).
- `data-perm-label="..."` — override the log label.
**What to exercise:**
- **Overlays** (Dialog, Popover, Drawer, Tooltip, NavigationMenu, DropdownMenu, ContextMenu, Menubar) — open / dismiss via every declared path (trigger click, Escape, outside click when applicable).
- **Toggleable items** (Checkbox, Switch, Toggle, ToggleGroup.Item, Tabs.Trigger, RadioGroup.Item, Accordion.Trigger) — click to flip state; for multi-value pickers, advance through at least three values.
- **Composite roving** (Listbox, Menu, Tree, Toolbar) — focus first item, arrow-key to next, arrow-key past the loop boundary.
- **Forms** — empty → invalid input → valid input, to catch any validation-effect loops under change / blur modes.
- **RTL** — if the component has `dir` semantics, include a `data-perm-step` that swaps `dir="rtl"` on the root and validates arrow keys flip.
**What to skip:**
- Alerts / confirmations / anything that triggers `window.alert()` or `window.confirm()` — Playwright hangs on those by default. If the demo has them, use a non-alert callback for the perm-step path.
- File uploads — native file picker is browser-modal and not scriptable from Playwright without `setInputFiles` .
**Pragmatic coverage target:** every component with a non-trivial state
machine (≈40 of the 66 morfos) should have ≥2 permutation steps. Plain
uix: drawer/popover/toast/accordion eidos wrappers + sema packs + /uix docs scaffold
- eidos: migrate drawer / popover / toast / accordion to wrapper subdirectory
pattern (provider + parts + tokens + recipe per component); avatar wrapper
added; toggle/switch/collapsible recipe polish.
- sema: per-component packs (accordion, drawer, popover, toast); engine /
resolver / event / sema-map updates; visual channel hold refinements.
- morfo: drawer event surface (variant, dragToDismiss, isDragging, minSize/
maxSize, onDragStart); popover/toast/accordion event refinements; schema +
types updates.
- soma: drawer provider rewrite (variant, dragToDismiss, isDragging binding,
drag callbacks); toast provider/toaster restructure with toast-main +
toast-status parts; popover content/arrow polish; gesture/floating layer
updates; manipulation layer scaffold; avatar removed (eidos owns it now).
- web/routes: legacy demos updated for new APIs; new /uix docs scaffold
(web/routes/uix/) with Radix-style shell, theme/lang/format/semantic prefs,
and standardized component template (Drawer page first — 6 tabs, live
controls, drag-hint arrows, scroll-safe body wrapper).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
leaf components (Progress, Meter, Announce) don't need any.
morfo: add permutation runner (A37) — third validation layer for state transitions
## Why
`morfo:check` validates single-state DOM at page load; `smoke` catches
hydration-time errors. Neither ejercices state TRANSITIONS — and that is
where every reactivity bug we caught this week actually lived:
- toolbar A35 loop (mount-time, not caught by either)
- form A36 microtask loop (mount-time with onChange)
- slider RTL transform (static value, off-by-thumb-width)
All passed the existing CI. All would have failed a "click, re-validate"
pass.
## What
- **`scripts/permutation-check.ts`** — Playwright runner. For each demo,
collects `data-perm-step="N"` elements, executes them in ascending
order (re-discovering after each step so portal-gated controls get
counted), and re-validates the component's morfo + captures any
`pageerror` / `console.error` between steps.
- **`data-perm-*` authoring convention** — step index, human label, mode
(click / focus / type / key), settle delay, skip-validate flag. Demos
opt in incrementally; the runner SKIPs annotation-less pages without
failing.
- **`src/uix/morfo/PERMUTATION_RUNNER.md`** — full design doc covering
what it validates, the annotation convention, example, failure format,
and v2–v4 roadmap (URL-driven states → morfo-inferred cycles →
MutationObserver ordering for Sema).
- **COMPONENT_GUIDE A37 + checklist item 40** — instrumentation is now a
ship-gate rule; doc lists coverage targets (overlays / toggleables /
composite roving / forms / RTL) and explicit skips (alerts, file
pickers).
- **`npm run perm:check`** — pipeline entry, exit codes 0/1/2
parallelling `morfo:check`.
## Instrumented demos (v1 seed)
- dialog — 2 steps: open via trigger → close via Close button. Step 1 is
inside `{#if open}` + Portal; runner's dynamic re-discovery handles it.
- tabs — 3 steps: initial tab 1 → switch to tab 2 → switch to tab 3.
- toolbar — 3 steps: toggle bold / italic / underline GroupItems
(exercises the A35 pattern that previously looped).
Result: 8/8 permutations pass; 63 demos SKIPPED pending instrumentation.
## Verification
- `npm run check`: 0 errors in soma/morfo/scripts scope.
- `npm run smoke`: 65/65 routes.
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 3/3 instrumented demos, 8/8 permutations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
Run `npm run perm:check` before shipping a new component; the runner SKIPs
annotations-less demos without failing, so onboarding is incremental.
**Reference:** `src/uix/morfo/PERMUTATION_RUNNER.md` has the full authoring
convention and roadmap (v2 URL-driven states, v3 morfo-inferred cycles,
v4 MutationObserver ordering for Sema).