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/dialog.test.ts

62 lines
2.5 KiB

morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired Morfo (src/uix/morfo/): machine-readable contract of a component's public DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment. Types, sium-based validator with cross-field invariants, dialog.ts as the first canonical morfo with data-last-action for Sema-causal exits, 7 passing unit tests. createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy inline signature removed. Dialog provider refactored to derive parts + data contract from dialogMorfo. The other 65 soma components need porting (svelte-check lists them) — tracked work. Tooling: scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes scripts/morfo-check.ts — validates emitted DOM vs morfo scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency npm run smoke / morfo:check / morfo:vocabulary WAI-ARIA APG components added (100% APG coverage now): Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress, SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel pattern; hierarchical Table rows deprecated — use TreeGrid instead). air: Link, Banner. Sema prep: src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements. Morfo already provides everything Sema needs (data-last-action patterns, transition markers, enumerable state values, cross-component consistency). Sema implementation deferred. COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM topology vs .require() audit, npm run smoke is part of done) + A34 rule with incident log. WAI-ARIA pattern listed as soma membership criterion. Pagination translation bug fixed (soma.pagination.page -> idlangref PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.* -> components.table.*). Avatar demo now offline-safe (SVG data URL). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
import { describe, it, expect } from 'vitest';
import { validateMorfo, MorfoInvariantError } from '../schema';
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
import type { Morfo } from '../types';
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired Morfo (src/uix/morfo/): machine-readable contract of a component's public DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment. Types, sium-based validator with cross-field invariants, dialog.ts as the first canonical morfo with data-last-action for Sema-causal exits, 7 passing unit tests. createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy inline signature removed. Dialog provider refactored to derive parts + data contract from dialogMorfo. The other 65 soma components need porting (svelte-check lists them) — tracked work. Tooling: scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes scripts/morfo-check.ts — validates emitted DOM vs morfo scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency npm run smoke / morfo:check / morfo:vocabulary WAI-ARIA APG components added (100% APG coverage now): Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress, SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel pattern; hierarchical Table rows deprecated — use TreeGrid instead). air: Link, Banner. Sema prep: src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements. Morfo already provides everything Sema needs (data-last-action patterns, transition markers, enumerable state values, cross-component consistency). Sema implementation deferred. COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM topology vs .require() audit, npm run smoke is part of done) + A34 rule with incident log. WAI-ARIA pattern listed as soma membership criterion. Pagination translation bug fixed (soma.pagination.page -> idlangref PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.* -> components.table.*). Avatar demo now offline-safe (SVG data URL). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
import { dialogMorfo } from './dialog';
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
// `dialogMorfo` is authored `as const satisfies Morfo` so runtime
// assertions on its literal shape stay precise. Invariant tests below
// clone it into a mutable `Morfo` to deliberately corrupt fields.
function cloneMutable(m: typeof dialogMorfo): Morfo {
return structuredClone(m as Morfo) as Morfo;
}
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired Morfo (src/uix/morfo/): machine-readable contract of a component's public DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment. Types, sium-based validator with cross-field invariants, dialog.ts as the first canonical morfo with data-last-action for Sema-causal exits, 7 passing unit tests. createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy inline signature removed. Dialog provider refactored to derive parts + data contract from dialogMorfo. The other 65 soma components need porting (svelte-check lists them) — tracked work. Tooling: scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes scripts/morfo-check.ts — validates emitted DOM vs morfo scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency npm run smoke / morfo:check / morfo:vocabulary WAI-ARIA APG components added (100% APG coverage now): Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress, SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel pattern; hierarchical Table rows deprecated — use TreeGrid instead). air: Link, Banner. Sema prep: src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements. Morfo already provides everything Sema needs (data-last-action patterns, transition markers, enumerable state values, cross-component consistency). Sema implementation deferred. COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM topology vs .require() audit, npm run smoke is part of done) + A34 rule with incident log. WAI-ARIA pattern listed as soma membership criterion. Pagination translation bug fixed (soma.pagination.page -> idlangref PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.* -> components.table.*). Avatar demo now offline-safe (SVG data URL). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
describe('dialogMorfo', () => {
it('passes shape + invariant validation', () => {
expect(() => validateMorfo(dialogMorfo)).not.toThrow();
});
it('declares the 7 parts the provider emits', () => {
const kebabs = dialogMorfo.parts.map((p) => p.kebab).sort();
expect(kebabs).toEqual(
['close', 'content', 'description', 'overlay', 'provider', 'title', 'trigger'].sort()
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired Morfo (src/uix/morfo/): machine-readable contract of a component's public DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment. Types, sium-based validator with cross-field invariants, dialog.ts as the first canonical morfo with data-last-action for Sema-causal exits, 7 passing unit tests. createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy inline signature removed. Dialog provider refactored to derive parts + data contract from dialogMorfo. The other 65 soma components need porting (svelte-check lists them) — tracked work. Tooling: scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes scripts/morfo-check.ts — validates emitted DOM vs morfo scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency npm run smoke / morfo:check / morfo:vocabulary WAI-ARIA APG components added (100% APG coverage now): Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress, SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel pattern; hierarchical Table rows deprecated — use TreeGrid instead). air: Link, Banner. Sema prep: src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements. Morfo already provides everything Sema needs (data-last-action patterns, transition markers, enumerable state values, cross-component consistency). Sema implementation deferred. COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM topology vs .require() audit, npm run smoke is part of done) + A34 rule with incident log. WAI-ARIA pattern listed as soma membership criterion. Pagination translation bug fixed (soma.pagination.page -> idlangref PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.* -> components.table.*). Avatar demo now offline-safe (SVG data URL). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
);
});
it('declares data-last-action on Content for Sema causal exits', () => {
const content = dialogMorfo.parts.find((p) => p.kebab === 'content')!;
const causal = content.data.find((d) => d.attr === 'data-last-action');
expect(causal).toBeDefined();
expect(causal!.values).toContain('saved');
expect(causal!.values).toContain('cancelled');
});
it('fails validation when a partRef targets a non-existent kebab', () => {
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
const broken = cloneMutable(dialogMorfo);
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired Morfo (src/uix/morfo/): machine-readable contract of a component's public DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment. Types, sium-based validator with cross-field invariants, dialog.ts as the first canonical morfo with data-last-action for Sema-causal exits, 7 passing unit tests. createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy inline signature removed. Dialog provider refactored to derive parts + data contract from dialogMorfo. The other 65 soma components need porting (svelte-check lists them) — tracked work. Tooling: scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes scripts/morfo-check.ts — validates emitted DOM vs morfo scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency npm run smoke / morfo:check / morfo:vocabulary WAI-ARIA APG components added (100% APG coverage now): Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress, SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel pattern; hierarchical Table rows deprecated — use TreeGrid instead). air: Link, Banner. Sema prep: src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements. Morfo already provides everything Sema needs (data-last-action patterns, transition markers, enumerable state values, cross-component consistency). Sema implementation deferred. COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM topology vs .require() audit, npm run smoke is part of done) + A34 rule with incident log. WAI-ARIA pattern listed as soma membership criterion. Pagination translation bug fixed (soma.pagination.page -> idlangref PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.* -> components.table.*). Avatar demo now offline-safe (SVG data URL). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
const trigger = broken.parts.find((p) => p.kebab === 'trigger')!;
const controls = trigger.aria.find((a) => a.attr === 'aria-controls')!;
// Force a partRef to a non-existent target.
(controls.value as { kind: 'partRef'; target: string }).target = 'nonexistent-part';
expect(() => validateMorfo(broken)).toThrow(MorfoInvariantError);
});
it('fails validation when stateRef refers to a state not declared in the part', () => {
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
const broken = cloneMutable(dialogMorfo);
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired Morfo (src/uix/morfo/): machine-readable contract of a component's public DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment. Types, sium-based validator with cross-field invariants, dialog.ts as the first canonical morfo with data-last-action for Sema-causal exits, 7 passing unit tests. createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy inline signature removed. Dialog provider refactored to derive parts + data contract from dialogMorfo. The other 65 soma components need porting (svelte-check lists them) — tracked work. Tooling: scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes scripts/morfo-check.ts — validates emitted DOM vs morfo scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency npm run smoke / morfo:check / morfo:vocabulary WAI-ARIA APG components added (100% APG coverage now): Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress, SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel pattern; hierarchical Table rows deprecated — use TreeGrid instead). air: Link, Banner. Sema prep: src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements. Morfo already provides everything Sema needs (data-last-action patterns, transition markers, enumerable state values, cross-component consistency). Sema implementation deferred. COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM topology vs .require() audit, npm run smoke is part of done) + A34 rule with incident log. WAI-ARIA pattern listed as soma membership criterion. Pagination translation bug fixed (soma.pagination.page -> idlangref PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.* -> components.table.*). Avatar demo now offline-safe (SVG data URL). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
const trigger = broken.parts.find((p) => p.kebab === 'trigger')!;
const expanded = trigger.aria.find((a) => a.attr === 'aria-expanded')!;
(expanded.value as { kind: 'stateRef'; state: string }).state = 'nonexistent-state';
expect(() => validateMorfo(broken)).toThrow(MorfoInvariantError);
});
it('fails validation when two parts share the same kebab', () => {
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
const broken = cloneMutable(dialogMorfo);
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired Morfo (src/uix/morfo/): machine-readable contract of a component's public DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment. Types, sium-based validator with cross-field invariants, dialog.ts as the first canonical morfo with data-last-action for Sema-causal exits, 7 passing unit tests. createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy inline signature removed. Dialog provider refactored to derive parts + data contract from dialogMorfo. The other 65 soma components need porting (svelte-check lists them) — tracked work. Tooling: scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes scripts/morfo-check.ts — validates emitted DOM vs morfo scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency npm run smoke / morfo:check / morfo:vocabulary WAI-ARIA APG components added (100% APG coverage now): Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress, SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel pattern; hierarchical Table rows deprecated — use TreeGrid instead). air: Link, Banner. Sema prep: src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements. Morfo already provides everything Sema needs (data-last-action patterns, transition markers, enumerable state values, cross-component consistency). Sema implementation deferred. COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM topology vs .require() audit, npm run smoke is part of done) + A34 rule with incident log. WAI-ARIA pattern listed as soma membership criterion. Pagination translation bug fixed (soma.pagination.page -> idlangref PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.* -> components.table.*). Avatar demo now offline-safe (SVG data URL). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
broken.parts[1].kebab = 'content'; // trigger → content (duplicate)
expect(() => validateMorfo(broken)).toThrow(MorfoInvariantError);
});
it('fails validation with empty scope', () => {
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
const broken = cloneMutable(dialogMorfo);
morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired Morfo (src/uix/morfo/): machine-readable contract of a component's public DOM surface, shared between soma/sema/eidos. Shape v5 consolidated across three independent AI reviews (Gemini, Grok, ChatGPT) plus Sema alignment. Types, sium-based validator with cross-field invariants, dialog.ts as the first canonical morfo with data-last-action for Sema-causal exits, 7 passing unit tests. createAttrs(morfo) and registerContract(morfo) now consume a Morfo — legacy inline signature removed. Dialog provider refactored to derive parts + data contract from dialogMorfo. The other 65 soma components need porting (svelte-check lists them) — tracked work. Tooling: scripts/smoke-check.mjs — Playwright smoke over 65 soma demo routes scripts/morfo-check.ts — validates emitted DOM vs morfo scripts/morfo-vocabulary-check.ts — canonical vocabulary consistency npm run smoke / morfo:check / morfo:vocabulary WAI-ARIA APG components added (100% APG coverage now): Announce, Avatar, Clipboard, DragDrop, Feed, GridList, Meter, Progress, SearchField, TagGroup, TreeGrid + Table.RowDetail/Trigger (detail panel pattern; hierarchical Table rows deprecated — use TreeGrid instead). air: Link, Banner. Sema prep: src/uix/sema/sema_pre.md documents the semantic layer's DOM requirements. Morfo already provides everything Sema needs (data-last-action patterns, transition markers, enumerable state values, cross-component consistency). Sema implementation deferred. COMPONENT_GUIDE updated: items 37-39 (translation namespace grep, DOM topology vs .require() audit, npm run smoke is part of done) + A34 rule with incident log. WAI-ARIA pattern listed as soma membership criterion. Pagination translation bug fixed (soma.pagination.page -> idlangref PAGINATION_LANGS.PAGE). Table demo namespace fix (soma.table.* -> components.table.*). Avatar demo now offline-safe (SVG data URL). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
broken.scope = [];
expect(() => validateMorfo(broken)).toThrow(MorfoInvariantError);
});
});

Powered by TurnKey Linux.