astra
alpha-0.1-background
alpha-0.1-dir-prefs
alpha-0.1-sec-dom
menubar-v4-safe
active-uix
morfo-runtime
morfo-driven-soma
semantuix
glm-5
main
sium-v1.0
${ noResults }
41 Commits (e0eb5f718e694a8ea9dfa7f9f2e0cd55ccd2de55)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
cb5097b3c3 |
Add active eidos runtime and morfo lang registry
|
5 months ago |
|
|
7214ae7940 |
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 |
|
|
e6862265e2 |
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color
sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
d10281bc94 |
collapsible: migrate to eidos wrapper pattern (third pilot)
Third component migrated to the eidos wrapper subdirectory pattern,
following the new "check air baseline first" discipline.
Air baseline recovered from commit 0a391408^:
- air/components/collapsible: 3 wrappers (Provider/Trigger/Content)
with `class` passthrough, `bind:open`, `onOpenChange`, manual
`air.interaction.play('expansion', enter|exit)` perceptual emit.
- No size, intent, color, or chevron slot.
Migration decisions (presented BEFORE coding per scope-approval rule):
- Two doctrinal events (`expand` + `collapse`, both emerge family,
sequence='pre', no intent — emerge is transitional). Replaces air's
manual `air.interaction.play` with `runtime.trigger('expand'|
'collapse')` which atravieses visual + sound + vibra channels.
- No intent/color/size — air didn't have them and disclosure is
non-evaluative per guide §3.1.
- Multi-part API: Provider + Trigger + Content (no flat default per
doctrine §10).
Files:
- src/uix/morfo/components/collapsible.ts: scope expanded to ['soma',
'sema'], events array added with two emerge events.
- src/uix/soma/components/collapsible/collapsible-provider.svelte.ts:
runtime.events handlers `expand`/`collapse` mutate state; toggle()
fires runtime.trigger(next ? 'expand' : 'collapse').
- src/uix/eidos/components/collapsible/{collapsible.css,
collapsible-provider.svelte, collapsible-trigger.svelte,
collapsible-content.svelte, types.ts, index.ts, README.md}.
- src/uix/eidos/components/collapsible.css (legacy flat) deleted.
- src/uix/eidos/index.css: import path updated to subdirectory.
- web/routes/collapsible/+page.svelte: full interactive testbed with
tabs (Live, Soma, Morfo, Sema, Eidos, Notes) mirroring switch's
structure. Sema tab shows per-channel signature cards for both
expand and collapse events.
Disciplina formalizada en CLAUDE.md hand-off + memory entry: "Check
air baseline first" before any future eidos migration. Switch shipped
without this check and lost ResponsiveProp<Size> (regression). Don't
repeat with collapsible/dialog/drawer/popover/toast/avatar.
npm run check holds at the 39-error baseline. Tests 218/218 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
3d0b234ec8 |
soma/morfo/active-uix: relocate runtime + rename SomaRuntime + sema opcional
Architectural correction. Morfo was importing Svelte runes, $adom, and
$uix/sema — all violations of "morfo is pure declarative DNA". The
runtime that interprets a CompiledMorfo lives where it executes: soma.
Move:
- src/uix/morfo/runtime.svelte.ts → src/uix/soma/runtime.svelte.ts
- src/uix/morfo/runtime.svelte.test.ts → src/uix/soma/runtime.svelte.test.ts
Rename (consistent with the move; "soma's runtime", per the doctrine
"morfo declares, soma executes"):
- class MorfoRuntime → SomaRuntime
- createMorfoRuntime() → createSomaRuntime()
- type MorfoRuntimeSources → SomaRuntimeSources
- error prefixes [morfo-runtime] → [soma-runtime]
Sema is now genuinely ornamental:
- ActiveUix.semantic returns EngineSemantic | undefined (no longer
throws when no engine is configured). Components in SSR / headless
tests / audio-disabled environments stay functional.
- SomaRuntime.trigger() skips the emit step AND the target-resolution
check when no engine is present — only requires a DOM target when
something will actually be emitted.
- Soma.semantic getter returns | undefined too (was non-optional).
Consumers updated:
- src/uix/active-uix/active-uix.svelte.ts — imports createSomaRuntime
from $soma; semantic getter returns | undefined
- src/uix/active-uix/types.ts — interface SomaRuntime, semantic | undefined
- 8 providers (toggle, switch, collapsible, dialog, drawer, popover,
toast, avatar) — type SomaRuntime, import path $soma
- src/uix/soma/index.ts — re-exports createSomaRuntime + types
- src/uix/morfo/index.ts — drops the runtime re-exports (morfo is pure)
- src/uix/sema/signal.ts — JSDoc reference updated
- src/uix/soma/core/soma.svelte.ts — semantic getter | undefined
Test added: SomaRuntime.trigger() succeeds without a semantic engine
(skips emit + target check, runs handler directly).
Docs aligned to the new shape:
- active_architecture.md — runtime locations, sema as ornamental
- soma/SOMA_ARCHITECTURE.md, soma/README.md — name + import paths
- morfo/README.md — file listing now flags morfo as PURE TS, points
at soma/runtime.svelte.ts for the interpreter
- sema/README.md — SomaRuntime references
- README.md — root architecture diagram
- CLAUDE.md — hand-off note
Tests 218/218 green. npm run check holds at the 39-error baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
4d26761263 |
docs + switch: align active docs to canon, migrate switch to eidos wrapper
Phase 4 (docs alignment with the canonical guide):
- active_architecture.md — Dialog example refactored to the new event
shape (target inside semantic, explicit verb + sequence). Verbs
table grouped by family with the post-canon vocabulary (select/
toggle moved to commit, edit removed from handle, new verbs added).
Section 10 (Estado actual) updated to 2026-05-08 with current
progress: doctrina cerrada, eidos en producción, SoundChannel
eager-init, switch migrated, etc. "Para profundizar" §14 points at
the canonical guide.
- morfo/README.md — Step 5.5 added covering the doctrinal event
shape with all fields (semantic.family, .verb, .target, .sequence,
.intent) and the two naming shapes ({verb}-{variant} and
{family}-{verb}).
- sema/README.md — verbs section restructured to Record-by-family
matching the runtime; documents the verbs that moved between
families per the canon. Naming shapes section updated with both
forms and validateEventName signature.
- CLAUDE.md — "Session hand-off" rewritten with the post-canon
state. Pointer at GUIA_IMPLEMENTACION_SEMAUIX.md as the canonical
source. Pending list refreshed: switch is the immediate next
component, persistence/a11ySemantic/polymorphism deferred until
real consumers appear.
Switch migration to the eidos wrapper pattern:
- morfo/components/switch.ts — adds events: [{ commit-toggle }] with
the doctrinal shape (verb=toggle, sequence=post, intent fromProp
binding to the 4 supported intents). New data-* (data-color with
the 6-token Toggle/Switch subset, data-readonly, data-invalid,
data-required) and corresponding aria-*. scope expanded to
['soma', 'sema'].
- soma/components/switch/types.ts — adds intent, color, readonly,
invalid, aria-labelledby props with full JSDoc. Subset constrained
per guide §3.1 (Toggle/Switch row).
- soma/components/switch/switch-provider.svelte.ts — runtime-direct,
Field integration via FieldProvider.get(), isDisabled/isReadonly/
isRequired/isInvalid OR-merge derivations, intent ↔ color
resolution (intent wins when evaluative), runtime.trigger(
'commit-toggle') in onclick.
- soma/components/switch/components/switch.svelte — wires the new
props through bindProps.
- eidos/components/switch/{switch.css, switch.svelte, types.ts,
index.ts, README.md} — full subdirectory pattern, mirroring
toggle. Recipe drops legacy success/warning/danger/info selectors;
declares the 6 doctrinal data-color values directly. Wrapper
auto-renders Switch.Thumb inside the track and exposes an optional
`thumb` snippet for in-thumb glyphs.
- eidos/components/switch.css (legacy flat) deleted; eidos/index.css
updated to import the subdirectory recipe.
- web/routes/switch/+page.svelte — interactive testbed with all soma
props as live controls + tabs (Live, Soma, Morfo, Sema, Eidos,
Notes). Mirrors toggle's structure.
npm run check holds at the 39-error baseline. Tests 217/217 green.
Pending:
- Migrate collapsible / dialog / drawer / popover / toast / avatar
to the eidos wrapper pattern.
- Persistence + holds-by-intent — defer until first signal.warn /
signal.alert consumer.
- a11ySemantic + polymorphic events — defer until concrete need.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
d4dfdaffa9 |
sema/morfo/eidos: align with GUIA_IMPLEMENTACION_SEMAUIX (canonical guide)
The new doctrinal guide at src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md
supersedes src/docs/sema-implementation-guide.md and locks the
canonical vocabulary, event shape and color tokens. This commit aligns
the codebase to that canon in three concrete moves.
Phase 1 — sema/verbs.ts vocabulary:
- Restructured from flat array to family-keyed Record per the guide.
- Moved select / toggle / acknowledge → commit (these fix state, not
contact). Removed edit from handle (it's shift.enter-mode now).
- Added new verbs per the canon: contact (tap, focus, trigger,
release), commit (complete, restore, expire, set, remove, reorder),
signal (remind), handle (rotate, scroll, reorder), shift (route,
step, return, context), sustain (start, loading, waiting, syncing,
processing, streaming, pending, retrying, end).
- validateEventName now recognises both naming shapes:
{verb}-{variant} (head=verb) and {family}-{verb} (head=family).
- New helper familyForVerb. Tests rewritten — 16/16 green.
Phase 2 — morfo event shape:
- target moved from event-level into event.semantic. Adds optional
semantic.verb (advisory, validated against SEMA_VERBS[family]) and
optional semantic.sequence ('pre' | 'coincident' | 'post').
- compile.ts and schema.ts updated. The 22 events across toggle,
toast, popover, drawer, dialog refactored to the new shape with
explicit verb + sequence.
- Polymorphic capability (allowedFamilies + defaultSemantic) and
a11ySemantic deferred — no current consumer needs them; will land
when the first concrete usage appears.
Phase 5 — color tokens (doctrinal 8):
- themes/base/light.css and dark.css rewritten to expose primary,
secondary, neutral, affirm, fulfill, risk, threat, loss. Renames
success→fulfill, warning→risk, danger→threat (same hex). New
primitives for secondary (slate-blue), affirm (teal-mint, low
activation), loss (deep violet-grave, posterior). info palette
deleted entirely — per guide, "info no es intent, es signal.announce
+ neutral".
- _static.css legacy compatibility aliases (--color-bg, --color-success,
--color-error, --color-accent, ...) deleted. Clean cut, no
transition. Consumers reference doctrinal palettes directly.
- 29 consumers (token files + recipes) migrated:
- All --*-color-info-* declarations dropped.
- Component palettes extended with secondary / affirm / loss.
- switch + checkbox recipes updated to use [data-color='affirm'|
'risk'|'threat'|'secondary'] (was 'success'|'warning'|'danger'|
'info').
- toast tokens + recipe renamed to affirm/fulfill/risk/threat/loss
(was success/error/warning/info).
- meter, progress, mark, badge, button tokens renamed.
- Contracts (contracts/contract.css + contracts/components/*)
updated.
- focus-ring-color-error in themes now points to --color-risk-border
(validation errors are signal.warn + risk per doctrina, not threat).
Phase 4 (partial):
- src/docs/sema-implementation-guide.md deleted (superseded by the
canonical guide).
Tests: 217/217 green across uix. npm run check holds at the 39-error
baseline (all pre-existing in unrelated test files; none introduced).
Pending in follow-up commits:
- Update active_architecture.md, sema/README.md, morfo/README.md,
CLAUDE.md to reflect the canon.
- Switch component migration to the eidos wrapper pattern.
- Persistence field (untilAction / untilFix / stateBound) and
holds-by-intent — defer until the first signal.warn / signal.alert
consumer appears.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
2d35f4b3e9 |
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 |
|
|
f5a2a7fb49 |
eidos: pilot wrapper pattern + doctrinal API conventions
Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper +
types + index + README) replacing the flat CSS-only form. Pattern is
documented in eidos/components/README.md and the toggle README.
Shared types live in eidos/lib/types.ts. First export is `Size` (8 values
xxs..xxl + full); components narrow with `Extract<Size, ...>` per the
per-component-subset doctrine. No `Eidos` prefix on types — module path
already conveys the layer.
API doctrine:
- soma stays compound (Toggle.Provider) for symmetry with multi-part
- eidos exports both default + Provider so single-part components
accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>`
(compound-style consumers)
SoundChannel eager-init fixes the autoplay race: AudioContext is created
+ resumed synchronously on the first user gesture (capture-phase
listener registered in the constructor), avoiding the previous race
where the first emit() scheduled the resume in a microtask outside the
gesture window.
Demo page (web/routes/toggle/+page.svelte) restructured so the live
preview renders ALWAYS above the tablist — Sema-tab Play buttons can
fire on the real toggle. Motion preview amplifies scale ×8 visually
only; doctrinal values stay in the <dl>.
Conventions 7-13 added to src/docs/sema-implementation-guide.md
covering: directory structure, wrapper composition, no Eidos prefix,
soma compound vs eidos flat, iconOnly sr-only body, sound eager-init,
docs-preview amplification.
CLAUDE.md gets a session hand-off block listing where things stand and
next concrete steps (migrate switch/collapsible/dialog/drawer/popover/
toast/avatar; wire topbar sound mute to masterGain; rename theme
tokens to drop the success/warning/danger fallback aliases).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
52532f5542 |
soma/provider: dedupe against the morfo compiler (Phase 2b minimal)
The Provider base class re-implemented every helper from the morfo
resolver verbatim — 9 functions plus its own `resolveMorfoProps` walk.
The runtime already routes through `compileMorfo` + `evalAttrPlan`
since Phase 2a; the Provider is now wired the same way.
What changed in soma/provider/provider.svelte.ts:
- Drop the local `findMorfoPart`, `shouldEmitMorfoEntry`,
`resolveMorfoPrimitiveSource`, `resolveMorfoSource`,
`resolveMorfoDataValue`, `resolveMorfoAriaValue` (~75 LOC of literal
duplication of resolver.ts).
- Drop the manual `resolveMorfoProps` walk (~28 LOC). New body is
seven lines: spread `compiledPart.staticAttrs`, then `evalAttrPlan`
each `dynamicAttrs` plan and skip undefined results.
- Drop the `createAttrs(spec.morfo)` call from the constructor; read
`compiled.parts.attrs[part]` directly. No semantic change.
- Drop the `findMorfoPart` walk for the part metadata; read
`compiled.parts.byKebab.get(part)` (O(1)).
- `_morfoPartMeta: MorfoPart | undefined` becomes
`_compiledPart: CompiledPart | undefined`. Same role, richer info.
Net diff: provider.svelte.ts goes 287 → 149 lines (-138).
What stayed identical: the public API (`baseProps` getter, `assertProps`
helper, `resolveMorfoProps(bindings)` signature, the dual constructor
overload for legacy subclasses without a morfo spec) plus the
behavioral contract — same context registration, same data-attr
contract validation, same prop snapshot shape for subclass
`\$derived.by` blocks. Subclasses are not touched.
Side effect: `resolveMorfoProps` now emits ARIA boolean values as the
literal strings 'true'/'false' (via `evalAttrPlan`'s `aria-boolean`
mode) instead of JS booleans. Aligns the Provider with the morfo
runtime's behavior (resolver.ts already returned strings; only
provider.svelte.ts had been emitting booleans). Svelte stringifies
both identically when spread onto an element, so the rendered DOM is
unchanged. All 209 UIX tests still green.
Also re-export the compile.ts surface (`compileMorfo`, `evalAttrPlan`,
`CompiledMorfo`, `CompiledPart`, `AttrPlan`, `KeyboardPlan`,
`ActionPlan`, contracts, etc.) from `\$uix/morfo` so consumers don't
have to dig into the file. Fix a stale comment that still pointed at
the deleted `\$uix/lib/types`.
Phase 2b proper (kernel rewrite with the 8 abstractions:
ComponentInstance / PartScope / PartRegistry / SourceRegistry /
ActionDispatcher / SemanticEmission / LayoutFacts / BehaviorRegistry)
is parked on purpose. None of those abstractions has two concrete
consumers today; deduplicating against the existing compiler is the
honest first move and unblocks any future kernel work without churn.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
7fe9cbffc4 |
morfo: wire MorfoRuntime to consume CompiledMorfo (Phase 2a)
The runtime is now the first production caller of `compileMorfo`. Each
per-tick effect previously walked the raw morfo tree; now it reads
pre-resolved plans off the compiled output.
What changed in runtime.svelte.ts:
- Single `compileMorfo(morfo)` call at the top (cached by WeakMap, so
multiple component instances of the same morfo share the result).
- `findMorfoPart` linear scan → `compiled.parts.byKebab.get(...)` O(1).
- `createAttrs(morfo)` walk → `compiled.parts.attrs` (pre-built).
- `findEvent` linear scan → `compiled.actions.byName.get(...)` O(1).
- `resolvePartAttrs` per-effect walk over `data` + `aria` arrays →
`{ ...staticAttrs, ...evalAttrPlan(plan, bindings) for plan in dynamicAttrs }`.
`staticAttrs` (literals + role) are pre-resolved at compile time;
only `dynamicAttrs` is re-evaluated per tick.
- Keyboard matching: `matchesKeySpec(specString, event)` re-parsed
the string every keystroke. Now the compiler emits `KeyboardPlan`
with a precomputed `ParsedKey`; matching is a flat field compare.
- Event dispatch uses `ActionPlan` (compiled view) instead of raw
`MorfoEvent`. `event.target.target` partRef-unwrap → `action.target`.
What was added in compile.ts:
- `evalAttrPlan(plan, bindings)` — evaluates one AttrPlan: short-circuits
on falsy condition, then branches once on `mode` instead of on
`source.kind` × `values.length` × `value.kind` per tick. Reuses the
pure helpers in resolver.ts (`shouldEmitMorfoEntry`,
`resolveMorfoSource`).
- Bug fix in `parseKey`: do not trim the literal key segment. `' '`
(Space) was being collapsed to empty string. The pre-existing
`matchesKeySpec` had a comment warning about this; the refactor
needed to preserve the same invariant. Caught by an existing
runtime test (Switch keyboard with Enter+Space).
Verified: 209/209 UIX tests green (morfo + soma + sema + eidos +
active-uix). The remaining 2 TS errors in runtime.svelte.test.ts
(lines 366 and 385) are pre-existing test casts and unrelated.
resolver.ts is no longer called from runtime.svelte.ts directly except
for the two pure helpers `shouldEmitMorfoEntry` (used by evalAttrPlan
and by keydown's condition check) and `MorfoBindings` (the binding
shape). The non-pure walk functions (`findMorfoPart`,
`resolvePartAttrs`) remain in resolver.ts for any consumer that still
imports them, but the runtime no longer does.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
8df2e9f748 |
uix: relocate Layer + PartRef to src/uix/types.ts
When src/uix/lib/types.ts went down with the rest of src/uix/lib/ in phase A of the cleanup, the Layer and PartRef cross-layer primitives went with it. Three files still imported them via `'../lib/types'`: - src/uix/sema/types.ts - src/uix/morfo/types.ts - src/uix/morfo/compile.ts Move the two types to src/uix/types.ts (top-level shared, sibling to all UIX layers — no layer "owns" a cross-layer primitive), translate the doc comment to English, and update the three importers to `'../types'`. No semantic change. Verified: svelte-check went from 49 errors to 46 (the 3 caused by the deletion are gone); the remaining 46 are pre-existing and unrelated. Vitest: morfo + eidos + sema = 136/136 green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
2c32c842bd |
morfo: introduce compile.ts — Phase 1 of the UIX refactor
`compileMorfo(morfo)` returns a `CompiledMorfo` with pre-resolved plans,
dependency analysis, and frozen contracts. Memoised by morfo identity via
`WeakMap` so the runtime pays compile cost once per morfo (HMR-friendly).
What the compiled form replaces:
- `createAttrs(morfo)` walk → `parts.attrs` (kebab → marker)
- `findMorfoPart(parts, target)` linear search → `parts.byKebab` map
- `resolvePartAttrs()` source.kind branching every effect → split into
`staticAttrs` (literals + role hoisted at compile time) and
`dynamicAttrs[]` (`AttrPlan` with explicit `mode`)
- keyboard string parsing each tick → `KeyboardPlan` with `ParsedKey`
- `flattenMorfoParts` walk per `registerContract` call →
`contracts.dataAttrsByPart` + `contracts.cssSelectors`
- per-part `deps.{states,props,parts,needsTranslations}` for narrow
effects; aggregate `requiredSources` for cross-cutting audits
Mode taxonomy (`AttrMode`): `html-presence`, `aria-boolean`, `enum`,
`string`, `data-state-derived` — the runtime branches once on a
discriminated union instead of re-walking source/condition/values.
Tests cover: parts walking + tree links, static/dynamic split, keyboard
parse, actions, CSS selector contracts, required-source aggregation,
per-part deps, mapRef inner-source extraction, cache identity, frozen
output, duplicate-kebab errors. 28 tests, all green.
No runtime callers wired up yet — that's Phase 2 (kernel + runtime
consumes CompiledMorfo). Old `resolver.ts` / `create-attrs.ts` paths
remain functional for the existing runtime.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
d9b6521020 |
fix dependency direction: morfo no longer imports from soma
The previous DOM consolidation commit left a wrong-direction edge —
`src/uix/morfo/runtime.svelte.ts` imported `createAttrs` from
`$soma/attrs` and `attachRef`/`RefAttachment` from `$soma/provider`.
Morfo is the declaration layer; soma is one of its consumers; morfo
must not depend on soma.
Moves:
- `createAttrs` (`src/uix/soma/attrs/create-attrs.ts`) →
`src/uix/morfo/create-attrs.ts`. It's a pure morfo→data-attr-name
derivation; its natural home is alongside the morfo declarations it
derives from. Soma is a consumer like any other.
- `contracts.ts` (registerContract / assertContract / getContract)
→ `src/uix/morfo/contracts.ts`. Same reasoning: contracts derive
from a morfo's `data: [{ values }]` enum.
- `attachRef` + `RefAttachment` (extracted from
`src/uix/soma/provider/provider.svelte.ts`) →
`src/libs/reactive/attach-ref.svelte.ts`. It bridges Svelte's
`{@attach}` system with the `State` reactive box; both ends are in
libs/reactive, so it lives there.
- Bool/data-attr formatters from `src/uix/soma/attrs/helpers.ts`
(boolToStr, boolToEmptyStrOrUndef, boolToTrueOrUndef,
getDataOpenClosed, getDataChecked, getAriaChecked) →
`src/libs/dom/attrs.ts`. Pure DOM-attr value formatters with no
morfo dependency — they're libs/dom material.
Critical bug fix uncovered by the move:
`src/uix/soma/reactive/reactive.svelte.ts` was a parallel
implementation of the reactive primitive system, with its OWN
`ActiveSymbol` / `WritableSymbol` instances — different from the
ones in `$libs/reactive`. As soon as `attachRef` (now in libs)
tried to detect a soma-created `State` via `isState`, the guard
returned false and the function fell into the setter-fn branch,
throwing "ref is not a function" in 10+ form tests.
Fix: rewrite `soma/reactive/reactive.svelte.ts` as a thin re-export
of `$libs/reactive`. They now share the same symbol identities, so
every cross-layer guard works consistently.
Compatibility:
- `$soma/attrs` is now a backward-compat barrel that re-exports from
`$uix/morfo` (createAttrs, contracts) and `$libs/dom` (helpers).
Existing soma providers continue working unchanged.
- `$soma/provider/provider.svelte.ts` re-exports `attachRef` /
`RefAttachment` from libs for any importer that still uses the
legacy path.
Verified post-cleanup:
- `grep -r '$soma' src/uix/morfo` → 0 matches (no morfo→soma)
- `grep -r '$uix' src/libs` → 0 matches (no libs→uix)
- vitest 14 files / 197 tests
- smoke 68/68
- morfo-check 66/66
|
5 months ago |
|
|
9b07cead6b |
uix: consolidate DOM + relocate morfo runtime to its layer
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.
|
5 months ago |
|
|
a6e2c1e443 |
sema: align vocabulary with libro_semantica (signal, shift, loss)
Updates the canonical semantic vocabulary to match the latest
formulation in src/docs/libro_semantica_completo.txt:
Families (7, was 6):
contact, commit, signal, handle, emerge, shift, sustain
- `alert` → `signal` (rename: same perceptual function — attention /
salience — but the book's name reflects the broader scope including
notify, warn, emphasize, announce).
- `shift` (NEW, transitional): frame change. Modal mount, navigation,
mode change. Differs from emerge in that it reorganizes planes
(heavier backdrop, deeper shadow) — "cruce de umbral" not just
"aparición". Per book rule, the frame doesn't carry intent on its
own; evaluation belongs to a companion `signal` event.
Intents (6, was 5):
neutral, affirm, fulfill, risk, threat, loss
- `loss` (NEW): consummated negative consequence (negative valence,
low activation, posterior). Differs from `threat` (anterior, high
activation, convokes action): loss registers, doesn't convoke. Per
book §11.5 "la amenaza no es la pérdida".
sema-map.ts perceptual values:
- `signal` family entry: kept the prior `alert` numbers verbatim
(semantics is the same — attention/salience) under the new name.
- `shift` family entry: motion 260ms ease-in-out 0.98→1, sound
pitch 500 ascending gain 0.18 (deliberately quieter than emerge's
0.20 to not compete with companion signal), presence with stronger
backdrop (0.5) + shadow (blur 32 y 12 op 0.2) emphasizing
plane reorganization.
- `loss` intent deltas: descending sound contour (-150 pitch),
contraction (scale.to -0.04, motion duration ×0.95 — brief but
grave), color toward purple-grave (hue replace 280, lower
saturation/intensity), withdrawn presence (backdrop 0.05, no glow).
ARIA `role="alert"` (W3C live region) is unrelated and untouched in
soma/announce, soma/toast and morfo announce/field/form. The toast
morfo gains `loss → role:'status' aria-live:'polite'` mapping —
loss is consummated so it shouldn't interrupt.
Verbs added per the book's worked examples: signal {warn, notify,
emphasize}, shift {enter-mode, exit-mode, navigate}, commit {save,
discard, delete}, contact {press, click, hover}, handle {pick, carry,
drop}, emerge {reveal, show, hide}.
Verified: vitest 9 files / 99 tests, smoke 68/68, morfo-check 66/66.
|
5 months ago |
|
|
c3dbd47f5b |
morfo/schema: migrate $lib/sium/core → $sium (Phase 3)
Drop-in path change. The new $sium barrel ($arts/sium) re-exports the exact same surface the legacy $lib/sium/core provided: object, array, string, boolean, literal, union, discriminated, optional Schema (type) SiumValidationError Three import statements updated. Zero behavior change. Verification: morfo tests 21/21, smoke 68/68, morfo-check 66/66. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
b5453af9c4 |
popover: extend morfo with events + soft-migrate provider + exit tinting
Same harmonization pattern as Drawer (which mirrored Dialog):
Morfo (`morfo/components/popover.ts`):
- Added 6 events: present + 5 close-* (save/cancel/dismiss/
dismiss-outside/after-fail). present + close-cancel/dismiss/
dismiss-outside use family='emerge'; close-save uses commit/fulfill;
close-after-fail uses alert/threat.
- Added data-last-action to Content's data array.
- scope: ['soma', 'sema'].
Eidos (`eidos/components/popover.css`):
- Tint rules: combine [data-state='closed'] + [data-side] +
[data-last-action] to color the trigger-facing edge (opposite to
the popover side) during exit. saved → success, failed → danger,
cancelled/dismissed/dismissed-outside → neutral.
Soma (`soma/components/popover/popover-provider.svelte.ts`):
- PopoverProvider injects MorfoRuntime + dismissWith(action).
- Registers content part on mount.
- Wires close paths through runtime.trigger():
Close.onclick → 'close-cancel'
Escape (Dismissal) → 'close-dismiss'
InteractOutside → 'close-dismiss-outside'
- PopoverProvider class re-exported from $soma/components/popover.
Demo (`test/eidos/popover/+page.svelte`):
- Tinted-exit section with Save/Cancel/Fail buttons via local
_dismiss-button.svelte (mirror of Dialog/Drawer pattern).
Verification: smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
106b972075 |
drawer: extend morfo with events + soft-migrate provider + exit tinting
The Drawer's "show" semantic was missing from the morfo, so close paths
went straight to handleClose() without the prewrite → emit → handler
cadence. User flagged this — drawer DOES have a present/dismiss
semantic, equivalent in surface to Dialog.
Morfo (`morfo/components/drawer.ts`):
- Added `events[]` mirroring Dialog: present, close-save,
close-cancel, close-dismiss, close-dismiss-outside, close-after-fail
- Added `data-last-action` (saved/cancelled/dismissed/dismissed-outside/
failed) to Content's data so the prewrite has a declared slot
- scope: ['soma', 'sema'] now (sema participates via signal emit)
Eidos (`eidos/components/drawer.css`):
- Reserved a 3px transparent border on the LEADING edge of Content
per side (the edge facing the user, opposite to the slide-out
direction): right→border-left, left→border-right, top→border-bottom,
bottom→border-top
- Tint rules: combine [data-state='closed'] + [data-side] +
[data-last-action] so the right edge gets colored during the close
animation (saved → success, failed → danger, cancelled/dismissed →
neutral)
Soma (`soma/components/drawer/drawer-provider.svelte.ts`):
- DrawerProvider injects a MorfoRuntime (same pattern as Dialog soft-
migration). Registers content part. Wires events.{close-save,
close-cancel, close-dismiss, close-dismiss-outside, close-after-fail}
to handleClose().
- dismissWith(action) public method — same API as Dialog.dismissWith.
- Replaced 4 close paths with runtime.trigger():
Close.onclick → 'close-cancel'
Escape (modal) → 'close-dismiss'
Escape (non-modal) → 'close-dismiss'
Overlay.onclick → 'close-dismiss-outside'
Drag-to-dismiss (gesture) keeps direct handleClose() — the visible
slide-out IS the close, no 240ms hold makes sense.
- DrawerProvider class re-exported from $soma/components/drawer.
Demo (`test/eidos/drawer/+page.svelte`):
- New "Exit tinting by causa" section with Save/Cancel/Fail buttons
that call dismissWith() through a local _dismiss-button.svelte
(same pattern as Dialog's).
Verification (playwright):
save t=83ms lastAction=saved hold=240ms (commit family)
cancel t=119ms lastAction=cancelled hold=240ms (emerge)
fail t=127ms lastAction=failed hold=600ms (alert)
Smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
e4d131b58d |
sema + morfo: per-event hold override (declarative in morfo)
The hold of a component's event is now declared by the component itself,
not buried in sema's family fallback table.
Resolution precedence (in priority order):
1. signal.hold imperative, caller passes at emit time
2. morfo events[].hold declarative, component-level (this change)
3. sema family fallback 'emerge' → 'brief', 'alert' → 'noticed', ...
Sema's family table becomes the technical fallback when neither caller
nor morfo specifies. The numbers come from a single perceptual scale in
sema/durations.ts (subliminal/glimpse/brief/noticed/insistent/persistent),
so any change to the scale propagates to the family fallback automatically.
Public surface:
sema: SEMA_DURATIONS, SemaDurationLabel, SemaDurationSpec,
resolveSemaDuration() — exported from $uix/sema.
morfo: MorfoEvent.hold?: SemaDurationSpec — number or label.
soma: MorfoRuntime.trigger() reads event.hold, resolves the label →
ms via resolveSemaDuration(), passes it as signal.hold.
Tests:
- sema/durations.test.ts (4): scale values + resolver
- soma runtime (3 new): hold-less event sends no signal.hold (sema
falls back), numeric hold propagates, label hold resolves to ms.
Verification: sema 44/44, soma runtime 25/25, smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
3d9a834886 |
docs: align cross-layer docs with channel-based Sema
Cleanup of stale references to the pre-refactor Sema model across architectural docs: - EngineSemantic → SemanticEngine (registry + dispatch) + VisualChannel (built-in materializer); engine no longer "depends on Dom" - "1 rAF + 1 hold frame (~13ms)" → perceptually-anchored hold (240ms emerge/commit/handle, 600ms alert/sustain, 120ms contact) - Promise semantics: sequential strict — cleanup BEFORE resolve, so structural commit lands AFTER perceptual feedback completes - Toast cadence trace updated: prewrite folded into VisualChannel.handle; CSS uses @keyframes (animation), not transition Touches: src/uix/README.md, src/uix/active_architecture.md, src/uix/sema/README.md, src/uix/eidos/README.md, src/uix/morfo/README.md, src/uix/soma/SOMA_ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
28f93ab089 |
docs: cross-layer articulation — eidos README + 2-of-3 rule across all layer docs
Closes the documentation loop on the cross-layer extension pass: morfo
now articulates between soma, sema, and (future) eidos. The "2-of-3 rule"
formalizes when an extension to morfo is justified vs when it should
stay as provider logic.
src/uix/eidos/README.md (new)
- Documents eidos's role and what it consumes from morfo + sema BEFORE
any code exists, so the contract is preparedly clean when implementation
starts.
- Catalogs which morfo fields eidos reads (parts, archetype, states,
data values, events, prewrite, focus, supportsNesting) and which it
ignores (computed state, runtime internals, layers).
- Documents the DOM-as-channel pattern: sema writes data-event* on emit;
eidos reacts to selectors like `[data-event^="dismiss"]`.
- Establishes the boundary with `air` (dead branch reference, not base).
src/uix/README.md (top-level)
- §8 Reglas de dependencia: adds the 2-of-3 rule table making the
morfo-extension contract explicit, plus a list of canonical vocabularies
(archetypes, verbs).
- §10 Reading order: includes eidos README + lib/dom + clarifies which
layers are dead branches.
src/uix/morfo/README.md
- New "Archetypes" section documenting the 24-verb vocabulary, the
Provider-as-trigger vs Provider-as-container distinction, and the rule
for adding new archetypes (≥2 components share the role).
- New "The 2-of-3 rule" section with the same table as the top-level,
listing which extensions did/didn't make it past the rule and why.
- `parts[].archetype` mentioned in the "What morfo contains" list.
src/uix/sema/README.md
- New "Vocabulario canónico de verbs" section listing SEMA_VERBS by
family and the `{verb}-{variant}` composite naming convention.
- Documents `validateEventName()` as advisory tooling.
src/uix/soma/SOMA_ARCHITECTURE.md
- partProps documentation now mentions data-archetype emission.
- New "Cross-layer hooks que soma emite por la regla 2-de-3" section
listing the data-* attrs soma writes that sema and eidos consume.
- Reading-order links updated.
No code changes — all docs.
|
6 months ago |
|
|
aba18117ba |
morfo: catalog `archetype` across 65 components (~265 part declarations)
Bulk classification pass that gives every part a cross-component archetype where one applies. Done with `scripts/catalog-archetypes.ts` — a one-shot mechanical mapping based on a kebab → archetype table, plus three manual overrides for components where Provider IS the interactive element. Coverage - 440 part declarations examined. - ~265 received an archetype (provider, trigger, content, overlay, viewport, item, indicator, thumb, track, label, title, description, close, action, header, image, fallback, arrow, separator, group, input, segment). - ~175 left without one — genuinely component-specific (Slider.Range, PinInput.Segment internals, color-picker channel-slider, etc.). Manual overrides (Provider IS the trigger) - Toggle, Switch, Checkbox: their `Provider` part is a `<button>` that the user actually interacts with, not a root container. Archetype set to `'trigger'` instead of the default `'provider'` so eidos can style them alongside Dialog.Trigger / Popover.Trigger / etc. Script (scripts/catalog-archetypes.ts) - Walks every `*.ts` under `src/uix/morfo/components/`. - Inserts `archetype: '...'` directly after the matching `kebab:` line when a mapping exists. - Idempotent — skips parts that already declare an archetype. - Indent-aware — only operates on part kebabs (≥2 tabs), never the morfo root kebab. Bug discovered + fixed mid-pass when radio-group's root kebab `'radio-group'` matched the `'radio-group' → 'group'` alias and inserted at the wrong level (cleaned up by hand). - Reports per-file counts + the list of unmapped kebabs at the end so future passes can extend the mapping. Tests - Updated `omits data-archetype when not declared` test to use a tiny inline morfo (Toggle's Provider now has archetype 'trigger', so it can't be the "no archetype" fixture anymore). Verification - 22/22 runtime tests, full focused suite green. - 66/66 morfo-check (every component still validates against its DOM — archetype is additive, doesn't change emitted attrs apart from the new `data-archetype="..."` which morfo-check doesn't enforce yet). - 68/68 smoke routes. - svelte-check unchanged at 155. |
6 months ago |
|
|
bf394873e0 |
morfo: add `archetype` field — cross-layer part categorization
First step toward making morfo articulate the three layers (soma, sema,
eidos) instead of just serving soma. Archetype is a small canonical
vocabulary that lets a part declare its conceptual role beyond its
component-specific kebab name.
Articulation purpose:
- Eidos can style transversally — `[data-archetype=trigger] { ... }`
applies to every Trigger across all 60+ components without enumeration.
- Sema can map archetypes to canonical action verbs (`trigger` may fire
`activate`, `item` may fire `select`).
- Docs can categorize parts cross-component for discovery.
The "2-of-3 rule" justifies the extension: at least two layers consume it.
Vocabulary
24 archetypes covering the common cross-component roles:
provider, trigger, content, overlay, viewport, item, option, indicator,
thumb, track, label, title, description, close, action, header, image,
fallback, arrow, separator, group, input, segment, preview.
Designed to stay small. New archetype only added when at least two
existing components share the role. Genuinely-unique parts (Slider.Range,
PinInput.Segment) omit the field.
Implementation
- `MorfoPart.archetype?: MorfoArchetype` — optional union type.
- `ARCHETYPE_VOCABULARY` exported as `as const satisfies` array for
enumeration tooling.
- `partShallowSchema` (sium validator) gets `archetype: optional(union(...))`.
- MorfoRuntime caches `partMeta` in PartRegistration so `partProps` can
read `archetype` without re-walking the morfo tree per render.
- `runtime.partProps(part)` emits `data-archetype="..."` when declared.
Static identity (never mutates), so it ships through partProps not
through `dom.apply` — Svelte renders it once on first paint.
Tests
2 new tests:
- partProps emits data-archetype when morfo declares one (inline test
morfo to avoid coupling to catalog state).
- partProps omits data-archetype when not declared (Toggle, pre-catalog).
Verification
- 22/22 runtime tests green.
- svelte-check unchanged at 155.
- No morfo currently declares archetype yet — catalog pass follows in the
next commit. All existing morfos remain valid (field is optional).
|
6 months ago |
|
|
e7e32a8355 |
soma: Avatar migrated to runtime + V2 props bridge
First component of the bulk migration phase (step 2 of the 1-3-2 plan).
Pattern is now well-rehearsed across Toggle / Collapsible / Toast / Switch:
- morfo: add `value` sources to data entries that were enum-only
- provider: replace manual Opts interface with `OptsFromProps<...>`,
create runtime in constructor, register parts, drop manual data-* /
aria attrs from props (runtime emits via dom.apply)
- wrapper: replace per-prop `readableActive(() => x)` list with one
`bindProps({ ... })` call
- keep manual: HTML primitives (style, src, etc.) and event handlers
Avatar specifics
- 3 parts (provider/image/fallback) all share `data-status` from the root's
internal `status` State (idle/loading/loaded/error). morfo's `propRef('status')`
reads from runtime sources where the root provider exposes it.
- `status` is not a public Props field — it's an internal $state in the
wrapper. Manually intersected into `AvatarOpts` rather than derived from
AvatarProps.
- Image's preload logic, src/crossorigin/referrerpolicy passthrough, and
the style-based show/hide on each part stay in the provider's render —
none of these are morfo-managed.
- Wrapper cast uses `as unknown as Parameters<typeof X.create>[0]` for
parts whose Opts have only `id`+`ref` (TS can't extract the specific
fields from the generic `Record<string, Active<unknown>>` return of
`bindProps`). Toggle/Switch don't need this because their richer Opts
shape provides enough overlap.
Bulk migration paused at this point — Progress / Meter need a design
decision around `aria-label` priority logic (the legacy provider has
"consumer-passed wins over translated default", which conflicts with the
"dom.apply is the only writer of mutable attrs" rule). Either:
(a) Drop aria-label from those morfos and keep provider-managed (legacy
pattern preserved, morfo loses one declaration).
(b) Extend the resolver with a priority chain (propRef → translationRef
fallback) so the morfo can express "consumer wins, translation
otherwise".
Verification
- 66/66 morfo-check (Avatar passes; all data-status emitted by runtime).
- 68/68 smoke routes.
- svelte-check unchanged at 155.
|
6 months ago |
|
|
55a5ad2647 |
soma: runtime.keydown() + Switch keyboard pilot
V1.5 of MorfoRuntime — adds the keyboard dispatch piece and validates it
against Switch (the simplest component with a real `morfo.keyboard` block).
src/uix/soma/morfo/runtime.svelte.ts
- New `runtime.keydown(part, event): boolean` walks `partMeta.keyboard`,
matches `event.key` plus modifier flags against each entry's spec
("Enter", " ", "Shift+Tab", "Ctrl+A", …), and dispatches to the matching
handler from `sources.actions[entry.action]`. Returns whether a handler ran.
- New `sources.actions: Record<string, KeyboardActionHandler>` source map.
Handlers receive the raw `KeyboardEvent` so they can `preventDefault()`
selectively. The runtime never calls preventDefault on its own.
- Modifier matching is exact: `Shift+Tab` does NOT match plain Tab; `Tab`
does NOT match Shift+Tab. Each entry's `condition` (`prop-truthy` etc.)
is also honored.
- Key-spec parsing keeps the literal key untrimmed (the spacebar is `" "`,
trimming would collapse it to empty). Modifier segments are trimmed
individually.
Toast pilot — keyboard wiring NOT included
Toast Item declares `{ key: 'Escape', action: 'close' }` in morfo, but the
legacy Toast doesn't actually wire Escape either. Out of scope for this
commit; a follow-up can add `events.close` + the keydown listener on the
Item element.
Switch pilot — first consumer
- `morfo/components/switch.ts`: added `value` sources to `data-state` /
`data-disabled` (same pattern Toggle/Collapsible/Toast needed for the
runtime to manage them).
- `soma/components/switch/switch-provider.svelte.ts`:
- Manual `interface SwitchOpts` (10 lines) replaced with `OptsFromProps<
SwitchProps, ManagedKeys, 'checked'>` (5 lines).
- The custom `onkeydown` block (`if (e.key === KEYS.ENTER) preventDefault;
toggle()`) collapses to `(e) => this.runtime.keydown('provider', e)`.
The morfo's `Enter` and ` ` (spacebar) both dispatch to action `'toggle'`,
handled by `actions.toggle: (e) => { e.preventDefault(); this.toggle() }`.
- props derivation uses `runtime.partProps('provider')` for identity,
keeping only the HTML `disabled` attr and event handlers manual.
- `soma/components/switch/components/switch.svelte`: 8 lines of
`readableActive(() => x)` collapsed to a single `bindProps({ ... })` call
(the V2 helper from commit
|
6 months ago |
|
|
eb0f128c3e |
soma: V2 props bridge (OptsFromProps + bindProps) — Toggle refactor
Eliminates the triplication that every component used to suffer:
1. Props (autoral, in types.ts) — public consumer API
2. Opts (mechanical) — provider's reactive contract
3. wrapper's `readableActive(() => x)` list (mechanical)
After V2 only (1) is hand-written. (2) is derived; (3) collapses to one
`bindProps({ ... })` call.
src/uix/soma/reactive/opts.ts (new)
- `OptsFromProps<P, Managed, StateKey>` type maps a Props interface to the
matching Opts: `id` + `ref` baked in; `Managed` selects which keys reach
the provider (HTML passthroughs stay out); `StateKey` is the bindable
subset (the rest become `Active`). `Exclude<P[K], undefined>` strips the
optionality the wrapper resolves via Svelte destructure defaults.
- `bindProps(config)` runtime helper: each entry is either a bare getter
(→ `readableActive`) or a `{ get, set }` object (→ `writableActive`).
Detection is structural (no marker function — avoids collision with
Svelte's `$bindable` rune in `<script>` blocks).
- `WritableSpec<T>` defaults to `unknown` for storage but
`PropsConfigEntry<T>` defaults to `any` for variance — heterogeneous bag
literals are bivariant under `any`, contravariant under `unknown`.
Toggle migration as the proof
- `morfo/components/toggle.ts`: rename `propRef('ariaLabel')` → `propRef('aria-label')`
so the morfo and the public Props key match. Bracket notation in the
provider handles the kebab-cased key.
- `soma/components/toggle/toggle-provider.svelte.ts`:
- `interface ToggleOpts { ... }` (15 lines hand-rolled) replaced by
`type ToggleOpts = OptsFromProps<ToggleProps, ManagedKeys, 'pressed'>` (10 lines)
- sources read `opts['aria-label'].current`
- `soma/components/toggle/components/toggle.svelte`:
- 11 lines of `name: readableActive(() => name)` collapsed to a single
`bindProps({ ... })` call where each line is just `name: () => name` or
`pressed: { get: () => pressed, set: (v) => (pressed = v) }`
Tests
- `opts.svelte.test.ts` — 4 unit tests verifying readable/writable
detection, mixed configs, and kebab-keyed entries (`'aria-label'`).
Verification
- 111/111 soma tests green.
- 66/66 morfo-check (Toggle's emitted DOM unchanged).
- 68/68 smoke routes.
- svelte-check unchanged at 155 (no new errors).
Recipe for migrating other components:
1. List `Managed` keys (everything in Props that isn't HTML passthrough).
2. List `StateKey` subset (anything bindable in the wrapper).
3. Replace the manual Opts interface with `OptsFromProps<...>`.
4. Wrap the wrapper's prop forwarding in `bindProps({ ... })`.
|
6 months ago |
|
|
53d6539abe |
soma: Collapsible pilot — multi-part runtime migration
Second pilot of MorfoRuntime V1, validating the multi-part case left
unproven by Toggle. Collapsible has three providers (provider/trigger/
content), shares root state across them, and uses partRef cross-references
(`aria-controls` ↔ `aria-labelledby`).
Note on scope
The morfo doesn't declare `keyboard` — the Trigger is a `<button>` and
relies on native Enter/Space via onclick. So this pilot does NOT exercise
`runtime.keydown`; that's deferred to the next pilot with a real keyboard
contract (Tabs, Accordion, Listbox).
What this pilot validates
- Single runtime instance shared across three part-providers, created at
the root and accessed by children via the Provider context.
- Cross-part state source: all three parts read `open` and `disabled`
from the root provider's opts via `states` / `props` source maps.
- Cross-part id resolution: `parts.trigger`/`parts.content` getters read
from the root's reactive `triggerId`/`contentId` State; partRef on
Trigger and Content resolves once the corresponding sibling registers.
- Conditional emission with partRef: `aria-controls` (when Content present)
and `aria-labelledby` (when Trigger present) toggle correctly as siblings
mount/unmount.
- Effect timing across multiple parts: each part's effect runs in its own
component scope, cleans up on unmount.
Morfo update
Same fix as Toggle: data entries got explicit `value` sources. `data-state`
references `stateRef('open')`; `data-disabled` references `propRef` with
`prop-truthy` condition. Without this the runtime had no way to source
the values (the legacy provider hard-coded them).
Provider migration
- CollapsibleProvider creates the runtime in its constructor with shared
states/props/parts sources and registers the 'provider' part.
- CollapsibleTriggerProvider and CollapsibleContentProvider call
`provider.runtime.registerPart(...)` for their slot, and publish their
id to the root's State so partRef resolves.
- Each part's `props` derivation collapses from ~6 attrs (data-state,
data-disabled, role, aria-*, type, etc.) to just `runtime.partProps()`
+ the few primitive attrs not in the morfo (`disabled`, `onclick`,
`hidden`).
Verification
- 76/76 focused tests green.
- 66/66 morfo-check (Collapsible's emitted DOM matches the morfo).
- 68/68 smoke routes 200 OK.
- svelte-check unchanged.
|
6 months ago |
|
|
8ede535305 |
soma: MorfoRuntime V1 + Toggle pilot
First piloted slice of the closed architecture: a runtime that interprets
the morfo declaration and writes mutable attrs to the DOM via dom.apply,
removing the need for the provider to hand-roll resolveMorfoProps bindings
on every part.
src/uix/soma/morfo/
- resolver.ts — pure morfo → attrs resolver, extracted from Provider so
both legacy Provider (still in place) and the new runtime can share it
- runtime.svelte.ts — `createMorfoRuntime(morfo, sources)` with V1 surface:
- registerPart(part, opts) — registers per-instance id/ref/sources and
sets up a $effect that reapplies attrs whenever sources change
- partProps(part) — returns ONLY identity (id + marker + ref attachment),
never mutable attrs (those go through dom.apply)
- runtime.svelte.test.ts — 4 tests verifying initial application, reactive
reapplication, and unknown-part rejection
ARIA stateRef refinement
The shared resolver now stringifies ARIA stateRef values to 'true'/'false'.
Boolean false on aria-* must mean "explicitly unpressed/unexpanded", not
"attribute absent" — the previous boolean return wired into dom.apply's
`false → removeAttribute` semantics, which is correct for data-* presence
flags but wrong for ARIA. Distinct contracts; resolver decides at the
boundary, not dom.apply.
Toggle morfo
Added explicit `value` sources to all data entries. The legacy provider
hard-coded data-state etc.; the runtime needs the morfo to declare where
each attr's value comes from. Now data-state references stateRef('on') and
data-disabled / data-readonly / data-required / data-invalid reference
their corresponding propRef with prop-truthy conditions.
Toggle provider migration
- ToggleProvider creates a MorfoRuntime in its constructor with Soma's dom.
- registerPart('provider', { id, ref }) wires the runtime to the button.
- props derivation now returns only:
- runtime.partProps('provider') — id + data-toggle + ref attachment
- disabled (HTML attr; not in morfo by design — primitive button concern)
- onclick handler
- All morfo-declared attrs (data-state, aria-pressed, data-disabled,
aria-disabled, aria-label, etc.) are written by the runtime's effect via
dom.apply.
Verification
- Unit tests: 4/4 morfo runtime + full focused suite green.
- morfo:check: 66/66 — Toggle's emitted DOM matches the morfo declaration
end-to-end now that data-* sources are pinned.
- smoke: 68/68 routes 200 OK.
- svelte-check: no new errors.
V2 (deferred — not part of this pilot):
- runtime.keydown(part, event) for morfo.keyboard dispatch.
- runtime.trigger(eventName) — prewrite + semantic.emit + handler chain
(Toast will be the first component to exercise this).
- OptsFromProps + bindProps helper to absorb the props bridge boilerplate.
|
6 months ago |
|
|
13caaf215f |
docs: closed architecture (Morfo + MorfoRuntime + Provider + Effects + Sema + Dom)
Reflect the architectural decisions reached on 2026-04-25 across the
layer-level READMEs:
- src/uix/README.md
- rewrite ADom section: no longer a "broker semántico"; only DOM mutation surface
- rewrite Sema section: vocabulary + EngineSemantic with Promise-returning emit
- new §2.bis "Cómo se ejecuta un componente": six-piece chain with disjoint
responsibilities (Morfo declares, Runtime transcribes, Provider supplies,
Effects sync, Semantic emits, Dom applies)
- update §8 dependency rules to match the closed design
- new one-line summary in §9
- src/uix/morfo/README.md
- new "How morfo gets executed" section: maps each morfo field to its runtime
executor; documents trigger() sequence and provider responsibilities
- src/uix/sema/README.md
- rewrite around the Promise contract: emit() resolves after 1 rAF
- document lifecycle (id → write signal → wait frame → resolve → hold → cleanup)
- error policy and the three composition scenarios with dom.apply
- src/uix/soma/SOMA_ARCHITECTURE.md
- new §3.bis "Arquitectura cerrada" introducing MorfoRuntime as the missing
piece between Morfo (declaration) and Provider (execution)
- documents API V1, three commit operations, trigger() sequence, operational
rules, and pilot order (Toggle → Collapsible → Toast → Dialog)
No code changes; this commit pins the architecture before implementation.
|
6 months ago |
|
|
a0a1485b9f |
Refactor sema and add shared dom runtime
|
6 months ago |
|
|
f62ede4a23 |
sema: add runtime foundation and dialog integration
|
6 months ago |
|
|
545d47148c |
morfo: permutation runner v3 — per-demo axis matrix via `<meta name="perm-axes">`
v2 ran every demo under a hard-coded `ltr` + `rtl` matrix. v3 lets each demo declare which additional axes it honours — `orientation`, `disabled`, `readonly`, `invalid`, `loading` — and the runner adds ONE variant per declared axis (axis-at-a-time, not cartesian, so the matrix stays bounded at O(N) instead of O(2^N)). ## Convention ```svelte <svelte:head> <meta name="perm-axes" content="dir,orientation,disabled" /> </svelte:head> ``` `dir` is universal (always runs). The other axes are opt-in: they only produce variants when the demo both lists them in the meta AND wires the corresponding state from `Perms.getOr(...)`. Without the meta, only `ltr` + `rtl` run. ## Runner changes - New `discoverAxes(base, morfoKebab)` pass: loads the baseline URL, reads `<meta name="perm-axes">`, returns the declared axis list. - `buildVariants(declaredAxes)` composes `ltr` + `rtl` + one `?perm.X=Y` variant per declared axis from the `AXIS_FLIP` map. - `[initial seed]` step validates morfo at each URL before any click, so URL-seeded state alone is exercised (catches seed regressions even when the click cycle never fires). ## Demo opt-in (this commit) - `dialog` — `perm-axes="dir"` (no orientation / disabled / loading axes apply to Dialog). - `tabs` — `perm-axes="dir,orientation,disabled"` + wires `orientation` + `disabled` to the first `Tabs.Provider`. - `toolbar` — `perm-axes="dir,orientation"` + wires `orientation` to the first `Toolbar.Provider`. Result: 3 demos, 9 URL variants, 34 step validations per CI run (v2 was 22 steps across 6 variants). ## Docs `src/uix/morfo/PERMUTATION_RUNNER.md` v3 section: supported axis table, 3-step opt-in recipe, roadmap updated (v4 = morfo-inferred value cycling, v5 = MutationObserver ordering for Sema). ## Verification - `npm run check`: 0 errors in soma/morfo/test-route scope. - `npm run smoke`: 65/65 routes. - `npm run morfo:check`: 66/66 morfos. - `npm run perm:check`: 3/3 demos · 34 steps · 9 variants. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
6 months ago |
|
|
575748f079 |
morfo: permutation runner v2 — URL-driven state seeds + RTL variant
v1 shipped click-based state cycles. v2 adds URL-seeded initial state so
the runner can reach any state the component exposes without walking a
click sequence, and runs every instrumented demo TWICE in CI (LTR + RTL)
to catch direction-specific regressions like the slider thumb transform
that landed this week.
## New test-route infrastructure
- `src/routes/test/soma/_perms.svelte.ts` — parses `?perm.X=Y` query
params into a typed `Perms` record (naive coercion: `'true'` → boolean,
digits → number, else string). Exposed via `Perms.getOr({})` context.
Underscore prefix so SvelteKit does not route it.
- `+layout.svelte` — parses perms once at mount, sets context, honours
`?perm.dir=rtl` and `?perm.locale=en` by routing through
`App.setDir` / `App.setLocale` so the existing control strip stays in
sync.
## Demo opt-in
Three demos seed initial state from URL params (v1 + v2 combined):
- `dialog` — `?perm.open=true` lands open.
- `tabs` — `?perm.value=tab-2` lands on that tab.
- `toolbar` — `?perm.format=bold,italic` + `?perm.align=center` preselect
toggles.
## Runner extension
`scripts/permutation-check.ts` now runs each instrumented demo under a
matrix of URL variants (currently `ltr` + `rtl`), validates morfo at the
URL-seeded state BEFORE any clicks (logged as `[initial seed]`), then
runs the v1 click cycle. Output format updated to show per-variant
blocks.
Result: 3 instrumented demos × 2 variants = 22 step validations per CI
run. RTL seeding reproduces the specific state machine the slider
`translate(50%, -50%)` bug needed to fail — had v2 been in place that
day, the regression would have fired before shipping.
## Docs
`src/uix/morfo/PERMUTATION_RUNNER.md` gains the v2 section (layout
plumbing, demo opt-in, URL variant table, `[initial seed]` step
semantics) and marks v1 + v2 as shipped 2026-04-22. v3 (morfo-inferred
per-component axis matrix) and v4 (MutationObserver ordering for Sema)
remain on the roadmap.
## Verification
- `npm run check`: 0 errors in soma/morfo/test-route scope.
- `npm run smoke`: 65/65 routes.
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 3/3 demos · 22 steps across 2 variants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
6 months ago |
|
|
61e4ba6d26 |
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 |
|
|
8a4bbb4aa6 |
sium: v2.0 → v2.2 — Issue idlangref, SiumProvider, progressive forms, plurals
Multi-phase delivery closing fase-9 through fase-13 (v2.0 → v2.2).
sium core + langs:
- Issue shape unified to idlangref: { path, code, message: '#?sium.errors.X|fallback', params }
- Dropped MESSAGES registry, resolveIssue, message-resolver (CDX-201, GLM-201, CDX-202)
- Split min/max codes by runtime context: min_value/max_value (numeric, no plural) +
min_length/max_length (string/array, p()-pluralized via count:actual). ISSUE_CODES: 15 → 17.
- BREAKING: Issue.code === 'min' | 'max' consumers must migrate to the 4 new codes.
- toStandardIssues now preserves code + params at the SS bridge (was silently dropping
them, which collapsed interpolation and plurals downstream).
- New core/introspect.ts: serializeSchema, countLeafFields, walkSchema (WeakSet cycle
detection + maxDepth).
sium svelte:
- SiumProvider class + <Sium.Provider {schema}> component (scoped context, schema-only).
- Lang is NOT a Sium concern — orthogonal layer consumed directly from Soma.langs.
soma form integration:
- Form.Provider reads SiumProvider context, creates form internally, exposes bind:form.
- Form.AutoFields translates errors at render via lang.ts / interpolateTemplate.
- form.issues now carries structured Issue data (code + params) alongside form.errors.
- validationBehaviour gains 'progressive' mode + becomes default: quiet until first
invalid submit, onChange until a submit succeeds, quiet again.
- AutoFields wires onblur → form.setFieldTouched, enabling onBlur mode end-to-end.
- createForm hardened with $effect.root fallback; onChange/onBlur hang fixed via
untrack() in runValidate's async branch.
- Form.AutoFields now part of the Form namespace barrel (<Form.AutoFields>).
dias + colors (public API completions):
- dias: isCalendarDate / isCalendarDateTime / isTime / isZonedDateTime type guards +
parseDateSafe / parseDateTimeSafe / parseTimeSafe non-throwing wrappers.
- colors: isColorValue(unknown): value is ColorValue type guard.
Demos + docs:
- /test/sium/+layout.svelte mounts App + Soma with siumLangs registered globally.
Locale switch propagates via app.setLocale() so AutoFields sees translated errors.
- Design language extracted to /test/sium/_design.css + loaded by layout (specimen-book
aesthetic: Fraunces + Geist Mono, hairline rules, cinnabar accent, § sections,
numbered PLATE debug panels).
- Kitchen-sink demo gets live validation-mode switcher (4 modes) via child FormShell
remount with {#key mode}.
- README / GUIDE / INTRO / COOKBOOK aligned to v2.2. New IMPLEMENTATION_STATUS.md as
single-page snapshot of everything delivered and accepted.
- Coordination shards (CDX-201..211, GLM-201..204) live under lib/sium/status for
reproducibility and audit trail.
Tests: 378/378 green across sium core, langs, schema, primitives, refines, combinators,
standard-schema, introspect, form.svelte, form-auto-fields.svelte.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
6 months ago |
|
|
8d4f53a73f |
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 |
|
|
91257e8526 |
soma/morfo: rename root part → provider + fix toolbar effect loop
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>
|
6 months ago |
|
|
0222af7a0b |
morfo: enrich 65 components with aria + keyboard + focus contracts
Each morfo now declares what its provider actually emits (via agent-based
extraction from provider code). Before: parts + data only. After: parts +
data + ARIA per part + keyboard per part + focus policy for overlays.
Contract source: provider's `readonly props = $derived.by(...)` blocks,
classified into the v.* value kinds — literal, stateRef, partRef, propRef,
translationRef. Conditions extracted from ternary / guard patterns.
Focus policies added on overlay components: popover, drawer, alert-dialog,
dropdown-menu, context-menu, select, date-picker, date-range-picker,
time-picker, time-range-picker, color-picker. Context-menu uses
return:'previous' (no discrete trigger). Tooltip, link-preview, combobox,
command omit focus (no trap).
Tag-group Link variant: morfo-check tolerates sibling parts sharing a
physical element (Link IS Item in the DOM) via allPartAttrs cross-scope.
README.md added — developer guide: authoring a morfo step-by-step, the
v.* builders, severity rules, validation layers, polymorphism handling
via defaultElement + role, Sema alignment (data-last-action, transition
markers).
Verify:
npm run check — 5 errs, all pre-existing in sium/examples.
vitest run src/uix/morfo — 7/7 pass.
morfo:check — 64/66 pass (2 flaky Playwright navigation timeouts,
re-run clean when isolated).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
6 months ago |
|
|
8a5b402dd3 |
morfo: port 65 components + sium.lazy + morfo-check passes 66/66
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>
|
6 months ago |
|
|
8c16ffac81 |
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 |