You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/morfo/components/drawer.ts

353 lines
8.8 KiB

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
import type { Morfo } from '../types';
import { v } from '../types';
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
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
export const drawerMorfo = {
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
name: 'Drawer',
kebab: 'drawer',
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
scope: ['soma', 'sema'],
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
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/',
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
events: [
{
name: 'present',
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
semantic: {
family: 'emerge',
verb: 'present',
target: v.partRef('content'),
sequence: 'pre'
},
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
commits: {
part: v.partRef('content'),
attr: 'data-state',
value: 'open'
}
},
{
name: 'close-save',
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
semantic: {
family: 'commit',
verb: 'save',
target: v.partRef('content'),
sequence: 'post',
intent: 'fulfill'
},
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
regime: 'lock',
prewrite: [{ part: v.partRef('content'), attr: 'data-last-action', value: 'saved' }],
commits: {
part: v.partRef('content'),
attr: 'data-state',
value: 'closed'
}
},
{
name: 'close-cancel',
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
semantic: {
family: 'emerge',
verb: 'close',
target: v.partRef('content'),
sequence: 'pre'
},
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
regime: 'lock',
prewrite: [{ part: v.partRef('content'), attr: 'data-last-action', value: 'cancelled' }],
commits: {
part: v.partRef('content'),
attr: 'data-state',
value: 'closed'
}
},
{
name: 'close-dismiss',
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
semantic: {
family: 'emerge',
verb: 'dismiss',
target: v.partRef('content'),
sequence: 'pre'
},
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
regime: 'lock',
prewrite: [{ part: v.partRef('content'), attr: 'data-last-action', value: 'dismissed' }],
commits: {
part: v.partRef('content'),
attr: 'data-state',
value: 'closed'
}
},
{
name: 'close-dismiss-outside',
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
semantic: {
family: 'emerge',
verb: 'dismiss',
target: v.partRef('content'),
sequence: 'pre'
},
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
regime: 'lock',
prewrite: [
{ part: v.partRef('content'), attr: 'data-last-action', value: 'dismissed-outside' }
],
commits: {
part: v.partRef('content'),
attr: 'data-state',
value: 'closed'
}
},
{
name: 'close-after-fail',
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
semantic: {
family: 'signal',
verb: 'alert',
target: v.partRef('content'),
sequence: 'pre',
intent: 'threat'
},
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
regime: 'lock',
prewrite: [{ part: v.partRef('content'), attr: 'data-last-action', value: 'failed' }],
commits: {
part: v.partRef('content'),
attr: 'data-state',
value: 'closed'
}
},
// ── Direct manipulation (handle family, canon §1.3) ──────────────────
// The drawer is a manipulable object; its drag interaction is a
// first-class semantic surface. Channels per-event let the consumer
// silence the perceptual signal where it would be noisy (e.g. drop
// haptic from drag-progress) without the morfo deciding for them.
{
name: 'drag-start',
semantic: {
family: 'handle',
verb: 'pick',
target: v.partRef('content'),
sequence: 'pre'
}
},
{
name: 'drag-progress',
semantic: {
family: 'handle',
verb: 'drag',
target: v.partRef('content'),
// The signal is concurrent with the visible motion of the
// finger. Soma throttles the trigger to rAF so we don't queue
// hundreds of holds per second.
sequence: 'coincident'
}
},
{
name: 'drag-end',
semantic: {
family: 'handle',
verb: 'drop',
target: v.partRef('content'),
sequence: 'post'
}
},
{
name: 'resize',
semantic: {
family: 'handle',
verb: 'resize',
target: v.partRef('content'),
sequence: 'post'
}
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
}
],
focus: {
initial: 'first-focusable',
trap: true,
return: 'trigger',
restore: true
},
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
parts: [
{
name: 'Provider',
kebab: 'provider',
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
archetype: 'provider',
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
kind: 'virtual',
defaultElement: 'none',
optional: false,
states: ['open', 'closed'],
data: [
{ attr: 'data-state', values: ['open', 'closed'], value: v.stateRef('open') },
{
/**
* Drawer presentation mode. Three variants:
* 'overlay' — modal: backdrop + focus trap + dismissible (default)
* 'inline' — non-modal: no backdrop, no focus trap, dismissible
* via Escape; sits over content without blocking it
* 'persistent' — always-visible: no Trigger, no close-* events,
* part of the layout (sidebar pattern)
*
* Eidos consumes `data-variant` to pick positioning (fixed vs
* static) and overlay rendering. Soma derives `modal: boolean`
* internally as `variant === 'overlay'` for the aria-modal
* condition + focus/scroll-lock behavior.
*/
attr: 'data-variant',
values: ['overlay', 'inline', 'persistent'],
value: v.propRef('variant')
}
],
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
aria: []
},
{
name: 'Trigger',
kebab: 'trigger',
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
archetype: 'trigger',
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
kind: 'public',
defaultElement: 'button',
role: 'button',
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
optional: false,
states: ['open', 'closed'],
data: [{ attr: 'data-state', values: ['open', 'closed'] }],
aria: [
{ attr: 'type', value: v.literal('button') },
{ attr: 'aria-haspopup', value: v.literal('dialog') },
{ attr: 'aria-expanded', value: v.stateRef('open') },
{
attr: 'aria-controls',
value: v.partRef('content'),
condition: { when: 'part-present', part: 'content' },
severity: 'recommended'
},
{
attr: 'aria-label',
value: v.translationRef('#?components.drawer.trigger|Open drawer'),
severity: 'recommended'
}
]
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
},
{
name: 'Content',
kebab: 'content',
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
archetype: 'content',
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
kind: 'public',
defaultElement: 'div',
role: 'dialog',
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
optional: false,
states: ['open', 'closed'],
data: [
{ attr: 'data-state', values: ['open', 'closed'], value: v.stateRef('open') },
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
{ attr: 'data-side', values: ['top', 'bottom', 'left', 'right'] },
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
{ attr: 'data-dragging', severity: 'optional' },
{
// Mirrors `data-variant` on the provider so recipe selectors
// can scope by variant directly on Content (`[data-drawer-content]
// [data-variant='inline']`) without a descendant combinator.
attr: 'data-variant',
values: ['overlay', 'inline', 'persistent'],
value: v.propRef('variant')
},
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
{
/**
* Sema alignment: causal exit reason. Updated BEFORE
* `data-state` flips to `'closed'` so eidos can tint the
* exit animation per-action. Same pattern as Dialog.
*/
attr: 'data-last-action',
values: ['saved', 'cancelled', 'dismissed', 'dismissed-outside', 'failed'],
severity: 'optional'
}
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
],
aria: [
{
attr: 'aria-modal',
value: v.literal('true'),
severity: 'optional',
condition: { when: 'prop-truthy', prop: 'modal' }
},
{
attr: 'aria-labelledby',
value: v.partRef('title'),
condition: { when: 'part-present', part: 'title' },
severity: 'recommended'
},
{
attr: 'aria-describedby',
value: v.partRef('description'),
condition: { when: 'part-present', part: 'description' },
severity: 'optional'
}
],
keyboard: [
{ key: 'Escape', action: 'close' },
{ key: 'Tab', action: 'focus-next' },
{ key: 'Shift+Tab', action: 'focus-prev' }
]
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
},
{
name: 'Overlay',
kebab: 'overlay',
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
archetype: 'overlay',
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
kind: 'public',
defaultElement: 'div',
optional: true,
states: ['open', 'closed'],
data: [
{ attr: 'data-state', values: ['open', 'closed'], value: v.stateRef('open') },
{
// Overlay mirrors the provider's variant so the recipe can
// hide the backdrop in `'inline'` and `'persistent'` modes
// directly via attribute selector — even when a consumer
// renders `<Drawer.Overlay>` explicitly in a non-overlay
// variant. The wrapper's flat form skips Overlay anyway.
attr: 'data-variant',
values: ['overlay', 'inline', 'persistent'],
value: v.propRef('variant')
}
],
aria: [{ attr: 'aria-hidden', value: v.literal('true') }]
},
{
name: 'Handle',
kebab: 'handle',
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
archetype: 'thumb',
kind: 'public',
defaultElement: 'div',
optional: true,
data: [],
aria: [{ attr: 'aria-hidden', value: v.literal('true') }]
},
{
name: 'Title',
kebab: 'title',
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
archetype: 'title',
kind: 'public',
defaultElement: 'div',
role: 'heading',
optional: true,
data: [],
aria: [{ attr: 'aria-level', value: v.propRef('level'), severity: 'recommended' }]
},
{
name: 'Description',
kebab: 'description',
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
archetype: 'description',
kind: 'public',
defaultElement: 'div',
optional: true,
data: [],
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
aria: []
},
{
name: 'Close',
kebab: 'close',
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
archetype: 'close',
kind: 'public',
defaultElement: 'button',
role: 'button',
optional: true,
data: [],
aria: [
{ attr: 'type', value: v.literal('button') },
{
attr: 'aria-label',
value: v.translationRef('#?components.drawer.close|Close'),
severity: 'recommended'
}
]
}
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
]
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
} as const satisfies Morfo;

Powered by TurnKey Linux.