Adds external comparison table (WAI-ARIA / React Aria / Ark UI / Base UI / Bits) with sources and a Gaps table with disposition markers, bringing the css-field audit scorecard to parity with number-field (only the systemic E-2.2 css-import rule + R-1.3 readonly warning remain, both shared with the reference component). Smoke PASS for /uix/components/css-field.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
El "+" ya no desaparece al llenar la columna (resuelve "las columnas solo
admiten un bloque"). Ahora hay un "+" por columna: centrado si está vacía,
abajo en el borde punteado si tiene bloques. Cada click hace APPEND al final
(insertBlockInColumn -> [...col.children, block]).
El botón "+" pasa a redondo total (border-radius 999px) con borde + sombra
elevada.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Insertar bloque -> rejilla de tipos:
- palabras-block-types.ts (nuevo): lista curada de 10 (Párrafo/Encabezado/Imagen/
Lista/Separador/Cita/Código/Callout/Columnas/Tabla; headings y listas
colapsados; imagen incluida) con icono explícito por tipo (los definió el
usuario). Sale del registro del motor por id, sin duplicar create().
blockToInsert rellena placeholder en heading/list; imagen via pickImageFile.
- Iconos en el menú de acciones (Settings2/LayersPlus/ArrowBigUp/ArrowBigDown/
Trash2).
- Menú PORTALIZADO (DropdownMenu.Portal): el slot del grip tiene transform, que
lo hace bloque contenedor de los position:fixed -> la colisión de floating-ui
se desajustaba y el submenú se volteaba a la izquierda. Portalizar lo saca del
slot transformado.
Columnas (palabras.css, espejo de Words words.css):
- Un bloque de columnas vacío se renderizaba con ALTURA 0 (palabras no carga
words.css), y de ahí salían 4 bugs de la MISMA raíz: invisible, grip "se
vuelve loco" (las bandas Voronoi-on-Y de blockAtY se degeneran con altura 0),
no se podía seleccionar (0px sin área clicable) y el insert caía off-by-one.
Fix: flex-row + min-block-size 3em + borde dashed por columna (Words ya lo
documentaba: "without this, an empty columns block collapses to 0 height").
"+" por columna (palabras-column-inserter.svelte nuevo):
- Overlay "+" sobre cada columna VACÍA -> la misma rejilla (sin Columnas) ->
insertBlockInColumn(columnsIdx, colIdx, block) (comando del motor, como el
inspector de Words). Posicionado frame-relative, menú portalizado,
data-words-external-tool. El "+" desaparece al llenarse la columna.
Deuda anotada en continuar-palabras.md (decisión del usuario: al cerrar el
componente): labels -> langs, morfo de palabras. Pendiente abierto: grip "fuera
de la card" (el offsetParent del slot cae al marco exterior).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El grip pasa a ser un DropdownMenu con acciones (Propiedades, Mover
arriba/abajo, Insertar, Eliminar bloque). Arreglado el "parpadeo" del menú,
que eran CUATRO fallos compuestos (por eso cada fix suelto no lo resolvía):
- disabled reactivo (leía api.html) re-creaba el item enfocado al abrir ->
cierre/reapertura en bucle. Fix: index/count congelados en el objeto grip +
grip congelado mientras el menú está abierto.
- pointerdown sobre el grip llegaba a la capa Dismissal del menú, que lo veía
"fuera del contenido" -> cerraba, y el click reabría. Fix: stopPropagation en
el pointerdown del trigger SOLO con el menú abierto.
- preventScroll (default true) bloqueaba el scroll del body al abrir -> el
scrollbar desaparecía y la página saltaba. Fix: preventScroll={false}.
- doble animación de entrada (present-rise de sema/motion durante el hold +
dropdown-menu-enter del recipe) reiniciaba la opacidad a 0 al acabar el hold;
como el menú no está portalizado, se veían los bloques detrás. Fix: fijar una
sola animación con !important en palabras.css.
Nuevo palabras-block-actions.ts (moveBlock / insertParagraphBelow /
deleteBlockAndReanchor). words-provider: marcador genérico
data-words-external-tool en isInsideWordsTool para que abrir el menú no dispare
el bucle blur->refoco del editor. Diagnóstico completo en continuar-palabras.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El hover-follow resolvia el bloque con closest('[data-words-id]'), que da null
cuando el cursor esta sobre el carril (el rail no es un bloque), asi que el grip
se congelaba ahi. Ahora onMove, cuando no hay bloque directo bajo el cursor,
resuelve el bloque por la Y del cursor (blockAtY: banda vertical Voronoi-on-Y de
cada bloque), de modo que el grip se desplaza igual por el gutter que por los
bloques.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El piloto envolvia cada parrafo en <div data-words-node=block><p>, pero
el unico beneficio neto (drag vertical de raiz) ya esta cubierto por el
`dropZone` del sink, y el borde activo punteado funciona igual sobre el
elemento pelado. paragraphSpec.render vuelve a emitir un <p> plano.
Tests del render del motor en verde otra vez (engine de words 430/430).
palabras verificado en navegador: render, grip, drag y borrado intactos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Estado funcional del editor palabras (verificado en navegador) como
punto de retorno antes de revertir el piloto del wrapper.
- Grip: framework <Button> draggable copiado del gutter de Words
(ghost/xs/iconOnly + dots svg). dragstart escribe el indice en el MIME
canonico; click abre el inspector via onPress; cursor grab/grabbing.
- Drag vertical: <WordsBlockDrag> acepta `dropZone` opcional y escucha
dragover/drop en toda la fila (rail+canvas) en vez de solo en content,
asi un arrastre recto hacia abajo sobre el carril ya engancha.
- Borrado de bloque (palabras-panel): mueve el activo al bloque
superviviente leyendo el documento YA actualizado (cubre el caso del
ultimo bloque, que deja un parrafo en blanco), cierra el popup, y el
grip se re-ancla en vez de quedar fantasma.
- active-eidos: getter `timers` (ActiveTimers) sobre el uix service.
- Piloto wrapper: paragraphSpec.render emite <div data-words-node=block>
envolviendo un <p data-words-inner> (deja tests del render del motor
por consolidar/revertir).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
Regression from the onMount→$effect conversion (increment 3): eidos.langs.extend
both reads AND mutates the reactive langs schema, so the $effect tracked that
read, the mutation retriggered it, and it looped — re-extending the bundles
thousands of times, each logging a DEV leaf-overwrite warning (~90k warnings,
hung the editor page). Wrapped the body in untrack ⇒ the effect has no deps ⇒
runs exactly once on mount, like the old onMount. Verified: words page loads
fast, zero console warnings, editor + inspector render. api-contract test still
green (untrack is not flagged).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
words-inspector.svelte carried an intentionally-empty <style> (the styles
live in words.css, imported by the root) — removed. words.svelte used the
legacy onMount only to flip `mounted` + register langs bundles (no reactive
reads) → converted to a run-once $effect, dropped the lifecycle import.
component-api-contract test now green; eidos suite 196/197 (only the words
orphan-token test remains → Phase A).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
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>
Soma is clean (95/100, headless). The mess is words.css drifting from its
own token contract: ~117 tokens declared, ~42 used, 41 orphaned, the same
concept expressed 3-4 ways (radius, mix%, focus ring, sizes, durations,
padding), 3 duplicated color palettes, rail hardcoded over orphaned tokens.
Phased plan A-E; pilot = inspector. Working doc, updated per phase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real fix, not a dodge. Corner families act on CORNERS; a pill (radius 999)
has none — its ends are continuous curves — so cut/scoop eat the whole end
into a hexagon. The correct behavior is to give the control corners: under an
angular family the switch track adopts a FINITE radius (a coherent cut-capsule)
while round/continuous keep it a pill. The thumb stays a round dot. Verified:
cut → track radius 6px / corner-shape bevel; rounded/continuous → 999px pill.
Refined the pendiente doctrine accordingly: angular families on pill/circle
controls aren't vetoed — the control either adopts a finite radius to express
the family, or stays shape-neutral by deliberate choice. The eidos Switch
recipe should follow the same pattern.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
applyTheme(seed) covers only the 5 visual channels. Recorded the complete
theme model by layer (visual=eidos, sema=sound/haptic separate+nullable,
iconography=art, variants=canon/preview-only), the architectural note that a
full cross-layer theme belongs at active-uix (uix.applyBrand) not eidos, and
the framing debt: /temas/tema overclaims "un tema" — it's the visual channels.
Three reopen options (reframe copy / richer page / applyBrand). Deferred.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The switch track is a pill (border-radius: 999px); applying data-shape made
corner-shape: bevel turn it into a hexagon (and scoop into a blob). Pills and
circles must not take shape families — same rule the avatar/thumb already
follow (pendiente.md). Dropped data-shape from the track; it stays round in
every family while the rectangles (card, button, badge, chips) still morph.
Verified: cut mode → switch corner-shape round / radius 999px.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The toggle changed data-theme but the preview stayed dark: applyColorScheme
resolves donor scales from the instance's EFFECTIVE mode (#resolveDonorScales
ignores the per-call mode option), and the boot had no modeSource — so the
scheme always derived dark neutrals/surfaces and overrode the foundation's
light tokens. Bound a modeSource to the `mode` state (grafito pattern) +
fire its listeners on change, so the instance mode follows the toggle and the
scheme re-derives correctly. Verified: surface oklch(0.21)↔(0.98), content
flips, both modes read cleanly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A live theme builder: one seed → the five runtime builders (color · type ·
depth · shape · space) composed in a single uix.eidos.applyTheme() call,
scoped to [data-tema-preview] so the themed mini-app re-skins while the
controls stay put. Five presets set all axes at once (a whole personality);
per-axis sliders + swatches for fine control; live code mirrors the actual
call. Self-contained (boots its own minimal ActiveUix + ActiveEidos with
applyDom). Preview is hand-built from foundation tokens (--color-{role}-* ·
--space-* · --radius-* · --font-size-* + data-shape/data-depth) — the exact
surface applyTheme drives — since component recipes are code-split.
Verified in browser: all 5 axes retune live (color hue follows the seed,
type ratio, shape smoothing/corner-shape, depth elevation, space density),
presets apply atomically, stable (no cycling), prerenders under
adapter-static.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Composes the five runtime builders (color · type · depth · shape · space)
from one ThemeSeed { color?, type?, depth?, shape?, space? } in a SINGLE
managed write — vs five separate apply* calls. Atomic whole-theme
semantics: axes you provide are set, axes you omit revert to the authored
foundation; clearTheme() reverts all. Per-axis apply{Color,Type,…} stay
for surgical tweaks. Returns ApplyThemeResult for per-axis introspection.
New: ThemeSeed, ApplyThemeOptions, ApplyThemeResult (exported from
$uix/eidos); also exported the previously-omitted ApplySpacingOptions.
2 tests (composition + atomic-replace). Closes the "jaula abierta" arc —
no referent reunites the 5 perceptual axes under one runtime theme builder.
Updated CHANNELS_SYNTHESIS §5 (quintet → sextet). check clean (the 28
errors are the untracked heroscrolling WIP demo).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
base.css pruned to the 9 role-referenced scales (full palette opt-in):
113.5 → 36.2 KB gz per page total (−68% from the original monolith).
Non-Gregorian calendar lazy-loading (5b) deliberately deferred — it needs
a sync→async change to the vendored date core for a narrow date-pages-only
win; documented the trade-off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
The "imports every component CSS recipe from the Eidos entrypoint" test
encoded the OLD invariant (index.css aggregates all 104 recipes), which
Phase 3 intentionally retired. Replaced it with the new contract: every
component recipe loads EXACTLY ONCE — foundation @import (layout
primitives) XOR self-import in its own .svelte. This also enforces the
no-double-load invariant from Phase 0. Eidos suite back to its
pre-existing baseline (192/195; the 3 remaining failures are the
known words-track issues, unrelated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
table tree-view tree-grid feed virtual-list virtual-grid announce
clipboard drag-drop words.
words (the 55 KB recipe) composes via mounted eidos components
(Button×5, Icon×7, NumberField×3, ColorPicker×1, TextArea files), so
their recipes load on mount — no extra imports needed. Verified: words
content/toolbar styled, mounted button 26px/radius 6px.
index.css now holds ONLY the foundation: base.css + archetypes + events
+ the 10 layout primitives (box/flex/grid/stack/group/wrap/container/
section/aspect-ratio/auto-grid, kept because they're used pervasively and
recipes layer on them).
FINAL: monolith 850,879→335,100 raw / 113,489→54,429 gz — −515 KB raw /
−59 KB gz off EVERY page, landing at the base.css floor. A 5-component
page now ships ~54 KB foundation + a few KB of chunks instead of 113.5 KB
flat. check 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
field form number-field toggle toggle-group switch checkbox radio-group
rating-group search-field select combobox tags-input file-upload editable.
Caught + fixed a real coupling: toggle-group items are DOM-equivalent to
a standalone <Toggle> (carry data-toggle via the morfo, per the 2026-05-28
structural-identity refactor) and rely on toggle.css for their base
treatment — but the group never mounts an eidos <Toggle>. Without the
aggregate, items rendered as raw buttons (bg #f0f0f0, 2px outset border).
Fix: toggle-group.svelte imports toggle.css FIRST (before its own grouping
rules, so attached-corner overrides still win). Verified: item now bg
srgb(0.133), 1px border, font-weight 500, min-block-size 36px.
All recipes left the monolith (residual [data-*] are base.css token
contracts, verified base==monolith counts). Portaled select content
verified styled. index.css 51→36 @imports. Monolith 633,277→533,103 raw
(−100 KB) / 86,535→74,716 gz (−11.8 KB). check 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dropdown-menu context-menu menubar command listbox navigation-menu
grid-list now self-import their recipes; removed from index.css.
menu-indicator.css (shared by dropdown/context checkbox+radio rows) had
a load-ORDER dependency: its longhand padding-inline-start (0,1,0) only
beat the menu row's padding-inline shorthand (0,1,0) by coming later in
the aggregate. Code-splitting breaks that order, so the partial is made
ORDER-INDEPENDENT via a specificity bump (doubled attribute → 0,2,0).
It now travels in dropdown-menu/context-menu's chunk (imported from their
.svelte) instead of the foundation. Verified in browser: checkbox item
padding-inline-start = 26px (indicator wins), plain item = 8px.
index.css 58→51 @imports. Monolith 666,436→633,277 raw (−33 KB) /
90,381→86,535 gz (−3.8 KB). check 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pilot for the index.css break-up. Establishes the pattern for compound,
portaled components:
- the `import './x.css'` goes in the ROOT `.svelte` (dialog.svelte); the
barrel imports the root, so the CSS loads when ANY part mounts.
- portaled content stays styled: recipe ships in dialog's chunk, tokens
(--dialog-content-bg, …) come from base.css global → verified in
browser that the portaled [data-dialog-content] keeps bg/radius/
shadow/padding.
`@layer` evaluated and DISCARDED (evidence-based): 0 current usage, 16
!important that @layer would silently invert, and per-component
[data-*] scoping already prevents cross-component collisions. Kept as a
documented escape hatch.
index.css 94→93 @imports; monolith 814,420→805,692 raw (−8.7 KB) /
−1.1 KB gz. Reworded the policy comment to be list-free (no churn across
the remaining 93). check 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verified every figure in the audit against the real build (all exact).
Two corrections: (1) the 10 "code-splittable" components were actually
double-loaded — still @import-ed in index.css on top of self-importing;
(2) base.css (41% of the monolith, 52 KB gz) is the real floor and
carries per-component token contracts ([data-badge]×8 stays in base.css
after removing badge.css), so splitting recipes alone can't go below it.
Added the phased execution plan (0–5) with the cascade-order risk and
the cross-coupling audit (only words→textarea, safe). Recorded Phase 0:
index.css 104→94 @imports, monolith −36 KB raw / −4.5 KB gz, double-load
eliminated, no broken coupling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bundle Phase 0. The 10 components that already `import './x.css'` from
their `.svelte` (badge, card, image, password-field, s-text,
s-text-virtual-list, scroll-frames, skeleton, spinner, textarea) were
ALSO @import-ed in index.css — shipping their CSS twice (verified: badge
in both its own chunk and the 851 KB monolith). Removed their @import
lines so they ship only as code-split chunks; added a policy comment so
they're not re-added. Build green (1m21s).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full-bleed lost the inset frame entirely — "¿dónde está el radio
interior?". A 16:10 cover can't be concentric on all 4 corners at full
(its height caps every corner to ~half-height), but its TOP corners CAN
match the card if the bottom stays square — the iOS-player geometry.
CSS can't read the card's dimension-capped radius (computed value is
still 9999px; the cap is a *used* value), so measure it via
ResizeObserver (min(w,h)/2 − gap) and apply to the cover's top corners;
bottom corners square, meeting the metadata. The inset frame is kept on
top/sides so the inner radius reads.
Finite radii unchanged (full 4-corner concentric). Toggle at full shows
"auto: solo top". Docs reconciled in SHAPE_ENGINE_RFC §5 + THEMING §30.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous fix dropped the cover to a clean --radius-xl, but a sharp
16px corner floating inside the card's 169px pill curve still read as
broken. The only way a child's corner can match a `full` parent (whose
radius caps per-dimension, so concentric math is impossible) is to let
the parent CLIP it: at `full` the cover now bleeds edge-to-edge with no
radius of its own, and the card's overflow:hidden + real corner round
its top to match exactly. Updated SHAPE_ENGINE_RFC §5 accordingly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
At `full` (--radius-full = 9999px) the border-radius caps to half each
element's shorter dimension. A tall card and its 16:10 cover cap to
different values, so they can never be concentric — the nest inset broke
("el borde interior no sigue el radio exterior").
Concentric nesting requires FINITE radii; `full` is for leaf elements
(chips, buttons, avatars, pill inputs). So at `full` the demo now:
- gates `[data-shape-nest]` off and dims the "radios concéntricos" toggle
with «n/a a full — geometría de hoja»
- drops the cover to a clean --radius-xl (.cover-full) instead of a
mismatched pill
Documented the finite-only constraint in SHAPE_ENGINE_RFC §5 + THEMING §30.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The radius scale ships `--radius-full` (9999px = pill / totally-rounded); the
/temas/forma magnitude chips just omitted it (sm/md/lg/xl). Added `full` so the demo
exposes totally-rounded. Magnitude (rounded / --radius-*) is orthogonal to the shape
family (corner-shape).
Verified: check 0 errors; card --sample-radius -> 9999px on `full`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Overcorrected the prior fix into "todo es sema / eidos solo hace visible", which
flattened eidos and skipped morfo + soma. Reframed to the real layer chain, each with
a role: morfo declares the event (family · intent) -> soma triggers (writes data-state,
calls sema.emit) -> sema emits (executes sound + haptic; projects the visual channel as
data-event-* tokens) -> eidos materializes (reads data-state + data-event-* and renders
via its OWN token engines: color · motion · depth · shape · space). Sema = the QUE
(semantic), eidos = the COMO (visual vocabulary) — co-layers, eidos the sole visual
owner, not a shim.
Applied to CHANNELS_SYNTHESIS §3 + the /temas/orquesta footnote + top comment.
Verified: check 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refine the doctrine per review: the two moments are the producer/consumer split —
the SEMA moment (it emits: evaluates the occurrence and stamps data-event-* tokens;
sound + haptic it executes there; no DOM/CSS) and the EIDOS moment (it reads those
tokens + data-state and materializes the visual channel in CSS). The token is the
contract — sema writes, eidos reads — which is why sema is DOM-agnostic and eidos is
the sole visual owner. The temporal axis (state persistent / event transient) runs on
top: which token, read by eidos as presets / signatures.
Applied to CHANNELS_SYNTHESIS §1 + the /temas/orquesta "two moments" section.
Verified: check 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two fixes:
- Sound + haptic now fire from the REAL EngineSemantic.emit (channels filtered by the
mixer; `visual` excluded so no data-event-* conflict with the toggleable facets) —
replacing the Web Audio / Vibration approximation. One real emit, the chosen channels.
- Correct the architecture framing (was conflating LAYER with CHANNEL): the channels are
SEMANTIC (sema). Sema projects the `visual` channel (-> eidos materializes color ·
motion · depth · forma) and executes sound + haptic. motion/depth/shape/color are NOT
"eidos channels" — they are sema visual channel, made visible by eidos. Mixer now groups
"canal visual · sema -> eidos" vs "canales runtime · sema"; dropped the misleading
per-track eidos/sema tag. CHANNELS_SYNTHESIS reframed: 8 book expression channels -> 3
sema runtime channels (visual / sound / haptic).
Verified: check 0 errors; browser — two mixer groups render, real engine.emit fires
without error, visual facets compose.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The book thesis, playable: ONE press fires the firma across channels, with a mixer to
mute each and watch them layer. The 4 visible channels (color · motion · depth · shape)
compose from one event with REAL eidos tokens — verified: press -> primary->affirm-solid
+ scale(0.93) + shadow overlay->raised + --shape-smoothing 2->3.4. Sound + haptic via
Web Audio / Vibration (in-app the sema engine emits them from the same event). + the
two-moment model section.
CHANNELS_SYNTHESIS.md ties the five channel RFCs: two moments x N channels from one
event, the layer ownership (sema runs sound/haptic + projects data-event-*; eidos
materializes the visual channels), the cross-modal firma, the runtime-builder quintet.
Verified: check 0 errors. Browser: all four visible channels compose on press.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live structural workshop: a real settings panel whose every gap/padding is
var(--space-*), retuned by four controls — densidad (data-density) · zoom
(data-scaling) · unidad base · fluido — composing on the panel via the preserved
calc(value * density * scaling). The space ladder (buildSpaceScale steps) visualizes
the modular rhythm + the fluid grow range. Plus THEMING 31 (three structural axes;
space as rhythm).
Verified: check 0 errors. Browser: panel --space-4 = calc(16px * density * scaling);
compact -> 0.84, base/growth re-derive the ladder live.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The space scale was the one structural primitive without a builder — density and
scaling were already strong, but the base space scale stayed flat / static / arbitrary.
buildSpaceScale (pure) + ActiveEidos.applySpacing/clearSpacing regenerate the
--space-{key} ladder from one base unit, optionally FLUID (growth > 1 -> each step
clamp()s with the viewport, reusing the type scale fluidClamp), PRESERVING the
density x scaling composition (calc(value * --density-space-scale * --scaling)).
Opt-in over the authored STATIC_SPACE, same posture as applyTypeScale. Completes the
runtime-builder quintet (color/type/depth/shape/space).
Thesis (STRUCTURE_ENGINE_RFC): space is rhythm, not a flat px lookup table — modular,
fluid, composed with density x scaling from a seed. Structural = state-only (no
two-moment; honest).
Verified: check 0 errors; eidos config 58/58 (incl. modular + fluid space tests).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The shape prop was usable but invisible — add a live `shape` chip control
(rounded/continuous/cut/scoop) to each demo, wired to the live preview + code
snippet, plus a "shape families" patterns row in Badge and a tag-shape control on
TagsInput items. The family system is now discoverable, not just documented.
Verified live (Chrome 146): badge cut -> bevel, button continuous -> squircle,
both flow from the control to the rendered component.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`<TagsInput.Item shape="cut">` — typed `shape` (rounded/continuous/cut/scoop) on the
tag chip, the case the family system was made for. Emits `data-shape` -> the
foundation rule.
Also clarified (pendiente.md): every component that spreads `{...rest}` already
accepts `data-shape="…"` directly (verified on inputs + tags), so the families are
usable everywhere today; the typed `shape` prop is ergonomic sugar on the common
surfaces (Badge / Card / Button / TagsInput.Item).
Verified: check 0 errors. Browser: real tag chip data-shape=cut -> bevel.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Makes the shape families usable per-instance, not just in the demo. New canonical
SHAPE_FAMILIES + ShapeFamily in lib/types (canon, like the variant archetypes).
Badge / Card / Button gain a typed `shape` prop (rounded - continuous - cut - scoop)
that emits `data-shape` -> the foundation [data-shape] corner-shape rule. Orthogonal
to `rounded` (radius magnitude); default = arc (no attr); degrades to arc where
corner-shape is unsupported.
A differentiator for chips/badges (<Badge shape="cut">) and a premium touch for
buttons (<Button shape="continuous"> = squircle). For cut/scoop prefer filled
variants (concave/angular corners seam with borders) — documented per prop + pendiente.md.
Verified: check 0 errors; eidos 190/193 (3 pre-existing `words` failures, unrelated).
Browser: real Badge data-shape=cut -> bevel, real Button data-shape=continuous ->
squircle, radius magnitude preserved.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The scoop family chip cast its box-shadow INTO the concave notches and the 1px
border seamed at the concave corners (native corner-shape, not clip-path) — looked
broken. Flat-filled chips render every family cleanly and are the right home for
cut/scoop (chips/badges), matching the per-component idea below.
Add pendiente.md: per-component shape family (chips/badges differentiator), general
shape adoption, corner-shape support (canon = degrade to arc, no polyfill), depth
scrim cue.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuilt from abstract tiles to a real "now playing" card whose every corner is
driven by a control panel — switch family / smoothing / magnitude / concentric and
the whole card retunes live. The avatar stays a true circle (families do not deform
it, by design). Plus: de-cerca round-vs-squircle, the 4 families on one chip, the
press-morph buttons, and a "what only Eidos does" strip.
Fix: the cover no longer sets its own border-radius, so the [data-shape-nest]
concentric rule owns it — the cover inset is now truly concentric (16 - 8 = 8px).
Verified: check 0 errors; browser (Chrome 146) — the family switch retunes card +
cover + play + tag + input live, avatar stays circular, cover concentric.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>