Per architectural feedback: when relocating code to its correct layer,
do the migration completely — update every consumer directly and
delete the original. No re-export barrels at the old paths.
Removed shims:
- `src/uix/soma/attrs/` — was re-exporting `createAttrs` /
`registerContract` / `assertContract` / `getContract` from
`$uix/morfo` and the bool/data-attr formatters from `$libs/dom`.
All 66 importing files updated to import from the canonical
locations directly.
- `src/uix/soma/reactive/` — `reactive.svelte.ts` was a thin
re-export of `$libs/reactive`. The 79 consumers (every soma
provider + layers + types) now import primitives directly from
`$libs/reactive`. The `opts.ts` Props↔Opts bridge moved to
`src/uix/soma/provider/opts.ts` (its natural home — provider
plumbing, not reactive primitives).
- `attachRef` / `RefAttachment` re-export from
`src/uix/soma/provider/provider.svelte.ts` and from
`src/uix/soma/index.ts`. Both removed. Consumers
(`floating.svelte.ts`, `date-field-provider.svelte.ts`) now
import directly from `$libs/reactive`.
Also:
- Moved `ActiveProps<T>` / `StateProps<T>` types to
`src/libs/reactive/types.ts` — they're trivial generic helpers
over `Active<T>` / `State<T>` and shouldn't have lived in soma.
- Stripped soma's root barrel (`src/uix/soma/index.ts`) of every
re-export of libs/morfo concerns. It now only re-exports
actually-soma surfaces (Provider base, mergeProps, css helpers,
id, types, Soma class).
Result:
- `grep -r '\$soma' src/uix/morfo` → 0 matches
- `grep -r '\$uix' src/libs` → 0 matches
- `src/uix/soma/attrs/` and `src/uix/soma/reactive/` no longer exist
- Every import statement now reflects the real architectural layer
Verified: vitest 19/20 files / 214 tests (1 pre-existing air dead-
branch fail using `bun:test`), smoke 68/68, morfo-check 66/66.
Three architectural cleanups in one cut:
1. **morfo runtime relocates to its own layer.** The MorfoRuntime + the
pure morfo→attrs resolver (and their tests) move from
`src/uix/soma/morfo/` → `src/uix/morfo/`. They were never
soma-specific — they interpret a morfo declaration on behalf of any
consumer. Soma was just the only consumer at the time. The new
barrel at `$uix/morfo` re-exports `createMorfoRuntime`,
`MorfoRuntime`, `MorfoRuntimeSources`, `RegisterPartOpts`,
`SourceMap`, `SemanticEmitter`, `TriggerOptions`, plus the resolver
helpers (`findMorfoPart`, `resolvePartAttrs`, `shouldEmitMorfoEntry`,
etc.).
2. **`src/uix/adom/` is gone.** The runtime pieces (`createActiveDom`,
`BodyScrollLock`, `DOMContext`, `RovingFocusGroup`, viewport) all
live at `$adom` (`src/arts/adom/`) which is the canonical
ecosystem-level ActiveDom. The `apply.ts` types and helpers
(`DomAttrValue`, `applyChange`, `removeAttrs`, `StructuralChange`)
live in `$libs/dom` and are re-exported from `$adom`. The 4 last
`$uix/adom` import sites updated to `$adom`.
3. **`src/uix/soma/dom/` is gone.** The 14 utilities (`isBrowser`,
`getDocument`, `isHTMLElement`, focus + tabbable helpers, etc.) all
exist as a superset in `$libs/dom`. The 12 soma consumers updated
their imports from `'../dom'` / `'$soma/dom'` → `'$libs/dom'`. No
functional change — purely import-path consolidation.
`src/uix/active-uix/` is preserved as an empty placeholder for the
future cross-layer composition class that will set up morfo + soma +
sema + eidos together.
The relocated runtime now imports cleanly:
- `ActiveDom`, `DomAttrValue` ← `$adom`
- `EngineSemantic`, sema helpers ← `$uix/sema`
- `Active`, `State` ← `$libs/reactive` (was `$soma/reactive`)
- `createAttrs` ← `$soma/attrs` (cross-layer; createAttrs is morfo
logic that lives in soma for now — slated for relocation in a
follow-up)
- `attachRef`, `RefAttachment` ← `$soma/provider/...` (idem)
Verified: vitest 14 files / 197 tests, smoke 68/68, morfo-check 66/66.
Naming coherence across the part layer: the root part's `kebab` field now
matches its `name: 'Provider'`. The `createAttrs` special case still emits
`data-{component}` (no suffix) for the provider part — the DOM contract is
unchanged, only the authoring name changes.
Affected:
- createAttrs special-case: 'root' → 'provider'
- 66 morfos: kebab 'root' → 'provider'
- 77 soma provider files: `attrs.root` → `attrs.provider`, constructor
part arg 'root' → 'provider'
- Docs: A2 rule rewritten, morfo/README, soma/README example updates
- Test / schema / morfo-check: updated for new kebab
Toolbar fix: remove `itemsVersion` counter and per-item `$effect` blocks
that called `notifyItemsChanged()`. The pattern was an A30 violation —
each item's $effect read `ref.current`/`disabled` and wrote back to the
provider, which cascaded through `firstTabStop` → per-item `props`
$derived → DOM reattach → effect re-run, triggering
`effect_update_depth_exceeded` on page load. `isTabStop` now uses
`untrack` around the DOM walk so per-item `props` derivations only
depend on `lastFocusedElement`.
Verification: 7/7 morfo tests, 65/65 smoke, 66/66 morfo-check, no effect
loops detected across any demo page.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All 65 soma components now consume their morfo as the cross-layer contract.
createAttrs(morfo) and registerContract(morfo) overloads removed — the
Morfo form is now mandatory. Each component provider has a 3-line morfo
import + wire-up block instead of 20+ lines of inline declarations.
Additions:
sium/core/lazy.ts — lazy() combinator for recursive schemas (unblocks
morfo's eventual lazy part-tree validation; tests included).
sium/examples/ — booking, color, login, settings, signup, survey, tree
form demos.
Route demos at src/routes/test/sium/{booking,color,login,settings,
signup,survey,tree}.
Tooling:
morfo-check.ts — now tolerates sibling parts sharing a physical element
(Link variant of Item, etc.). Queries via allPartAttrs cross-scope.
morfo-vocabulary-check.ts — 28 WARN (legitimate per-component
vocabularies or subsets like switch missing "indeterminate"). All
reviewed, none are bugs.
Fixes caught by morfo-check:
table: data-sorted / data-pinned marked severity:'optional' (only
emitted when sorting/pinning active, not always).
tree-grid: data-expanded on Row severity:'optional' (branches only).
menubar: data-menubar-value declared on Trigger.
splitter: data-splitter-panel-id declared on Panel.
Verify:
npm run check — 0 morfo errors (4 pre-existing in sium/examples,
unrelated).
npx vitest run src/uix/morfo — 7/7 tests pass.
npm run smoke — 65/65 routes pass.
npm run morfo:check — 66/66 morfos validate against real DOM.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>