Floating label (`<Field floatingLabel>`), the label typography doctrine, and
its propagation across the text-field family.
Floating label (Material outlined, text inputs only):
- CSS-only via :focus-within + :placeholder-shown — no new soma state. The
label starts as a placeholder inside the control and floats onto the top
border when focused/filled. Reuses Field.Label + Field.Input. Requires the
input to carry `placeholder=" "`. Segmented controls (Date/Time) not covered.
- Floated position is themeable via `--field-floating-label-top` (default 0 =
on the border) without touching the recipe.
Label weight doctrine ("label one step below the component"):
- The field VALUE is now `medium` and the label stays `regular` — one weight
step below (the size offset was already derived one step down). Base Field via
`--field-control-font-weight`; propagated to the specialized text fields
(number/css via spin-field, mask, password, search, textarea, date, time,
date-range, time-range, color) so every field value reads at medium.
Verified: recipe-css-contract + component-visual-attrs + api-contract (30),
eidos-lint 0 invalid, smoke, check 0 field errors. Browser: floating resting↔
floated transitions correctly; value 500 / label 400 across fields.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Native <label> accessible primitive with `for` association — the gap the
Field family left open (Field.Label is context-bound; nothing labelled a
control outside a Field). Ported from morfo-runtime:terra/label into the
current eidos-native leaf shape.
- Eidos-native leaf (scope: ['eidos'], no soma/sema): morfo is the contract
(one Provider part -> data-label); the wrapper renders <label> directly.
- Visual axes size/weight/color follow the Text typography mechanism
(--style-label-* base + inline --_label-* overrides via ActiveEidos.resolve),
not a prop-less shell — real selectable axes in the demo.
- Multi-click mousedown guard (Radix convention) inline in the wrapper;
composes the consumer's onmousedown.
- Complements Field.Label (auto-wired inside a Field); floating label is a
deferred Field concern, not a prop here.
Passes component:audit label (0 err / 0 warn), check (0 label errors),
eidos-lint (0 invalid), recipe-css-contract, smoke. Browser-verified: size/
weight/color change the label live; for-association focuses the control.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebuilds the GradientPicker on the generic Picker.Provider<Gradient>
transaction core (open / commit / cancel / clear / workingValue + a composed
Popover), like the natural-time-picker — the bespoke coordinator is now a thin
domain provider (morfo runtime + working-value CSS + the presets state).
- deferValue prop (live vs deferred draft-until-Listo). The trigger chip
reflects the COMMITTED value, so a deferred picker's chip stays put while you
edit the working draft.
- Clear restores the INITIAL bound value the picker was given, not a hardcoded
default gradient.
- No tabs: the editor IS the popover body; the presets grid opens in a panel
from a Layers button next to "Añadir degradado" (the Add <-> Remove toggle) —
ColorPicker-style "select a saved preset -> Remove", and it reclaims the
tab-header height.
- Add / Remove presets fire commit.fulfill / commit.loss firmas (new
gradient-picker sema pack, registered in the demo). Per the Button doctrine the
press is a neutral contact.activate and the consumer emits the RESULT commit;
the events target the REGISTERED `provider` part (targeting the unregistered
`content` part was also why the Clear commit-reset was silent).
- Stop color editor hides the remove-stop button for the endpoint stops
(first / last by position — they anchor 0% / 100%).
- maxPresets cap (default 10). StopList in a ScrollArea (autosize, slim
scrollbar with a gap so the rows never sit under it).
Composes the existing components AS-IS (Popover / Button / ScrollArea / the
GradientBuilder parts) without overriding their styles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GradientBuilder (this session):
- Stop color editor uses the canonical ColorField value row (swatch +
styled Select), not a native select; rails at 90% width + xl track.
- Composable footer: ColorPicker.Cancel / ColorPicker.Close + trash
remove-stop, as a sibling of PickerShell.Body.
- Surface-agnostic by default (variant='ghost'): no card, adapts to host.
- '+' add-stop on the kind-switch row; inserts between the selected stop
and the next; rail click-to-add re-enabled (insert at cursor).
- GradientBuilder.StopList composable stops list (rendered by default).
- presets prop (Gradient[]) rendered below the editor (flex, not tabs);
save-to-presets with a maxPresets cap; the action button flips
Save <-> Delete when a saved preset is selected (ColorPicker mechanic).
Also lands the in-progress gradient tree so HEAD is self-consistent
(langs/components/index.ts already imported ./gradient-picker, whose file
was untracked). gradient-builder + gradient-picker stay on ACTIVE_DEV_TRACK;
the GradientPicker 2-tab redesign is next (see gradient-builder/CONTINUE.md).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ValueField: optionally-editable centre value composing NumberField, wired to
the knob's value (time-picker-hour-slider pattern); de-chromed to read as
text, width-locked to the max value's digit count, and its pointer/keyboard
events isolated from the rotate gesture so editing never rotates or resets
the dial.
- handle-drag: continuous rAF-throttled perceptual tick during rotation,
mirroring the Slider (morfo event + provider emit + sema rule).
- NumberField: first-class maxLength prop (native maxlength, unlimited when
unset); the knob caps the field to the max value's digit count.
- Rotation: publish --knob-start-angle / --knob-sweep so the arc reacts to the
sweep; snap to the nearest end inside the bottom dead zone so overshooting the
max lands on the max and never wraps around to the min.
- Register knobSema in the uix demo layout so the knob emits sound + haptic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The relocated Knob didn't render (only the number showed). Root causes, all
found by inspecting the loaded stylesheet (not guessing):
- A stray `*/` inside the header comment (`--scale-*/--primitive-*`) closed the
comment early and corrupted the FIRST rule, so the base `[data-knob]` rule
(which sets `--_knob-diameter`) never loaded → the control collapsed to height 0.
- Phantom tokens inherited from the Fable draft that don't exist in the theme:
`--color-neutral-content` (→ `--color-neutral-text`), `--state-hover`
(→ a `filter: brightness` hover); the pointer was transparent because its
fallback token didn't exist.
Design (per the user's reference image + directives):
- Value readout centred in the dial's hole (grid place-items).
- Donut arc: replaced an improvised hard-edged radial mask (aliased inner edge)
with an opaque face plate over the conic pie — a border-radius circle is
GPU-antialiased, so the inner ring is clean. Colours use theme-adaptive
tokens (surface-raised face, neutral-track ring, neutral-text value,
neutral-text-strong pointer).
- Value + label fonts scale with the knob via the same size coordinate as the
diameter (`--size-{k}-*` bundle), not an invented "one step below" rule.
Demo: value composed inside the Control (centre), plus a `needle` control that
conditionally composes `<Knob.Indicator>` (the compositional way to show/hide it).
Verified structurally (sizes, tokens, toggle) via DOM inspection; the screenshot
tool timed out all session, so the final pixel check is the user's. Recipe
contract green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Canonical-depth demo at web/routes/uix/components/knob mirroring the Slider
demo (Live / API / morfo / sema / recipe / a11y tabs; every public prop is a
live control; sema event trace + play buttons; soma/eidos code snippets).
Serves as the visual/behavioral verification the relocation lacked. Confirmed
live at /uix/components/knob:
- renders role=slider, aria-valuemin/max/now, aria-roledescription resolved
from the langs catalog ("dial giratorio" under the es locale);
- provider-published `--knob-angle` / `--knob-progress` drive the conic-gradient
arc + rotated indicator (angle = startAngle + progress*sweep, verified exact);
- keyboard loop works end-to-end (ArrowUp/PageUp → value 40→52, aria-valuenow +
CSS vars + bound ValueText all update); no console errors.
npm run check: 0 errors in the demo (total unchanged at the 59 baseline).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
User-caught drift, and it was live in the UI, not just prose: the demo
harness's hand-copied PALETTE_GROUPS enumerated 31 scales — fuchsia and
steel (the two non-Radix scales) never appeared in any demo's
PalettePicker even though PALETTE_SCALES (the const, 33) and the recipe
cascade (paletteScaleDecls maps the const) resolve them fine. Fixed the
two missing entries, typed the groups as PaletteScale[], and added a
dev-time completeness assertion so growing the const without grouping
throws at module load.
Prose swept (the numbers were inherited verbatim from the pre-book
sources, which contradicted their own '33-scale library' headers):
decisions.md color-model row, demo-authoring x4 (incl. the '31-scale
palette' lede), rfc-color-engine x4 (ground-truth + templates
open-question). Where the count was incidental the text now points at
PALETTE_SCALES instead of restating a number.
Guard: docs-check I1 gains the palette count (EN + ES patterns,
~-approximation and type/space-scale exclusions; s-mark added to the
section-number skip). The check line now prints palette=33. This was
the exact class I1 already covered for families/intents/archetypes and
had not been extended to the palette.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>