astra
alpha-0.1-background
alpha-0.1-dir-prefs
alpha-0.1-sec-dom
menubar-v4-safe
active-uix
morfo-runtime
morfo-driven-soma
semantuix
glm-5
main
sium-v1.0
${ noResults }
197 Commits (6f42eebfe80bc9ac5656be2bbf621c6d62e768fb)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
cc7e9a162a |
feat(theming): THM-2 - avatar root palette to the shared layer + 33 scales
The avatar body's per-instance palette now resolves through the shared
per-instance layer (size collapse, behavior-preserving for roles, adds
the 33 scales):
- avatar.svelte: isCanonicalColor recognizes palette scales, so a scale
name routes to data-color instead of the custom CSS-color path ('teal'
is also a CSS color name, so it used to fall to --avatar-color-custom).
- recipes/base.ts: _palette-{solid,surface,contrast,text,border} (host
fallback only - the avatar always emits a canonical data-color, so the
presence-guarded forward routes roles + scales) + _bg/_fg/_border move
from a variant x color matrix to variant-only readers of
--_avatar-palette-*. The matrix helper + intermediates stay (the badge
still uses them; 0 orphans). avatar.css unchanged.
- demo: scale picker.
Verified end-to-end with the REAL component (drive the demo color control
-> the avatar emits data-color -> the shared layer resolves the exact
scale): affirm+soft behavior-preserving, teal+soft = scale-teal-a2
(translucent surface), teal+solid = scale-teal-9.
The Avatar.Badge sub-part (aria-hidden decorative chip with its own color
+ default) stays on its role palette + custom-CSS: migrating it needs a
per-part forward + pruning ~36 orphaned intermediates, disproportionate
for a decoration whose arbitrary-color case already works. Documented for
a decision.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
05f0031cad |
feat(theming+soma): media-player Batch-4 (SOM-4) + THM-2 shared-palette rollout to 14 surfaces
media-player Batch-4 (closes SOM-4): the 10 sub-parts migrate to
renderProps() (35 attr-dup hits -> 0). Register the sources the morfo
reads (label propRef, on stateRef, direction/type) so renderProps emits
type/role/aria-*/data-state; keep only contract-only soma-owned attrs.
Pin the soma-attr-audit ceiling to 0.
THM-2 rollout - collapse each per-component palette cascade into the
shared per-instance layer (size win, behavior-preserving for roles,
adds the 33 scales):
- Rename palette tokens to canonical _palette-{slot} + route via the
presence-guarded forward: tags-input, stepper, badge, card, select
(parts-aware trigger+content), file-upload, editable, tag-group.
- Extend the shared vocabulary with alpha surface(a2)/surface-hover(a3)
slots (soft variant of variant-aware recipes) and hover(4) - the
latter also fixes Toggle's latent scale-hover.
- tag-group: variant-aware migration (solid/outline -> canonical,
soft -> alpha surface; merge outline-fg into text, prune orphans).
- toggle-group: replace the 4-role cross-recipe composition with a
hand-authored presence-scoped route to the shared layer (the
composition DSL can't presence-scope); covers roles + 33 scales.
- Widen color to ComponentColor (roles + 33 scales) where arbitrary
color is meaningful: badge, tag-group, tags-input, toggle-group, and
add the scale picker to their demos. Semantic controls stay restricted.
Verified end-to-end with the REAL components (drive the demo color
control -> the component emits data-color -> the shared layer resolves
the exact scale). Gates: recipe-css-contract/config/contracts/
soma-attr-audit/media-player suites green; 0 orphan tokens.
avatar remains for a focused pass: its variant-resolved matrix and badge
sub-part don't fit a mechanical migration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
0e75890f60 |
feat(theming): THM-2 — paleta universal por instancia (capa compartida + forward), 5/17 superficies
El mecanismo genérico ya existía (universalPaletteDecls auto-completaba las 33
escalas para recetas con tokens palette-*), pero con PREFIJO de componente
(--button-palette-*) — cada superficie pagaba ~21 KB, por eso solo button/toggle
lo tenían (rollout a las 17 = ~+315 KB). Colapso al modelo compartido:
- renderSharedPaletteLayer: UNA cascada component-agnóstica
[data-color='{role|scale}'] { --palette-{slot}: … } — 8 roles + 33 escalas,
emitida una vez (~19 KB). Roles 1:1 a --color-{role}-*; escalas con el step
map + on-solid contrast, valor idéntico a la cascada per-component retirada.
- renderRecipePaletteForward: toda receta con palette-* (público) o _palette-*
(privado) recibe [data-{c}][data-color] { --{c}-palette-{slot}:
var(--palette-{slot}, host-default) }. La guarda de PRESENCIA es la seguridad
de anidamiento — un hijo sin color dentro de un ancestro coloreado matchea
solo [data-{c}] (su default) y nunca lee el --palette-* heredado.
- Generalización a la paleta PRIVADA: checkbox/radio-group/switch ganan las 33
escalas con CERO cambio de CSS (ya consumen --_{c}-palette-*).
- universalPaletteDecls eliminado; 2 guards migrados al modelo compartido
(recipe-css-contract "shared layer + forward"; active-eidos-config contrast
en la capa compartida).
DONE (per-instance): button, toggle, checkbox, radio-group, switch (5/17).
Rollout de las 12 restantes en docs/process/continue-cleanroom-fixes §THM-2.
Doctrina en theming/reference §25 ("The per-instance palette layer").
Verificado en vivo (button + checkbox): color="teal" → escala teal exacta ·
affirm → rol exacto · primary → primary · nested-nocolor en ancestro teal →
primary (sin contaminación). Peso 345K → 329K. Gates: matriz 141/141 ·
eidos 314 · recipe-css-contract + active-eidos-config 98/98 · contracts 38/38.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
4addfd7dbf |
fix(cleanroom): P1s ejecutados (EID-1/EID-2/SEM-1/MOR-4/THM-1/THM-3) + auditoría + plan scene/ambient
Clean-room fixes (2026-07-11, sobre los P1 de la auditoría):
- EID-1: el anillo de foco de la fundación migra a `outline` (§32) — el último
box-shadow del catálogo; :where()-wrapped para que las recetas siempre ganen;
doctrina actualizada en theming/reference + changelog.
- EID-2: opacidades literales de archetypes.css → var(--opacity-disabled/hover).
- SEM-1: política de live-region unificada — la urgencia va SOLO por intent
(threat/loss → assertive), runtime y AnnounceChannel ya coinciden.
- MOR-4: card-group con morfo propio (scope eidos) + guard de enumeración.
- THM-1: component-audit + recipe-css-contract escanean los *.css hermanos
(calendar-select, color-picker-spectrum, date-range-picker-time…); R-2.1
caza también hsl()/oklch(); violaciones de los secundarios corregidas.
- THM-3: canario de censo de toggle volteado (lint.test); commit-block promovido
a firma global block-shake; regen de generated/base.css.
Docs nuevos:
- docs/audit-new-10-july/audit-active-uix.md — auditoría clean-room 2026-07-10
del ecosistema (hallazgos P1–P3 con evidencia ruta:línea, matriz y gap analysis).
- docs/process/PLAN-scene-ambient-pack.md — plan por fases: motor arts/scene +
pack Ambient (45 animaciones de demos) + vía de promoción agéntica (Aura);
gate de decisiones D1–D9.
- docs/Disenando_lo_que_ocurre_FINAL.{docx,pdf} — edición FINAL del libro.
- README.md raíz (esqueleto de paquete).
Excluidos deliberadamente de este commit: words/palabras/chronos (pista
separada), web/routes/alpha (terminado, sin commitear por decisión), rt.css
(volcado de referencia Radix Themes).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
83e13be649 |
refactor(eidos): promote calendar range colors to public API (S8)
The range endpoint colors lived as private `--_calendar-range-*` tokens declared
in range-calendar.css AND redundantly re-tuned by date-range-picker (reaching into
a sibling's privates). Promote them to the calendar-surface PUBLIC API: 8
`--calendar-range-{start,end}-{solid,solid-hover,border,text}` tokens in the
`calendar` recipe (`:root` defaults), consumed by range-calendar via the public
name. DateRangePicker drops its 6 `--_calendar-range-*` blocks — the composed
range calendar already paints from range-calendar.css, so the drp declarations
were redundant.
Deliberate asymmetry (documented in range-calendar.css): START defaults live in
the recipe `:root` so a theme can override them via config; END binds to the live
`--_calendar-accent-*` on the host (the accent is a per-color private that only
cascades there), so its `:root` defaults are the nominal public default. Both stay
public per the S8 verdict, preserving the original "end may differ from accent"
capability.
Value-preserving: recipe-css-contract 25/25, baseline 59, eidos-lint invalid 0,
browser-verified identical across default/affirm/secondary/fulfill on the
standalone RangeCalendar and the DateRangePicker popover.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
d1670caa45 |
refactor(eidos): de-alias date/time pickers off sibling field recipe tokens
The date-range / time / time-range pickers referenced --date-field-* / --time-field-* recipe tokens to paint their field-shaped parts (standalone trigger box, wrap gap/font, embedded trigger). That is a cross-component alias — the exact drift the S6 Field composition eliminates. Point every one at the FIELD FAMILY public tokens instead (--field-control-* / --field-* / the B.1 --field-segment-height), or at the underlying primitive where Field has no matching token (the wrap column-gap keeps the tighter segment rhythm). Each substitution is computed-value-identical (both sides resolve to the same primitive) — adversarially verified + browser-confirmed on all four pickers. Also drop dead private declarations the compositions left behind (--_time-field-padding-inline / --_time-field-segment-padding-inline, and --_time-field-gap in time-picker) and, now that nothing aliases them, prune the 32 newly-orphaned date-field/time-field chrome tokens from the recipe + generated CSS (recipe-css-contract orphan guard green, 25/25). TimeRangePicker's embedded clock-trigger keeps sizing from the picker-local --_time-field-height (not --field-segment-height): its composed endpoints don't carry their own [data-size], so the field-family segment-height would pin to md. That endpoint size-threading gap is pre-existing (DateRangePicker threads it, TimeRangePicker doesn't) and tracked separately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
a820e5f337 |
fix(eidos): prune 167 orphaned field recipe tokens after S6 field.css move
The S6 Field composition moved each field's chrome (border / radius / bg / height / padding / font / label) into field.css, leaving the per-component recipe tokens still emitting but unconsumed. recipe-css-contract.test.ts's "does not leave declared public recipe variables orphaned" test went red with 167 tokens across spin/date/time/color/search/mask/password-field. Remove the confirmed-orphaned keys from each recipe (mask-field emptied → entry dropped entirely); regenerate generated/base.css. Value-preserving: the removed tokens had zero consumers (the test is the oracle), browser- confirmed identical rendering on search-field + password-field. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
5eb92132b4 |
feat(fields): date-* family composes real DateFields via field.css (S6)
The date-* family now IS-a-Field then evolves, per the S6 mandate + the no-token-alias doctrine — no more marker-overlay borrowing of date-field.css. - date-field: chrome / label / invalid / disabled / focus come from field.css via the fieldRole identity (data-field / data-field-control); date-field.css keeps only segment specifics. No tokens aliasing the Field base. - Field recipe: add --field-segment-height (size-axis, family level). - DateField.Provider: expose syncPlaceholderToValue for controlled composition. - date-range-field: COMPOSES two real <DateField>s (DateField.Provider > DateField.Input per endpoint) bound to the range slice; size/variant/color thread through a visual context; CSS is layout-only. Fixes the field.css-not-loaded chrome regression. - DatePicker (single field): root carries data-field (A27); imports field.css; drops the dead date-field token re-declarations. - DateRangePicker: inherits the composed endpoints; imports field.css; removes the endpoint box that double-boxed the composed DateField. Baseline unchanged (59). Verified in browser: paint, edit coordination, size reactivity, no double-box. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
53b6f629f2 |
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress)
Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
92f9723388 |
feat(field): floating label mode + value-weight doctrine
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>
|
3 months ago |
|
|
153d1ced98 |
feat(mask-field): MaskField — single-input character mask (Tier-1)
Generic pattern mask for free-form fixed-literal inputs (phone, SSN, credit card, IBAN, postal code, plate) — the confirmed gap across ark/bits/radix/react-aria (radix #1412 "not planned"). - Zero-dependency mask engine (mask-engine.ts): tokens 9/A/*, literals, escape, optional [] tail, accept-if-fits + caret mapping. Pure/DOM-free, node-tested (25 tests). Promotable to arts/mask later. - Soma provider composes base Field (OR-merge state, inputId register); reject-and-revert input + caret restore via tick(); commit-set on blur/Enter only. value = masked string, unmaskedValue derived. - Single role=textbox (never fake spinbutton segments); truthful value announcement; inputmode from mask; date/number/OTP delegate out. - Sema pack commit-set -> form.commit.subtle + tap (mirrors NumberField). - Eidos option-C wrapper + recipe (25 tokens) + demo (v2 6-tab testbed). Passes component:audit mask-field (0 err), 30 unit tests, recipe-css- contract, component-api-contract, eidos-lint (0 invalid), check, smoke. Note: soma barrel export + demo-app registration (sema pack + nav link) live in soma/components/index.ts and web/routes/uix/+layout@.svelte, left unstaged because they carry unrelated branch WIP. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
bbd9c467e5 |
test(eidos): triage the 15 long-red suite failures — 289/289 green, check 61→59
Accumulated test debt (the eidos suite had been red across several feature
landings — THEME-SYS-1 named z-index, palette-first static donor library,
spin-field extraction, icon stroke-map refinement — none of them regressions;
baselined against HEAD). Each triaged stale-vs-bug by confirming current
behavior in code before touching a line.
Stale tests (code correct/improved, assertion not updated):
- z-index '76' -> 'var(--z-index-overlay-tooltip)' (the code is guarded by
THEME-SYS-1 in contracts.test.ts — the raw integer is forbidden).
- frost '80%' -> 'var(--depth-overlay-translucency, 80%)' (parameterized).
- C7 size-bundle coordinate ('--control-height-md' -> '--size-md-control-height').
- icon visual-stroke ratios 1.5->1.125, 1->0.923 (stroke map md 2->1.5, xxl
icon 48->52px; the arithmetic follows).
- delegate/CSS-only purple-9: the slim foundation legitimately emits the
role-referenced donor scales (purple=primary) as overridable :root defaults;
the real delegation guarantee is 'no baked [data-theme] block', which the
assertion now checks.
Test over-flagging (fixed the test, not the code):
- CSS-only shared layer: spin-field (no wrapper/index) excluded from the
wrapper/barrel/namespace contracts (CSS_ONLY_LAYERS).
- soma-runtime vars: 'var(--x, fallback)' is runtime-optional (the STUMBLES #7
soma->eidos surface) — only bare 'var(--x)' requires a recipe declaration.
- orphan check made corpus-wide: composition means a sibling consumes a
component's tokens (date-picker/month-grid consume calendar-control-*).
- natural-time-picker fixed-tone sky colors documented-exempt (Words-rail
doctrine).
- dialog drift-detector converted to a synthetic fixture (dialog.css no longer
ships the dead rule it self-tested against).
Real code fixes:
- declare '--pin-input-cell-readonly-bg' (I consumed it in C2b without
declaring it).
- remove 5 truly-dead tokens (drawer content-bg + 4 color-field
format-select/segment — 0 consumers anywhere in eidos/soma/web).
- clamp time-picker's 'size' to PopoverSize before forwarding (xs->sm):
TimePickerSize has xs, PopoverSize doesn't — a latent type bug my recipe
change surfaced via a complexity-budget flip. The fix dropped check 61->59,
also clearing navigation-menu-content of the same class.
card-group flagged as a REAL gap (KNOWN_MISSING_MORFO): it ships parts + chrome
but no morfo (morfo-first violation) — tracked, not silenced.
Verified: vitest src/uix/eidos 23 files / 289 tests green; npm run check 59
(better than the 61 baseline); component:audit 0 BROKEN.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
6001d9bb47 |
feat(eidos): C7 — size-bundle adoption swept across the recipe catalog
The size bundle (--size-{k}-control-height / -font-size / -icon-size,
theming/reference §5) was emitted + guarded but consumed by ONE recipe
(the toggle pilot). This sweep points every recipe at the canonical
size COORDINATE instead of the raw primitive of the same coordinate:
366 refs across 34 recipes (--control-height-{k} -> --size-{k}-control-
height, and the font-size / icon-size coordinates likewise). Retuning a
size's bundle now reaches every consumer; a recipe that maps a key to a
DIFFERENT size's coordinate stays visible as a deliberate deviation.
The bundle vars are pure aliases of the primitives, so this is
computed-value identical — verified in a live browser: the accordion
chain --accordion-trigger-min-height-md = --size-md-control-height =
--control-height-md all resolve to calc(36px * 1 * 1). The generated
diff is 366-for-366 pure name swaps, nothing else moved. base/xxxl have
no size bundle and correctly stay on the typographic primitive.
Guard: recipe-css-contract forbids the raw size-coordinate primitive in
recipe token values (var(--control-height-{k}) etc.) — the drift can't
creep back. This is the ⚠️->✓ that theming/notes' comparison table flags
as the size canon's remaining asterisk (canon + guard, now + consumption).
component:audit 0 BROKEN; npm run check at the 61-error baseline; the
size-bundle guard + palette-cascade guard both green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
62815cb485 |
feat(eidos): C6 — universal palette cascade moves into the generator
palette-{slot} is now RESERVED recipe vocabulary: normalizeRecipeTokens
(render-css) appends the full per-scale color:{scale} cascade for every
donor scale in PALETTE_SCALES to any token named palette-track/element/
border/solid/solid-hover/text/contrast — author-declared color:*
declarations win, only absent scales are appended. This is the
structural end of the hand-maintained-subset era: a recipe opts into
the per-instance palette by naming the token, and the generator
guarantees all scales (the 31-vs-33 drift class cannot recur at this
layer). Applies to app-config recipes too, since the hook sits where
every RecipeTokenSet is normalized.
Button's seven manual spreads are gone from base.ts (the helpers moved
into the generator); its generated output is byte-identical. Toggle —
which already exposed palette-* tokens — universalized itself: +330
generated lines, and <Toggle color="teal"> / color="steel" verified
resolving in a live browser (steel and fuchsia are exactly the two
scales the hand-kept lists used to miss). eidos-lint now classifies
donor-scale values on data-color as sanctioned eidos-only vocabulary
(the TSC color:* axis extension) instead of invalid — the morfo enum
keeps declaring the semantic roles, per the pilot's deliberate
runtime-open design. Guard: recipe-css-contract pins the LAST scale of
PALETTE_SCALES for every palette-bearing recipe in the generated css.
Also: the stale '31 physical color scales' test TITLE says 33 (its body
already asserted 33). Recorded in the plan: the eidos suite carries 15
pre-existing failures in 6 files (baselined against HEAD before this
change) — their triage is its own batch. npm run check at the 61-error
baseline; lint toggle baseline green again.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
3095065f0c |
feat(eidos): recipe contract R-4.x + motion-channel migration 15/15 + theme-builder fixes + inventory decisions
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> |
3 months ago |
|
|
044221e4c7 |
feat(eidos): 33-color palette + palette-first theme builder + color guards
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> |
3 months ago |
|
|
56eb9bd265 |
feat(ethereal): finalize — own engine is the runtime + dual-engine native path
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> |
3 months ago |
|
|
9fecb80fac |
chore(eidos): fold tabs trigger hover into the --state-* layer
The last neutral component on a bespoke hover. tabs trigger (transparent base) moves to the shared state layer: background: var(--tabs-trigger-bg-hover) -> background-image: linear-gradient(var(--state-hover), var(--state-hover)) Overlay (background-image) keeps it filled-safe for the segmented/pill variant. Removed the now-orphan --tabs-trigger-bg-hover recipe token + regenerated base.css. tabs.css is concurrently being reworked by the other session (segmented variant + focus outline migration, uncommitted). Staged ONLY the hover line via line-level staging (checkout HEAD + sed the single line) so their uncommitted segmented/focus work stays untouched + unstaged in the working tree. Completes the --state-* neutral-hover unification: every neutral component now uses the state layer; only valenced (palette) hovers keep their own. Verified: tabs.css uses --state-hover, token gone from base.css, segmented tokens intact; eidos suite 15 failed (pre-existing) / 255 passed. |
3 months ago |
|
|
3daa0e218e |
chore(eidos): fold color-picker neutral hovers into the --state-* layer
color-picker.css is clean (committed in
|
3 months ago |
|
|
4a90f3e688 |
chore(eidos): prune 16 orphan --{x}-bg-hover recipe tokens (post --state-* rollout)
The --state-* rollout ( |
3 months ago |
|
|
72e8fb57ea |
feat(uix): ColorPicker — ColorSwatch primitive, presets/saved colors, readonly rollout
- 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. |
3 months ago |
|
|
353ad59586 |
fix(uix): --size-* bundle follows the 1:1 font canon (md=16, not 14)
The --size-{tier}-font-size bundle (STATIC_SIZE) still encoded the superseded coupled
model -- each tier used the font ONE STEP DOWN (md->sm=14px, lg->md, xl->lg, xxl->xl),
the "compact control text" doctrine. But THEMING.md section 5 already revoked that on
2026-06-17 ("1:1 universal: control text follows the type scale; md-control is 16px,
not 14") and flagged the bundle's 14px as a stale fossil that "no longer reflects the
rule". This realigns the orphan bundle to the already-decided canon:
STATIC_SIZE md/lg/xl/xxl fontSize -> their own tier (1:1)
=> --size-md-font-size = var(--font-size-md) = 16px (was var(--font-size-sm) = 14px)
No component changes -- the bundle is orphan (0 recipe consumers; recipes already
declare 1:1 directly). This fixes the orphan reference + the docs so the size primitive
stops contradicting the live canon. Also realigns the paired line-height + letter-spacing.
- static.ts: 4 tiers fontSize -> 1:1
- active-eidos-config.test.ts: assertion --font-size-sm -> --font-size-md
- THEMING.md section 5: value, the revoked "two md / compact" box, the orphan note
- base.css: regenerated (--size-* hunks only; the concurrent session's base.ts work
left unstaged)
Verified: eidos suite 15/239 unchanged; no new type errors.
|
3 months ago |
|
|
151873d795 |
feat(uix): squircle is the surface-tier default (hybrid shape doctrine)
Materializes the framework's signature corner. SURFACES (overlay panels +
non-floating cards) carry the continuous (squircle) corner by default; CONTROLS
stay arc. At surface-scale radii (>=~10px) arc and squircle visibly diverge
(premium); at control radii they're indistinguishable, so the split costs no
coherence.
The foundation rule (renderShapeBlocks) ENUMERATES the tier — NOT a
`[data-archetype='content']` hook, which also marks tabs/accordion/collapsible/
table content (non-surfaces) and would over-apply (verified per-attr). `:where()`
(specificity 0) lets a `[data-shape]` override win, so the `shape` prop flips from
opt-IN (activate squircle) to opt-OUT (`shape='rounded'` escapes to arc) on
surfaces. Theme knob `--shape-surface-default` reverts the whole tier; degrades to
arc where `corner-shape` is unsupported (border-radius magnitude is universal).
- Tier A — floating panels [data-{c}-content] (pickers inherit via Popover) + [data-command]
- Tier B — [data-card], [data-banner], [data-radio-cards-item]
- Guard test: surfaces resolve to squircle, controls/rows/pills stay arc
- THEMING.md §30 updated; pilot moved out of card.css into the foundation
Verified live (Chrome 150 renders the squircle); per-attr computed coverage
matches the taxonomy.
|
3 months ago |
|
|
61ea97b5bb |
feat(uix): menu gap canon + keyboard focus-ring fix + overscroll + archetype :where
- floating-gap canon: --floating-gap-menu 0 -> var(--space-1); FloatingContent reads --floating-gap via a reactive $derived (was a one-shot rAF that never reached portaled menus -> they fell back to sideOffset, flush); nav-menu wired to the token via CSS. - focus-ring: exclude item/option/content from the universal archetype ring; menu rows use the canonical highlight on :focus-visible too (no thick keyboard border, no ring framing the whole float). - overscroll-behavior: contain on the ScrollArea viewport + 7 overlay scroll regions (no scroll chaining to the page). - archetypes.css: default rules wrapped in :where() so component recipes always win. - radius decoupled from size (Radix model) in toggle/textarea/menubar/nav-menu/ virtual-list/grid; toggle `rounded` prop. Canon-read + token reach portaled menus pending a fresh-load check (the dev tab cached the floating module) -- see CONTINUE-floating-gap-canon.md. |
3 months ago |
|
|
584df80654 |
feat(uix): v2 demo layout (shared harness) + Button palette-color pilot
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> |
3 months ago |
|
|
c474fc6bae |
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold
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>
|
3 months ago |
|
|
cb1d02a479 |
Revert Phase 5 (SYS-5 / SYS-A33 / THEME-SYS-2) to the post-z-index state
Reverts |
3 months ago |
|
|
6c5616be5e |
fix(eidos): THEME-SYS-2 — dropdown/context-menu disabled items use --opacity-disabled
`item-disabled-opacity` hardcoded `0.55`, off the framework's canonical disabled opacity (`--opacity-disabled` = 0.4, used by Dialog and others). dropdown-menu + context-menu now reference the token — disabled menu items match every other disabled surface. Browser-verified: the resolved token is 0.4. Scoped to the disabled-opacity drift only. The other THEME-SYS-2 candidates are left as justified non-drift: `overlay-opacity 62%` / calendar `day-outside 0.62` are off-scale (no `--opacity-62`; the 0.05-step scale has no slot) named recipe tokens — a deliberate scrim density, which the off-scale doctrine permits; and select `item-description-font-size 0.85em` is a RELATIVE unit with no absolute `--font-size-*` equivalent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
8606232125 |
fix(eidos): THEME-SYS-1 — consolidate overlay z-index into a named scale (+ guard + docs)
The ~12 overlay recipes hardcoded an ad-hoc parallel z-index scale (raw integers
60–99/1200) that duplicated nothing reusable and had drifted out of order
(tooltip 76 < dropdown 80 — a tooltip painted BEHIND a dropdown). They are now a
named scale.
- `STATIC_Z_INDEX_OVERLAY` (static.ts) → emitted as `--z-index-overlay-{inline,
backdrop,content,floating,tooltip,detached,toast}`. A SEPARATE scale from the
global `--z-index-*` ladder (which orders the depth planes) — overlays portal
to <body> as siblings of modals, so they share one flat low band where each
rung sits just above the modal scrim. Mapping them to the 300–900 ladder would
hide a dropdown/select/popover opened INSIDE a dialog (dropdown 300 < modal
700); the combobox recipe already warned about this. `tooltip` now sits above
`floating` (fixes the inversion); `toast` stays above the soma FloatPanel band.
- Every overlay recipe token (`content-z`/`overlay-z`/`inline-z`/`toaster-z`/
`preview-z`) now references `var(--z-index-overlay-*)` — zero raw integers.
dialog/drawer gain an explicit `content-z` rung (drops the `calc(... + 1)`).
- Guard (contracts.test.ts, "overlay z-index against raw integers"): a recipe
`*-z` token must reference the scale, never a bare integer. Proven to catch
drift (a raw `'76'` makes it fail). Local `z-index: 0..5` (avatar/tabs/sticky)
is intra-component relative stacking — out of scope, stays.
- Docs: THEMING.md §35 rewritten to describe the consolidated scale + the
flat-band rationale + the guard; token table gains `--z-index-overlay-*`;
testing-and-tooling.md documents the catalogue guards (VG-8/SYS-1/A31/A30/
THEME-SYS-1).
Stacking order verified from the resolved CSS (deterministic z compare: content
70 < floating 80 < tooltip 90 < toast 1200; dropdown-in-dialog preserved). A
live browser check was blocked by a port conflict with another session's server.
check: 0 new type errors; the 5 guards green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
98954a7c35 |
fix(uix): audit Phase 2 — 5 isolated HIGH findings
- alert-dialog: `: Morfo` -> `as const satisfies Morfo` (restores createAttrs key narrowing; the only morfo using the loose annotation). [alert-dialog-001] - breadcrumb: drop `archetype:'item'` from the display <li> Item part — it pulled cursor:pointer + hover from archetypes.css onto a non-clickable container (false affordance). Matches Timeline's pattern. [breadcrumb-001] - navigation-menu: replace the leaked `$effect.root` (never disposed) with a bare constructor `$effect` that auto-disposes with the component scope. [navigation-menu-006] - select: `scrollSelectedIntoView` now reuses `resolveItemEl` (which CSS.escapes the consumer value) instead of an inline unescaped querySelector. [select-001] - dialog: delete the dead `[data-dialog-trigger]` chrome envelope (the trigger composes <Button>, so it fought Button's recipe on the same element) + its orphaned `trigger-font-size` recipe token; regen eidos css. [dialog-002] Verified: 12/12 provider tests, morfo:check pass, svelte-check 0 errors in touched files, eidos recipe contract unchanged (3 failures pre-existing/palabras), browser-verified dialog trigger (solid Button) + breadcrumb (li cursor:auto). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
31671bb323 |
feat(eidos): Metrics — KPI/stat component (Untitled/shadcn-grade)
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> |
4 months ago |
|
|
4c7e493edc |
feat(charts): stacking + entrance animations (grow/draw/grow-x/bloom)
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> |
4 months ago |
|
|
7092450734 |
feat(eidos): Lote A closes — toast composes IconButton; close family defers to Button defaults
- toast-close now composes the canonical <IconButton rounded="full"> via soma's child snippet (keeps the round dismiss shape); removed the bespoke [data-toast-close] chrome + the orphaned --toast-close-* recipe tokens; the toast item grid still places it in the trailing column. aria-label routes through the common close label. - Per the close-family decision: variant/color defer to Button's canonical defaults (solid/primary) across the family — no hardcoded ghost/neutral. banner-close dropped its ghost/neutral defaults to match popover/dialog/drawer. - component-visual-attrs manifest: toast-close delegates icon-only to IconButton. Verified in browser (toast close = round solid-primary ✕, correctly placed). svelte-check + orphan test clean for touched files. Excludes words/palabras/chronos. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
4 months ago |
|
|
0a43225ec2 |
feat(eidos): Lote A closes — popover + banner compose IconButton; clean drawer orphans
DIM 1 composition (component-coherence audit), close family:
- popover-close + banner-close now compose the canonical <IconButton>/<Button>
(via soma child snippet for popover; direct for the eidos-only banner) instead
of bespoke chrome. They inherit Button's variant/size/color/focus-ring; the
aria-label routes through langs ("Cerrar" / "Descartar"). Consistent with the
already-migrated dialog/drawer closes (solid/primary default; consumer overrides
for subtle).
- Removed the redundant [data-popover-close] / [data-banner-close] chrome from CSS
and the now-orphaned --popover-close-* / --popover-transition-* recipe tokens;
regenerated base.css. banner keeps only `margin-inline-start: auto` for end
placement, scoped under [data-banner] so it out-ranks [data-button] (composing
IconButton flips CSS load order — the equal-specificity bare selector loses).
- Cleaned the 15 orphaned --drawer-close-* recipe tokens the earlier drawer-close
migration left behind, and fixed the component-visual-attrs manifest: drawer +
popover closes delegate icon-only to IconButton, so they stamp no wrapper attr.
eidos suite 9->8 failures (the 8 remaining are inherited: spin-field + palabras +
icon, outside this work). svelte-check clean for all touched files. Verified in
browser. Excludes words/palabras/chronos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
4 months ago |
|
|
c19a72b620 |
feat(charts): zero-dep chart family — $libs/plots engine + eidos components + demos
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> |
4 months ago |
|
|
c1effca1b4 |
feat(timeline): cross-layer Timeline component (history/feed + progress)
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> |
4 months ago |
|
|
1590684c2e |
feat(floating): token-driven canonical gap — foundation + popover pilot
floating-ui's numeric `sideOffset` can't take a token, so the trigger->panel gap was a hardcoded per-component number (0/4/6/8, inconsistent). Canonize it: register a `--floating-gap` <length> @property, set it per archetype via a foundation rule (`--floating-gap-menu: 0`, `--floating-gap-panel: --space-1-5`), and have the shared positioner (soma FloatingContent) read the resolved px off the content and feed it as the offset -- token-driven (follows density x scaling) and arrow-safe (the arrow rides with the offset). A content opts in by stamping `data-floating-gap`; otherwise the positioner falls back to its numeric `sideOffset`, so the rest migrates one at a time. Pilot: Popover.Content stamps `data-floating-gap="panel"`. Verified in-browser: overriding `--floating-gap-panel` to 20px moves the popover gap to 18px (20 minus the popover's ~2px border overlap); the default 6px is a no-op vs the old sideOffset=6. check 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
c00bac1345 |
feat(eidos): action components — fab, onion-menu, button-group, split-button
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> |
4 months ago |
|
|
3ae9ff339a |
fix(eidos): --radius-default sm → md (radius archetype default)
Reconsidered Decisión 2 against the reference systems: the default radius should equal the eidos control center-of-gravity (button / field / select-trigger = md / 6px), like Bootstrap / Chakra / Radix-medium. `sm` (4px) would leave the default tighter than the controls themselves — backwards — and would shrink any control that later adopts the token; md makes that adoption a no-op. `--radius-default` = var(--radius-md). Verified in-browser: resolves to 6px. check 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
845d6579d1 |
feat(eidos): radius foundation — --radius-default + --radius-factor + scaling-responsive (A2)
First increment of the A2 (radius) phase. The radius scale was fixed px; now: - `--radius-default` (= var(--radius-sm)) — the archetype default radius for components without a radius reason of their own (Decisión 2 / §13). - `--radius-factor` (default 1) — a global Radix-style roundness knob; a theme sets it on :root to dial all corners up/down at once. - Radius responds to `--scaling`: each real radius emits `calc(base * var(--radius-factor) * var(--scaling))`. `none` (0) and `full` (the pill sentinel) stay verbatim. At factor=1 + scaling=100 both resolve to 1 → identical output (zero regression). Verified in-browser: md=6px default · --radius-default=4px · factor 1.5→9px (at :root) · scaling 1.1→6.6px · full=9999px. check 0. Pending (A2 increment 2): components consume --radius-default + adopt [data-shape-nest] (drop the hand-rolled calc(--radius-md − space)); panel-radius sub-decision (modal xl vs overlay lg). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
d578f83281 |
feat(select, combobox): plane-constant popup elevation (B2)
Per the elevation doctrine (variant = emphasis, elevation = plane), the select / combobox popups now take their surface · border · shadow from the `overlay` plane via `data-depth="overlay"` — constant across variants. The variant (surface / outline / ghost) tints only the trigger / control, no longer the popup elevation. - Drop the content's own border / bg / shadow + the per-variant elevation overrides (outline/ghost were `raised` / lighter / borderless). - Point the arrows at `--depth-overlay-surface` / `--depth-overlay-border`. - Prune select's 6 now-orphaned `content-*` elevation recipe tokens. Verified in-browser (measured): combobox surface AND ghost both paint bg oklch(0.285) + 1px border + overlay shadow from the plane (ghost was transparent/borderless before — now flattened to the plane). select is the identical change; its open popup hangs the preview renderer (no console errors — preview infra), so it rides the same plane mechanism + check 0 + eidos-lint 0 invalid + contract test (no new orphans). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
009f5367a7 |
feat(menu-family): adopt the `overlay` elevation plane (B2)
Extend the dropdown's overlay-plane adoption to the rest of the menu family:
- context-menu: Content / SubContent carry `data-depth="overlay"`; prune the
recipe's content-border / bg / shadow tokens + point the arrow at
`--depth-overlay-surface`.
- menubar: its per-menu panel reuses `[data-dropdown-menu-content]`, so the
dropdown's prune (
|
4 months ago |
|
|
f80f222596 |
feat(dropdown-menu): adopt the `overlay` elevation plane (B2)
Content / SubContent now carry `data-depth="overlay"` and take their surface · border · shadow · halo from the overlay plane instead of the recipe. Prune the now-redundant recipe tokens (content-border-width / border-color / bg / shadow) — the plane is the single source, no double-paint — and point the arrow fill at `--depth-overlay-surface` so it always matches the panel. Radius / font / color stay the panel's own. Verified in-browser: open menu paints bg oklch(0.285) + 1px border + shadow/halo from the plane; orphan-token contract test passes; check 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
fe597f3fa9 |
fix(motion): audit follow-ups — spring reduced-motion, Motion+loop exit, loop tokens
Three fixes from the motion-ecosystem audit:
- `spring()` honours `ctx.reduced`: snap to the target and settle instead of running
the physics. The engine only short-circuits `reduce: 'instant'`, the driver ignored
`ctx.reduced`, and Presence never applied the declared CSS `fallback` — so spring-pop
played the full bounce under `prefers-reduced-motion`.
- `<Motion>` leave(): a loop preset is infinite — detect `animation-iteration-count:
infinite` and unmount promptly instead of holding the node for one loop iteration
(1–2s, still looping) on removal.
- Loop durations are now canonical `:root` tokens (`--motion-loop-{name}`), declared
like the `--duration-*` scale instead of living only as a `var()` fallback.
Tests: engine-motion (spring reduced) + motion (loop tokens); check 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
9e4187bc3c |
feat(motion): loops + state-domain + staggered-exit + debug-stagger
Close the content domain of the universal `motion` prop (RFC §D.12/§D.13):
- State domain: a dedicated `data-motion-state` attribute lets a component with
its OWN data-state machine (Card selected/idle) drive a preset on its transition
without clobbering its semantic `data-state`. Enter-only (animate-on, snap-off).
New emphasis preset `select-pop` (no opacity-from-0, for still-visible elements).
- Loops (content domain, infinite): Tailwind's canonical `spin`/`pulse`/`ping`/
`bounce`, emitted as un-gated `[data-animation-style='X'] { animation: … infinite }`
rules. Themeable per loop via `--motion-loop-{name}`; reduced motion stops them.
- Staggered-exit bridge: the container-driven cascade now covers exit too —
`[data-stagger][data-state='closed'] > …` cascades children OUT in reverse
(`--motion-stagger-index-rev`, last leaves first). The container is retained for
the stagger window then unmounts (pragmatic CSS bridge for bounded lists; the
parent-waits-for-children version still needs the retired PresenceGroup).
- `[data-debug-stagger]`: opt-in dev affordance — a CSS counter badge per child
showing its `:nth-child - 1` index (the analog of UIX_DEBUG_MOTION).
- Fix `<Motion>` exit: a `{ duration }`-only Svelte transition never completes (the
node sticks at the exit frame, a re-show reuses the dead node); add a `tick` no-op
so it times the unmount.
Generated base.css regenerated; motion.test 27/27; docs (eidos-motion + RFC) updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
5a132ead22 |
feat(eidos): button audit — focus canon, press archetype, intrinsic box, plain pure-emphasis, spinner via Spinner
- focus ring: adopt canonical `--focus-ring-*` (outline, HCM-safe, primary-tint) — drops the per-color fork - press archetype: new `STATIC_PRESS` primitive (`--press-scale: 0.985` / `--press-duration: 80ms`) emitted from foundation. Button switches from hardcoded `translateY(1px)` to canonical `scale(var(--press-scale))`. Card recipe repointed to the shared token — single press signature across the system - intrinsic box: `inline-size`/`block-size: fit-content` defends the Button against flex/grid stretch (was filling 400×160 in a grid; now 94×36). `data-block` still opts into width-stretch, `data-icon-only` still square - variant=emphasis only: `plain` no longer mutates layout (`min-block-size: auto` + `padding-inline: 0` removed) — same control box as the other 5 variants. Inline link-like actions belong to <Link>, not here - spinner composition: Button drops its bespoke SVG ring + `@keyframes button-spinner-spin` + `--button-spinner-duration: 720ms` and composes the canonical <Spinner variant="ring"> via two new modes: `presentation` (no role=status/aria-live, marks aria-hidden — Button owns the a11y) + `color="inherit"` (currentColor + 25% track). One spinner across the system, unified duration (0.9s) and reduced-motion behaviour Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
9d40ebe9c8 |
feat(motion): event-driven item cascade — first real consumer (DropdownMenu)
Productizes the event-driven cascade on a real component (not the explicit `<Cascade>` orchestrator). When a DropdownMenu opens, its items fade in staggered by structural order — the appearance flows from the `open` (emerge) event, nothing in the app wires it. Architecture: a +1 GENERIC selector branch in the motion generator, NOT a per-item `data-state`. A `menuitem` isn't open/closed — the menu is — and eidos writing a state attr is a smell. The new container-driven form `[data-stagger][data-state='open'] > [data-animation-style='X']` fires each child's ENTER off the container's `data-state` (which soma already writes via the morfo `commits`); items carry only `data-animation-style`. ENTER only: the coordinated EXIT cascade (retain the container until children finish) is the deferred soma PresenceGroup work, so a closing container can't drive a child exit that would be cut off on unmount. - render-css.ts: `renderCssPresetRules` adds the container-driven enter branch (same declarations, shared comma selector) — reuses the existing presets + the foundation `:nth-child` stagger index; zero new system. - DropdownMenu: Content marks itself `[data-stagger]` + `--motion-stagger-each: var(--motion-stagger)` (20ms, themeable, no magic number); item/checkbox-item/radio-item carry `data-animation-style="fade"` (overridable). `fade` so item opacity doesn't fight the panel's own scale. - regen generated/base.css (8 css presets gain the container-driven enter). Verified in browser (frozen frame): index from `:nth-child`, `delay = idx × each`, opacity gradient in flight (Ruler 0.98 → Grid 0.89 → Guides 0.69, Log out 0.00). Honest limit (documented): only DIRECT-child items cascade — items in a <Group>/<RadioGroup> DOM wrapper aren't `:nth-child` of the panel, so they appear instantly (the "transparent intermediate"). Docs: dropdown README §Motion, cascade README §2 (concrete consumer), RFC §D.11.4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
27012a5c68 |
feat(motion): structural stagger index + Cascade orchestrator on the verified model
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> |
4 months ago |
|
|
6307d03055 |
refactor(motion): retire parallel orchestration motor (Plan A) + verified model + Fase 1 prototype
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> |
4 months ago |
|
|
b8637976a0 |
feat(uix): elevation archetype (A1) + coherence audit + popover/dialog/option-list polish
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> |
4 months ago |
|
|
e16c169f37 |
feat(eidos): color-field leading value swatch + full-width coherence
- <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>
|
4 months ago |
|
|
baa1604f5b |
fix(eidos): resolve segment-min-width orphan; color 3ch / hex 6ch segment width
The prior commit (
|
4 months ago |
|
|
e0bb7ed5f5 |
feat(eidos): shared field control-trigger + segment state/width layers; uix- id prefix
Consolidate the per-field / per-picker duplication the audit surfaced: - Shared control trigger — new field/field-control-trigger.css selected by a `data-field-control-trigger` marker: flush (no border, no shadow), UNIFORM neutral hover tint (not the field accent), font-relative size. Migrated date-picker + the password visibility toggle; deletes their per-picker chrome. (date-range / time / time-range pickers still to migrate.) - Shared segment state layer — new field/field-segment-state.css selected by a `data-field-segment` marker (date / time / color fields): one hover (editable) + active (focus) rule. Fixes a specificity bug where the hover selector out-ranked :focus, so hovering the active segment downgraded its highlight. - Segment fixed width — min-inline-size: calc(2ch + 2*segment-px) + font-variant-numeric: tabular-nums so a segment never reflows when its value changes 1<->2 digits or between glyphs (no horizontal jitter). date-field done. - date-range-picker trigger uses the same calendar icon as date-picker (was a distinct calendar-range glyph). - Generated element ids use the framework-neutral `uix-` prefix instead of the layer name `soma-` (createId / useId + announce / menubar inline ids) — the doctrine that bans data-soma-* / --soma-* keeps a layer name out of ids too. - field.css: label + helper text scale one step below the control (font-relative calc); optional-indicator weight to regular. Foundation tokens added: --field-segment-hover-bg, --field-control-trigger-hover-bg. Verified in-browser by measurement (date-field segments: states + no-jitter; date-picker trigger: flush + uniform hover). check = 58 baseline (0 new); recipe-css-contract clean (only pre-existing palabras WIP). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
a9111572cd |
feat(eidos): universal 1:1 control-text scaling + icon font-coupling
Supersede the "compact control md=14" archetypes (Fase 7) with a single rule: control text scales 1:1 with its size (md=16) across all components, so changing --font-size-md re-themes the whole system. Applied to button, badge, breadcrumb, calendar, pagination, radio-group, toolbar, file-upload, tag-group, stepper, toggle, tooltip (the field family was already 1:1). Button + search-field icons now follow the font 1:1 (icon ~= font+2; density-independent, like the type scale). Fix button icon vertical centering: the [data-button-icon] slot didn't flex-center, so the inline <svg> sat on the text baseline ~1px below the button center. Exceptions left as-is (not control text): avatar (initials sized to the circle diameter), accordion (section-heading prose scale), password-field icon-size (it is the visibility-trigger button box, control-coupled by design), radio-cards (icon follows the card title). THEMING.md section 5 rewritten: 1:1 universal mapping + "density is orthogonal to typography" (density scales space + control-height only; font + icon ride only the global --scaling zoom). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
8f84b36f6d |
feat(eidos): field-family font-scaling coherence (input 1:1 + label step-down)
- 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> |
4 months ago |
|
|
dc62a0268b |
feat(eidos): canonize theming scales + tokenize magic numbers
Audit-driven canonization of the eidos theming layer: a canonical scale that components bypass with literals drifts into N variants. Every axis is now retunable-by-theme AND consumed via token, never a bare literal. Bloque A/B (scales): --blur-* (Tailwind-aligned, depth planes consume it); inner-shadow --shadow-inset-* (mode-aware) for the recessed plane; gradient angle/named tokens; --breakpoint-* sourced from ActiveDom (runtime, dev-settable) + container queries; opacity dual numeric+semantic scale; border-width none/thin/medium/thick/heavy (adds real 3px); tracking caps/widest. Fase 7: 3 size archetypes documented + coherence guard (no px/rem in recipe font-size/icon-size). Bloque C (magic numbers): z-index 80/50/99 (combobox/nav-menu/drag-drop) -> recipe content-z/preview-z tokens (overlay micro-band soma mirrors); on-scale durations -> var(--duration-*) (dialog fast/slow, card slow); 12 single border/ring widths -> var(--border-width-*), value-preserving. Off-scale kept only as justified recipe tokens (continuous spinner/loading periods, sub-fast press). Self-contained dimensional scales (avatar ring, ring-thickness) left whole. Docs: THEMING.md SS35 + SS6 + SS29, THEMING_NOTES, README, TSC. Excludes words/palabras/chronos (active dev tracks). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
4ff9f0d277 |
feat(motion): modo (c) — composición sema + motion (opt-out visual en eidos)
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>
|
4 months ago |
|
|
48b183672a |
feat(motion): servicio de coordinación de presencia (M1–M6)
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>
|
4 months ago |
|
|
145e5b020c |
feat(eidos): icon follows component font (button sm + radio-cards) + fix button icon sizing
Apply the icon↔type correlation (option 1): the icon follows the font the
component actually shows, no font step-up. button icon-size-sm: icon-xs→icon-sm
(follows its font); radio-cards icons follow the card title (16/16/18/18/20,
were 18-32 hand-inflated). STATIC_SIZE's font step-down stays — control-density
convention, not a bug.
Fix pre-existing button bug: the Icon's inline `width: var(--icon-size)` beat
`[data-button] svg { inline-size }`, pinning the button icon at 18px regardless
of size. button now feeds `--icon-size` from `--_button-icon-size`, so the icon
follows the button size (14/16/16/18/20). Verified in browser.
CONTINUE-icon-type-scale.md updated with the resolution + a pending audit of the
same Icon-sizing bug in other components.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
4 months ago |
|
|
6b29e7dc44 |
feat(eidos): tie icon scale to type scale + button icons on tokens
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> |
4 months ago |
|
|
e5303f8217 |
fix(eidos): button tonal variants render bg/border + variants×colors gallery
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>
|
4 months ago |
|
|
e7a3d9085b |
fix(eidos): QrCode logo visibility + demo content presets
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> |
4 months ago |
|
|
e0579f7e24 |
feat(eidos): add QrCode — themeable QR with own zero-dep encoder
Own QR encoder in $libs/qr (ISO/IEC 18004: segment analysis → Reed-Solomon over GF(256) → matrix layout → 8-mask penalty selection), no npm dependency. Eidos <QrCode> renders the module matrix as one SVG path with square/rounded/dots cell shapes; function patterns stay square (via the encoder's `reserved` mask) so every shape scans. Optional logo clears the centre + auto-boosts ECC to H. <QrCode.DownloadTrigger> exports PNG/SVG, baking resolved colours into the clone first (a serialized SVG loses the document's CSS custom properties). Fixed-tone default colours for scannability; themeable via color/background. morfo qr-code (scope: eidos; parts Provider/Pattern/Overlay/ DownloadTrigger) + 'path' added to MorfoElement; recipe with 11 --qr-code-* tokens; canonical demo + nav entry. 11 encoder unit tests; verified end-to-end via jsQR round-trip + browser. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
4 months ago |
|
|
f59396b51a |
feat(uix): RadioCards — card-style single-select reusing RadioGroup
New eidos component built ON TOP of RadioGroup with zero duplicated behaviour. RadioCards reuses RadioGroup's soma wholesale (selection, roving focus, role=radio, native <form> via HiddenInput) and adds only the card chrome: icon · title · description + an always-present corner radio. Vertical, horizontal and grid layouts. - morfo: scope:['eidos'], thin contract for presentation parts only (Provider marker, Item-card, Indicator, Icon, Content, Title, Description). The interactive contract + commit-select stay in radio-group, reused via its soma. No new soma, no new sema. - eidos: `radio-cards` recipe (card chrome) + wrappers + radio-cards.css. radio-group.css is NOT loaded here, only its foundation tokens — the card owns its visual, reusing --_radio-group-palette-* (inlined, since a :root token referencing the palette is invalid-at-computed-value). - demo: canonical structure (always-on stage + trace, tabs, chip/switch controls, Untitled-UI example patterns) + nav entry + README. check 0 · eidos-lint 0 invalid. Browser-verified: select (click + keyboard roving), disabled, the three layouts, controls drive the preview. Follow-up: icon/circle sizes are still px (mirroring radio-group's own size tokens) — making the control-size scale density-aware is a separate, family-wide change deferred per request. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
4 months ago |
|
|
3c059ff087 |
refactor(eidos): share number-field + css-field visual via spin-field
NumberField and CssField are the same visual (a bordered field + input +
increment/decrement triggers + scrubber, split/stacked layouts, sizes/
variants/colors, themeable glyphs); only their value model differs. They
were two cloned recipes + CSS that drifted — a refinement to one (square
flush buttons, divider, contrast) didn't reach the other.
Unify into ONE shared source (the toggle-group structural-identity pattern):
- New eidos/components/spin-field: recipe key `spin-field` (`--spin-field-*`)
+ spin-field.css with all the stepper-field rules, selecting `[data-spin-
field*]`. Loaded via the foundation @import in index.css.
- number-field + css-field morfos declare structural identity (`data-spin-
field*` presence attrs on each part). The Provider emits them via syncAttrs;
the sub-parts emit them in their soma `props` getter (number-field's soma
hardcodes sub-part attrs rather than syncing the morfo).
- Removed the `number-field` / `css-field` recipe keys; their CSS files are
now stubs. A theme tints one component by scoping `[data-number-field] {
--spin-field-… }`.
- css-field thereby adopts number-field's refined steppers (square, flush,
divider) — the drift fix the user asked for, now structural (no clone).
Verified bit-for-bit in browser: number-field identical to baseline (split
flush, stacked symmetric xs..xl, RTL, glyph token/children override);
css-field now square/flush/divider. eidos-lint invalid 0; recipe contract
passes (no orphans, loads-once); check + morfo:check clean for these.
Docs: THEMING sections 33 (glyph tokens now `--spin-field-*`) + 34 (the
shared layer); number-field / css-field READMEs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
4 months ago |
|
|
bd9d924577 |
feat(eidos): themeable stepper glyphs + css-field compact steppers
NumberField and CssField steppers gain a `stacked` compact layout and themeable default glyphs. - Glyphs render from recipe tokens via `:empty::before` (split plus/minus, stacked up/down triangles); passing children overrides per instance, a theme overrides the token. 4 tokens per component, unicode-escaped. - CssField gains the `steppers` prop (split | stacked); its stacked layout mirrors NumberField (self-contained — the split layout left untouched). - NumberField split: square buttons flush to the field edge with an inner divider and higher hover/focus contrast (control-radius 0). - Stacked rows use minmax(0,1fr) + a height-proportional glyph so the two arrows stay symmetric and uncut at xs. - Demos: steppers controls + stepper-layout / sizes / themed-glyph showcases; code snippets use the themed defaults. - Docs: THEMING section 33, number-field and css-field READMEs. Note: also sweeps in the pre-existing palabras WIP already in the tree. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
4 months ago |
|
|
ded673ad0f |
fix(eidos): unify input focus into a parameterised two-ring model
Each input drew its focus ring differently — the foundation [data-archetype] :focus-visible ring landing on the <input>, ad-hoc per-component input rings (css-field/number-field), the textarea's own color-mix — so editing showed a DOUBLE frame (inner + outer), inconsistent across components. Unify into one theme-parameterised two-ring model: - New --focus-ring-inner-width (0 by default), folded into --focus-ring and every field *-focus-shadow: inner inset ring + outer ring. 0 = single outer frame; raise it in a theme to show the inner line everywhere at once. - Foundation [data-archetype]:focus-visible now excludes form-internal elements (:not(input):not(textarea):not(select):not([data-archetype='segment'])) — their focus is shown by the wrapping control. - Removed the ad-hoc input rings (css-field/number-field); textarea uses var(--focus-ring). - Cleaned the now-dead per-data-color _accent-* cascade in css-field/number-field (recorded as backlog in THEMING.md §32). Verified with real keyboard on field/css-field/number-field/date-field/textarea: single outer frame. Also includes the earlier textarea `resize` fix (morfo data-resize values + soma stops inlining resize + eidos materialises it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
4 months ago |
|
|
7da7285d94 |
feat(uix): cropper, image-picker, image-adjustments + reusable zoom-pan layer
Three new components across all 4 layers (morfo/soma/sema/eidos) + demos + langs + READMEs: - ImageAdjustments — reusable image-filter sliders subcomponent. - ImagePicker — composes file-upload + image + image-adjustments (fill modes, rotation). - Cropper — Ark image-cropper anatomy: normalized 0-1 crop rect, rect + round shapes, 8 resize handles, fixedSize move-only (avatar), canvas Blob output (5-arg drawImage for SVG safety), wheel/pan zoom, two-zone gesture (selection moves the area, the background pans to set focus; img draggable=false guards native DnD per zag). New reusable soma/layers/zoom-pan: cursor-centered zoom, clamped pan, transform + viewport-frac-to-content-frac mapping. Image-viewer / diagram-pan / map can reuse it. sema: add 'zoom' verb to the handle family (direct manipulation, documented extension). slider: track visible at zero (gray, not white), thumb lifts on drag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
4 months ago |
|
|
1e6a9df874 |
feat(eidos): drag pickup-lift as a global motion token (--motion-scale-lift)
A grabbed surface scales slightly toward the viewer (depth 'picked up' cue). This
is a cross-component primitive, not a float-panel literal:
- New --motion-scale-lift (1.02) in STATIC_MOTION.scale + MOTION_SCALE_KEYS (the
only >1 in the scale family); validated by validateEidosConfig.
- FloatPanel consumes it: [data-float-panel-content][data-dragging] { scale:
var(--motion-scale-lift) } + will-change: translate, scale, eased lift/settle,
static during the move (composited), suppressed under reduced-motion.
- Documented in eidos-motion.md §11 (primitives table) and THEMING.md §14 (new
'Superficies arrastrables: el lift de pickup' pattern) so any draggable surface
(slider thumb, sortable item, drawer) reuses the SAME token, not its own literal.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
4 months ago |
|
|
b3ef470646 |
feat(uix): FloatPanel — draggable/resizable floating panel
New general-purpose overlay component across all 4 layers (morfo/soma/sema/eidos) plus a shared stacking layer and i18n catalog. - Drag (2D free, anchored→free) + resize with device-pixel-snapped, GPU-composited 1:1 movement; elevation lifts on pointerdown for the pickup metaphor. - Stages: minimize / maximize / restore (StageTrigger, computed maximized geometry so restore needs no saved-rect bookkeeping). - Composable header buttons: Controls + Action (custom) + Close + StageTrigger. - Keyboard a11y (the industry gap): grab-mode move/resize on the focusable header and a se-corner ResizeGrip (pointer + keyboard), with debounced live-region announcements, bounds/min-max cues, aria-roledescription/keyshortcuts. - Stacking registry (bring-to-front/topmost), Presence motion preset (scale-fade), es/en langs catalog. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
4 months ago |
|
|
7ff1738a12 |
feat(uix): add CssField — validated CSS-value spinbutton + sium CSS helpers
New 4-layer UIX component for CSS dimension values (px/rem/%/vh/…) modeled on number-field: allowedUnits whitelist, optional increment/decrement triggers, pointer scrubber, keyboard stepping, wheel, Field integration. Validation is ambivalent — built-in pure sium functions by default, or delegate to a sium schema via the validate prop (untilFix feedback on invalid units). sium: parseCssValue/formatCssValue/isCssValue/cssValue + CSS unit vocabulary in arts/sium/types/css.ts; css_value issue code + es/en messages; tests. morfo/soma/sema/eidos: css-field across all layers; recipe tokens + interactive demo + sidebar link + READMEs. Verified: check 0 errors; sium 395, soma css-field 9, morfo+sema 214 tests; morfo:check + eidos-lint clean; browser render/behavior/sema-trace confirmed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
4 months ago |
|
|
e277fd0547 |
palabras: panel de imagen rico (componentes del framework) + fit/filtros en el motor + cssLength en sium
- Motor (words): ImageBlock gana `fit` (fill/fit/crop/tile) + 7 filtros CSS (saturate/brightness/contrast/hueRotate/grayscale/sepia/blur) + `width`/`height` number|string (CSS-length). Render: object-fit + branch tile bg + figcaption. Fase A: caret-carry al insertar, guard columnas-en-columnas, deleteColumnAt siembra parrafo cuando el superviviente queda vacio. block-path.test.ts (14). - Panel de imagen reescrito con primitivas eidos (ToggleGroup/Switch/Slider/ NumberField/Accordion), labels xs sans arriba del slider, header con icono definitorio del bloque, secciones pinned + accordion collapsible, footer band. - sium: `cssLength()`/`isCssLength()`/`CSS_LENGTH_REGEX` en core/refines.ts, exportados por la fachada EngineSium. El panel valida unidades CSS (data-invalid). - accordion: tamano `xs` (types + recipe base.ts + accordion.css + generated). - words-activate: guard atomico (lastAtomicAt + isActiveBlockAtomic) evita que el caret-follower degrade el bloque imagen activo a heading. - saturate/contrast max 200% (estandar, antes 300% inventado). Caption visible: fit-frame movido de figure a img/tile. Sin tocar la capa sema ni la animacion (restriccion del usuario). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
4 months ago |
|
|
913d49d0ea |
words: harmonize inspector control tier (one height, one body font)
The block inspector mixed three control heights (chip 26 · number-field 28 · color-trigger 36px) and two fonts (chip/trigger 10px vs label/value/input 12px), so the PRIMARY control — the chip — was the smallest text in the panel. - New recipe tokens `--words-control-height` (1.75rem — was a magic literal on the number-field) and `--words-field-width` (7.5rem), applied uniformly to the whole inline tier (chip · color-trigger · number-field) → one 28px height + an aligned right-edge column. - Chip + color-trigger lift to the 12px body tier (the ToggleGroup `xs` is kept ONLY for its compact padding; lifting the font without the padding keeps the four-segment groups on one line). - Cramped long labels shortened (Predeterminada→Auto, Semi-negrita→Semi) so Fuente/Grosor read as four even segments. - Collapsed the dead, inconsistent font-size fallbacks (`xs, 0.6875rem` / `xs, 0.75rem` / `sm, 0.8125rem`) to the bare declared token. Chrome scale is now a clean 14 (section) / 12 (body) / 11 (micro). Verified in browser: chip · color-trigger · number-field all 28px·12px, no group overflow. 19/19 recipe contract green; check adds zero errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
08f2d82d94 |
fix(words): one sans across all chrome — kill the mixed-typeface inspector
The inspector mixed THREE fonts: labels in Lora (serif!), titles in system-ui, inputs in Instrument Sans. Root cause: eidos's "content" archetype (Accordion content) maps to the reading serif, and the inspector's `[scope] *` rule is only (0,1,0) — equal to the component's `[data-x]` font rule — so the Phase 3 code-split flipped the load order and the serif won. Fix: a `--words-chrome-font` token (= --font-ui, independent of the content `font-family` a theme may set to serif) + a rule forcing it across EVERY chrome surface (toolbar·bubble·slash·drawer·inspector·gutter menus) with a DOUBLED scope attribute (0,2,0) that beats any single-attribute component font regardless of code-split order. Verified in browser: all chrome = Instrument Sans (was Lora/system-ui/Instrument). Content keeps its reading font. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
ec77ec5a0b |
refactor(words): prune 55 orphan recipe tokens — eidos suite green (Phase A)
The words recipe declared a token vocabulary the CSS never used (the root of the drift). Pruned 55 declared-but-unused tokens: 38 direct (gap-*, toolbar-* ×9, button-size-*, command-*-hover, content-px-*, status-*, rail-*, selection-color, _accent-* ×5, line-height, max-inline-size, heading-font-size, content-py-after-placeholder) + 17 cascaded (the intent palette *-solid/ *-solid-hover/secondary-* slots that only fed _accent-* — callouts use the tinted track/border/text slots, kept). No wire candidates (rail had no consumer; ::selection already uses wash-soft). The recipe contract now matches actual usage → the orphan test goes green; full eidos suite 197/197 (all 3 words failures now fixed across increments 1/3/A). Visually inert: words.css references nothing removed (the "declares every var" test guarantees it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
059f36d2fe |
refactor(words): unify radius scale (audit increment 2)
The small-control radius was a soup of 8 syntaxes for the same thing:
4px / var(--radius-2,4px) / 3px / even var(--space-1) (a spacing token used
as radius). Added --words-radius-xs (= --radius-sm, 4px) — the missing
small-control step under sm — and snapped all of them to the
--words-radius-{xs,sm,md,lg} scale. Micro decorative radii (1-2px) and circles
(50%) left as-is by design. Verified radius-xs=4px, editor renders, no
regression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
54d1f658ab |
refactor(words): wash scale + token contract fix (audit increment 1)
Kills the biggest visual-inconsistency soup: 18 ad-hoc color-mix opacities
(4/6/8/12/14/16/22/30/35/40/45 %) collapsed to a 4-step canonical scale
--words-wash-{faint,subtle,soft,strong} (6/16/30/44 %), applied across
words.css; geometry %s (circles, widths) untouched. Hardcoded transition/
animation durations (120/140/100 ms) collapsed to --words-transition-duration.
Declared the 2 used-but-undeclared tokens (--words-font-size-xs, --words-swatch)
→ the "declares every public var" recipe test goes green (1 of 3 words failures
fixed). Editor verified in browser, no regression. See components/words/AUDIT.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
95be3b6cb1 |
perf(eidos): prune base.css to role-referenced scales (Phase 5) — −18 KB gz
base.css shipped all 31 Radix donor scales, but the theme's roles only
reference 9 (gray green indigo orange plum purple red slate teal). The
other 22 were dead weight on every page: no component references raw
--scale-* (they use --color-{role}-*), and runtime theming builds from JS
scale data and writes RESOLVED values (build-scheme: variables[k]=hex),
so it never reads the CSS scale tokens.
renderThemeCss now emits only role-referenced scales by default; the full
31-scale donor palette ships opt-in in generated/palette.css (new
renderColorPaletteCss). The CSS contract stays the full vocabulary
(apps can still override any scale; load palette.css to use the extra 22).
base.css 53.7→35.4 KB gz. Monolith (every page) 54.4→36.2 KB gz —
combined with Phase 3, 113.5→36.2 KB gz total (−68%). Verified: roles
resolve (primary=purple, button bg correct), pruned scales empty on
regular pages, restored on /temas/color via palette.css. Updated the
contract test to verify coverage against the full palette. check clean
(13 errors are untracked heroscrolling demo + gitignored tmp/lexical);
eidos suite at baseline (192/195, 3 pre-existing words failures).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
7255a52a0a |
feat(eidos): shape Fase 3 — eventful corner morph (the differentiator)
Shape becomes a two-moment channel: resting form (Fase 1) + the morph (event).
Folded cross-modally into the existing `press-squeeze` contact firma — one press
now drives scale (motion) + shadow-flatten (depth) + corner-firm (shape): the
`continuous` corner squares up at the press peak (--shape-smoothing 2 -> 3 -> 2).
- `@property --shape-smoothing { syntax: <number>; inherits: true }` so the number
interpolates smoothly (mirrors the motion @property pattern).
- Set only at the keyframe peak -> interpolates from/to the element underlying
smoothing (respects a theme override). No-op for non-continuous corners (they do
not read --shape-smoothing). Degrades with reduced-motion via the firma system.
Material 3 Expressive is the only other system with shape morph (bespoke, closed);
this rides the unified firma — one event, many modalities.
Verified: check 0 errors; eidos config+motion+generated 78/78. Regenerated base.css.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
e04c098787 |
feat(eidos): shape Fase 2 — concentric nested-radius harmony
`--shape-nest-gap` token + `[data-shape-nest]` rule: a nested element derives `border-radius: max(0px, var(--shape-outer-radius) - var(--shape-nest-gap))`, so its corners stay parallel to the parent (concentric). The parent exposes its radius via `--shape-outer-radius` (inherited); gap defaults to the nest token. nestGap is optional on ShapePrimitiveSet (a shape system can omit nesting); the token + rule are gated on its presence. Only SwiftUI (recent, platform) has concentric corners — none on the web as a token rule. Verified: check 0 errors; eidos config+generated 55/55. Regenerated base.css. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
7e26608ee0 |
feat(eidos): shape engine — Fase 1 (continuity + families) + SHAPE_ENGINE_RFC
The 8th book channel (forma) — the one no web design system has elevated. Fase 1
lays the foundation: corner continuity + perceptual families as portable tokens.
- ShapePrimitiveSet (config-driven): `smoothing` (superellipse exponent, 1=arc ->
2=squircle) + `families` map. STATIC_SHAPE ships rounded/continuous/cut/scoop.
- Emission: `--shape-smoothing` token + `[data-shape=`{family}`]` rules setting
`corner-shape` (round / superellipse(var(--shape-smoothing)) / bevel / scoop).
Opt-in: magnitude stays in `--radius-*` (untouched), so corners degrade to the
plain border-radius arc where `corner-shape` is unsupported (progressive, like
the wide-gamut oklch of color). var() works inside superellipse() (Chrome 146).
- Validation (validateShapePrimitives) + 2 tests + regen.
Why it matters: the whole web field (Tailwind/shadcn/Chakra/Mantine/Ant/Radix
Themes/Carbon/Fluent/Spectrum/Polaris/Primer) is "radius scale + circular arc +
static". Continuity exists only in Apple (platform-locked); none on the web ships
squircle as a token. This is the first.
Verified: npm run check 0 errors; eidos config+generated 54/54. data-shape applies
in the live runtime (continuous->squircle, cut->bevel, scoop->scoop).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
6998c5bde2 |
feat(eidos): depth Fase 4 — atmosphere (frost) + applyDepth runtime builder
Closes the depth channel. Atmosphere as the channel's materials layer:
- Per-plane `blur` cue (overlay 10px, modal 16px) + an opt-in frost rule
`[data-depth=`{plane}`][data-frost]` -> translucent surface (color-mix 80%) +
backdrop-filter blur. Gated on data-frost so it never turns an opaque overlay
translucent by default; specificity 0,2,0 reliably overrides the component bg.
- buildDepth(planes) (pure) + ActiveEidos.applyDepth/clearDepth — retune any
plane cue (surface/shadow/halo/blur/scrim/z) at runtime, the depth sibling of
applyColorScheme / applyTypeScale. Exported from $uix/eidos.
Showcase: /temas/profundidad section Materiales — a frosted-glass panel over a
vivid color mesh (frost blur + shadow + halo).
The `scrim` cue stays an available token without a wired rule — modal backdrops
are component-managed.
Verified: npm run check 0 errors; eidos 185/188 (3 pre-existing `words` failures,
unrelated). New tests: frost emission + applyDepth. Regenerated generated/base.css.
Docs: DEPTH_ENGINE_RFC Fase 4 + token contract, THEMING 29.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
271a2f8ff0 |
feat(eidos): depth adoption — elevated components consume the channel (shadow + halo)
The Fase 2 rim halo now reaches real UI, not just the showcase. Each elevated
component's shadow signal (recipe tokens in recipes/base.ts + the few direct
box-shadow uses) now composes `var(--depth-{plane}-shadow), var(--depth-{plane}-halo)`.
Reaches: popover, dialog, drawer, dropdown/context/navigation-menu, menubar,
select, tooltip, card, combobox, command, link-preview, words.
Shadow signal only — z-index stays component-managed (the z bands are finer than
the 5 planos), so zero stacking risk. Verified: dialog keeps its own z-index 71
and composes drop shadow + oklab halo. Retuning a plane now retunes every
component on it.
Verified: npm run check 0 errors; eidos 183/186 (3 pre-existing `words` failures,
unrelated). Regenerated generated/base.css. THEMING 29 updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
0e0fbc05d7 |
feat(eidos): depth Fase 2 (oklab rim halo) + reference-grade /temas/profundidad
Depth engine — Fase 2 (mode-adaptive mezcla): - New `halo` cue per plane: a top-edge rim-light computed in oklab (color-mix(in oklab, white N%, transparent); 5/7/8% on raised/overlay/modal). The `[data-depth]` box-shadow now composes `shadow, halo`. Invisible on light surfaces (the drop shadow leads), the lift cue on dark surfaces (where the drop shadow barely shows) — the mode-adaptive answer to "shadow lies in dark", scoped to the depth channel (global --shadow-* untouched). - Wired through config-types (DepthPlane.halo) + render-css (declare + compose) + config validation + STATIC_DEPTH + regenerated generated/base.css. Showcase — /temas/profundidad to reference depth (4 -> 9 sections): matches Material elevation catalog breadth and adds the two axes it lacks (eventful + open cage): - Responde a cada estado — dynamic elevation, live interactive control - La escalera de planos — z-stack of the 5 planes - Catalogo de planos en reposo — the resting-elevation spec table, our vocabulary - Luz vs sombra — light/dark side-by-side showing the halo mechanism - Accesibilidad — never the only channel, reduced-motion, forced-colors, contrast - Fix: mirror data-theme onto <html> so :root depth tokens stay mode-aware Docs: DEPTH_ENGINE_RFC (Fase 2 + 5 done, token contract + halo), THEMING 29. Verified: npm run check 0 errors; depth tests 50/50 (updated the box-shadow assertion to the shadow,halo composition). Pre-existing `words` recipe-contract failures unrelated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
a289ce666f |
feat(eidos): depth engine — Fase 3 (canal eventful / momento-evento)
The novel core: depth as something that OCCURS. The elevation dimension now lives
in the event firma (the two-moment model event moment) — leveraging the existing
signature system, NOT a parallel one:
- present-rise (emerge): the box-shadow grows from flat -> the element resting
plane shadow as it rises. A flat element (no resting shadow) is a no-op; a
raised/overlay element animates its elevation proportional to ITS plane.
- press-squeeze (contact): the shadow flattens to the surface at the press peak,
then returns (recede).
So depth + position/scale + sound + haptic all fire coordinated from one event.
Degrades with reduced-motion (the global events.css cap); a theme overrides the
keyframes/signatures (jaula abierta). No reference framework treats depth as eventful.
Fase 2 (computed mode-adaptive shadow mix) deferred — the shadows are already
mode-aware, so it is a refinement, not a gap (documented in the RFC).
Verified: check 0 errors; motion + config + generated tests pass; browser — the
present-rise keyframe is live with the box-shadow dimension.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
290f5d0c12 |
feat(eidos): depth engine — Fase 1 (plano unificado) + DEPTH_ENGINE_RFC
Arranca el sprint de profundidad: depth como un plano unificado, config-driven y
semántico — la base del canal "la profundidad es algo que OCURRE" del libro (el
disparo eventful por sema llega en Fase 3).
- DEPTH_ENGINE_RFC.md — la guía de diseño: estudio de los límites de los
referentes, la tesis novel, y la doctrina "default fuerte, jaula abierta".
- EidosConfig.depth.planes (DepthPlane + DepthPrimitiveSet). Set canónico:
flush · raised · overlay · modal · recessed — cada uno COMPONE los primitivos
existentes (surface/shadow/z), sin matemática nueva → cero rotura.
- emite tokens --depth-{plane}-{cue} + reglas [data-depth='{plane}'] que aplican
las señales aditivas seguras (box-shadow + z-index); surface + blur/scrim
quedan como tokens opt-in (no pisan fondos de componente).
- validado; config-driven (un tema añade/retunea planos — jaula abierta).
- arregla un punto y coma latente en la emisión de variable-fonts, cazado aquí.
Verificado: check 0 errores; tests nuevos (emisión canónica + plano custom);
generated/base.css regenerado; navegador — data-depth='overlay' aplica la sombra
overlay + z 400, 'recessed' aplica una sombra inset. (3 fallos de words pre-existentes.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
ecc514951f |
feat(eidos): typography component props (Phase 3b) — wrap/tracking/leading/numeric/measure
Wires the Phase 3 scales into <Text> and <Heading> + fixes a token collision the scales
introduced.
- Text/Heading props (additive): `tracking` / `leading` (reuse the existing
--_x-letter-spacing / --_x-line-height vars → override the size-derived values),
`wrap` (text-wrap: balance/pretty/nowrap), `numeric` (tabular/oldstyle →
font-feature-settings), `measure` (max-inline-size). Heading defaults to
`text-wrap: balance` (reference-grade titles); Text defaults to the CSS initial so the
axes are no-ops until a prop is set. Heading reuses Text's scale unions.
- COLLISION FIX: Phase 3a's config-driven --tracking-{tight,normal,wide,wider} collided
with a pre-existing HARDCODED tracking scale in render-css (semantic badge/ui/… +
scale tight/normal/wide/wider, all 0) emitted later → it won (everything resolved to
0, so the tracking prop did nothing). Removed the hardcoded scale lines; the config
(typography.tracking, real optical values) now owns tighter/tight/normal/wide/wider.
The semantic tracking tokens (badge/label/ui/prose/heading/display) stay (recipes use
them, e.g. card-title --tracking-tight now picks up the real -0.02em). leading/features
/measure don't collide (distinct keys).
Verified in browser: tracking-wide 0.02em -> 0.32px; tracking-tight -0.02em -> -0.32px;
heading default text-wrap balance; wrap=pretty, numeric=tabular, measure, leading all
apply. check 0 errors; eidos suite green (3 pre-existing words failures only).
Note: the pre-existing hardcoded semantic typography block (leading-ui/prose/… +
tracking-badge/…) is still hardcoded, not config-driven — a separate cleanup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
d85c2fd376 |
feat(eidos): typography scales — tracking/leading/features/measure + optical tracking (Phase 3a)
TYPOGRAPHY_ENGINE_RFC Phase 3 (engine tokens). Additive scales, pure CSS, no binaries.
- config-types: TypographyPrimitiveSet += tracking / leading / features / measure
(optional Record<string,string>).
- render-css: emits --tracking-{k} / --leading-{k} / --font-feature-{k} / --measure-{k}.
- typography.ts defaults: tracking (tighter…wider), leading (none…loose), features
(tabular = "tnum"+"lnum" for data, oldstyle/smallcaps/ligatures), measure (54/66/78ch).
+ optical tracking baked into the size tokens: small text slightly looser
(xxs +0.01em), display tighter (xxxl -0.02em) — was all 0.
- config.ts: validates the 4 scales (CSS-value maps).
- generated/base.css regenerated. test: scales + optical tracking emission.
Additive tokens (recipes/components consume var(--tracking-tight) etc.) so the scales
are zero-risk; the only rendered change is the gentle optical tracking on headings/small
text. check 0 errors; eidos suite green (3 pre-existing words failures only).
Next (Phase 3b): wire the component props (wrap: balance/pretty, numeric: tabular,
tracking/leading/measure) on <Text>/<Heading>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
f53299d42b |
feat(eidos): config-driven @font-face (Phase 2) — fonts as theme data (next/font model)
TYPOGRAPHY_ENGINE_RFC Phase 2. Fonts are theme data (each theme owns its families), so
@font-face becomes config-driven + generated — like the color palette — instead of a
separate hand-written CSS file. This matches next/font / Fontaine (config -> @font-face),
above the token-only frameworks (Radix/Tailwind/MUI) that leave loading to you.
- config-types: FontFamily += faces (FontFace[]) / axes (FontAxes) / fallback
(FontFallback, metric-override) / display / preload. Additive — the family stack still
works from `family`+`fallbacks`.
- render-css: renderFontFaceBlocks generates @font-face per face from the config, deduped
by the real font name (a font shared across slots — Lora as secondary+display — emits
once). Optional metric-override fallback @font-face (anti-CLS) injected into the stack
as `'{family} Fallback'` when declared. Emitted first in renderStaticCss.
- typography.ts: the BASE THEME's 14 @font-face migrated from fonts.css into the config
(faces). TTF today (the theme's choice); a theme swaps to woff2/variable + fallback
metrics by editing config only.
- index.css: drops `@import './themes/fonts.css'` — the @font-face now ships in
generated/base.css. (fonts.css superseded; left in place, no longer imported.)
- generated/base.css regenerated (14 @font-face, Lora deduped). test: @font-face
generation + dedup; merge-without-mutation assertion updated for the faces field.
Verified in browser: 3 families registered, files resolve (200), fonts load on demand
(swap). check 0 errors; eidos suite green (3 pre-existing words failures only).
Deferred (capability typed, theme adopts when it has the assets): variable woff2,
metric-override numbers (need fontkit/precompute), <link rel=preload> (head markup).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
667aa49331 |
feat(eidos): fluid typography engine (Phase 1) — Utopia clamp, rem, fluid headings
TYPOGRAPHY_ENGINE_RFC Phase 1. Additive on TypographyPrimitiveSet, behind the frozen
token contract (--font-size-X keeps its name; only the value formula changes, like
color --scale-* hex -> oklch()).
- type-scale.ts (pure, isomorphic, no canvas): the Utopia clamp() formula. fluidClamp /
resolveTypeSize / isFluidSize. rem-based (a11y: scales with browser font-zoom).
- config-types: TextMetric.size accepts `string | FluidSize` ({min,max,minVw?,maxVw?}).
Plain length strings still valid -> backward-compatible.
- render-css appendTypographyDeclarations: emits calc(resolveTypeSize(size) * --scaling)
-> a fixed rem or a fluid clamp; the --scaling axis composes on top.
- config.ts: validateSizeValue accepts a FluidSize (validates min/max/minVw/maxVw) so
the base config validates (was the cascade root — FluidSize objects failed the
string-only CSS-value check).
- typography.ts: sizes in rem; headings (lg/xl/xxl/xxxl) fluid (min @480px -> max
@1280px, max = previous fixed px so desktop is unchanged); body (md) fixed. hero/h1/h2
drop the manual { base, md } responsive sizes — the clamp covers the viewport.
- generated/base.css regenerated. type-scale.test.ts (6 tests). 2 config-test assertions
updated to the new rem/clamp values.
Verified in browser: --font-size-xxxl 40px @480 -> 80px @1280; xxl 32->48; lg 18->20;
md 16 fixed. check 0 errors; eidos suite green (3 pre-existing words failures only).
canvas-text/<SText> unaffected (reads getComputedStyle real font, measures the clamp).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
21b2329a66 |
feat(eidos): prefers-contrast: more — stronger neutral chrome a11y (P3-2 follow-up)
Completes the forced-colors a11y work. Users who request more contrast (macOS "Increase contrast", Windows, etc.) now get strengthened neutral chrome: borders bumped to neutral 7/8/9 (subtle/default/strong) and de-emphasized text to 12/11 (secondary/muted). Solid fills + primary text are already high-contrast, so they stay. renderPrefersContrastBlock emits a @media (prefers-contrast: more) block using `:root:root` (specificity 0,2,0) so it wins over the theme's :root regardless of stylesheet order. Values reference --primitive-neutral-* (resolve from the cascade; a theme omitting them just no-ops the declaration — graceful). Strictly additive (gated by the media query) and strictly STRONGER, so it can't regress the default look. Also marks audit P3-6 resolved: dispose() already routes documentElement access via dom (#lastAttrs.target + dom.apply), no direct document access remains. - generated/base.css regenerated. test: renderStaticCss emits the prefers-contrast block. docs: THEMING §28 + audit P3-2/P3-6. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
76e1877281 |
fix(eidos): consistent surface ladder light/dark — overlay above muted (P3-11)
In the base light theme `overlay` was neutral-3, identical to `muted` (neutral-3), so popovers/menus/dialogs were indistinguishable from muted panels in light mode. Dark already had overlay=neutral-4. Light overlay -> neutral-4 makes the elevation ladder consistent across both modes: default(1) < raised(2) < muted(3) < overlay(4). Verified in browser (light popover): overlay L93% now distinct from muted L95.5% and default L99% (was overlay == muted). Dark unchanged. generated/base.css regenerated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
e6fd014d43 |
fix(eidos): snappier checkbox check — stroke draw 220ms -> --duration-fast (120ms)
The checkmark stroke-dashoffset draw used a hardcoded 220ms while the box fill ran at --duration-fast (120ms) — nearly 2x, so the check read as laggy when toggling. Tokenize it to var(--duration-fast) so the stroke draws in sync with the box (one 120ms motion). Not the motion service (uix.motion) — this is a plain CSS transition in checkbox.css driven by the recipe `stroke-duration` token. Verified in browser: path transition 0.22s -> 0.12s, synced with the box. generated/base.css regenerated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
4eab3306b8 |
feat(eidos): forced-colors focus a11y (P3-2) + role border ramp 6->7 (P3-3)
Closes the two color-quality items from THEMING_AUDIT P3.
P3-2 · forced-colors (Windows High Contrast): under @media (forced-colors: active) the
browser auto-maps borders/text/backgrounds to system colors BUT drops box-shadow — so
the box-shadow focus ring (--focus-ring) vanishes and keyboard focus disappears. The
foundation now always emits a system-colored outline fallback:
@media (forced-colors: active) {
:focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
}
Components that already focus via outline (e.g. Button) keep theirs by specificity; this
is the fallback for the box-shadow ones. renderForcedColorsBlock in render-css.ts.
P3-3 · role border ramp: the per-role `border` slot moved step 6 -> 7. In Radix's
functional scale 6 is a subtle separator and 7 is the UI element border; step 6 read
washed-out on real element borders (outline/surface/controls). element/hover/active
(3/4/5) stay — Radix-canonical for component bg. DEFAULT_COLOR_ROLE_SLOT_STEPS.
- generated/base.css regenerated (forced-colors block + --color-{role}-border -> step 7).
- Verified in browser: --color-primary-border now resolves to primitive-7 (oklch 0.80
0.092 vs the softer step-6 0.86 0.072); checkbox borders render defined, not broken.
- test: renderStaticCss emits the forced-colors outline block.
- docs: THEMING §28 + audit P3-1/2/3 marked resolved + README ref row.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
909ab7f944 |
feat(eidos): wide-gamut OKLCH output, default-on (RFC Phase 2, strategy A)
Each palette step is now emitted twice: the hex as a universal fallback, then an
oklch() sibling that wins where supported (Chrome 111+/Safari 15.4+/Firefox 113+).
The token layer is now OKLCH-native and wide-gamut-ready, with NO @media and NO
config flag (it is the default behaviour).
- render-css `appendColorScaleDeclarations`: hex line + `oklch()` sibling per
`--scale-{name}-{step}`. Only opaque, parseable steps get the sibling; empty/
non-color values keep just the fallback. `--primitive-*`/`--color-*` are var()
refs (untouched); alpha scales stay color-mix/rgba.
- generated/base.css regenerated (+744 oklch sibling lines: 31 scales x 12 x 2 modes).
Honest scope: the shipped Radix palette is authored in sRGB hex, so its oklch()
siblings are sRGB-equivalent (verified: --scale-purple-9 -> oklch(0.5556 0.1829
305.86) paints #8e4ec6) -- identical today. The win is the OKLCH-native foundation:
an OKLCH-authored theme or a vivid generated scheme now renders wider on P3 with no
extra work. Making the SHIPPED palette visibly wide-gamut is Phase 3.
Tests: full eidos suite green except 3 pre-existing words-track failures (confirmed
unrelated via baseline). Docs: RFC §7 (status) + THEMING §27 + README ref row.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
c4d2e34dbc |
feat(eidos): runtime theme builder API — eidos.applyColorScheme(seed)
RFC Phase 4: derive a whole-system color scheme from ONE brand seed at runtime.
Packages the demo-only builder into a first-class, tested API.
- build-scheme.ts (pure): buildScheme(seed, opts) composes the uix.color engine
(deriveScheme -> generateScale -> APCA on-solid -> compositing-inverse alpha)
into the `--primitive-{role}-*` (+ `--color-{role}-contrast`) override map.
seed -> { variables, roles }. No DOM. 6 tests.
- ActiveEidos.applyColorScheme(seed, opts) / clearColorScheme(): resolves donor
scales + background from the active theme, writes a managed `uix-eidos-scheme`
style block AFTER the theme block (wins the cascade), and RE-DERIVES on mode
change (follows light/dark). Returns BuildSchemeResult for introspection. opts:
variant (tonal|vibrant|monochrome) + temper (intent coherence, keeps hue) +
per-role overrides + selector. 4 tests (return value, intents, DOM block
ordering + clear, mode re-derivation).
- index.ts: export buildScheme + ApplyColorSchemeOptions + BuildScheme* types.
- temas/color demo: themeOverride now dogfoods buildScheme (drops the duplicated
emitRole/rgbaStr; identical output verified in-browser).
- generated/base.css: regenerated for the loss->plum role fix (binding layer
--primitive-loss-* now points at --scale-plum-*; keeps the contract test green).
- docs: THEMING.md SS26 + COLOR_ENGINE_RFC SS6.2 (status: landed) + README ref row.
Overriding the binding layer reprojects every --color-{role}-{slot} + the neutral
chrome downstream; the 31-scale palette stays put. Math in $color, composition in
eidos/lib (pure), DOM application in ActiveEidos.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
9bf7263366 |
feat(color): re-author gold + bronze as distinct metals (OKLCH)
Radix's gold/bronze were near-identical in UI (chroma ~0.05, indistinguishable). Re-authored via uix.color (generateScale from metallic seeds): gold #d4af37 (yellow-gold, OKLCH H91) and bronze #cd7f32 (copper, H61), chroma ~0.13 — ~2.6x more saturated so the hue gap reads clearly (gold yellow vs bronze copper). Verified in-browser: gold rgb(212,175,55) vs bronze rgb(205,127,50), distinct. The other 29 scales stay exact Radix v3; header notes the exception. Palette stays Radix-as-default (swappable) per the engine-not-hues principle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
2d1ccfbeab |
fix(eidos): base theme tertiary gray->indigo, distinct from secondary/neutral
Tertiary was `gray` — literally the same scale as neutral, and secondary (slate) is a near-gray too, so the three desaturated roles blurred together. Per Material 3's tertiary rule (rotate the primary hue ~60deg), tertiary now uses `indigo`: purple's cool neighbor, saturated enough to be distinct, in a hue band no intent occupies. Updated the one test that pinned tertiary=gray; regenerated base.css. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
be6bd540aa |
feat(motion): motion engine as arts/motion service + F6 tokens + F7 typegen
Relocate the motion runtime out of eidos into a runtime art (src/arts/motion,
$motion), exposed as uix.motion and consumed by BOTH soma (Presence.motion ->
motion.run) and eidos (delegates + registers presets) -- dissolving the
soma->eidos coupling. Remove DialogProps.runMotion / eidos.motionRunner; the
bridge is now EngineMotion.run (reads data-animation-style). Delete the 4
relocated dead files (lib/motion/{types,runtime,runner,presets/js}.ts); the
preset DATA (presets/css.ts) stays in eidos. Regenerate generated/base.css.
F6 - token rigor (Carbon): tokenize the raw firma durations (slower/deliberate/
emphatic/sustained holds, escalating by announce intent severity), add
--motion-distance-xl (30px shared-axis), --motion-scale-through (0.92), the
emphasized easing, and productive/expressive sets ([data-motion-set=expressive]).
F7 - extensibility + typegen: app-extensible, type-safe preset-name registry
(EidosMotionPresets, mirroring SemaChannelSignatures); MotionPresetName =
keyof EidosMotionPresets | none | (string & {}).
Also sweeps other in-progress working-tree edits (web/routes/temas/grafito).
Verify: npm run check -> 1 pre-existing error (grafito), 0 new; motion 22/22,
active-uix 25/25, Presence 2/2, Dialog 3/3. Pre-existing words-track failures
unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
44fe111ca0 |
fix(eidos): dropdown-menu z-index + finish ActiveDom canonization (tabs, resize-observer)
Bleed-through: the portaled [data-dropdown-menu-content] had z-index:auto, so
any positioned page element with a positive z-index (e.g. a selected
[data-toggle-group-item], z-index 1) painted THROUGH it. In the Words inspector
the Add-block menu showed the Typography toggle chips ("Predeterminada" /
"Normal") bleeding through. Added `content-z: 80` to the dropdown-menu recipe
(matches `select`, its sibling click-triggered overlay) + `z-index:
var(--dropdown-menu-content-z, 80)` on content + sub-content; regenerated
base.css. The soma floating layer reads this computed z-index and mirrors it
onto the positioner wrapper. The rest of the menu family (context-menu /
menubar / navigation-menu) shares the same latent gap — deferred.
Canonization (finishes the dom.raf rollout — zero raw requestAnimationFrame
left in src/uix):
- tabs-indicator.svelte: requestAnimationFrame/cancelAnimationFrame +
new MutationObserver + new ResizeObserver -> eidos.dom.raf /
observeMutation / observeResize (disposer pattern via ActiveEidos.require).
- tabs.svelte: new ResizeObserver -> eidos.dom.observeResize.
- resize-observer.svelte.ts: requestFrame/cancelFrame (raw handle) ->
dom.raf (disposer); fixed the stale "Debounces via requestAnimationFrame"
comment.
Browser-verified in the Words demo: column Add-block menu is opaque and on top
(elementFromPoint at the chip centers returns menu items, inMenu:true); the
menu options are collapsed + localized (one Encabezado, one Lista con viñetas);
empty-column delete works (column persists addressable with 0 children).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
4 months ago |