The footer affordances (Clear / Cancel / Close) lived inline in each
demo as picker-actions.svelte. Promoted to canonical Eidos surface so
the API is consistent and the contract is auditable.
morfo:
- date-picker + date-range-picker: added Footer (archetype 'footer'),
Clear/Cancel/Close (archetype 'trigger', kind 'public', optional).
Each part declares aria-label via idlangref and data-action="…".
- types.ts: added 'footer' to ARCHETYPE_VOCABULARY array + the
MorfoArchetype union (the type had it, the runtime list did not).
- lang catalogs: added clear / cancel / close idlangref entries to
date-picker.ts and date-range-picker.ts.
eidos:
- Created date-picker-footer/clear/cancel/close.svelte and the range
mirrors. Each part:
- Pulls the provider from context via DatePickerProvider.require()
(analogous DateRangePickerProvider.require() for the range).
- Renders nothing when the corresponding *Button opt is false; the
Close part stays visible whenever mode === 'modal' (modal pickers
need a way out — outside-click and Escape are blocked).
- Emits data-{component}-{part} + data-action so the recipe selector
matches the morfo declaration.
- aria-label resolves via uix.langs.ts('#?components.X.{action}|…').
- onclick calls provider.clear() / cancel() / commit() then forwards
any consumer-supplied onclick.
- index.ts barrels: registered Footer/Clear/Cancel + replaced
Close (was popover-close.svelte) with the new picker-aware Close.
Soma popover-close still drives the old aria; date-picker's Close
adds the modal-conditional visibility + commit semantics.
- *.css: folded the inline picker-actions styles into the recipes —
data-{name}-footer flex row + data-{name}-{clear,cancel,close}
buttons (clear/cancel margin-inline-end: auto so close sits flush
right). prefers-reduced-motion already covered.
soma:
- date-picker exports.ts: surfaces DatePickerProvider + DatePickerMode
+ datePickerAttrs so the Eidos parts can consume them (mirrors what
date-range-picker already exposed). No new behavior — just plumbing.
demo:
- date-picker + date-range-picker demos: replaced
<PickerActions /> with <DatePicker.Footer>
<DatePicker.Clear /><DatePicker.Cancel /><DatePicker.Close />
</DatePicker.Footer>. The two picker-actions.svelte files are
deleted.
Verification: 0 type errors, morfo:check PASS (Footer parts not
required in DOM; conditional visibility honored), component:audit
67/67 PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replicates the date-range-picker pattern on the single date-picker:
soma:
- DatePickerMode type ('inline' | 'modal') exported.
- Provider gains mode/clearButton/cancelButton/closeButton opts plus
commit() / cancel() / clear() action helpers; cancel() reverts the
value snapshot taken via watch() on the OPEN edge of opts.open.
- handleDateSelect now early-returns when mode === 'modal' so modal
pickers don't auto-close on selection.
- exports.ts surfaces DatePickerProvider + DatePickerMode for demo /
footer wiring (mirrors date-range-picker's barrel).
- Test fixture extended with the new opts (closeOnDateSelect stays
true by default; mode='inline', all buttons false).
morfo:
- scope=['soma','sema'], apg=dialog-modal.
- 6 events: open / close-commit / close-cancel / close-dismiss /
close-dismiss-outside / commit-clear. prewrite stamps data-last-action
with the causal exit reason so Sema can tint the exit animation.
- Calendar part declares the state machine (open/closed,
data-last-action, data-starting-style, data-ending-style) and the
modal-keyboard surface (Escape / Tab / Shift+Tab).
demo:
- New picker-actions.svelte mirrors the range demo: pulls provider via
context, renders Clear / Cancel / Close conditionally, forces Close in
modal mode. CSS is local to the file (uses --color-primary-* +
--color-surface-overlay tokens, no raw colors).
- +page.svelte adds mode radio chip group + 3 footer-button switches and
passes mode/clearButton/cancelButton/closeButton through to the Eidos
wrapper (which spreads to soma).
The Eidos wrapper needs no change — it already spreads everything via
...rest, so the new soma opts reach Soma without further wiring.
Verification: 0 type errors, 3/3 soma tests pass, morfo:check PASS,
component:audit 67/67 PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The clear affordance is the standard search-input pattern: an X glyph
inside the input, with the localized "Clear" / "Borrar" string moved
to `aria-label`. Showing the word "Borrar" inline and then a hover
button on top read as two redundant affordances.
Replaced the visible text with `<X size="1em" decorative />` from the
icon set. The aria-label still pulls from
`'#?components.search-field.clear|Clear'` so screen readers get the
localized affordance name.
Code snippets in the API panel updated to match the new pattern
(`<SearchField.ClearTrigger aria-label="Clear"><X ... /></...>`)
instead of the text-only variant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The demo hardcoded the visible Clear button label inline:
<SearchField.ClearTrigger>Clear</SearchField.ClearTrigger>
So the toolbar's locale toggle (ES/EN) only swapped the aria-label
via the morfo's translationRef, not the visible text. With locale=ES
the panel showed a Spanish UI everywhere except the literal "Clear".
Replace with `uix.langs.ts('#?components.search-field.clear|Clear')`
so the visible button picks up the active catalog entry. Catalog
strings shortened to just `Borrar` / `Clear` (one-word action label
that fits the inline-button affordance — the previous "Borrar
búsqueda" was too long for the visible chrome and wrapped in two
lines). The full phrase still reads naturally for aria-label.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two visible inconsistencies in dark theme that the user flagged:
1. Scrollbars in code blocks (and any other scrollable inside the
demos) rendered with the user-agent default chrome — bright white
track + thumb on a dark surface. Only `[data-uix-rail]` (the left
navigation column) had a themed scrollbar.
Added a universal WebKit scrollbar theme scoped to
`[data-uix-docs]`: transparent track, `--uix-line` thumb with
2px padding-clipped border so it reads as a thin rounded handle
that hovers to `--uix-line-strong`. Both axes (10px width).
Firefox handled via `scrollbar-color` + `scrollbar-width: thin`
on the root.
2. `[data-uix-radio]` had zero CSS rules — the date-range-picker
demo's `mode` (inline / modal) selector fell back to native radio
inputs and an unstyled label, while its sibling controls used the
themed `[data-uix-switch]`. Two switches-as-controls + one set of
native radios in the same panel.
Mirrored the data-uix-switch chrome at radio size: 16x16 circle,
`--uix-line` border, `--uix-surface-sunken` background, inner
pill in `--uix-accent` when checked, focus ring matches the rest.
Label uses the same color + size as data-uix-switch-label.
Verified in browser on date-range-picker demo: the `mode` radios now
read as the same control family as the surrounding switches, and the
code-snippet scrollbar is dark-themed instead of bright white.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `[data-uix-control] input[type='text|number']` rule only covered
two input types, so demos that used `<input type='date'>` (date- and
date-range pickers' min/max/start/end inputs, calendar bounds, etc.)
or `<input type='email|search|tel|url|password|time|month|week|
datetime-local'>` (form/field demos) fell back to the user-agent
chrome — different padding, borders, fonts, and the native
calendar/clock indicator with no theme alignment.
Replaced the type-specific selector with an exclusion list:
[data-uix-control] input:not([type='checkbox']):not([type='radio']):not([type='range'])
Every text-like input now picks up the demo's standard chrome
(6px / 32px / `--uix-line` border / `--uix-surface` bg / focus ring).
Checkbox + radio keep their bespoke toggle treatments; range keeps
its native track with the demo accent.
Added:
- Consistent `min-block-size: 32px` + `box-sizing: border-box` so
inputs line up vertically with the demo's button-style pill
controls.
- Disabled state: 0.55 opacity + `not-allowed` cursor.
- `::-webkit-calendar-picker-indicator` recolored for
date/datetime-local/month/time/week inputs — inverted 70% so the
calendar/clock icon reads on dark theme and matches the field
text tone, with full opacity on hover.
- `::-webkit-search-cancel-button` recolored.
Verified in browser: date-range-picker demo's start/end/minValue/
maxValue inputs now match minDays/maxDays (number) and label/name
(text) inputs. Form demo Field-rendered inputs and the controls
panel both read as one consistent surface. date-field controls
panel (locale select + name text + min/max date inputs) matches.
Closes#47 in the task list.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The demo seeded `cover.png` with `new Uint8Array(size)` — empty
bytes. `URL.createObjectURL` then handed a blob the browser couldn't
decode as PNG, so the preview rendered the user-agent broken-image
icon next to the row.
Fix: when the demo file type starts with `image/`, prepend a valid
1x1 transparent PNG header before the size-padding bytes. The
browser decodes a 1px image cleanly and the preview slot stays
empty-looking (which is the intended visual) instead of showing a
broken-image affordance. atob lives inside the `typeof File`
runtime check so SSR doesn't choke on it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback batch from incidencia 2026-05-20:
1. Modal vs inline mode
- New `mode: 'inline' | 'modal'` prop on the soma `DateRangePicker.Provider`.
- Modal wires the popover `modal: true` → outside-click and Escape are
ignored; user must commit via the footer Close button (or revert via
Cancel).
- Provider exposes helpers `clear()`, `cancel()`, `commit()` for the
footer. `cancel()` restores the value snapshot taken on the OPEN edge
(captured via a `watch` on `opts.open` true-edge transition).
2. Footer buttons as boolean props
- `clearButton`, `cancelButton`, `closeButton` props on the picker.
The footer renders only if at least one is true. In `mode='modal'`
the closeButton is forced on (the user always needs an exit).
- `picker-actions.svelte` in the demo reads the picker context via
`DateRangePickerProvider.require()` and renders the enabled buttons
against `provider.clear/cancel/commit`.
3. Range field shape (revert to Chakra-style two boxes)
- Removed the `data-date-range-field-group` wrapper from the demo so
the start and end inputs are rendered as two separate boxed fields
with the icon embedded in the end box, matching Chakra's layout.
- The recipe CSS rules for `data-date-range-field-group` stay
available as an opt-in for consumers who prefer the unified pill.
4. Demo defaults
- `open` starts at `false` so the picker exercises the real
open/close flow when the user clicks the trigger — the segments are
for direct keyboard entry, the popover is for visual exploration.
- Mode toggle (inline / modal) + footer button switches surfaced as
controls in the demo.
5. Plumbing
- DateRangePickerOpts gains `mode`, `clearButton`, `cancelButton`,
`closeButton` (StateProps for mode, ActiveProps for the booleans).
- `DateRangePickerProvider` and `DateRangePickerMode` are now re-
exported from the soma barrel for consumers that wire footer
actions in the calendar tree.
- Test factory updated to seed the new opts.
Verified in browser: trigger opens; click-outside in modal mode is
ignored; Close commits & closes; Cancel reverts to snapshot & closes;
Clear empties the range & keeps open. Heading "May – June 2026" (year
collapse) and centered per-calendar titles still working from the
previous commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes incidencia point B (range field shouldn't show two separate
boxes). New CSS contract: `[data-date-range-field-group]` on the
wrapper element collapses the chrome around its descendants:
- The group owns the border, background, focus ring, invalid state,
variant/ghost treatment, and disabled opacity.
- Each `[data-date-range-field-input]` inside loses its own border,
background, and padding; it just contributes its segments to the
shared row.
- The "to" separator (.range-separator or `[data-range-separator]`)
picks up the field's literal tone.
- The embedded trigger from the previous commit sits flush at the
trailing edge of the group, since `data-date-field-input` already
detects the embedded case.
Demo: add `data-date-range-field-group` to the `.range-field-row`
wrapper. Visually now reads as one input pill: `05 / 20 / 2026 to
06 / 03 / 2026 📅`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes incidencia points D + E:
- Heading collapses the shared year when both visible months are the
same year: "May – June 2026" instead of "May 2026 – June 2026".
Cross-year still renders both ends explicitly ("June 2025 – July
2026"). Implemented in `RangeCalendarProvider.headingValue`.
- date-range-picker demo: drop the per-month title that previously
rendered BELOW each grid (duplicate of the main heading). Instead,
render one centered title per visible calendar INSIDE the header
bar between the prev/next buttons. The grid below now shows just
the weeks. A `range-header-titles` grid container splits the
available header space equally across `month-count` titles, so
each label sits centered over its calendar grid.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The calendar icon now lives INSIDE the date input as a flush trailing
affordance, not as a separate sibling button. The change is markup +
recipe CSS only — no API change.
- Demo (date-picker): drop the `.picker-control` grid wrapper; put
`<DatePicker.Trigger />` as the last child of the Input snippet,
after the segments. Codeblock snippets updated to match.
- Demo (date-range-picker): keep the dual-input + separator layout
but move `<DateRangePicker.Trigger />` into the END input, since
the trigger represents the trailing affordance of the whole range.
Both code snippets updated.
- Recipe CSS (both): add nested rules that detect the embedded case
(`[data-date-field-input] > [data-date-picker-trigger]`,
`[data-date-range-field-input] > [data-date-range-picker-trigger]`).
Embedded trigger drops its own border/background, matches the
segment height, sits flush at the trailing edge with
`margin-inline-start: auto`, and uses the segment focus ring on
hover/focus instead of the standalone field ring.
The standalone (sibling) treatment stays available for consumers who
prefer the icon as a separate control.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per the doctrine update in GUIA §13: eidos for multi-part components
exposes BOTH a flat default that auto-composes Provider/Trigger/Content
AND the compound parts for advanced cases. Soma + morfo remain the
universal composable foundation underneath.
Why: with the previous "compound only" rule for multi-part, eidos
collapsible was a pure pass-through that added nothing operational —
the consumer wrote the same import + same shape as soma. The flat
default makes eidos earn its layer by collapsing the 90% case to a
single call:
// 90% case — flat ergonomic
<Collapsible bind:open>
{#snippet trigger()}Show details{/snippet}
<p>Body content.</p>
</Collapsible>
// Advanced — compound (parts in different subtrees, multi-trigger,
// conditional content)
<header><Collapsible.Provider bind:open>
<Collapsible.Trigger>Show</Collapsible.Trigger>
</Collapsible.Provider></header>
<aside>
<Collapsible.Content>...</Collapsible.Content>
</aside>
The architectural virtue: if the flat doesn't fit, a developer drops
to soma + morfo and composes freely. No vendor lock-in in the visual
layer; eidos is the design system convention, soma is the universal
primitive.
Files:
- src/uix/eidos/components/collapsible/collapsible.svelte (NEW): flat
default that auto-composes Provider + Trigger + Content via a
`trigger` snippet for the toggle label and `children` for the body.
- src/uix/eidos/components/collapsible/types.ts: adds
`CollapsibleFlatProps = SomaCollapsibleProps & { trigger?: Snippet }`.
- src/uix/eidos/components/collapsible/index.ts: exports `default`
(flat) plus named `Provider`/`Trigger`/`Content` (compound).
- src/uix/eidos/components/collapsible/{trigger,content,provider}.svelte:
fixed children destructuring so snippets propagate cleanly to soma
(was previously spreading $props() blindly which can lose snippet
bindings in Svelte 5).
- src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md: NEW §13 "Doctrina del API:
soma compound, eidos opinionado" formalises the two-shape rule and
the architectural virtue (soma+morfo as universal foundation).
Renumbered §13/§14 → §14/§15.
- web/routes/collapsible/+page.svelte: live preview now uses the flat
form; eidos snippet shows the flat shape; soma snippet keeps the
compound shape; Notes tab updated.
- src/uix/eidos/components/collapsible/README.md: rewrote API section
with both shapes + the "if neither fits, use soma+morfo" escape.
Tests 218/218 green. npm run check baseline 39.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The collapsible demo only rendered the eidos snippet, which made the
"compound shape is the same" doctrinal point invisible: a reader
couldn't see that soma and eidos expose the same multi-part API,
differing only in import path and what each layer guarantees.
Now both code blocks stack in the Live tab:
- soma: includes the `<script>` boilerplate (`let open = $state(false)`)
to make the headless/structural-only nature explicit
- eidos: same shape, different import, no setup boilerplate (consumer
binds open directly with `onOpenChange={handleChange}` for the demo
trace)
Per doctrine §10, only single-part components (toggle/switch) get a
flat default export — multi-part keeps compound on both layers, so the
two snippets share shape. That's correct. The DIFFERENCE is the import
path, the visible boilerplate, and what each layer adds (soma:
structural contract; eidos: same surface + recipe CSS).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two real bugs reported on the collapsible demo:
1. **Dark mode broken across all migrated components.**
- The docs site layout sets `document.documentElement.dataset.theme =
'light'|'dark'` — those are the selector names the docs CSS uses.
- The eidos themes (themes/base/{light,dark}.css) only respond to
`[data-theme='base-light'|'base-dark']`. Different namespace.
- Result: docs grays swap in dark mode but eidos doctrinal palette
(primary/secondary/neutral/affirm/fulfill/risk/threat/loss) stays
in light values, so component text renders dark on dark.
- Fix: each eidos theme now accepts BOTH selectors. `[data-theme=
'base-light'], [data-theme='light'], :root { ... }` and equivalent
for dark. Existing consumers that use `base-*` keep working; the
short `light/dark` aliases handle the docs site convention.
2. **Collapsible trigger jumps from center to left when expanded.**
- The preview-stage uses `display: flex; justify-content: center;
align-items: center;` — natural for buttons/switches that don't
grow.
- `Collapsible.Provider` is `display: block` so it shrink-fits its
content as a flex item. Closed = trigger-width (centered visually);
open = trigger + content width (longer, so the visual center
anchors trigger at the left edge).
- Fix: collapsible demo overrides the preview-stage style with
`justify-content: flex-start; align-items: flex-start` and wraps
the provider in a `max-inline-size: 32rem` panel. Disclosure
ergonomics need anchor-left + grow-down, not center.
- Also swapped the body's `--gray-50` (docs site token) for
`--color-surface-raised` + `--color-content-primary` (eidos
doctrinal tokens) so the panel inherits the dark-mode swap.
npm run check holds at the 39-error baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Third component migrated to the eidos wrapper subdirectory pattern,
following the new "check air baseline first" discipline.
Air baseline recovered from commit 0a391408^:
- air/components/collapsible: 3 wrappers (Provider/Trigger/Content)
with `class` passthrough, `bind:open`, `onOpenChange`, manual
`air.interaction.play('expansion', enter|exit)` perceptual emit.
- No size, intent, color, or chevron slot.
Migration decisions (presented BEFORE coding per scope-approval rule):
- Two doctrinal events (`expand` + `collapse`, both emerge family,
sequence='pre', no intent — emerge is transitional). Replaces air's
manual `air.interaction.play` with `runtime.trigger('expand'|
'collapse')` which atravieses visual + sound + vibra channels.
- No intent/color/size — air didn't have them and disclosure is
non-evaluative per guide §3.1.
- Multi-part API: Provider + Trigger + Content (no flat default per
doctrine §10).
Files:
- src/uix/morfo/components/collapsible.ts: scope expanded to ['soma',
'sema'], events array added with two emerge events.
- src/uix/soma/components/collapsible/collapsible-provider.svelte.ts:
runtime.events handlers `expand`/`collapse` mutate state; toggle()
fires runtime.trigger(next ? 'expand' : 'collapse').
- src/uix/eidos/components/collapsible/{collapsible.css,
collapsible-provider.svelte, collapsible-trigger.svelte,
collapsible-content.svelte, types.ts, index.ts, README.md}.
- src/uix/eidos/components/collapsible.css (legacy flat) deleted.
- src/uix/eidos/index.css: import path updated to subdirectory.
- web/routes/collapsible/+page.svelte: full interactive testbed with
tabs (Live, Soma, Morfo, Sema, Eidos, Notes) mirroring switch's
structure. Sema tab shows per-channel signature cards for both
expand and collapse events.
Disciplina formalizada en CLAUDE.md hand-off + memory entry: "Check
air baseline first" before any future eidos migration. Switch shipped
without this check and lost ResponsiveProp<Size> (regression). Don't
repeat with collapsible/dialog/drawer/popover/toast/avatar.
npm run check holds at the 39-error baseline. Tests 218/218 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier switch demo Sema tab was a stub linking to the toggle docs
page. Now it ships the full per-channel signature cards (motion preview
with x8 amplification, sound spectrum bars, color affects, presence
preview), the live intent picker, and the Play button that fires on
the live switch via uix.semantic.emit. Mirrors the structure used in
toggle so the two pilots stay symmetric for future migrations.
The play-button targets data-switch (was data-toggle in the toggle
demo). The intent-picker drives the same `intent` state that the
soma-driven recipe consumes through data-color, so Play replays motion
+ sound + the data-color theme on the actual track without flipping
state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 4 (docs alignment with the canonical guide):
- active_architecture.md — Dialog example refactored to the new event
shape (target inside semantic, explicit verb + sequence). Verbs
table grouped by family with the post-canon vocabulary (select/
toggle moved to commit, edit removed from handle, new verbs added).
Section 10 (Estado actual) updated to 2026-05-08 with current
progress: doctrina cerrada, eidos en producción, SoundChannel
eager-init, switch migrated, etc. "Para profundizar" §14 points at
the canonical guide.
- morfo/README.md — Step 5.5 added covering the doctrinal event
shape with all fields (semantic.family, .verb, .target, .sequence,
.intent) and the two naming shapes ({verb}-{variant} and
{family}-{verb}).
- sema/README.md — verbs section restructured to Record-by-family
matching the runtime; documents the verbs that moved between
families per the canon. Naming shapes section updated with both
forms and validateEventName signature.
- CLAUDE.md — "Session hand-off" rewritten with the post-canon
state. Pointer at GUIA_IMPLEMENTACION_SEMAUIX.md as the canonical
source. Pending list refreshed: switch is the immediate next
component, persistence/a11ySemantic/polymorphism deferred until
real consumers appear.
Switch migration to the eidos wrapper pattern:
- morfo/components/switch.ts — adds events: [{ commit-toggle }] with
the doctrinal shape (verb=toggle, sequence=post, intent fromProp
binding to the 4 supported intents). New data-* (data-color with
the 6-token Toggle/Switch subset, data-readonly, data-invalid,
data-required) and corresponding aria-*. scope expanded to
['soma', 'sema'].
- soma/components/switch/types.ts — adds intent, color, readonly,
invalid, aria-labelledby props with full JSDoc. Subset constrained
per guide §3.1 (Toggle/Switch row).
- soma/components/switch/switch-provider.svelte.ts — runtime-direct,
Field integration via FieldProvider.get(), isDisabled/isReadonly/
isRequired/isInvalid OR-merge derivations, intent ↔ color
resolution (intent wins when evaluative), runtime.trigger(
'commit-toggle') in onclick.
- soma/components/switch/components/switch.svelte — wires the new
props through bindProps.
- eidos/components/switch/{switch.css, switch.svelte, types.ts,
index.ts, README.md} — full subdirectory pattern, mirroring
toggle. Recipe drops legacy success/warning/danger/info selectors;
declares the 6 doctrinal data-color values directly. Wrapper
auto-renders Switch.Thumb inside the track and exposes an optional
`thumb` snippet for in-thumb glyphs.
- eidos/components/switch.css (legacy flat) deleted; eidos/index.css
updated to import the subdirectory recipe.
- web/routes/switch/+page.svelte — interactive testbed with all soma
props as live controls + tabs (Live, Soma, Morfo, Sema, Eidos,
Notes). Mirrors toggle's structure.
npm run check holds at the 39-error baseline. Tests 217/217 green.
Pending:
- Migrate collapsible / dialog / drawer / popover / toast / avatar
to the eidos wrapper pattern.
- Persistence + holds-by-intent — defer until first signal.warn /
signal.alert consumer.
- a11ySemantic + polymorphic events — defer until concrete need.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper +
types + index + README) replacing the flat CSS-only form. Pattern is
documented in eidos/components/README.md and the toggle README.
Shared types live in eidos/lib/types.ts. First export is `Size` (8 values
xxs..xxl + full); components narrow with `Extract<Size, ...>` per the
per-component-subset doctrine. No `Eidos` prefix on types — module path
already conveys the layer.
API doctrine:
- soma stays compound (Toggle.Provider) for symmetry with multi-part
- eidos exports both default + Provider so single-part components
accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>`
(compound-style consumers)
SoundChannel eager-init fixes the autoplay race: AudioContext is created
+ resumed synchronously on the first user gesture (capture-phase
listener registered in the constructor), avoiding the previous race
where the first emit() scheduled the resume in a microtask outside the
gesture window.
Demo page (web/routes/toggle/+page.svelte) restructured so the live
preview renders ALWAYS above the tablist — Sema-tab Play buttons can
fire on the real toggle. Motion preview amplifies scale ×8 visually
only; doctrinal values stay in the <dl>.
Conventions 7-13 added to src/docs/sema-implementation-guide.md
covering: directory structure, wrapper composition, no Eidos prefix,
soma compound vs eidos flat, iconOnly sr-only body, sound eager-init,
docs-preview amplification.
CLAUDE.md gets a session hand-off block listing where things stand and
next concrete steps (migrate switch/collapsible/dialog/drawer/popover/
toast/avatar; wire topbar sound mute to masterGain; rename theme
tokens to drop the success/warning/danger fallback aliases).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First demos under the new web/routes location (configured by phase C
of the cleanup as `kit.files.routes`). One demo per component:
toggle, switch, collapsible, avatar, dialog, drawer, popover, toast.
Layout (web/routes/+layout.svelte):
- Bootstraps the UIX via createActiveUix({ lang: { schema: {},
defaultLocale: 'es' } }) — minimal config, full UIX surface.
- Wraps in <Uix> which sets the active-uix context and creates the
Soma scope so providers can require it.
- Imports the eidos index.css so the visual layer is applied.
- Tiny demo-only stylesheet (web/routes/layout.css) for the page
chrome (topbar, demo grid, sections) — explicitly NOT part of
eidos.
Each demo page exercises the public API:
- toggle: bound, initially-pressed, disabled
- switch: bound, disabled
- collapsible: bound show/hide, disabled trigger
- avatar: loaded, error fallback, delayed
- dialog: basic with overlay + focus trap + escape dismissal
- drawer: right / left / bottom (with handle)
- popover: click + hover trigger
- toast: 4 intents (fulfill, risk, threat, neutral) via createToaster
Verified end-to-end with scripts/demo-probe.ts (Playwright):
- All 9 routes (landing + 8 demos) serve 200.
- Zero console errors except avatar's pravatar.cc network failure
(sandbox limitation, not a real bug).
- Interactive checks: dialog open shows modal + overlay backdrop +
focused Cancel button; popover open positions floating content
next to trigger; toast Fulfill shows the green-tinted shadow
driven by data-event-intent='fulfill' from sema's visual channel
reacting to the morfo's announce event semantics — confirms the
full morfo → sema → eidos pipeline works end-to-end on a migrated
soma component.
scripts/demo-probe.ts: small Playwright runner that loads each route,
captures console errors, takes a screenshot of the static state, then
clicks the trigger on dialog/collapsible/popover/toast for a second
"open" screenshot. Output goes to g:/tmp/demos/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
svelte.config.js / vite.config.ts:
- Drop dead aliases: \$lib, \$glob, \$langs, @/ling, @/logr, @/glob,
@/actx, @/uiux, \$terra (their target dirs were removed in phase B).
- Add \$reactive at the top level (was previously hidden under the
"legacy" block; the target src/libs/reactive is current).
- Set kit.files.routes to "web/routes" — demos move out of src/ and
will be re-authored from scratch alongside the new architecture.
- vite.config.ts: extract the alias map into a single `aliases` const
shared by both Vite resolve and the server-test project, instead of
duplicating it.
web/routes/+layout.svelte + +page.svelte: minimal SvelteKit boot so
`npm run dev` and `npm run build` find a routes tree. Real demos
(active-uix, soma, morfo, sema, eidos) will be added as sibling routes
once the runtime is wired.
Verified: `npx svelte-kit sync` regenerates .svelte-kit/tsconfig.json
without any of the dead aliases. .svelte-kit/types/web/routes/ is
populated, confirming the new routes path is active.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>