Visual-only primitives translated from the legacy `air/components/layout`
on the morfo-runtime branch. Architectural adjustments for the eidos
target: drop the `air-` class prefix, target `[data-{name}]` directly,
rename CSS vars to `--{name}-*`, replace `var(--air-space-N)` with the
eidos foundation `var(--space-N)` token, resolve responsive props via
`ActiveEidos.resolve(prop)`.
- `box` — universal box-model utility (size, padding, margin, gap,
position, overflow, flex/grid item props). Every prop maps to a
CSS custom property; unset props fall through via `revert-layer`.
- `flex` / `grid` / `stack` / `group` / `wrap` — layout containers
that render through Box (single `<div>` per primitive, no
semantic-tag prop in this batch). Markers stack as
`[data-box][data-{name}]`.
- `container` — max-width centered (sm/md/lg/xl/full).
- `section` — semantic-padding block with background tone variants.
Shared helpers at `_layout/shared.ts` (formatLayoutSpace /
formatLayoutLength / formatLayoutRaw / pushStyleVar / composeStyle)
plus an optional `Layout.*` namespace barrel for consumers who prefer
that style; the individual `import { Box }` from each component dir
also works.
Each primitive ships a demo at `/uix/components/{name}/` with a live
example, a props table, and a reference comparison row (radix-themes /
chakra-ui / mantine).
svelte-check: 0 errors / 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Builds the Eidos time-range-picker layer end-to-end and brings color-picker's
morfo into parity with the other pickers.
time-range-picker
- soma: `mode` prop, `pickerShellContext` registration with
commit/cancel/clear and `valueOnOpen` snapshot for revert; Provider now
renders a div with data-state/disabled/readonly/required + aria mirrors.
- soma: new `Clock` part (mirrors TimePicker.Clock with role="dialog" and
data-state).
- morfo: scope ['soma','sema'] + apg dialog-modal; promoted Provider to
public div with states/data-state; added Clock part; declared the 6
canonical picker events (open / close-commit / close-cancel /
close-dismiss / close-dismiss-outside / commit-clear); slider parts
declare data-endpoint + data-disabled.
- eidos: full namespace (Trigger / Content / Clock / HourSlider /
MinuteSlider / SecondSlider / DayPeriodToggle + re-exported PickerShell
Footer / Clear / Cancel / Close). Sliders compose generic SliderProvider
(N-8) and ship with optional rulers above hour / below minute+second.
- eidos css: 2-column endpoint grid in the clock; per-endpoint range
tint; AM/PM toggle styling; reuses time-field tokens.
- demo: interactive testbed at /uix/components/time-range-picker mirroring
the time-picker demo — every soma + eidos prop has a live control,
closeOnRangeComplete + start/endReadonlySegments toggles, footer
composition switches, sema play buttons, soma+eidos snippets.
- nav: added entry between Time picker and Color field.
color-picker (audit fixes)
- morfo: Provider declares states ['open','closed'] + data-state attr;
added the same 6 canonical picker events targeting the `content` part
(handle-pick / handle-drag / commit-set preserved).
- soma: runtime now feeds open state + disabled/readonly/required/invalid
props to the morfo runtime so data-state actually emits.
Verified: svelte-check --threshold error → 0 errors / 0 warnings / 4524 files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces PickerShell — a soma context + eidos parts shared by every
composite picker (date, time, color, future range pickers). Aligns with
norm N-8 (reuse components across complex composites) so the
Footer/Clear/Cancel/Close behavior lives in ONE place instead of being
duplicated per picker family.
New files
---------
- src/uix/soma/components/picker-shell/
picker-shell-handle.svelte.ts → pickerShellContext +
PickerShellHandle interface
(getMode / commit / cancel / clear).
exports.ts, index.ts → public surface.
- src/uix/eidos/components/picker-shell/
picker-shell-footer.svelte → [data-picker-footer][data-mode]
picker-shell-clear.svelte → [data-picker-clear][data-action]
picker-shell-cancel.svelte → [data-picker-cancel][data-action]
picker-shell-close.svelte → [data-picker-close][data-action]
picker-shell.css → canonical action-row recipe.
index.ts, types.ts → PickerShell namespace + prop types.
- Registered in src/uix/eidos/index.css.
Color-picker now consumes the shell
-----------------------------------
- ColorPickerProvider gets `mode: 'inline' | 'modal'` opt + a
`valueOnOpen` snapshot via watch(open) so cancel() can revert.
- New `commit()` / `cancel()` / `clear()` methods with shell-picker
semantics. The previous `commit()` (fires onValueChangeEnd) was
renamed `commitChange()` since the picker-shell `commit()` means
"save + close". All internal callers (area-thumb pointerup,
swatch click, eyedropper open, channel-slider drop) updated.
- Constructor registers the handle:
`pickerShellContext.set(this.pickerShellHandle)`.
- PopoverProvider receives `modal: mode === 'modal'` so outside-click
+ Escape get blocked when modal.
- Eidos `ColorPicker.Footer / .Clear / .Cancel / .Close` re-export
the shared parts. The old `ColorPicker.Close = PopoverClose` was
renamed `ColorPicker.PopoverClose`; the canonical `Close` is now
the picker-shell save action.
Demo
----
- New `mode` chip (inline | modal).
- Footer composition switches (render footer / clear / cancel / close).
- Renders <ColorPicker.Footer> with shell parts inside <Content>.
Visual fixes
------------
- Horizontal overflow on the popover content: added `min-inline-size: 0
/ max-inline-size: 100% / box-sizing: border-box / overflow-x: hidden`
on `[data-popover-content][data-color-picker-content]` and on its
direct children. Inner ChannelInput allowed to wrap segments instead
of pushing the popover wider.
- ColorField segments inside the popover gain `flex: 0 1 auto`.
Norms
-----
- N-8 case study appended to PENDIENTES.md documenting the
PickerShell extraction alongside the channel-slider refactor.
Checks
------
- 67/67 component-audit PASS
- 40/40 morfo:check PASS
- svelte-check 0 errors / 0 warnings
Follow-up (task #81): migrate date-picker / time-picker Footer to use
PickerShell too — drop the duplicate `date-picker-footer/clear/
cancel/close.svelte` and `time-picker-footer/clear/cancel/close.svelte`
files; update CSS selectors to `[data-picker-footer/...]`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Composite popover: 2D saturation/brightness area, hue + alpha channel
sliders, preset swatches, eye-dropper, and a segmented channel input
composed from ColorField. Builds on the existing soma color-picker
(23 parts) — eidos layer is mostly thin pass-throughs adding visual
chrome.
What landed
-----------
- New `src/uix/eidos/components/color-picker/`:
- Root visual (Provider) + visual-context (size/variant/color)
- Trigger with a default swatch dot tinted from
`--cp-trigger-color` (the air pattern from morfo-runtime)
- Content (popover wrapper), Area (+ Background + Thumb),
TransparencyGrid
- ChannelSlider (+ Track + Thumb) — generic per-channel slider;
default-composes Track+Thumb when no children
- SwatchGroup + SwatchTrigger + Swatch + SwatchIndicator
- EyeDropper (Pipette icon by default)
- ChannelInput + ChannelSegment + FormatSelect (composed from
ColorField parts; same provider injected automatically)
- Label, Control, ValueSwatch, ValueText, HiddenInput
- Recipe section `color-picker` in `recipes/base.ts` (regenerated
`generated/base.css`) — trigger, content, area, channel slider,
swatches, eye-dropper tokens.
- CSS reuses color-field tokens for the segmented input portion and
adds picker-specific visuals. Transparency checker on trigger swatch
+ value swatch + alpha slider track via conic-gradient pattern.
- Morfo updates: scope `['soma','sema']`, `apg: dialog-modal`, added
`commit-set` event so audit classifies it as interactive.
- Soma exports: ColorPickerProvider class so eidos Trigger can read
the current value for the swatch tint.
- Demo at `/uix/components/color-picker` with the standard 6 tabs;
live stage exercises area drag + channel sliders + preset swatches
+ eye-dropper + format switch.
- README documents the surface, comparativa vs air/Ark/Chakra, and
flags the picker contract (mode/Footer/commit/cancel) as gap to
align with date/time-picker in a follow-up.
Checks
------
- 67/67 component-audit PASS
- 40/40 morfo:check PASS (new color-picker demo validates against
morfo DOM)
- svelte-check 0 errors / 0 warnings
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the date-field pattern (Provider visual root + Label / Input /
Segment / HiddenInput parts; ColorField also exposes FormatSelect).
Soma owns segment editing, ARIA spinbuttons, validation, format
switching and form submission; Eidos adds size (xs..xl), control
variant (surface/outline/ghost) and intent color recipes.
Adds time-field and color-field recipe sections to recipes/base.ts
(regenerated generated/base.css) and registers both routes in the
sidebar nav. apg=spinbutton declared on both morfos. READMEs follow
the Baseline / Superficie / Comparativa / Decisiones / Gaps /
Referencias template required by audit F-1.x.
All 67 component-audit components PASS, 38/38 morfo:check PASS,
svelte-check 0 errors/0 warnings.
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>
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>
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>
Translates `air/components/tooltip/tooltip.css` (small — 31 lines, only
Content + sizes styled) and adds:
- Arrow fill (matches tooltip bg)
- Per-side entrance/exit animations keyed off [data-side] (Floating
layer) and [data-state]. Distance is smaller than Popover (2px vs
4px) — tooltip is lighter visually.
- State handling for tooltip's three-value [data-state]:
'closed' → exit animation
'delayed-open' → entrance animation (hover after openDelay)
'instant-open' → no animation (keyboard focus, deliberate
invocation; the user wants it now, not after a
slide)
Demo (test/eidos/tooltip): 4-side grid + sizes (sm/md/lg).
Verification: smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Translates `air/components/popover/popover.css` (small — 67 lines, only
Content + Close styled) and adds three eidos extensions:
1. Trigger envelope ([data-popover-trigger]) — air relied on the
Button recipe via .air-button class. Eidos applies a baseline
button-style envelope here, consuming Capa 2 directly.
2. Overlay fade ([data-popover-overlay][data-state='open'/'closed'])
— for modal popovers. The morfo declares Overlay as optional;
consumer renders it when modal=true.
3. Per-side entrance/exit animations keyed off [data-side]
(top/right/bottom/left) and [data-state]. Soma's Floating layer
writes data-side based on resolved placement. The popover slides
into place from the trigger edge — fade + small translate (4px)
+ scale (0.985 → 1). Air's "revelation" pattern: ~180ms entrance
/ 120ms exit.
Demo (test/eidos/popover): basic + 4-side grid. Each side button
anchors a Popover.Content with the matching `side` prop so the
animation direction matches placement.
Verification: smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Translates `air/components/drawer/drawer.css` to `eidos/components/
drawer.css` (mechanical: --air-* → --eidos-*, drop .air-* gates) and
adds the entrance/exit animations air's recipe was missing — air drove
drawer transitions through `air.semantic.contextIn/Out` Svelte
transitions, which don't exist in the morfo-driven path. Without them
the drawer snapped in/out instantly.
Per-side keyframes:
right → translateX(100%)
left → translateX(-100%)
top → translateY(-100%)
bottom → translateY(100%)
Plus overlay fade keyed off data-state. All ~240ms entrance / 180ms
exit (moderate / normal). The Presence layer waits for
getAnimations().finished so the content stays mounted during exit.
Demo (test/eidos/drawer):
- Modal section: 4 side buttons, render Drawer.Overlay (backdrop
+ focus trap, page non-interactive while open).
- Non-modal section: 4 side buttons, omit Drawer.Overlay + pass
modal={false}. No backdrop. Page behind stays interactive. Soma's
Dismissal layer auto-adjusts (interactOutsideBehavior default
flips from 'ignore' to 'close').
The Drawer morfo doesn't declare `events` yet, so no runtime.trigger()
cadence + no exit-tinting like Dialog. Recipe + animations only.
Verification (playwright probe):
top t=140ms translateY(-372) → t=406ms 0
right t=99ms translateX(452) → t=389ms 0
bottom t=94ms translateY(372) → t=390ms 0
left t=111ms translateX(-349) → t=428ms 0
Smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the V1.5 hand-rolled motion/easing/tint/opacity/focus-ring
tokens with the air-translated equivalents already in eidos/themes/
base/_static.css and eidos/themes/base/light.css.
Substitutions applied to archetypes.css, events.css, components/
dialog.css:
--eidos-motion-fast → --eidos-duration-fast (120ms)
--eidos-motion-base → --eidos-duration-slow (320ms)
--eidos-easing-standard → --eidos-ease-default
--eidos-easing-decelerate → --eidos-ease-out
--eidos-easing-emphasized → --eidos-ease-spring
--eidos-tint-neutral → --eidos-color-neutral-element
--eidos-tint-affirm → --eidos-color-success-element
--eidos-tint-fulfill → --eidos-color-success-element
--eidos-tint-risk → --eidos-color-warning-element
--eidos-tint-threat → --eidos-color-danger-element
var(--eidos-opacity-hover) → 0.85 (single-site, archetype-internal)
var(--eidos-opacity-disabled) → 0.5 (single-site, archetype-internal)
Focus ring tokens (--eidos-focus-ring-{color,width,offset}) unchanged
— same names already exist in air's _static.css/light.css.
`affirm` and `fulfill` collapse to the same `success-element` because
air's intent vocabulary doesn't distinguish them. The morfo's
data-intent attr still preserves the distinction at the structural
layer; the visual mapping is just the same color.
Updated scripts/check-eidos-recipes.ts to navigate to the per-component
demo URLs (/test/eidos/{toggle,switch,dialog}) since the index page
no longer renders the recipes directly.
Verification: smoke 68/68, morfo-check 66/66. Playwright entrance/exit
curves intact (opacity interpolates over the configured durations).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Translates the foundation of air's visual system into eidos:
- eidos/contracts/ public token interfaces (4 root + 64 components)
- eidos/tokens/ per-component token values (62 components + motion)
- eidos/themes/base/ light + dark + _static + motion (4 files)
Mechanical translation: `--air-*` → `--eidos-*` everywhere, relative
imports preserved. The CSS is currently NOT wired into the legacy air/
folder, which stays for reference until phase 4 cleanup.
The `--eidos-*` prefix is documented in eidos/index.css with the two
reasons it earns its keep:
1. Authorship clarity at debug time (DevTools can identify the layer)
2. Override discipline (consumers know they're targeting public API)
Phase 1 only adds the foundation without touching component recipes.
The hand-rolled recipes (toast, dialog, toggle, switch, collapsible)
and the legacy hand-rolled tokens.css continue to load alongside the
new tree. Phase 2 will replace recipes with air-translated equivalents
and reconcile the duplicate motion/tint tokens.
Architectural note: air targeted terra and gated its CSS with
`.air-{component}` class on a wrapper. Eidos targets soma+morfo and
applies universally to the data-attrs the morfo declares — no class
gates, no Svelte wrappers. The morfo layer is the one that decides
which `data-*` attrs exist, which is the substantive shift between
the two systems.
Verification: smoke 68/68, morfo-check 66/66, focus rings + dialog
entrance/exit still working per Playwright check (curve unchanged).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Second per-component recipe. Composes archetype interactive defaults
(trigger, overlay, close, content, focus-visible) with the visual
envelope (button shape, modal card, backdrop) and the asymmetric exit
tinting from events.css.
The recipe consumes `--_eidos-exit-tint` via a top accent stripe on
[data-dialog-content], so close-save tints fulfill, close-after-fail
tints threat, close-cancel/dismiss/dismissed-outside tint neutral. The
private var stays inside eidos — recipes consume it; consumers don't.
events.css extends the data-last-action coverage to dismissed and
dismissed-outside (both map to neutral tint, like cancelled).
Public CSS API: --eidos-dialog-{overlay-bg, overlay-z, content-bg,
content-radius, content-padding, content-shadow, content-max-width,
content-z, button-bg, button-bg-hover, button-border, button-border-hover}.
Default z-indices (overlay 50 / content 51) are sensible defaults the
integrator can override via tokens — Dialog without z-index would be
useless out of the box.
Test page (/test/eidos) gets a Dialog section so the recipe is visible
end-to-end.
Verification: smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First per-component recipe in eidos/components/. Composes tokens +
archetypes + intent palette into a finished Toast visual:
- viewport stack layout (no positioning — integrator's call)
- card with intent-tinted left border (reuses --eidos-tint-*)
- absolute close button so layout doesn't depend on a header wrapper
- typography for title / description, secondary-button style for action
- swipe state (data-swipe='move'/'cancel') translates the card
- loading state adds an accent stripe via inset shadow
Toast.Item carries archetype='item' which sets cursor:pointer + user-
select:none in archetypes.css; the recipe overrides those because a
notification is passive, not selectable. Possible future morfo decision:
introduce a 'card' or 'notification' archetype.
The eidos test page drops its :global([data-toast-*]) scoped CSS — the
recipe now suffices. Stale '~13 ms / 1 rAF' text replaced with the
correct per-family hold (600ms for alert).
Wired via @import './components/toast.css' from eidos/index.css.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First eidos code. Closes the cross-layer cycle that's been documented but
unproven: an actual CSS layer that consumes the morfo + sema contracts
and applies styles transversally without enumerating components.
src/uix/eidos/
- tokens.css — motion durations (quick/fast/base) + easings (standard /
emphasized / decelerate) + opacity scale (hover/disabled/faded) +
intent tints (neutral/affirm/fulfill/risk/threat) + focus ring tokens.
~30 lines. No theming dimension yet — when it arrives, these are the
base theme.
- archetypes.css — transversal selectors keyed by `data-archetype`.
Covers trigger, overlay, content, indicator, thumb, track, close,
action, item, option. Plus a universal `:focus-visible` ring rule that
applies to every archetype-classified element. Single attribute
selector specificity — components can override freely.
- events.css — reactions to perceptual signals during their ~13ms life.
`[data-event^="dismiss"]` fades out, `[data-event^="announce"]` pulses
with intent-tinted box-shadow, `[data-last-action]` tints exit
animation by causa.
- index.css — entrypoint that imports the above in cascade order.
Consumer: `import '$uix/eidos/index.css'`.
src/routes/test/eidos/+page.svelte (demo)
- Sets up Soma + App context inline (independent of /test/soma layout).
- Imports `$uix/eidos/index.css` and demonstrates the cross-layer story:
- Toggle + Switch buttons share trigger styles via `[data-archetype="trigger"]`
without per-component CSS.
- Collapsible expands/collapses with state-driven attrs.
- Toast intent buttons fire `data-event="announce" data-intent="risk|threat|affirm"`,
which eidos's events.css briefly tints via box-shadow during the signal window.
- Component-specific layout (positioning, switch thumb transform) lives in
the page's `<style>` block — eidos provides only the cross-component
baseline, NOT per-component recipes.
What this proves
- The runtime emits `data-archetype` correctly in browser (not just in
unit tests).
- A single CSS rule can target ALL triggers across all components
without enumeration. Add a new component with `archetype: 'trigger'`
and it inherits the styling automatically.
- Sema's `data-event*` signals live in the DOM long enough for CSS to
react (the ~13ms window measured by `scripts/probe-toast-dismiss.ts`
is observable as a brief box-shadow flash on toasts).
- The cross-layer cycle Morfo declara → Soma transcribe → Sema emite →
Dom aplica → Eidos lee runs end-to-end.
What V1 deliberately excludes
- Theming dimension (no `[data-theme]` overrides yet).
- Per-component recipes (each component will get its own CSS later).
- Keyframes / @starting-style / animation primitives.
- Sound / vibra (those are separate engines that subscribe to sema).
Verification
- /test/eidos returns 200; manual browser check shows triggers share
hover dim + focus ring across components, toasts flash intent tint on
appear.
- 68/68 smoke routes (smoke only scans /test/soma — eidos route is
outside that scope, manually verified).
- svelte-check unchanged at 155 (pure CSS additions).