Phase 1c of PLAN-docs-reconciliation (fable-eidos-audit D1/D3/D4, B6/B7).
- THEMING s1 vs s2 ('5 layers' vs '6 capas'): s2 owns the layer story,
retitled count-free and rewritten against the REAL index.css imports
(generated incl. font-faces / archetypes / events / aggregated recipes;
code-split recipes + shared partials + runtime theme blocks noted);
s1 links s2. TOC updated.
- THEMING s3 capa 3 + s6 rule 7: hardcoded slot lists (13 and 9 — both
wrong, code has 12 after border-hover removal) replaced by pointers to
COLOR_ROLE_SLOTS + DEFAULT_COLOR_ROLE_SLOT_STEPS.
- THEMING s4: physical-scale mapping table (primary=indigo, risk=amber —
both stale) replaced by pointer to THEME_BASE_COLOR_ROLES with the
convention-vs-authorship note (CANONICAL_INTENT_SCALES != base theme).
- eidos/README: 'Cambios 2026-05-21' + 'Estado actual (2026-05-17)'
(frozen wrapper list ~20 vs 137 real) extracted to
docs/process/handoffs-2026-05.md; pointer left.
- eidos/components/README: frozen migration table (2026-05-20) extracted
likewise; live inventory = components tree + component:audit.
- COMPONENT_AUDIT_GUIDE: stale v1 s12.7/s12.8/s12.9 citations re-pointed
at DEMO_AUTHORING_GUIDE v2 s6 / the eidos composition doctrine.
- eidos-motion s15: title contradicted its own body (the firma moved to
EidosConfig.motion signatures in F2) — retitled, TOC synced.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fable audit follow-through (fable_audit.md + fable-eidos-audit.md):
- RECIPE_CONTRACT.md (E2 canon): the transversal systems every recipe must
consume, enforced by component-audit R-4.1-4.6 (all at error; escape
valves /* literal */ + /* functional */; WIP tracks excluded). Stale
audit rules fixed against the current architecture (E-2.2 wrapper
imports, D-1.2 v2 9-tab union, D-3.1 single snippet, TabsVariant
mirror) - verdicts went 0/117/15 -> 75/50/5.
- Motion migration 15/15: recipes off local @keyframes onto the channel -
preset stamps (dropdown/context/select/combobox/tooltip/link-preview/
clipboard), new expand/collapse + value-flash signatures, shared-axis
reverse pair, delayed-open open-alias in the preset trigger
(PRESET_STATE_ALIASES), materials pattern for irreducible triggers
(card/timeline/tabs/nav-menu/metrics). Duration/scale hooks keep every
recipe's tuned values.
- buildScheme (fase C): full a1..a12 alpha ramp per role (was a2/a3 -
stale alphas after applyColorScheme), intentSeeds so temper starts from
the ACTIVE theme's intent mapping (risk stays orange), alpha background
self-derived from the scheme's own neutral step 1 (was hardcoded
#fff/#111); mode now forces the donor variant.
- Inventory decisions (fase D): semanticTracking axis removed (all-zero),
border-hover slot dropped (0 consumers), separator slot adopted across
line dividers (step 6, Radix divider tone), size-bundle consumption
pilot on toggle (canonical coordinates consumed, deliberate deviations
kept visible).
- Docs: building-a-component.md (the one door, 9-phase route + known
traps), PLAN-docs-reconciliation.md (fase 6 kickoff for a fresh
session), THEMING wiring updates.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Palette:
- Add `fuchsia` (~H334) and `steel` as the 32nd/33rd named scales; rename
radix-scales.ts -> color-scales.ts and drop the "Radix parity" framing
(seeded from Radix, but the palette is ours).
- generatePalette: per-family nearest-hue donor (L-per-hue) so bright hues
(yellow/amber/lime) stay vivid instead of landing muddy.
Theme builder (web/routes/temas):
- estudio: new "palette-first" color mode — character (vivacity/tone/neutral)
regenerates the 33 families; roles are selected by hue harmony or manually
and applied live to real components via --primitive-* overrides.
- paleta: palette generator + role harmony chooser (the model, visualized).
Color guards (THEMING.md §25.2/§25.4):
- G1: completeColorRoleMap throws when a hierarchy alias and a valenced intent
share a scale; the builder picker excludes intent-occupied scales.
- G2: re-expose role slots bg2*2 / separator*6 / borderHover*8 / textStrong*12
(13 slots) — real UI/a11y needs the original nine had dropped.
- G3: document the CVD / WCAG 1.4.1 rule (an intent needs a non-color cue) plus
the affirm/fulfill activation distinction; audit-grounded.
- G4: palette-invariant.test.ts checks the delta-E floor on every generator's
OUTPUT (multi-step, monochrome-exempt); clamp builder tone to +0.08.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Widens `avoidCollisions` from `boolean` to `boolean | 'flip'` on the 6 overlays
that expose it (combobox, context-menu, dropdown-menu, link-preview, select,
tooltip) — the FloatingContent engine already decoupled flip from shift, so
'flip' (flip-only, no slide) works everywhere and lets an arrowless, non-shift
overlay take the native CSS-anchor path. Additive: `boolean` is unchanged.
- Public prop types + provider opts widened (types.ts + *-provider). JSDoc on
the 3 descriptive ones (combobox/select/tooltip) documents the 3 modes; the
terse `@default true` menu types keep their inline style (the type shows 'flip').
- Demos: the 4 that had an avoidCollisions checkbox (dropdown-menu, context-menu,
link-preview, tooltip) now expose a 3-way full/flip/off control.
Verified in a real foreground browser (Claude-in-Chrome):
- dropdown-menu + avoidCollisions='flip' + no arrow → routes to NATIVE
(data-floating-native, position-anchor link to trigger, zero JS transform,
placed correctly).
- context-menu correctly STAYS on JS with 'flip' — it anchors to the cursor
(a Measurable), which the dispatcher's virtualAnchor gate forces to JS. The
discriminator works as designed.
- tooltip / link-preview 3-way controls render.
0 new check errors; 41 overlay + floating tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two parts: the cutover (own JS engine replaces @floating-ui as the runtime, dep
out of the library) and the native CSS Anchor Positioning path layered on top.
Cutover (P4, reconciled):
- Flip USE_OWN_ENGINE -> true, then delete it (flag.ts removed). Collapse the A/B
in use-floating + floating.svelte to own-only (drop the fui imports, the factory
shim, the `as unknown as` casts). useFloating's `dom` is now required.
- @floating-ui moved dependencies -> devDependencies: the LIBRARY (src/arts/ethereal
+ src/uix/soma) imports it NOWHERE; it stays only for the visual demo + the parity
oracle. Zero-dep doctrine met for consumers.
Dual engine (P2 — built + proven end-to-end):
- `selectPositioningStrategy` ($ethereal/strategy.ts, 5 tests) picks native vs JS
per instance on discriminators (arrow / shift / virtual-anchor / explicit-boundary;
sticky is subsumed by shift). The behavioural twin of supportsCssAnchor.
- eidos `renderFloatingNativeBlock`: @supports (anchor-name) and (position-area)
with position-area from data-side/align (logical axes — validated against the spec),
per-side gap margin, position-try-fallbacks for flip. base.css regenerated.
- soma wrapper: when native, stamps data-floating-native + an inline anchor-name<->
position-anchor link and PAUSES the JS positioner (no computePosition, no autoUpdate).
- New `avoidCollisions: 'flip'` (flip-only, no slide) decouples flip from shift so an
overlay can flip natively. Popover demo: avoidCollisions = full / flip / off.
- Proven in a real browser: a flip-only arrowless popover routes to native, positions
via position-area, and flips natively (flip-block) at an edge — zero JS transform.
Honest scope: shift has no native primitive, so native lights up only for non-shift
overlays; the JS engine stays the default for collision-avoiding ones. data-side
doesn't track a native flip, so native is gated to the arrowless case.
Verified: 537 tests green (ethereal + 7 overlays + floating + generated-css), 0 new
check errors. Docs: CONTINUE.md, PERF.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
24h picker driven by a horizontal daylight gradient — drag the sun/moon
thumb across the night→day→night sky, jump with moment chips, or nudge
with hour/minute/second steppers. Inline panel + trigger/popover variants,
built on the generic Picker (deferValue) composing Slider, Button,
PickerShell, Popover and Tooltip.
- granularity (hour|minute|second) + optional band props; steppers keep a
fixed 3-column grid; shape/size propagate to panel, trigger and chips.
- fixed-tone sky palette as named recipe tokens (theme-independent).
- fix soma Tooltip.Arrow (empty children slot hid the default SVG) and
make PickerShell footer labels locale-reactive.
The in-house positioning engine (relocated to its own art in 58683b03) is
renamed floating -> ethereal: src/arts/floating -> src/arts/ethereal, alias
$floating -> $ethereal (vite + svelte config), all soma wrappers + the demo
repointed. The reactive wrappers stay in soma/layers/floating (soma's
floating-overlay concept); the *library* is ethereal.
Also lands the cross-browser validation + perf characterization the rename
batch was verified against:
- engine-dom parity suite runs on Chromium + WebKit + Firefox via
UIX_CROSS_BROWSER=1 (env-gated in vite.config; default chromium). 411 runs
green — exercises the isWebKit-gated paths Chromium never ran.
- PERF.md: honest write-up. Bundle measured (own 6.1 KB vs @floating-ui
8.2 KB gzip -> ~2 KB net win). Speed numbers labelled CPU-work, NOT
perceived wall-clock; the +1-frame latency documented as a real cost.
- web/routes/demos/ethereal: visual A/B demo vs @floating-ui.
@floating-ui untouched — flag still OFF, P4 not executed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New value-type-agnostic Picker — the shared transactional core that will
later replace the five system pickers' duplicated coordinators (date /
color / time / range), parameterised by TValue.
- morfo/components/picker.ts — provider part only; open/close delegated to
the composed Popover (no dialog semantics, no events).
- soma/components/picker — PickerProvider<TValue> + root wrapper (composes
Popover). Exposes workingValue (draft-or-bound), commit/cancel/clear and a
PickerShellHandle for the shared footer.
- web/routes/uix/components/picker — interactive deferValue testbed.
Unified transaction (the deferValue axis):
- deferValue=false: workingValue IS the bound value (live); cancel() reverts
to the open-edge snapshot.
- deferValue=true: workingValue routes to an internal draft; the bound value
only updates on commit() (Accept); any close that is not an explicit accept
discards. The form / onValueChange only ever see accepted values.
Verified: npm run check 0 new errors. Browser — deferred edit/accept/cancel/
dismiss-discard + live edit + live cancel-revert all correct, no console errors.
Not committed (shared files carry a parallel session's WIP): the soma/components
barrel export + the docs sidebar entry. Demo imports the picker via subpath so
the commit is self-contained.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- floating: read the resolved content z-index via `dom.measure` instead of a raw
`requestFrame` + manual `cancelFrame` — coalesced post-layout, disposer returned
directly. The canonical `dom.measure` adopter.
- color-picker demo: resolve preset-swatch token colours via `eidos.resolveTokens`
(config + `$color`, pure JS) instead of the `getComputedStyle(probe)` round-trip
that forced the reflow. Restores the Clear footer button (`{#if showClear}`,
was a stray `{#if false}` debug edit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- New <ColorSwatch> eidos primitive: transparency-checker base + colour
overlay, opt-in inset ring, inherits the container corner-shape.
- ColorPicker presets tab + saved colours: `presets` prop, bindable
`savedColors`, ColorPicker.Presets / SavedSwatches / SaveAction parts
(canonical hsvToHex comparison; save/delete component-owned).
- Readonly: the trigger no longer opens the popover when readonly — gated
via a PopoverTrigger `disabled` opt, rolled out across the color / date /
date-range / time / time-range pickers.
- ColorField swatch respects its container (no bg/border, inherits shape via
corner-shape); picker trigger swatch matches the button shape.
- Recipe: popover content-width tokens.
Presets tab reorganized into 4 labeled groups: Sistema (3 roles) + Intent
(6, incl. neutral) side by side, Paleta (31 scales) + Guardados (savedColors)
full width. Role/scale tokens resolved to rgb() via a color-mix probe so the
SwatchTrigger can commit on click. Labels follow the field canon (one step
below the input, regular weight). Preset swatches drop the recipe checker +
border (opaque). Bottom padding equalizes the two tabs' height.
A "Guardar color" button in the footer (ghost/neutral, matching Borrar/
Cancelar) saves the current value to Guardados: enabled only when a color is
selected, not already saved, and under the 10-color cap.
KNOWN ISSUE: the footer overflows with 4 buttons (Listo clipped) -- the save
button needs to become icon-only. See color-picker/CONTINUE.md.
dropdown/context/select demos passed an explicit sideOffset that BYPASSED the
--floating-gap canon (showed 0/4/6 instead of the canonical --space-1). Default
to undefined so the canon (which now reaches portaled menus via the
FloatingContent $derived fix) applies; the sideOffset slider still overrides.
Verified live: menubar + dropdown show the 4px canonical gap on real interaction
(the canon-read $derived returns 4 → Floating UI offset 4).
Demo (web/routes/uix/components/color-picker): tabbed layout
(Seleccion | Presets) as a flat segmented band, a tools row (outline
eyedropper + two 12px sliders inset by the thumb diameter), and the real
<ColorField> as the value input (size-synced, value swatch, system
format-select). Icons sized canonically via --icon-size-{size}.
Framework:
- color-picker.css: popover viewport scrollbar-gutter stable->auto
(tabbed content fits; the reserved gutter only skewed the panel right).
- ChannelInput/ChannelSegment gain visual parity with <ColorField>
([data-color-field] root context + data-field-segment marker + state CSS).
Presets 4-group split (Sistema/Intent/Paleta/Guardados) + eyedropper
button-size decision pending; see color-picker/CONTINUE.md.
CardGroup rebuilt as an eidos-only composition shell — selection delegated to
ToggleGroup, disclosure to Collapsible, item = card via structural identity
(data-card), title = system Button. Eidos owns only grid / depth / concentric /
cascade / size propagation. The bespoke card-group morfo + soma were removed.
ToggleGroup now owns the canonical selection cardinality: one knob
enabledSelections (number | [min, max]) + whenFull + a commit-block event on
rejection, consuming $libs/selection. Backward-compatible: type / deselectable
derive to the limit (tests 5/5).
Replace the v1 demo template (which only exposed variant/size/role) with a
9-tab layout that surfaces the whole system: the full 31-scale palette on the
real `color` prop, border/shape/radius, the foundation axes
(density/scaling/mode/dir/border), the motion catalog, and the sema firma with
audible sound.
- Button `color` accepts the 31 palette scales as a REAL prop (THEMING §25.5,
the Radix `color="teal"` per-instance override), gated by the intent↔color
doctrine. PALETTE_SCALES/PaletteScale/ComponentColor/HierarchyColorRole in
eidos lib/types; soma `color` widened (layer-pure); eidos `color?:
ComponentColor`; recipe `palette-*` extended with the 31-scale cascade via
paletteScaleDecls/paletteContrastDecls (graceful fallback to primary so apps
without generated/palette.css degrade, not break). Pilot of
pendiente_color_demos.md; base.css regenerated.
- Shared harness in web/routes/uix/lib/: harness.svelte.ts (constants +
DemoTrace + signatureFor), PalettePicker, SystemAxes, MotionPanel, SemaPanel
+ uix.css primitives (swatches/firma/motion).
- Button canary rewritten to the 9-tab layout; DEMO_AUTHORING_GUIDE rewritten.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gradient axis — Phase 1 (engine + dogfood):
- $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and
zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops
reference color ROLES → a --gradient-{name} re-tints with the seed and flips
light/dark for free. Default `in oklch` interpolation.
- build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) +
ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme).
- shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12
hardcoded gradients with applyGradients role tokens.
Opacity = function of elevation (depth cue `translucency`):
- New translucency depth-plane cue, sibling of shadow/blur: frost opacity now
scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80)
via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated.
GradientBuilder component — Phase 2 (scaffold, WIP):
- morfo (contract) + soma provider state machine over the Gradient model: stops
add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard-
accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land.
Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table.
Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index
ladder; aurora/title → role-derived tokens.
contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors.
check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All 5 pickers (date / date-range / time / time-range / color) modelled
themselves as standalone APG dialogs while ALSO composing a Popover that
already provides the dialog — producing nested role="dialog" (the
calendar/clock was a role=dialog child of the popover-content dialog).
Reconcile per soma SOMA_ARCHITECTURE.md §2 (the Popover IS the authoritative
dialog): drop apg:dialog-modal, focus.trap, the content part's role:'dialog',
the trigger's redundant aria-controls, and the own open/close events from each
morfo; providers stop firing their own close (commit/cancel just toggle open).
time-picker/time-range-picker Clock made morfo-driven (renderProps).
Also lands this session's picker UX:
- composable <DatePicker.Time /> / <DateRangePicker.Time /> + shared
picker-time helpers/row
- TimeRangePicker: canonical column gap + vertical divider, slider sizing,
AM/PM ToggleGroup, granularity-aware sliders
Verified: all 5 pickers show a single dialog in the browser; morfo:vocabulary
clean; svelte-check 0 new picker errors; provider tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Constant-resistance circles + constant-reactance arcs (clipped to the unit
disk), with normalized impedance points mapped through Γ = (z − 1)/(z + 1)
and a z + Γ hover tooltip. The matched load (z = 1) sits at the centre.
Configurable grid (resistanceCircles / reactanceArcs), labels and fade.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New eidos-native Metrics component (morfo scope sema+eidos, soma runtime
for the live signal). Parts: featured Icon, Label, Value, Delta (direction
⟂ valence via goodTrend), Trend row (delta + caption inline), Chart
(composes Sparkline), Progress + Gauge (compose Meter), Actions, Description.
Opt-in `live` turns a value change into `signal-notify-update` — flashes the
value, pings sema, announces the new value.
Reference-grade hierarchy: the value is rebased ~2 steps UP the canonical
type scale (md→font-xl, lg→font-xxl) so the small-label → big-number jump
reads as a KPI (the 1:1 mapping collapsed it). Tight rhythm (label→value 8px,
value→trend 14px); featured icon = fixed 48px soft rounded-square.
Demo at /uix/components/metrics: Live (interactive) + Gallery (14 real
compositions, outline cards).
Also: fix Skeleton light-mode visibility (neutral-track is ~white in light,
so the placeholder vanished on a white surface). Defer exposing the full
31-scale palette in component `color` + demos to pendiente_color_demos.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Heatmap is now one component, two modes by data shape: matrix {x,y,value}
(columns × rows) and calendar {date,value} (GitHub-style weeks × weekdays,
with granularity year/month, orientation, locale-aware week start, fade).
New standalone charts: Gauge, Funnel (multicolor + labelPosition), Bubble
(size-encoded, axis-padded), PolarArea (Nightingale, padAngle gap between
sectors), CalendarHeatmap (internal renderer behind Heatmap).
Funnel/Bubble/Heatmap/PolarArea gain hover tooltips + dim. CalendarHeatmap
week start + month/weekday labels follow the uix langs locale reactively.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stacking: a `stack` prop (boolean | 'expand') on the bar + area marks, reusing
$libs/plots `shape-stack`; the frame sums the stacked series for the y-domain.
Entrance animations via an `animate` prop (content domain, reduced-motion-free);
each mark plays its idiomatic motion:
- bars grow from the baseline (new `grow` preset), staggered
- lines draw themselves (new `draw` preset: stroke-dashoffset + pathLength=1)
- area grows up from the baseline (`grow`)
- BarList/BarSegment grow from the left (new `grow-x` preset), staggered
- Pie slices + Radar polygons grow RADIALLY from the chart centre (new `bloom`
preset: scale 0->1 + fade, transform-box: view-box), staggered — so each pie
slice grows out and every radar vertex travels from the origin to its place
- scatter + the area's line edge fade/draw; Sparkline inherits
Four reusable, type-safe, reduced-motion-aware presets added to the eidos motion
engine (grow / draw / grow-x / bloom) + the F7 typegen test. `animate` controls
in every chart demo. Roadmap + MUI X comparison in components/chart/IMPROVEMENTS.md.
Note: generated/base.css is regenerated; it carries one inert `--metrics-*` token
from the metrics recipe that co-lives in the shared generated file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Month/year choosers replace the native `<select>` with a NON-MODAL popover whose
content is the reused `MonthGrid` / `YearGrid`. Stays non-modal so it also works
opened inside the date-picker's own popover (no nested modal trap). The viewed
month/year is highlighted with the selected style; `locale` is threaded and the
month is capitalized so es ("ene") and en ("Jan") read consistently. `YearGrid`
gains a `pageAligned` opt so the chooser can start on the selected year.
- Prev / Next compose the system `<IconButton>` (chevron glyph from the primitive);
the month / year triggers compose the system `<Button>` via the Popover.Trigger
`child` snippet. The bespoke border / bg / hover / focus / disabled chrome is
removed from `calendar.css` — it all comes from the primitive now; the trigger
label tracks the picker font (16px).
- The day grid is centered under the slightly wider header.
- The date-picker reuses these calendar parts, so it inherits all of the above.
WIP (parked, not composed anywhere): the soma `CalendarProvider` also carries an
unused `view` state + `Calendar.MonthView` / `YearView` / `HeadingTrigger` from an
in-place view-switch attempt. The popover approach above is the active one.
Excludes words/palabras/chronos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Net-new data-visualisation family on the in-house, zero-dependency $libs/plots
engine (scales · ticks · curves · shapes, all original). Compositional <Chart>
frame (context-shared scales, responsive, cursor-following tooltip) + primitives
Grid/XAxis/YAxis/Line/Area/Bar/Scatter/Tooltip/Legend + presets Line/Area/Bar/
Scatter/Sparkline (line + bars) + standalone Pie/Radar/BarList/BarSegment, each
with a themed hover tooltip. Themed via --chart-* recipe tokens + global colour
tokens; locale via eidos.format/langs; morfo `chart` (scope: eidos, role=img).
Point scales span edge-to-edge. Per-chart demos under web/routes/uix/components.
Roadmap + MUI X Charts comparison in components/chart/IMPROVEMENTS.md.
Note: recipes/base.ts + generated/base.css are shared files; this commit
unavoidably carries the (inert) metrics recipe block that co-lives there.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- MenuDial: a FAB trigger that fans a line OR a placement-driven arc of
mini-FAB actions. 9 zones mirror OnionMenu's onionArc (90 corner / 180
edge / 360 centre); radius auto-scales so the actions never overlap.
- Each action's NAME composes <Tooltip> (Floating-UI positioned, portaled,
collision-corrected) — tooltip on hover/focus, inline = forced open. No
hand-rolled label.
- Hover-open close delay (cross-gap); action size adapts one FAB step below
the trigger.
- Completes the FAB `xs` (30px) mini step: fab.css [data-fab-size='xs']
cascade + FabSize union (the recipe tokens already shipped with timeline).
- morfo + langs + soma (linear nav maths + runtime) + eidos + demo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Unifies the history/feed pattern (per-item status + evaluative intent) with the
progress/stepper pattern (a root `active` index) in one component. Two
independent color axes: status drives the marker treatment (filled / ring /
hollow); intent overrides the hue via the canonical data-intent channel and
feeds the `emerge-reveal` event in live feeds.
- morfo: <ol>/<li> contract — data-status, data-intent, aria-current; one
`emerge-reveal` event (fromProp intent); scope soma/sema/eidos.
- soma: Provider + Item — status derivation (active|explicit), auto-index,
live-feed emission gated to appends. 4 unit tests.
- sema: emerge-reveal pack (soften gain via op:add + haptic tick).
- eidos: rail · markers · connectors; vertical/horizontal + start/end/alternate;
independent `size` (type) and `markerSize` (circle) axes; connector
solid/dashed/dotted; live-feed reveal. Markers hold icons or numbers.
- demo: web/routes/uix/components/timeline (6-tab interactive testbed) + nav.
Connector runs behind the markers (continuous rail); horizontal entries stack
via flex; horizontal-alternate pins fixed-height rows for a straight rail.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ship the four action-cluster components. Per the audit the user requested, the
hardcoded magic numbers in their CSS move to recipes (base.ts), so the values
live in one place and pass the recipe-CSS contract:
- fab: the off-ladder Material size / icon / lift scale → `fab` recipe (named
tokens — the canonical control-height / icon-size ladders genuinely can't
express 40/56/72 + 20/24/28; documented as the FAB's own scale).
- onion-menu: the radial-chrome magics (sector ring widths, glyph bar, trail
opacities) → `onion-menu` recipe; the centre trigger's lift + shadow
REFERENCE `--fab-*` (it IS a FAB — the two can't drift apart).
- button-group: the segmented-divider strength → `button-group` recipe.
- split-button: the slim chevron tail's inline padding → `split-button` recipe;
DropdownMenu gains `customAnchor` so the menu spans the whole split surface.
All four compose the framework primitives (Group / Button / Icon / DropdownMenu),
declare a morfo, ship langs + a demo, and pass the recipe-CSS contract test (the
only flagged component left is palabras, an active dev track). check 0;
onion-menu engine tests 31/31.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Non-modal menus:
- DropdownMenu + ContextMenu default to trapFocus=false + preventScroll=false
(were modal — hard focus-trap + body scroll-lock that made them feel stuck).
Menus stay navigable while open (Chakra-style); the props remain for an opt-in
modal menu. morfo focus.trap=false + the demo control defaults aligned.
list-surface — ONE shared size source for list surfaces:
- new lib/list-surface.css (--list-* keyed on [data-list-surface][data-size], 1:1
with the control sizes) + lib/list-surface-context.ts (shared size context for
portaled sub-panels). DropdownMenu/ContextMenu/Menubar/Listbox bridge their
--{c}-* size tokens to --list-*; ContextMenu gained the size axis it lacked.
Row-height doctrine — unified to CONTROL-HEIGHT:
- archetypes.css [data-archetype=item/option] gains a fallback-guarded control-
height FLOOR (min-block-size: var(--list-item-height, auto) + padding-block:
var(--list-item-py, var(--space-1-5))) — ZERO regression off a list-surface
(the ~45 non-list archetype consumers are untouched). Select/Combobox/Command
stamp data-list-surface so their option rows inherit the floor, matching menu
items. Verified: select option md 36px / lg 44px = menu items; bare archetype
item unchanged (0 floor, --space-1-5 padding).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A `/demos/motion` page showcasing the content domain:
- Loops (spin/pulse/ping/bounce) on clean SVG via `motionAttrs`.
- Enter → loop → exit lifecycle on a pigeon SVG adapted from the `/demos` paloma
(the page has no ActiveEidos provider, so it loads the static eidos CSS and
replicates `<Motion>`'s exit inline).
- Staggered list: container-driven cascade IN (first item first) and OUT in reverse
(last leaves first), retained then unmounted — the §D.13.2 bridge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- `motion` prop: the Card is the state-domain pilot. Becoming `selected` plays the
preset once via `data-motion-state` (mount-guarded — animates on the transition,
not on mount/deselect). `card-emerge` yields to the motion system when `motion`
is set (otherwise toggling `data-motion-state` re-triggers the mount fade).
- Nested-interactive guard: clicking a control inside an interactive Card (a Footer
button, a link) must not select the Card. `handleActivate`/`handleKeydown` bail
when the event originates from an interactive descendant, and the `:active`
press-scale is suppressed via `:not(:has(button:active, …))` — the press cascade
reaches the ancestor `<button>`, which `stopPropagation` can't touch.
- Demo: `motion` chip control on the live Card.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a `motion` chip-picker (slide-fade · scale-fade · fade · spring-pop,
default spring-pop) on the live `Popover.Content`, plus the prop in the
generated code snippet. Demonstrates the spring engine tier woken in
4878ccfb running live on an overlay: selecting `spring-pop` stamps
`data-animation-style="spring-pop"`, which Presence resolves to the
registered JS `spring` driver and runs via `motion.run`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ergonomic companion to `motionAttrs`: wrap any content to animate it IN and
OUT. `<Motion motion="scale-fade">…</Motion>` plays the preset's enter on mount
and its exit on removal (Svelte `out:` flips to `data-state='closed'` + retains
the node for the eidos-declared duration, like `<Cascade.Item>`). Rides the
existing state-presets — no new mechanism, no JS engine.
- morfo `motion.ts` (scope ['eidos']): one Provider part declaring `data-state`.
- eidos `motion/`: component (spreads `motionAttrs` + `out:` retention) + types +
index + README (incl. the `<Motion>` vs `motionAttrs` table + the content-domain
boundary).
- badge demo: a `<Motion>` show/hide example next to the prop showcase.
Caught + fixed a real collision: `data-motion` is already the reduced-motion prefs
attr (`[data-motion='reduce']`), so the wrapper emits NO part-presence marker (a
prefs `closest('[data-motion]')` would have matched it) — only the functional
`data-animation-style` + `data-state` from `motionAttrs`.
Verified at runtime: enter (data-state=open → `scale-in, fade-in`, no data-motion)
and exit (Hide → retained with data-state=closed → `scale-out, fade-out`). `check`
clean for the morfo + component + demo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First real consumer of `motionAttrs`: `<Badge>` gains a `motion` prop so a
designer can apply any registered preset as the badge's entrance — `<Badge
motion="scale-fade">` plays the preset's enter on mount, zero per-component
machinery (the component just spreads `{...motionAttrs(motion)}`).
Badge is the clean content-domain pilot: pure eidos primitive, no morfo events,
no own `data-state` (so the helper's presentation `data-state="open"` doesn't
clash — unlike Card, which has selected/idle + a built-in emerge). Additive +
non-breaking: `motion` defaults to undefined (no animation).
- badge/types.ts: `motion?: MotionPresetName`.
- badge/badge.svelte: spread `motionAttrs(motion)` on the span.
- badge demo: a "Motion" showcase (fade / scale-fade / slide-fade + Replay) —
the prop demonstrated per the testbed doctrine.
Verified at runtime: the three motion badges render `data-animation-style` +
`data-state="open"` with the right enter `animation-name` (fade-in / scale-in,
fade-in / slide-from-bottom, scale-in, fade-in). `check` clean for badge. (A
fresh badge screenshot was blocked by preview-infra flakiness this round; the
enter keyframes themselves were confirmed visually earlier via the cascade.)
Establishes the pattern: any component becomes animation-selectable by accepting
`motion` + spreading the helper. RFC §D.12 (b).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`<Cascade>` was the last component using `animation` as its preset selector — a
vestige of the coordinated `cascade-*` system retired in Plan A. Every other
animatable component (Dialog, Popover, Drawer, …) already uses `motion`. Renames
`<Cascade>`'s prop + its context field `animation` → `motion`, so the framework
has ONE universal selector name (RFC §D.12 step a). `MotionPresetName` and the
low-level `data-animation-style` attr are unchanged.
- cascade: types `animation?` → `motion?`; context `CascadeContext.animation` →
`.motion`; component destructure + getter; item reads `ctx?.motion`.
- docs/demo: README props table + usage, index usage, panel-cascade demo.
Verified at runtime: `<Cascade motion="scale-fade">` produces identical wiring —
items carry `data-animation-style="scale-fade"`, `animation-name: scale-in,
fade-in`, structural index 0/1/2/3. `check` clean for the touched files; no
dangling `animation`-prop reference remains (only the `data-animation-style` attr).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- dialog + drawer adopt data-depth="modal" (surface·border·base-shadow from the plane);
recipes pruned of orphan content-bg/border/shadow. The bundle's on-surface font fixes
the Times New Roman of portaled content (Decisión 8).
- Dialog.Close inherits the dialog size via context.ts, capped at md (sm→sm, md/lg/xl/full→md;
full is layout, not a bigger control — Radix/Mantine/MD3). THEMING §5 container→part norm.
- Dialog title → h3 typography role (--dialog-title-* points at --style-h3-*, override knob kept).
- Dialog trigger + close compose the framework <Button> via the asChild `child` snippet
(the trigger was a passthrough that rendered soma's native <button>); variant/color/size
defer to the canonical Button defaults (solid/primary) — no hardcoded ghost/neutral.
- demo layout (web/routes/uix/+layout@.svelte): "Display" control — density·scaling·border-width
applied to :root (reaches portaled overlays) to observe the interrelated theming axes live.
- update the depth-plane test (z is token-only; on-surface font) + the archetype audit hand-off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cascade is no longer a separate system — it's the existing state-presets + the
Material stagger + one foundation rule. Retires the --cascade-* reinvention.
- render-css: foundation structural-index writer — `[data-stagger] > *:nth-child` →
`--motion-stagger-index` (forward) / `:nth-last-child` → `--motion-stagger-index-rev`
(reverse). Nobody writes the index; soma never writes a visual var. Every preset's
enter/exit rule consumes it (parallel = `--motion-stagger-each` 0, cascade = N).
- Cascade rewritten as a thin EXPLICIT orchestrator: marks `[data-stagger]`, reflects
open→data-state, passes the preset to items via context; the `out:` retention flips
to data-state=closed (reusing the exit preset), reading the duration via dom.getWindow.
Drops the whole `--cascade-*` namespace + bespoke keyframes.
Docs: MOTION_SERVICE_RFC §D.11 — the final model (three orthogonal axes sema/motion/
eidos, the full lifecycle, the realization ladder snap->transition->animation->JS, the
firma-vs-realization seam, the cascade = existing system + one rule, the motion service
as the engine). eidos-motion aligned.
Verified: browser (enter scale-in/fade-in + nth-child index, exit reverse, zero inline
writes), npm run check 0 mine, motion.test 22/22, eidos-lint 0 invalid.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the parallel motion-coordination service per the redesign: animation is a
channel of the EVENT's firma (sema owns it across all channels), not a parallel axis.
Code is gone; the RFC stays as historical record with a retirement banner.
- morfo: drop MorfoPart.animation + its schema/compile/types/exports
- arts/motion: drop CoordinatedPreset / MotionConfig.coordinated
- eidos: drop BUILTIN_COORDINATED_PRESETS / renderCoordinatedPresetRules / the
animation:none neutralization + registry block; regen generated/base.css
- soma: Presence reduced to a single-surface island; delete presence-group /
dom-cascade / coordination; dropdown-menu loses the cascade wiring
- delete Reveal / Rail (morfo + soma + demos)
Docs: MOTION_SERVICE_RFC gains the retirement banner + §D.8 (retirada) + §D.9
(verified model: morfo->soma->sema->eidos pipeline + the two hard rules — soma
never writes a visual --var; data-event-* is a single-target stamp, not a bus) +
§D.10 (Fase 1 prototype). Fix stale "5 canales" claim in GUIA §11; eidos-motion +
dropdown README aligned.
Fase 1 prototype (web/routes/temas/animations/panel-cascade): validates the model
end to end — panel->cards cascade (enter/exit), dynamic removal with Svelte out:
retention, nested cascade — all via :nth-child + custom-property inheritance, with
zero JS visual writes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Coherence audit (ARCHETYPE_COHERENCE_AUDIT_2026-06-19): 8 archetypes + 8 closed
decisions, the §13 construction contract, per-component deviation matrix (88/117
deviate). Master guides (soma/COMPONENT_GUIDE, eidos/components/README) point to it.
A1 — elevation bundle via `data-depth`: the plane paints surface·border·shadow·halo
(added `border` to the depth bundle). z-index is NOT painted — stacking is a
positioning concern owned by the floating-wrapper. Popover migrated to the overlay
plane (panel + arrow consume it); recipe orphans pruned.
Popover fixes: arrow un-clipped via an inner [data-popover-viewport] (Radix-style;
the absolute arrow escapes the scroll clip), sized via density tokens (not soma's
hardcoded 10×5), border-seam overlap keyed on data-side, hover-bounce guard in
scheduleHoverOpen (ignore hover-open when already open). Dialog.Close composes
IconButton/Button (neutral) — closes the Lote ② debt.
Option lists: density-driven padding/radius via --space-* + concentric radius,
ScrollArea adoption, emerge open/close events (combobox/select/command), and the
canonical option-highlight in archetypes.css.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- <ColorField.Input swatch> renders an opt-in leading colour chip showing
the field's current value, sized to the control icon (--icon-size-{size})
via a concrete token — never 1em, so it cannot recompute/jitter.
- field-segment-state: drop the active-segment font-weight 600; bold widened
the ch-based segment ~0.5px, resizing the highlight on every increment. The
accent background already marks the active segment.
- color-field stays full-width like the whole field/select family — width is
container-driven, not field-driven (no per-component exception).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Field font scale: input 1:1 (size N -> --font-size-N, md now 16px), label one
step below (10/12/14/16/18). Recipes: field, spin-field, date/time/color-field,
search-field, password-field, select, editable, tags-input. Segmented-field
labels via CSS calc; generic Field label via label-font-size tokens.
- Picker triggers (date/time/range): box now scales off the local
--_X-field-height + font:inherit so the 1em icon follows the field font; added
the missing xs size variant to time-picker + time-range-picker.
- color-field/time-field commit-set fires + sounds on increment (compare by
ColorValue.hex; time-field passes fallbackTarget for the double-registered
'input' part).
- color-field format-select: reverted the segment-active tint back to its focus ring.
- Docs: THEMING.md field font rule; COMPONENT_COHERENCE_AUDIT.md.
words / palabras / chronos untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prototipo descartable (web/routes/temas/animations/menu-native) que valida la
Decisión A del re-diseño del servicio de motion: el lifecycle de presencia se
apoya en Svelte nativo ({#if} + transition:|global) en lugar de la maquinaria
custom de F2 (DomCascade/pending/beginEnter).
Validado en navegador real:
- entrada escalonada sin ease-vs-jump; salida con stagger invertido
- exit-heavy nativo (panel retenido hasta el ultimo item, sin pending())
- adicion dinamica -> entrada individual (el contenedor arbitra opening/closing)
- roll-up del panel como firma de cierre opcional (when: exit:'after')
RFC Apendice D.7 documenta resultados + hallazgos (grid-template-rows no anima
por WAAPI; usar height/interpolate-size).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F1c (exit-heavy): DomCascade.pending() aggregates the items' finished so the owner Presence holds the subtree until the exit cascade settles (PresenceOptions.pending, Presence.startPhase). beginEnter drives the enter from the off-state with the transition suppressed (items mount in the on-state, so a passive mirror would ease toward off instead of jumping). pending() settles each finisher (then(noop,noop)) so one cancelled row can't collapse the wait.
F2: wire the children-DOM cascade into the real dropdown-menu. Content part declares animation.surface+staggerChildren; provider routes an opt-in 'animation' prop, runs a DomCascade over getCascadeRows (every visible row incl. disabled), forwards a pending hook through the floating shell. Coexistence fixes surfaced by the menu: item rows hand their transition to the cascade-* preset via a higher-specificity rule (longhands keep the stagger delay); disabled opacity gated off during the cascade; the panel's own dismiss-fade signature suppressed in cascade mode (data-cascade) so it doesn't fade the panel before the rows finish; the trigger is excluded from the menu's dismissal so the toggle closes (pre-existing bug). Verified in a real browser (Playwright): enter+exit cascade with reversed stagger, all four close methods, disabled row fades. dom-cascade 9/9, check 0 new errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El piloto M9 (dropdown-menu) destapó que el PresenceGroup coordina children
que son `Presence` REGISTRADOS (Reveal/Rail), pero un menú real es owner-
`Presence` (el content monta como unidad) + children-DOM (items por selector,
sin `Presence`). 3 grietas en 3 capas: registro, index per-item, y escribir
una CSS-var per-element (que ActiveDom no cubría).
Modo children-DOM (la 2ª coordinación): el owner PROPAGA su lifecycle a los
items DOM — escribe data-animation-style + data-starting/ending-style (espejo
del owner) + --motion-stagger-index/-count (por orden DOM), reutilizando el
preset coordinado de eidos SIN cambios (los items quedan con los mismos attrs
que un child-`Presence` produciría).
F1 (núcleo):
- morfo: `MorfoPart.animation.staggerChildren?: string` (types + schema +
invariante: requiere surface, excluye children).
- adom: `ActiveDom.writeProperty/removeProperty` per-element (la pieza que
faltaba; + stub disabled).
- soma: `DomCascade` (`dom-cascade.svelte.ts` — `sync`/`clear` puros y
testeables, `watch()` corre el $effect espejo) + barrel export.
F1b (validación aislada): demo `/temas/animations/dom-cascade` (owner Presence
+ items DOM + DomCascade.watch). Cazó un bug que el test no veía: DomCascade
usa $effect → debe ser `.svelte.ts`, no `.ts` (el test solo ejercita sync/clear,
nunca watch()). Cascada de ENTRADA verificada en navegador.
PENDIENTE (en la memoria del proyecto): F1c exit-heavy (el owner espera a los
items — getAnimations no ve subtree), F2 el dropdown-menu real (focus-trap/
dismissal/submenús), y volcar §M9 al RFC.
Tests: dom-cascade 5/5, morfo 70/70. svelte-check: 0 errores en mis archivos.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Un componente coordinado puede componer la cascada (motion) con sonido/háptico
(sema) en el mismo evento. El cabo: el meta-canal visual de sema estampa
data-event-* siempre que el evento tenga algún canal (channels != []), así que
channels:['sound'] arrastraba la firma visual genérica (present-rise), que pelea
con la cascada.
Solución EN MOTION (sema intacto): eidos neutraliza su PROPIA firma visual sobre
las superficies coordinadas. render-css.ts > renderCoordinatedPresetRules emite
`[data-animation-style='cascade-X'][data-event-phase='active'] { animation: none
!important }`. La firma usa `animation` -> muere; la cascada es `transition` ->
sobrevive. Aditivo: inerte hasta que un coordinado dispare un evento sema.
Ejemplo: Reveal pasa a modo (c) — open y close declaran channels:['sound']
(emerge suena, pitch 600) + expression:'family-default'; suena al abrir y al
cerrar a la vez que la cascada, sin pelea visual. Rail queda como modo (b) puro.
Layout de /temas/animations con events:{ sound: true }; la demo /reveal explica
el modo (c).
Honestidad: la versión inicial del RFC Apéndice B afirmaba que el modo (c)
componía "gratis" — falso (verificado en engine.ts/visual.ts). Corregido: el
Apéndice B documenta ahora el acoplamiento real y el opt-out en motion.
Tests: eidos/motion 25/25 (neutralización) · morfo 70/70 · morfo:vocabulary
limpio. Audio verificado en navegador (suena al abrir y cerrar).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Continuación del servicio de motion-coordination (sobre M1–M6 ya commiteado).
- M5 — naming `animation` / `motion` SEPARADAS por rol (no unificar: tras M6
son sistemas distintos — `motion`=momento `--state`/`data-state`/eidos;
`animation`=coordinado/`data-starting-ending-style`/soma). `animation` gana
type-safety vía el registry augmentable `MotionCoordinatedPresets` +
`CoordinatedPresetName` en `$motion` (la capa compartida, para que soma lo
tipe sin importar eidos); eidos lo puebla con `cascade-slide/-fade/-scale`
por declaration-merging — espejo de `EidosMotionPresets`.
- Reversa fluida — JS handoff (§8.3). El motor expone `MotionHandle.peek()`
(posición + velocidad por propiedad) y la reinyecta en la fase inversa vía
`MotionContext.handoff`: el `spring` continúa desde la posición/velocidad
actual en vez de reiniciar desde el `from`. Orquestado enteramente en el
engine (`takeHandoff` — captura en `cancel`, consume en `run`, y cancela un
run en vuelo cuando el caller no pre-cancela): cubre el camino island
(overlay-spring) y elimina un apilamiento preexistente. La reversa de los
coordinados (transiciones CSS) ya era fluida.
- Demo `/temas/animations/compuesto` — un `Rail` de `<Button>`s que usa los
presets coordinados PREDEFINIDOS de eidos (cero CSS de animación en la
página); el selector cambia `animation` en vivo y cada Button compone su
press (firma sema) con la cascada (motion coordinado).
- Docs — RFC Apéndice B (convivencia de los tres sistemas visuales: firma
`--event` / state-preset `--state` / coordinado, con diagrama + ejemplo
Button-con-tokens), §5 reescrito (separadas por rol), §8.3/§15 al día, y
cross-link del tercer eje desde eidos-motion.md.
Tests: arts/motion 9/9 (handoff) + eidos/motion 24/24 (M5 paridad).
svelte-check: 0 errores en mis archivos.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Servicio de motion cross-layer: morfo declara superficies animables +
coordinación; soma coordina presencia/lifecycle; eidos posee lo visual;
arts/motion ejecuta por-nodo. RFC en eidos/MOTION_SERVICE_RFC.md.
- M1 — contrato MorfoPart.animation (types/schema/compile); children
como { enter?, exit? }.
- M2 — PresenceGroup (soma/layers/presence-group.ts), rune-free;
Presence.group descubre el coordinador por context.
- M3 — exit con retención de DOM (§8.1).
- M4 — interrupción/reversa (§8.3): token de generación + motion.cancel
en flip + toHandle resuelve finished en cancel (sin AbortError suelto).
- M5 — prop `animation` enrutada a las parts surface:true del morfo
compilado (routeAnimation); Panel/Item emiten data-animation-style.
- M6 — stagger auto-derivado del orden de registro (--motion-stagger-*,
inversa en exit) + presets coordinados en la librería de eidos
(MotionConfig.coordinated; cascade-slide/-fade/-scale) que reaccionan
a data-starting/ending-style, NO a data-state.
Helper Coordination (soma/layers/coordination.ts) extraído y validado
por DOS consumidores reales: Reveal (raíz virtual + Panel owner) y Rail
(raíz=owner, together). Demos en /temas/animations/{reveal,rail,
presence-group}.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Icon scale large steps now track the type scale (validated by eye in a study
harness at /uix/icon-scale-study): --icon-size-xl 24→32, --icon-size-xxl 48→52,
so the icon keeps its optical weight beside its paired text. 0 impact — nothing
consumed those steps yet.
button: icon-size per size references var(--icon-size-X) (mapped by value → same
rendered px, now density-scalable) instead of hardcoded px.
Docs: THEMING.md §5 documents the type↔icon correlation; CONTINUE-icon-type-scale.md
captures the pending 1:1 work (size = font = icon per name, dropping the font
step-down) for the next session.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The recipe defined per-color tonal tokens inconsistently — only fulfill/loss had
`track`/`border`, so soft/surface/outline rendered with no background or border
for the other 6 colors (primary/secondary/neutral/affirm/risk/threat):
`palette-track`/`palette-border` referenced undefined `--button-{color}-track`/
`-border`, which collapsed the background/border declarations. Added the missing
track+border to all 6 (mapped to `var(--color-{role}-track|border)` like
fulfill/loss); the recipe is now uniform (7 slots per color). Verified all 8
colors × soft/surface/outline in dark + light.
Demo: new "Gallery" tab — a 6 variants × 8 colors matrix of real <Button>s
(hierarchy via `color`, evaluative palette via `intent`).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The logo overlay rendered invisible. Inside a viewBox-scaled foreignObject a
fixed px padding (var(--space-1)) equals that many *modules*, overflowing the
cleared centre and collapsing the content box; inline-size/block-size also
don't size an <svg> element. Fix: proportional inline padding + physical
width/height. The logo now renders and still decodes at ECC H. Dropped the
now-orphan --qr-code-overlay-padding token + regenerated base.css.
Demo: content presets (URL / vCard / WiFi / unicode) prove the encoder takes
any UTF-8 string, not just URLs (each verified decoding via jsQR); logo on by
default; quiet-zone chips fixed to canonical [4, 8, 16] (was [2, …], below the
spec's 4-module minimum). README documents the quiet zone as modules, not
pixels — the canonical unit; for px layout spacing, pad the container in CSS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>