soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
/**
|
|
|
|
|
* # createForm — runes-native form state
|
|
|
|
|
*
|
|
|
|
|
* A lightweight form state store built directly on Svelte 5 runes ($state,
|
|
|
|
|
* $derived). No external stores, no register() calls — fields auto-register
|
|
|
|
|
* via `Field.Provider`'s `name` prop once the Form context is present.
|
|
|
|
|
*
|
|
|
|
|
* Accepts any Standard Schema v1 validator (Zod, Valibot, ArkType, etc.) via
|
|
|
|
|
* the `schema` option. Returns a reactive `Form` object whose properties
|
|
|
|
|
* (`values`, `errors`, `isDirty`, `isTouched`, `isPending`, `isValid`) are
|
|
|
|
|
* $state proxies — consumers read them directly, mutations ripple
|
|
|
|
|
* automatically without subscribe/unsubscribe ceremony.
|
|
|
|
|
*/
|
|
|
|
|
|
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 { untrack } from 'svelte';
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
import { SvelteMap } from 'svelte/reactivity';
|
|
|
|
|
import { type StandardSchemaV1, isPromiseLike } from '$libs/standard-schema';
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
|
|
|
|
|
// ── Public types ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/** Aggregate error shape — keyed by dot-path, values are human messages. */
|
|
|
|
|
export type FormErrors = Record<string, string[]>;
|
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 type ValidationBehaviour = 'progressive' | 'onSubmit' | 'onBlur' | 'onChange';
|
|
|
|
|
export type FormIssue = {
|
|
|
|
|
message: string;
|
|
|
|
|
params?: Record<string, unknown>;
|
|
|
|
|
code?: string;
|
|
|
|
|
};
|
|
|
|
|
export type FormIssues = Record<string, FormIssue[]>;
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
|
|
|
|
|
/** Reason for the last invalid submit. */
|
|
|
|
|
export type SubmitFailureReason = 'validation' | 'cancelled';
|
|
|
|
|
|
|
|
|
|
/** Per-field UI state, derived by the form from values vs defaults + touched registry. */
|
|
|
|
|
export interface FieldState {
|
|
|
|
|
/** Field has been blurred since last reset / mount. */
|
|
|
|
|
touched: boolean;
|
|
|
|
|
/** Current value differs from the field's initial default. */
|
|
|
|
|
dirty: boolean;
|
|
|
|
|
/** Errors scoped to this field. Empty array when valid. */
|
|
|
|
|
errors: string[];
|
|
|
|
|
/** Convenience — `errors.length > 0`. */
|
|
|
|
|
isInvalid: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Options for `createForm()`. */
|
|
|
|
|
export interface CreateFormOpts<
|
|
|
|
|
Schema extends StandardSchemaV1 | undefined = undefined,
|
|
|
|
|
Values = Schema extends StandardSchemaV1
|
|
|
|
|
? StandardSchemaV1.InferInput<Schema>
|
|
|
|
|
: Record<string, unknown>
|
|
|
|
|
> {
|
|
|
|
|
/**
|
|
|
|
|
* Any Standard Schema validator (Zod/Valibot/ArkType/…). When present,
|
|
|
|
|
* `form.validate()` runs on every `submit()` and fills `form.errors`.
|
|
|
|
|
*/
|
|
|
|
|
schema?: Schema;
|
|
|
|
|
/**
|
|
|
|
|
* Initial values. Used as the "pristine" snapshot for dirty tracking and
|
|
|
|
|
* as the target of `reset()`. Mandatory when there is no schema.
|
|
|
|
|
*/
|
|
|
|
|
defaults: Values;
|
|
|
|
|
/**
|
|
|
|
|
* Called on submit **only when validation passes**. If it returns a
|
|
|
|
|
* Promise, `isPending` stays `true` until it resolves. Thrown errors set
|
|
|
|
|
* a generic form-level error (`form.errors['']`).
|
|
|
|
|
*/
|
|
|
|
|
onValidSubmit?: (
|
|
|
|
|
values: Schema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<Schema> : Values,
|
|
|
|
|
e: SubmitEvent
|
|
|
|
|
) => void | Promise<void>;
|
|
|
|
|
/** Called on submit **when validation fails**. */
|
|
|
|
|
onInvalidSubmit?: (errors: FormErrors, e: SubmitEvent) => void;
|
|
|
|
|
/**
|
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
|
|
|
* When `'progressive'` (default), validation stays quiet until the first
|
|
|
|
|
* invalid submit, then upgrades to on-change feedback while the user fixes
|
|
|
|
|
* the form. `'onSubmit'` only validates on submit. `'onBlur'` validates
|
|
|
|
|
* after blur. `'onChange'` validates on every change (noisy — use sparingly).
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
*/
|
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
|
|
|
validationBehaviour?: ValidationBehaviour;
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** The reactive form handle returned by `createForm()`. */
|
|
|
|
|
export interface Form<Values = Record<string, unknown>> {
|
|
|
|
|
/** Mutable values. Bind inputs to `form.values.fieldName` directly. */
|
|
|
|
|
values: Values;
|
|
|
|
|
/** Current errors keyed by dot-path. Empty object when valid. */
|
|
|
|
|
readonly errors: FormErrors;
|
|
|
|
|
/** Raw structured issues keyed by dot-path, preserving params/code when present. */
|
|
|
|
|
readonly issues: FormIssues;
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
/** `true` while an async `onValidSubmit` is in flight. */
|
|
|
|
|
readonly isPending: boolean;
|
|
|
|
|
/** `true` when no errors. */
|
|
|
|
|
readonly isValid: boolean;
|
|
|
|
|
/** `true` when any registered field's value differs from its default. */
|
|
|
|
|
readonly isDirty: boolean;
|
|
|
|
|
/** `true` when any registered field has been blurred since last reset. */
|
|
|
|
|
readonly isTouched: boolean;
|
|
|
|
|
/** Number of submit attempts (valid + invalid). */
|
|
|
|
|
readonly submitCount: number;
|
|
|
|
|
/** Name of the first field that has an error (for focus management). */
|
|
|
|
|
readonly firstInvalidField: string | undefined;
|
|
|
|
|
|
|
|
|
|
/** Read per-field state. Returns `undefined` when the field is not registered. */
|
|
|
|
|
getFieldState(name: string): FieldState | undefined;
|
|
|
|
|
/** Mark a field as touched (internal — called by Field.Provider on blur). */
|
|
|
|
|
setFieldTouched(name: string, touched: boolean): void;
|
|
|
|
|
/** Mark a field as untouched (internal — used by `reset()`). */
|
|
|
|
|
clearFieldTouched(name: string): void;
|
|
|
|
|
/** Register a field (internal — called by Field.Provider on mount). */
|
|
|
|
|
registerField(name: string, meta: { label?: string }): void;
|
|
|
|
|
/** Unregister a field (internal — on unmount). */
|
|
|
|
|
unregisterField(name: string): void;
|
|
|
|
|
|
|
|
|
|
/** Run validation synchronously (async schemas run in the background). */
|
|
|
|
|
validate(): void;
|
|
|
|
|
/** Imperative submit — returns `true` if valid and `onValidSubmit` completed. */
|
|
|
|
|
submit(e?: SubmitEvent): Promise<boolean>;
|
|
|
|
|
/** Reset values to `defaults`, clear errors/touched/submitCount. */
|
|
|
|
|
reset(): void;
|
|
|
|
|
/** Internal attach point for the `<form>`'s `onsubmit`. */
|
|
|
|
|
onsubmit(e: SubmitEvent): void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Registry entry (internal) ────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
interface FieldEntry {
|
|
|
|
|
name: string;
|
|
|
|
|
label?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type RawStandardIssue = StandardSchemaV1.Issue & {
|
|
|
|
|
code?: string;
|
|
|
|
|
params?: Record<string, unknown>;
|
|
|
|
|
};
|
|
|
|
|
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
// ── Implementation ───────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a runes-native form state store. Returns a reactive `Form` handle.
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* const form = createForm({
|
|
|
|
|
* schema: z.object({ email: z.string().email() }),
|
|
|
|
|
* defaults: { email: '' },
|
|
|
|
|
* onValidSubmit: async (values) => { await api.save(values); }
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // Later in markup:
|
|
|
|
|
* <Form.Provider {form}>
|
|
|
|
|
* <Field.Provider name="email">
|
|
|
|
|
* <input bind:value={form.values.email} />
|
|
|
|
|
* </Field.Provider>
|
|
|
|
|
* </Form.Provider>
|
|
|
|
|
*/
|
|
|
|
|
export function createForm<
|
|
|
|
|
Schema extends StandardSchemaV1 | undefined = undefined,
|
|
|
|
|
Values = Schema extends StandardSchemaV1
|
|
|
|
|
? StandardSchemaV1.InferInput<Schema>
|
|
|
|
|
: Record<string, unknown>
|
|
|
|
|
>(opts: CreateFormOpts<Schema, Values>): Form<Values> {
|
|
|
|
|
const schema = opts.schema;
|
|
|
|
|
const initial = structuredClone(opts.defaults) as Values;
|
|
|
|
|
|
|
|
|
|
// ── Reactive state ──────────────────────────────────────────────────────
|
|
|
|
|
const values = $state(structuredClone(opts.defaults) as Values);
|
|
|
|
|
let errors = $state<FormErrors>({});
|
|
|
|
|
let issues = $state<FormIssues>({});
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
let isPending = $state(false);
|
|
|
|
|
let submitCount = $state(0);
|
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
|
|
|
let shouldValidateEagerly = $state(false);
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
// SvelteMap (not plain Map) so `.set` / `.delete` / `.has` / `.keys` /
|
|
|
|
|
// `.values` / `.size` reads in the derivations below react to per-entry
|
|
|
|
|
// mutations. Plain `$state(new Map())` only tracks field reassignment —
|
|
|
|
|
// `.set()` is silently non-reactive and `isTouched` / `isDirty` /
|
|
|
|
|
// `firstInvalidField` would never re-run after a blur or field register
|
|
|
|
|
// (A33).
|
|
|
|
|
const touched = new SvelteMap<string, boolean>();
|
|
|
|
|
const registry = new SvelteMap<string, FieldEntry>();
|
|
|
|
|
|
|
|
|
|
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
function dotPath(keys: readonly (PropertyKey | StandardSchemaV1.PathSegment)[]): string {
|
|
|
|
|
return keys
|
|
|
|
|
.map((k) => (typeof k === 'object' && k !== null && 'key' in k ? String(k.key) : String(k)))
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
.join('.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function groupIssues(rawIssues: ReadonlyArray<StandardSchemaV1.Issue>): {
|
|
|
|
|
errors: FormErrors;
|
|
|
|
|
issues: FormIssues;
|
|
|
|
|
} {
|
|
|
|
|
const outErrors: FormErrors = {};
|
|
|
|
|
const outIssues: FormIssues = {};
|
|
|
|
|
for (const issue of rawIssues as ReadonlyArray<RawStandardIssue>) {
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
const key = issue.path ? dotPath(issue.path) : '';
|
|
|
|
|
(outErrors[key] ??= []).push(issue.message);
|
|
|
|
|
(outIssues[key] ??= []).push({
|
|
|
|
|
message: issue.message,
|
|
|
|
|
...(issue.params === undefined ? {} : { params: issue.params }),
|
|
|
|
|
...(issue.code === undefined ? {} : { code: issue.code })
|
|
|
|
|
});
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
errors: outErrors,
|
|
|
|
|
issues: outIssues
|
|
|
|
|
};
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
}
|
|
|
|
|
|
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
|
|
|
function hasErrors(nextErrors: FormErrors): boolean {
|
|
|
|
|
for (const key in nextErrors) {
|
|
|
|
|
if ((nextErrors[key] ?? []).length > 0) return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function installValidationEffect(effect: () => void): void {
|
|
|
|
|
try {
|
|
|
|
|
$effect(effect);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = String((error as Error)?.message ?? error);
|
|
|
|
|
if (!message.includes('effect_orphan')) throw error;
|
|
|
|
|
$effect.root(() => {
|
|
|
|
|
$effect(effect);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function runValidate(): { errors: FormErrors; issues: FormIssues } {
|
|
|
|
|
if (!schema) {
|
|
|
|
|
return {
|
|
|
|
|
errors: {},
|
|
|
|
|
issues: {}
|
|
|
|
|
};
|
|
|
|
|
}
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
const res = schema['~standard'].validate(values);
|
|
|
|
|
if (isPromiseLike(res)) {
|
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
|
|
|
// Async schema — kick off the promise, return the CURRENT sync
|
|
|
|
|
// errors/issues as a placeholder. The read must be wrapped in
|
|
|
|
|
// `untrack`: otherwise, when this function runs inside the
|
|
|
|
|
// `validation` $effect, the effect would track `errors` + `issues`
|
|
|
|
|
// as deps, then the `.then` microtask below writes them, which
|
|
|
|
|
// retriggers the effect → another validate call → another `.then`
|
|
|
|
|
// → infinite microtask-mediated loop (silent to Svelte's
|
|
|
|
|
// synchronous effect-depth detector). See
|
|
|
|
|
// `src/uix/soma/components/form/BUG-onchange-onblur-hang.md`.
|
|
|
|
|
//
|
|
|
|
|
// Note: sium's Standard-Schema adapter declares
|
|
|
|
|
// `validate: async (...)` unconditionally, so this branch is taken
|
|
|
|
|
// even for schemas with `~sium.async === false`. Without the
|
|
|
|
|
// `untrack`, onChange/onBlur modes hang on mount.
|
|
|
|
|
res
|
|
|
|
|
.then((r) => {
|
|
|
|
|
const grouped = r.issues ? groupIssues(r.issues) : { errors: {}, issues: {} };
|
|
|
|
|
errors = grouped.errors;
|
|
|
|
|
issues = grouped.issues;
|
|
|
|
|
})
|
|
|
|
|
.catch((e) => {
|
|
|
|
|
errors = { '': [String((e as Error)?.message ?? e)] };
|
|
|
|
|
issues = { '': [{ message: String((e as Error)?.message ?? e) }] };
|
|
|
|
|
});
|
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
|
|
|
return untrack(() => ({ errors, issues }));
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
}
|
|
|
|
|
return res.issues ? groupIssues(res.issues) : { errors: {}, issues: {} };
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isDirtyField(name: string): boolean {
|
|
|
|
|
// Shallow compare using JSON serialization — good enough for the kinds
|
|
|
|
|
// of values forms typically hold (primitives, small objects). Consumers
|
|
|
|
|
// with custom equality can override `dirty` via `Field.Provider`'s prop.
|
|
|
|
|
const path = name.split('.');
|
|
|
|
|
let a: unknown = values;
|
|
|
|
|
let b: unknown = initial;
|
|
|
|
|
for (const key of path) {
|
|
|
|
|
a = (a as Record<string, unknown> | null | undefined)?.[key];
|
|
|
|
|
b = (b as Record<string, unknown> | null | undefined)?.[key];
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
return JSON.stringify(a) !== JSON.stringify(b);
|
|
|
|
|
} catch {
|
|
|
|
|
return a !== b;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Derived ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
const isValid = $derived.by(() => {
|
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
|
|
|
return !hasErrors(errors);
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const isDirty = $derived.by(() => {
|
|
|
|
|
for (const name of registry.keys()) {
|
|
|
|
|
if (isDirtyField(name)) return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const isTouched = $derived.by(() => {
|
|
|
|
|
for (const v of touched.values()) if (v) return true;
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const firstInvalidField = $derived.by(() => {
|
|
|
|
|
for (const name of registry.keys()) {
|
|
|
|
|
if ((errors[name] ?? []).length > 0) return name;
|
|
|
|
|
}
|
|
|
|
|
// Fallback: first key in errors that matches a registered field.
|
|
|
|
|
for (const key in errors) {
|
|
|
|
|
if (registry.has(key) && errors[key].length > 0) return key;
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ── On-blur validation (optional) ───────────────────────────────────────
|
|
|
|
|
|
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 mode = opts.validationBehaviour ?? 'progressive';
|
|
|
|
|
|
|
|
|
|
if (mode === 'progressive') {
|
|
|
|
|
installValidationEffect(() => {
|
|
|
|
|
JSON.stringify(values);
|
|
|
|
|
if (!shouldValidateEagerly) return;
|
|
|
|
|
const validation = runValidate();
|
|
|
|
|
errors = validation.errors;
|
|
|
|
|
issues = validation.issues;
|
|
|
|
|
});
|
|
|
|
|
} else if (mode === 'onBlur' || mode === 'onChange') {
|
|
|
|
|
installValidationEffect(() => {
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
// Depend on values to re-run on change when onChange.
|
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
|
|
|
if (mode === 'onChange') {
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
// touch to track mutations
|
|
|
|
|
JSON.stringify(values);
|
|
|
|
|
} else {
|
|
|
|
|
// onBlur — depend on touched map size.
|
|
|
|
|
touched.size;
|
|
|
|
|
}
|
|
|
|
|
const validation = runValidate();
|
|
|
|
|
errors = validation.errors;
|
|
|
|
|
issues = validation.issues;
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Public API ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
function getFieldState(name: string): FieldState | undefined {
|
|
|
|
|
if (!registry.has(name)) return undefined;
|
|
|
|
|
const fieldErrors = errors[name] ?? [];
|
|
|
|
|
return {
|
|
|
|
|
touched: touched.get(name) ?? false,
|
|
|
|
|
dirty: isDirtyField(name),
|
|
|
|
|
errors: fieldErrors,
|
|
|
|
|
isInvalid: fieldErrors.length > 0
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setFieldTouched(name: string, v: boolean): void {
|
|
|
|
|
touched.set(name, v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearFieldTouched(name: string): void {
|
|
|
|
|
touched.delete(name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function registerField(name: string, meta: { label?: string }): void {
|
|
|
|
|
registry.set(name, { name, ...meta });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function unregisterField(name: string): void {
|
|
|
|
|
registry.delete(name);
|
|
|
|
|
// Don't clear errors — if the field was part of a schema, the errors
|
|
|
|
|
// are still valid context until the next validate() call.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function validate(): void {
|
|
|
|
|
const validation = runValidate();
|
|
|
|
|
errors = validation.errors;
|
|
|
|
|
issues = validation.issues;
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function submit(e?: SubmitEvent): Promise<boolean> {
|
|
|
|
|
submitCount++;
|
|
|
|
|
// Mark all registered fields as touched so errors display.
|
|
|
|
|
for (const name of registry.keys()) touched.set(name, true);
|
|
|
|
|
const validation = runValidate();
|
|
|
|
|
errors = validation.errors;
|
|
|
|
|
issues = validation.issues;
|
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 ok = !hasErrors(validation.errors);
|
|
|
|
|
|
|
|
|
|
if (mode === 'progressive') {
|
|
|
|
|
shouldValidateEagerly = !ok;
|
|
|
|
|
}
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
|
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
|
|
|
if (!ok) {
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
opts.onInvalidSubmit?.(errors, e ?? new SubmitEvent('submit'));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const validated = values as unknown as Schema extends StandardSchemaV1
|
|
|
|
|
? StandardSchemaV1.InferOutput<Schema>
|
|
|
|
|
: Values;
|
|
|
|
|
const handler = opts.onValidSubmit;
|
|
|
|
|
if (!handler) return true;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const out = handler(validated, e ?? new SubmitEvent('submit'));
|
|
|
|
|
if (isPromiseLike(out)) {
|
|
|
|
|
isPending = true;
|
|
|
|
|
try {
|
|
|
|
|
await out;
|
|
|
|
|
} finally {
|
|
|
|
|
isPending = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
errors = {
|
|
|
|
|
...errors,
|
|
|
|
|
'': [String((err as Error)?.message ?? err)]
|
|
|
|
|
};
|
|
|
|
|
issues = {
|
|
|
|
|
...issues,
|
|
|
|
|
'': [{ message: String((err as Error)?.message ?? err) }]
|
|
|
|
|
};
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function reset(): void {
|
|
|
|
|
// Replace keys in-place so `bind:value={form.values.x}` keeps working.
|
|
|
|
|
Object.keys(values as object).forEach((k) => {
|
|
|
|
|
delete (values as Record<string, unknown>)[k];
|
|
|
|
|
});
|
|
|
|
|
Object.assign(values as object, structuredClone(initial));
|
|
|
|
|
errors = {};
|
|
|
|
|
issues = {};
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
touched.clear();
|
|
|
|
|
submitCount = 0;
|
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
|
|
|
shouldValidateEagerly = false;
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onsubmit(e: SubmitEvent): void {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
void submit(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Return reactive handle ──────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
values,
|
|
|
|
|
get errors() {
|
|
|
|
|
return errors;
|
|
|
|
|
},
|
|
|
|
|
get issues() {
|
|
|
|
|
return issues;
|
|
|
|
|
},
|
soma: 10 new components + reactivity audit + guide rules A30–A33
Components shipped:
- AlertDialog (thin Dialog specialisation with Escape=close default)
- Breadcrumb (+ interactive Ellipsis composing DropdownMenu)
- Carousel (gesture + autoplay + indicators)
- Listbox (roving tabindex + typeahead + Ctrl+A multi-select)
- NavigationMenu (hover-intent + data-motion direction + Indicator CSS vars)
- PinInput (Field-aware segmented input with paste transformer)
- RatingGroup (half-step + clearable + keyboard digits)
- Toggle (standalone two-state with Field OR-merge)
- VirtualGrid (2D windowing for spreadsheets / thumbnail grids)
- VirtualList (fixed + dynamic heights, ResizeObserver anti-jump, WindowViewport)
Guide rules (src/uix/soma/COMPONENT_GUIDE.md):
- A30 + item 32: id registration via direct assign, never $effect — the
$effect(() => parent.id = opts.id) pattern causes reactive loops that
freeze the page. Ref: PinInput + Listbox incidents.
- A31 + item 33: per-entity $derived must not call provider methods that
read global state — lift to single provider $derived, per-entity
derivations pointer-compare. Ref: Command (2026-04-17), Listbox
rovingTarget.
- A32 + items 34–35: present comparison table in conversation before
declaring done. Every ❌/⚠️ gets an explicit decision (implement /
defer to v2 / drop); deferred features become their own README section.
- A33 + item 36: reactive collections use SvelteMap / SvelteSet from
svelte/reactivity — $state(new Map()) only tracks field reassignment,
not per-entry .set/.get/.has reads inside $derived.
Reactivity audits + fixes:
- VirtualList sizeCache → SvelteMap (dynamic heights were silently stuck).
- Form touched + registry → SvelteMap (isTouched / isDirty / onBlur
validation $derived never re-ran with plain Map).
- Combobox labelRegistry → SvelteMap (removed clone-and-reassign
workaround, O(N)→O(1) per registration).
Other fixes this pass:
- ColorField/ColorPicker RGB co-increment bug (HSV round-trip was lossy).
- 24 state_referenced_locally warnings swept across date/time/color
field+picker wrappers (removed `void X;` placeholders that Svelte 5
flagged, added svelte-ignore for intentional mount-time reads).
- AlertDialog Content wrapper forces escapeKeydownBehavior=close
(overrides Dialog's alertdialog→ignore default → matches WAI-ARIA).
- Breadcrumb Ellipsis interactive prop renders <button aria-haspopup>
so DropdownMenu composition works without manual trigger plumbing.
- VirtualList Viewport drops `contain: strict` (was breaking programmatic
scrollTo in some engines).
- Memory entries: feedback_id_effect_assign_directly,
feedback_scope_approval, feedback_svelte_reactivity_collections.
Sium library (delegated session, landed in tree):
- Core schema + primitives + combinators + pipe + refines + modifiers.
- Langs module with idlangref resolver.
- Svelte form adapter.
- Protocol docs + status ledger for Codex coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
get isPending() {
|
|
|
|
|
return isPending;
|
|
|
|
|
},
|
|
|
|
|
get isValid() {
|
|
|
|
|
return isValid;
|
|
|
|
|
},
|
|
|
|
|
get isDirty() {
|
|
|
|
|
return isDirty;
|
|
|
|
|
},
|
|
|
|
|
get isTouched() {
|
|
|
|
|
return isTouched;
|
|
|
|
|
},
|
|
|
|
|
get submitCount() {
|
|
|
|
|
return submitCount;
|
|
|
|
|
},
|
|
|
|
|
get firstInvalidField() {
|
|
|
|
|
return firstInvalidField;
|
|
|
|
|
},
|
|
|
|
|
getFieldState,
|
|
|
|
|
setFieldTouched,
|
|
|
|
|
clearFieldTouched,
|
|
|
|
|
registerField,
|
|
|
|
|
unregisterField,
|
|
|
|
|
validate,
|
|
|
|
|
submit,
|
|
|
|
|
reset,
|
|
|
|
|
onsubmit
|
|
|
|
|
};
|
|
|
|
|
}
|