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 }
257 Commits (f652f54d5b3af93f1beb4fced72a60d2ff4e6fe3)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
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 |
|
|
2e062f216b |
sema: canonical action verb vocabulary (SEMA_VERBS)
Cross-component verb names that morfo `events[].name` should preferably
align with. Lets sema / sound / vibra engines subscribe by verb instead
of by component-specific event name, and lets eidos write transversal
selectors like `[data-event^="dismiss"]`.
Vocabulary (24 verbs, grouped by family alignment)
- emerge: present, dismiss, open, close, expand, collapse
- commit: commit, cancel, confirm, submit, reset, fail
- alert: announce, alert
- contact: activate, select, toggle
- handle: acknowledge, edit, drag, resize
- sustain: tick, progress
Composite event names follow `{verb}-{variant}` (e.g. `commit-save`,
`dismiss-outside`). The verb is the head; the rest is component-specific
nuance the validator extracts and reports separately.
`validateEventName(name)` returns `{ name, head, matchesCanonical, variant }`.
Advisory — doesn't reject morfos, just surfaces drift for tooling /
review (a future `sema:vocabulary` script can use it to flag composite
event names whose head isn't canonical).
Existing event names already align well
- Toast: `present`, `announce`, `dismiss` — all canonical bare verbs.
- Dialog: `open`, `close-save`, `close-cancel`, `close-dismiss`,
`close-dismiss-outside`, `close-after-fail` — all canonical heads
(`open`, `close`) with variants.
Tests: 10 unit tests covering vocabulary integrity, isSemaVerb guard,
validateEventName splitting/matching, and a snapshot of current dialog +
toast event names against the catalog (all match).
This is the second extension justified under the "2-of-3 rule": verbs
are consumed by sema (subscription routing) AND eidos (selector
generation), with the original morfo authoring as the third producer.
|
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 |
|
|
6d805a4529 |
gitignore: ignore .kilo/ (Kilo IDE plugin scratch dir)
|
6 months ago |
|
|
ddde4601b0 |
scripts: probe-toast-dismiss — verify the runtime.trigger contract end-to-end
One-shot Playwright probe that drives the live Toast demo, installs a
MutationObserver on the toast item, clicks dismiss, and reports the full
attribute timeline.
Used to confirm that `runtime.trigger('dismiss')` actually fires the
`data-event*` perceptual signal in the browser. The signal lives ~13ms
in the DOM (1 awaited rAF + 1 hold frame + cleanup), which is too brief
for DevTools' attribute panel to refresh — this probe captures the full
sequence with millisecond timestamps so the contract is observable.
Run:
1. `npm run dev`
2. `node --import tsx/esm scripts/probe-toast-dismiss.ts`
Output ends with "Signal observed: ✅ YES" when the runtime is wired
correctly. Useful as a regression probe after touching the trigger
sequence or the Toast wiring.
|
6 months ago |
|
|
14aa218658 |
soma: Toast pilot — multi-instance runtime + trigger() integration
Commit B of the Toast pilot. The runtime.trigger() contract from commit A
now has its first real consumer: all three dismiss paths route through
`runtime.trigger('dismiss')`, which emits the perceptual signal one frame
before the toaster mutates state.
Multi-instance architecture validated
Toast is the first component with multiple per-instance subtrees: each
toast in the queue is its own scope with its own children. Two runtime
instances coexist per Toast tree:
ToastProvider (root)
└── runtime instance A — registers `viewport` only
ToastItemProvider (one per active toast in queue)
└── runtime instance B — registers `item` + its children
(`title`, `description`, `action`, `close`)
`partRef('item')` resolves to THIS toast's element because the lookup
happens on THIS Item's runtime, not a shared one. `runtime.trigger('dismiss')`
on Item-B targets Item-B's DOM element. Per-toast isolation by construction.
dismiss wired through runtime.trigger
Three call sites now go through the same path instead of calling
`toaster.dismiss(id)` directly:
- ToastCloseProvider.onclick (user clicks the × button)
- ToastItemProvider startTimer setTimeout callback (auto-dismiss)
- ToastItemProvider onpointerup (swipe gesture meets threshold)
The runtime fires `data-event="dismiss"` on the item, awaits one rAF for
CSS to observe it, then calls the dismiss handler that mutates
`toast.dismissing = true`. The item's effect re-derives `data-state="closed"`
from `isOpen = !dismissing` and writes it via dom.apply on the next tick.
intent-driven role / aria-live still works
The morfo's `mapRef(propRef('intent'), { neutral: 'status', risk: 'alert', … })`
for `role` and `aria-live` is resolved by the runtime per-Item: each toast
reads its own `intent` from `opts.toast.current.intent` via the per-Item
runtime sources. Risk/threat toasts get `role=alert` + `aria-live=assertive`;
the others get `role=status` + `aria-live=polite`. No code in the provider
selects this — the morfo declares it, the resolver computes it.
Per-part props
Action's `altText` is per-instance per-action — passed to the runtime via
`registerPart('action', { props: { altText: () => opts.altText.current } })`.
Validates that the runtime's per-part source override (introduced in
commit
|
6 months ago |
|
|
2b39a2423d |
soma + app: runtime.trigger() contract + Sema wired into App/Soma
Commit A of the Toast pilot: introduce the trigger() contract end-to-end
without migrating any provider yet. Toast's actual migration follows in a
separate commit so that bugs (if any) can be attributed to one layer at a
time.
App layer
- New `AppSemantic` contract in `$lib/ext/app/types`: a single-method
interface (`emit(signal): Promise<void>`) implemented by SemanticEngine
and accepted by the App constructor.
- App now owns `app.semantic`, defaulting to a fresh SemanticEngine wired
to `app.dom` when the consumer doesn't pass one. Fallback App also gets
`fallbackSemantic` so context-less calls don't throw.
- Soma re-exposes it as `soma.semantic` so providers reach it the same
way they reach `soma.dom` / `soma.langs`.
MorfoRuntime
- New `runtime.trigger(eventName): Promise<void>` with the full sequence
the architecture closed:
1. apply `prewrite` attrs imperatively (transient markers)
2. await `semantic.emit({ target, name, family, intent? })` — resolves
the intent from `event.semantic.intent` (literal SemaIntent or
SemaIntentBinding via `resolveSemaIntent`)
3. invoke the provider's handler from `sources.events[name]`
4. effects on the affected parts re-derive structural attrs and
write them via `dom.apply` (automatic — not awaited here)
- New optional `sources.semantic` and `sources.events` on the runtime.
Without `semantic`, step 2 is skipped. Without an events handler for
the name, step 3 is skipped.
- `morfo.events.commits` is intentionally NOT executed by the runtime —
it remains descriptive metadata documenting what an external observer
will see (per the architecture's "state is the source of truth" rule).
Tests
- 8 new trigger unit tests using the toast and dialog morfos:
- throws on undeclared event
- throws when target part has no DOM
- emits with intent resolved from prop (toastMorfo.announce)
- falls back to declared default intent when prop missing
- omits intent for transitional families (toastMorfo.present)
- prewrite applied before semantic.emit (dialogMorfo.close-cancel)
- handler invoked only after semantic.emit resolves
- skips semantic.emit when no semantic source provided
Verification
- 84/84 focused unit tests green (full adom/sema/lib/dom/morfo/soma).
- 66/66 morfo-check.
- 68/68 smoke routes.
- svelte-check unchanged at 155 (no new errors introduced).
Next (commit B): migrate Toast to use the contract — wire close.onclick,
swipe gesture, auto-timer, and Escape keydown to runtime.trigger().
|
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 |
|
|
62bcb1268f |
soma/morfo: fix conditional attr cleanup in resolvePartAttrs
Real bug surfaced by user testing the Toggle pilot: setting `invalid` to true emitted `data-invalid` and `aria-invalid` correctly, but flipping it back to false left the attrs on the DOM. The red border from CSS based on `[data-invalid]` stayed lit. Cause: `resolvePartAttrs` skipped declared attrs whose condition was falsy. The map handed to `dom.apply` lacked the key, so dom.apply (which only iterates the keys it receives) had no instruction to remove the attr from the previous tick. The function's JSDoc claimed it emitted `undefined` for non-matching attrs. The implementation didn't match. Now it does: - Conditional attrs with falsy condition are emitted as `undefined` → `dom.apply` removes them from the DOM and Svelte ignores them at render. - Data entries without a `value` source are still skipped entirely (they are documentation in the morfo, not runtime-managed attrs). Same fix covers all conditional attrs: data-disabled, data-readonly, data-required, data-invalid, aria-disabled, aria-readonly, aria-required, aria-invalid, aria-label — all toggled on/off cleanly now. Regression test added covering the invalid true→false→true cycle. Verification: 5/5 runtime tests, 76/76 focused suite, 66/66 morfo-check, 68/68 smoke. |
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 |
|
|
02fa963cb8 |
adom + sema: dom.apply + semantic.emit contract skeleton
The minimum primitives the closed architecture needs. No MorfoRuntime yet,
no provider migration — just the two operations that everything else will
compose on top of.
adom (only writer of mutable attrs):
- types StructuralChange + DomAttrValue
- applyChange(change) writes attrs (string/number → setAttribute, true →
presence flag, false/null/undefined → removeAttribute)
- removeAttrs(target, names) targeted cleanup
- ActiveDom exposes apply() + remove() — same surface every consumer uses
sema (perceptual signal channel):
- new SemanticSignal type (runtime payload distinct from declaration-time
SemaEvent): { target, name, intent?, family?, hold? }
- SemanticEngine accepts dom by construction (DI; not imported hard from
$uix/adom inside sema)
- new emit(signal) async method with the agreed lifecycle:
1. write data-event* via dom.apply
2. await one rAF (CSS gets a frame to observe)
3. resolve Promise (caller can dom.apply structural change)
4. hold N frames (default 1, configurable per signal)
5. clear data-event* via dom.remove
- legacy publish() flow untouched for backward compatibility
Tests: 12/12 green; full adom+sema+lib/dom suite 50/50 green;
svelte-check delta: -12 errors (no regressions in touched files).
|
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 |
|
|
d87421580d |
adom + lib/dom: foundation for morfo runtime
- Split lib/dom (pure) from adom (reactive runtime). - Move resize-observer and responsive to plain .ts (no runes needed). - Fold elements.ts utilities into core.ts. - Tabbable helpers consolidated in tabbable.ts via npm `tabbable`. - BodyScrollLock: explicit MANAGED_PROPERTIES tracking instead of stringly setAttribute. - RovingFocusGroup: pure getTabIndex reader, separate initializeDefaultTabStop. - DOMContext: drop redundant canUseDom, getRootNode without optional chaining. - ActiveDom: viewport tracking moved to its own module, viewport readonly facade. - New READMEs at lib/dom and adom documenting the layer pyramid. Foundation commit before introducing MorfoRuntime. |
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 |
|
|
51dba711e9 |
sema: spec v0.3.1 + cleanup (drop sema_pre + accordion audit_1)
Adds the full Sema specification at `src/uix/sema/sema-spec-v0.3.1.md`, replacing the earlier `sema_pre.md` working draft. The spec is now the source of truth for the perceptual layer — 6 families × 5 intents = 22 canonical events, 4 channels, the MorfoSema extension for cross-layer action contracts, and the SemaPort protocol between headless and Sema. ## Key architectural decisions codified - Tripartite resolution: morfo-sema (contract) / `.csem` (integrator overrides) / `sema-map.json` (canonical vocabulary). - `MorfoSema` minimal shape (7 fields, 5 optional with defaults). - `SemaEventLabel` typed union — 22 events, closed set. - `prewrite` makes the "data-last-action BEFORE data-state" convention explicit and validated. - `commits` declares structural effect, NOT preconditions — validity remains the provider's responsibility. - `keyboard.action` relaxed: only acquires Sema semantics when it matches a name in `sema.actions[]`. - `sound` disabled by default; opt-in only. - Caps: 200 ms blocking / 80 ms under accessibility reductions. - 4 arbitration regimes: `replace | collapse | lock | queue`. ## Cleanups - `src/uix/sema/sema_pre.md` removed — superseded by v0.3.1. - `src/uix/soma/components/accordion/audit_1.md` removed — historical artifact, consolidated into the canonical audits at `src/uix/soma/AUDIT_1.md` and the two recent `soma-audit-*.md` files. ## Next steps (from §14) 1. Extend `Morfo` type with `sema?: MorfoSema`. 2. Extend sium validator for the 7 new validations of §5.7. 3. Stub `SemaPort` (interface + no-op + test port). 4. Implement the engine runtime. 5. Implement `.csem` PostCSS plugin. 6. Define `sema-map.json` with the canonical values. 7. Validate end-to-end with 4 real components (Button, Input, Dialog, Toast). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
6 months ago |
|
|
0d8de2869f |
soma test: split forms demo into dedicated switch/checkbox/radio-group pages
The combined `/test/soma/forms` page covered three separate components
(Switch, Checkbox, RadioGroup) in one route. Each deserves its own demo
page — matching the convention of every other component having a
dedicated `/test/soma/{kebab}` route, and letting the permutation runner
actually exercise them (the runner navigates per morfo-kebab, not per
merged page, so the combined demo left the 3 morfos SKIPPED).
## New demos
- `/test/soma/switch` — basic + disabled variants. `data-perm-step="0"`
toggles the basic switch. Axes: `dir, disabled`.
- `/test/soma/checkbox` — basic + indeterminate + group variants. Steps
0-2 toggle the first checkbox + first two group items. Seeds
`?perm.checked=true` / `?perm.value=a,b`. Axes: `dir`.
- `/test/soma/radio-group` — active + disabled variants. Steps 0-2 select
each option. Seeds `?perm.value=option-2` / `?perm.orientation`.
Axes: `dir, orientation`.
Each demo reuses the CSS from the original combined page (via `:global`
selectors for `data-switch`, `data-checkbox`, `data-radio-group-item`).
All three have `aria-label` on the Provider — the DEV-mode A4 warning
added in batch 10 of audit 2026-04-21 requires it.
The combined `/test/soma/forms` page is kept in place (pre-existing
integration demo showing composition); future batches may decide
whether to drop it or repurpose it.
## Coverage delta
- Before: 63/66 demos · 131 variants · 202 steps.
- After: 66/66 demos · 139 variants · 228 steps. (smoke: 68 routes)
Coverage complete — every morfo now has at least one instrumented demo
under the permutation runner.
## Verification
- `npm run check`: 0 errors in new demo files.
- `npm run smoke`: 68/68 routes (was 65).
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 66/66 demos · 228 steps · 139 variants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
6 months ago |
|
|
db3ad3497f |
soma test: complete permutation runner instrumentation (63/66 demos)
Bulk-instruments the remaining 49 demo pages with `<meta name="perm-axes" content="dir" />`, bringing total coverage from 15/66 demos to 63/66. Only checkbox, radio-group, and switch remain skipped — they have no demo directory at `/test/soma/` (pre-existing gap, unrelated). ## Runner change `scripts/permutation-check.ts` now allows meta-only demos (no `data-perm-step` annotations) to participate: if a demo declares any axis via the meta tag, the runner still visits its baseline + RTL variants and runs the `[initial seed]` morfo validation at each, even without a click cycle. This gives passive LTR/RTL coverage to demos where a click-driven perm sequence doesn't apply naturally (leaf components like Avatar, Progress, Meter; grid/list components; pickers with inline controls). Key change in `probeDemo()`: a demo is "instrumented" if EITHER the meta tag OR at least one `data-perm-step` is present. Step-only demos get the v1 click cycle at baseline; meta-only demos get the v3 axis matrix with passive `[initial seed]` validation at each variant; declaring both is the full treatment. ## Bulk instrumentation For each of the 49 remaining demos, added: ```svelte <svelte:head> <title>… · Soma</title> <meta name="perm-axes" content="dir" /> </svelte:head> ``` Baseline axis (`dir`) only. Demos that benefit from additional axes (`orientation`, `disabled`, etc.) opt in incrementally — the bulk edit doesn't touch their `$state` seeds, only adds the meta tag. ## Coverage delta | Metric | Before | After | |--------|-------:|------:| | Instrumented demos | 15/66 | **63/66** | | URL variants | 35 | **131** | | Step validations | 106 | **202** | ~4× increase in URL variants visited per CI run. Most new demos contribute 2 variants each (ltr + rtl), validating morfo under direction flip. Fully-instrumented demos (tabs, toolbar, toggle-group, listbox) still contribute 3-4 variants with click cycles. ## Deferred - `context-menu` — right-click activation needs a `data-perm-mode='contextmenu'` runner extension. - `navigation-menu` — hover-driven, same as Tooltip. Tooltip was instrumented via `data-perm-mode="focus"` + long settle; NavigationMenu could follow the same pattern in a targeted batch. - `color-picker` — complex pointer state machine on Area / ChannelSlider. Meta-only covers the baseline structure. ## 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`: 63/63 instrumented demos · 202 steps · 131 variants. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
6 months ago |
|
|
b68b831320 |
soma test: instrument 7 more demos for permutation runner (composite + toggleables batch)
Adds `data-perm-step` + `<meta name="perm-axes">` + perms seed to: - **accordion** — expand item-1 → switch to item-2. `?perm.value=item-1` seeds the Provider's value array. `dir` axis. - **listbox** — select apple (first) → select apricot. `?perm.orientation` flips the layout, already wired. Items in a loop use conditional `data-perm-step` by index. `dir,orientation` axes. - **tree-view** — select readme → select license under the expanded docs branch. `?perm.expanded=docs&perm.selected=readme` seed. `dir` axis. - **combobox** — open via trigger (Combobox.Trigger). `dir` axis only — typing / selection is left to v4 per-component value enumeration. - **menubar** — open File menu → close via Escape (mode=key, 500ms settle). `dir` axis. - **toggle** — press Bold toggle (inside Field.Provider integration example). `dir` axis. - **toggle-group** — press left → press center (single mode). Added `aria-label="Text alignment"` to silence the A4 DEV warning that now fires (shipped in batch 10 of audit 2026-04-21). `?perm.value=bold` preselects. `dir,orientation` axes. ## Deferred in this batch - **context-menu** — needs `contextmenu` event (right-click). Requires a `data-perm-mode='contextmenu'` extension to the runner. Future batch. - **navigation-menu** — hover-driven only. Same future-batch treatment as Tooltip has with `data-perm-mode="focus"` + long settle. ## Coverage delta - Before: 8 demos · 19 variants · 62 steps. - After: 15 demos · 35 variants · 106 steps. ~1.7× jump in a single batch. Tabs still leads with 4 variants (adds `orientation` + `disabled` axes); ToggleGroup, Toolbar, Listbox each have 3 variants. ## 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`: 15/15 demos · 106 steps · 35 variants. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
6 months ago |
|
|
1e8500bfac |
soma test: instrument 5 overlay demos for permutation runner
Adds `data-perm-step` + `<meta name="perm-axes">` + perms seed to the 5 overlay demos sharing Dialog's open/close shape: - alert-dialog — trigger open → cancel. `?perm.open=true` seeds. `disabled` deliberately NOT in axes: disabling the Provider blocks the Trigger click path the runner depends on. - popover — trigger open → close button. `?perm.open=true` seeds open1. - tooltip — focus trigger (mode=focus, 1s settle — tooltip opens after 700ms delay). No close step: tooltip closes on blur. - drawer — trigger open → close button (500ms settle for exit animation). `?perm.open=true` seeds basicOpen. - dropdown-menu — trigger open → Escape close (mode=key, 500ms settle). Second step targets the Menu.Content selector because the Content only mounts after the Trigger step fires (runner handles dynamic step discovery). All five declare `dir` as the only axis (universal RTL variant). The other orientation / disabled axes don't apply to these overlay shapes. ## Coverage delta - Before: 3 instrumented demos · 9 variants · 34 steps. - After: 8 instrumented demos · 19 variants · 62 steps. 2x jump in per-CI-run validations with zero infra changes — just applying the v1-v3 convention to additional demos. ## 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`: 8/8 demos · 62 steps · 19 variants. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
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 |
|
|
6efbfeb795 |
sium: color demo widgets inline + queue CDX-212 (services registry)
- color demo now registers a ColorPicker-based widget inline so kind:color fields render properly instead of showing [object Object] in the fallback text input. Temporary — CDX-212 moves defaults to Soma.services.sium.widgets where they belong. - CDX-212 shard drafted with full v2.3 design (Soma services registry, SiumService.widgets, 3-layer override: instance > subtree > global). Includes all conversation context up to 2026-04-21 so tomorrow's session resumes without paginating chat history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
6 months ago |
|
|
a722d443b2 |
sium: migrate 6 remaining demos to App+Soma+extend pattern + specimen-book design
Closes the v2.2 demo sweep. All 8 sium test pages (booking, color, kitchen-sink,
login, settings, signup, survey, tree) now share:
- App-level langs: each page does `App.get().langs.extend('demo', {...})` to add
its own namespace. siumLangs + componentLangs already registered globally by
/test/sium/+layout.svelte. Locale switch goes through `app.setLocale()` so
Form.AutoFields sees translated sium.errors.* without per-page plumbing.
- Specimen-book design language: shared CSS in /test/sium/_design.css. Pages
contribute markup only — masthead with eyebrow + schema-name + shape-hint +
locale-switch, hairline rules, description, two-column .specimen layout with
numbered PLATE debug panels.
- No inline <style> blocks. No local createLangs. No ad-hoc helper functions.
Each demo went from ~98 v1-pattern lines (with 25 lines of inline CSS) to ~110
v2.2-pattern lines (zero CSS, all imports via canonical libs). Login was already
migrated as the pilot last turn.
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 |
|
|
2627dc2094 |
soma: fix slider thumb alignment + document morfo in README
## Slider thumb misalignment `SliderThumbProvider.thumbStyle` used `translate(50%, -50%)` for both LTR and RTL. For LTR that pushed the thumb RIGHT by half its width from the `left: X%` anchor — visible symptoms in the time-picker / time-range-picker demos: - thumb never reached `left: 0` (centre at value=0 was offset by thumbWidth/2) - thumb overshot the track on the right edge at value=max - drag felt off by the same amount throughout Fix: use `translate(-50%, -50%)` for LTR (thumb centre aligns with anchor) and keep `translate(50%, -50%)` for RTL (anchor is `right: X%`, thumb must shift right by half its width to centre-align). Matches the logic already used by `SliderTickProvider` which chose `tx = isRtl ? '50%' : '-50%'`. Browser probe confirms: value=0 now centres at 0%, value=30/59 at ~50.8%, style attribute reports `translate(-50%, -50%)` in LTR. ## soma README documents morfo Added §4b "Morfo — contrato declarativo cross-layer" covering: why morfo exists (6-place drift without it), how providers consume it (`createAttrs(morfo)` + `registerContract(morfo)`), the mandatory `as const satisfies Morfo` authoring pattern for literal-typed inference, the 3-layer validation pipeline (schema / assertContract / morfo-check), and the boundary (what does NOT go in morfo — props, prose, langs, state machines, visual recipes). Updated the §4 structure listing so `attrs/create-attrs.ts` shows the new signature. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
6 months ago |
|
|
50821aae44 |
soma: P3 cleanup — LinkPreview a11y section + TagGroup key API consistency
- LinkPreview README: elevate the "intentionally hidden from AT" note to its own `## Accessibility` section with the three reasons (duplicate content, hover-only activation, focus stays on trigger). Consumers reaching for an AT-visible preview are pointed at Popover / Tooltip. - TagGroup `handleItemKeydown`: vertical nav now calls `getDirectionalKeys(dir, 'vertical')` (returns UP/DOWN absolute) instead of hardcoded `KEYS.ARROW_DOWN/UP`. Functionally identical — vertical keys don't flip under RTL — but keeps the shape symmetric with the horizontal branch and matches the convention used across the library. 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 |
|
|
e1837e8727 |
docs: add A35 (ref+parent-write effect loop) + update CLAUDE.md
A35 codifies the toolbar incident (2026-04-19): per-item `$effect` that reads `opts.ref.current` and writes provider state read back by per-item `props` $derived triggers `effect_update_depth_exceeded` via the attachment reapply cycle. Register in the constructor (A30-style); use `$effect` for cleanup only; wrap DOM walks in `untrack` when called from `$derived`. - COMPONENT_GUIDE: §A35 rule, checklist item 38 - CLAUDE.md: checklist count updated (A1–A35, 38 items), verification mention of morfo-check + morfo kebab naming note 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 |
|
|
3b704286ba |
sium: v1.0 — schema core, domain types, i18n, Svelte adapter, AutoFields
Ship v1 of sium, the Schema-In-Use Module — soma's native validation layer.
Core (PR1): issue/result helpers, Schema<I,O> factory with ~standard + ~sium
wiring, 5 primitives (string/number/boolean/literal/enumOf), 3 modifiers
(optional/nullable/defaulted), 4 combinators (object/array/union/discriminated),
pipe + 4 step constructors (refine/transform/codec/meta), 7 refines
(min/max/length/regex/email/url/integer), barrel.
Domain types (PR2b): colorValue, dateValue, dateRange, timeValue + 15 segments
(year/month/day, hour/minute/second/dayPeriod, red/green/blue/hue/saturation/
lightness/brightness/alpha). All segments are kind:'number' + meta.channel —
keeps SchemaKind vocabulary tight at 14 entries.
i18n (PR2a): 15 stable issue codes, MESSAGES registry with English fallbacks,
siumLangs LangNode (es + en) for ext/lang integration, pure template resolver
with {{name}} syntax.
Svelte adapter (PR3): resolveIssue/resolveIssues bridge, createForm reactive
with $state/$derived + locale change reactivity + sync/async detection.
Form.AutoFields (PR4): Soma renderer driven by schema introspection. Widget
registry with 4-level priority (meta.widget → channel:X → kind:X → built-in).
Contract supports Component | Snippet widgets — compound widgets like
ColorPicker/Calendar compose inline without wrappers.
Polish (PR5): COOKBOOK with 12 recipes, GUIDE with vs-Zod comparison and
practical examples, kitchen-sink demo (src/routes/test/sium) with locale
switcher and live schema introspection panel, v1 readiness audit report,
doc consistency audit report, isColorValue hardening (rejects NaN + invalid
hex) after production bug report.
Coordination: all work tracked via protocol v2.1 (state.json + sharded orders
+ audit.mjs runner) in src/lib/sium/status/. 340+ tests, npm run check clean
(0 errors, 0 warnings against 28-budget).
Deferred to v1.1 (README §11.2): timeRange, lazy/recursive schemas, tuple/
record/intersection, bundle measurement, additional locales, jsDate coerce.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
6 months ago |
|
|
72a32e0176 |
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 |
|
|
16f706439e |
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 |
|
|
f07addf07b |
soma: tier 1/2 components, Field/NumberField integration, codex_audit fixes
New components: context-menu, field, file-upload, link-preview, menubar. Codex audit fixes (2026-04-17): - Wrappers thinned: table, scroll-area scrollbar, calendar, range-calendar - Toast Root → Item refactor (data-toast-item, resolves part collision) - TreeView tab-stop fallback, Table right-pinned offset, Toolbar O(N) registry - Combobox label registry, Pagination count=0, Splitter feasible-range clamp - TagsInput relatedTarget, Toast compound hotkey, RadioGroup closest filter - Accessible-name props (aria-label/-labelledby) tipados en RadioGroup/Slider/ Switch/Toolbar; Slider thumbLabels per-index - NumberField startValue saneado, Table getIsSomeSelected filtra visibles - Docs: DropdownMenu CheckboxGroup, NumberField terra-free, Pagination First/Last snippet Supporting changes: - App layer + ling/logr/glob/util consolidation, tests updates - Terra/scripts mechanical reformat (quotes, imports) - Test pages updated to match new APIs Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
6 months ago |
|
|
69a8e8d4b5 |
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 |
|
|
ca768f068a |
docs: update planned components with all 6 tiers (color + sound added)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
6 months ago |
|
|
a8498770f0 |
docs: add rationale for reactive layer in README §5
Explains why soma wraps Svelte runes instead of using them directly: compiler magic limitation, cross-class composition need, migration path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
6 months ago |
|
|
a1f03b4626 |
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 |
|
|
bc9b8246fc |
soma: resolve all remaining audit issues
- Stepper H3: trigger registry pattern replaces DOM queries for keyboard nav - Table H3: sub-row IDs use getRowId() (identity-based, not positional) - Tags-input H2: combobox ARIA pattern (role=combobox + listbox + option) - Tags-input H3: handlePaste concatenates with existing input text - Toast H2: Presence layer for exit animation, dismiss→dismissing→remove flow - Switch HIGH-1: contracts.ts normalizes case in register/assert - Select C1: pure aria-activedescendant (no mixed DOM focus) - Select H1: value-to-label Map registry (no DOM queries) - Tooltip H1: SafePolygon for hoverable tooltips - Toolbar HIGH-1/2: element-based focus tracking (no index, no O(N²)) - Scroll-area H3: mount flash prevention - Splitter H1/H2: collapse/expand respects adjacent constraints - Pagination H1: reactive page clamping like Ark-UI - Popover H1: aria-labelledby already present (verified) - Splitter: immediate pointer capture restored (moveBuffer breaks thin handles) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
6 months ago |
|
|
015d5e258d |
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 |
|
|
a1d0bee655 |
refactor: App layer with reactive lang, soma migration, 26 component audits, RTL fixes
- Add App service compositor (langs, nums, money, dates, units, presentation) - Add createSvelteLang() with reactivity for locale and schema changes - Migrate 25 soma providers from getSoma()/translator.svelte to Soma.getOr()/langs.t() - Add reactive dir resolution in all component wrappers - Add dir HTML attribute to 12 root providers (accordion, tabs, slider, etc.) - Fix slider RTL: range, thumb, tick use right/left based on dir - Add table structural parts: Header, Body, Footer wrappers - Implement ColumnPinning, ColumnSizing, Expanded in table-core - Remove soma core formatters, translator.svelte, prop-resolvers (moved to App) - Remove duplicate convenience getters (locale, dir, translate) from App/Soma - Audit all 26 soma components, create audit docs with 84 issues found - Update test page with pinning, sizing, expandable rows demos |
6 months ago |
|
|
8a88ad577a |
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 |