# Auditoría de herencia y escalado — capa Eidos / Theming > **Fecha:** 2026-06-27 · **Alcance:** `src/uix/eidos` (foundation + recipes + 80 > componentes). **Excluidos** (tracks WIP, regla de proyecto): `words/**`, > `palabras/**`, `chronos/**`. > > **Método:** lectura directa de la foundation (`lib/render-css.ts`, > `lib/primitives/static.ts`, `lib/config-types.ts`), de la doctrina > (`THEMING.md`, `SCALING_RFC.md`, `SHAPE_ENGINE_RFC.md`, > `STRUCTURE_ENGINE_RFC.md`, `TYPOGRAPHY_ENGINE_RFC.md`) y de los recipes > (`lib/recipes/base.ts`) + CSS por componente. Cada hallazgo lleva evidencia > `archivo:línea`. No se usaron agentes; los datos provienen de grep/lectura > verificables. --- ## 0. Las 5 reglas auditadas | # | Regla (enunciado del usuario) | Veredicto global | |---|---|---| | **R1** | Los componentes deben escalar con el **nivel de escala del tema** (`data-scaling`). | ✅ **Cumplida en lo esencial** (vía tokens) — 2 excepciones puntuales. | | **R2** | Los componentes deben reaccionar a la **densidad del tema** (`data-density`). | ✅ **Cumplida en lo esencial** (vía tokens) — mismas excepciones. | | **R3** | Un componente sin `size` propio debe **heredar el `size` de su padre**. | ❌ **Incumplida de forma sistémica** — ~todos hardcodean `size='md'`. | | **R4** | Los sub-componentes internos deben escalar con el `size` (tipografía, controles, etc.). | 🟡 **Parcial** — geometría + tipografía sí; el **radio NO**. | | **R5** | Las **formas de esquina/radios** deben heredar/escalar (radios concéntricos). | ❌ **Incumplida** — radio desacoplado del `size`; concéntrico casi sin adoptar. | **Tesis de la auditoría:** la disciplina de *tokenización* del framework hace que R1 y R2 se cumplan casi gratis (todo lo que es un token hereda `--scaling` y la densidad). Donde el sistema falla es en las tres reglas de **herencia/coherencia de tamaño y forma** (R3, R4-radio, R5): no existe herencia ambiental de `size`, el **radio no forma parte de la cascada `[data-size]`** salvo en `toggle`, y conviven **tres filosofías de radio incompatibles** sin doctrina única. --- ## 1. Cómo la foundation implementa cada eje (el contrato) ### 1.1 Scaling (zoom global `data-scaling` 90–110) `--scaling` se emite en `:root` (`render-css.ts:708` `appendScalingDeclarations`) y **multiplica** las métricas px en su `calc`: - `font-size` → `calc( * var(--scaling))` (`render-css.ts:1028`). - `icon-size`, `blur` → `appendScaledMetricDeclarations` (`render-css.ts:260,266`). - `space`, `control-height` → `calc(value * var(--density-*-scale) * var(--scaling))` (`render-css.ts:162-168`). - **NO** escalan: `radius`, `border-width`, `shadow`, `z`, `line-height` (ratio), `opacity`, `motion` (decisión deliberada, `SCALING_RFC.md:34-36`). → **Consecuencia:** cualquier componente que dimensione con `var(--font-size-*)`, `var(--icon-size-*)`, `var(--space-*)`, `var(--control-height-*)` (o tokens de recipe que los referencian) escala con `--scaling` **automáticamente**. Solo los **literales px/rem** quedan fuera. ### 1.2 Densidad (`data-density` compact/comfortable/spacious) `--density-space-scale` + `--density-control-scale` (`static.ts:169` `STATIC_DENSITY`) multiplican `--space-*` y `--control-height-*` (`render-css.ts:162-168`). La tipografía **no** lleva densidad por diseño (`THEMING.md:687-694`, paridad Radix). → **Consecuencia:** los componentes reaccionan a densidad si su padding/gap salen de `--space-*` y sus alturas de `--control-height-*`. Confirmado en los recipes: `button.height-md → var(--control-height-md)`, `field`, `combobox`, etc. (`base.ts:841,1065,1222`). ### 1.3 Size (prop `size` discreto xxs…xxl) La foundation **emite** un bundle `--size-{k}-{control-height,font-size,icon-size, padding-inline,padding-block,gap,radius}` (`render-css.ts:1328` `appendSizeDeclarations`, desde `STATIC_SIZE` en `static.ts:393`). **PERO ese bundle está huérfano: 0 consumidores** (`THEMING.md:605-606`). En su lugar, **cada recipe re-declara su propia familia de tokens por size** (`--{c}-height-{size}`, `--{c}-font-size-{size}`, …) y el CSS rebindea `--_{c}-*` en bloques `[data-size='X']`. El patrón canónico (piloto) es `toggle` (`toggle.css:64-98`). ### 1.4 Radio / forma (shape) - Escala `--radius-{none…full}` (4/6/10/16/20px + pill) (`static.ts:40`). - `STATIC_SIZE` mapea **cada size a un radio** (xxs/xs→`sm`, sm/md→`md`, lg→`lg`, xl→`xl`, xxl→`xxl`) (`static.ts:393-457`) — pero ese mapeo viaja en el bundle `--size-*` huérfano, así que **nadie lo consume**. - Concéntrico: `[data-shape-nest]` deriva `border-radius: max(0px, var(--shape-outer-radius) − var(--shape-nest-gap))` (`render-css.ts:1153-1159`, `SHAPE_ENGINE_RFC.md` Fase 2). - Familias `data-shape='rounded|continuous|cut|scoop'` + `--shape-smoothing` (superelipse) (`render-css.ts:1140-1151`). --- ## 2. Hallazgos por regla ### R1 — Scaling · ✅ cumplida en lo esencial · severidad de los huecos: BAJA Todo el dimensionado pasa por tokens scaling-coupled, así que el grueso cumple. Excepciones reales (literales que **no** siguen `--scaling`): | Evidencia | Problema | |---|---| | `carousel.css:128-142` | `--_carousel-indicator-size: 0.375rem … 0.75rem` — **literal rem por size**, no token. Los puntos de paginación no siguen `--scaling` (sí el zoom de navegador, pero no el eje del tema). | | `base.ts:1030-1031` (meter/progress) | `height-md: '6px'`, `height-lg: '8px'` — grosor de barra en px fijo (no escala). Menor: es grosor visual, discutible. | | Hairlines `1px`/`2px` (separadores, indicadores) — `button.css:245`, `command.css:153`, `navigation-menu.css:288`, etc. | **Legítimo**: un separador de 1px no debe escalar. No es violación. | ### R2 — Densidad · ✅ cumplida en lo esencial · severidad: BAJA Padding/gap salen de `--space-*` y alturas de `--control-height-*` (ambos density-coupled). Confirmado en recipes (`base.ts:341-352,481-525,841-842,1065-1076`). Mismas excepciones que R1 (el rem de carousel tampoco lleva densidad; grosor de barra fijo). La tipografía no escala con densidad **a propósito** (correcto). ### R3 — Herencia de `size` del padre · ❌ incumplida sistémica · severidad: ALTA **No existe herencia ambiental de `size`.** Prácticamente **todos** los componentes hardcodean `size = 'md'` como default (grep: 90+ wrappers `.svelte` con `size = 'md'`). Un componente sin `size` **no mira a su padre**: fija `md`. Las **únicas** propagaciones contenedor→parte (las excepciones que confirman la regla): | Mecanismo | Evidencia | Alcance | |---|---|---| | `dialog/context.ts` → `Dialog.Close` deriva el size del dialog (cap en su subset) | `dialog/context.ts:21-31`, `dialog-close.svelte:49` | solo Dialog→Close | | `list-surface-context.ts` — paneles flotantes anidados igualan el size de la superficie raíz | `lib/list-surface-context.ts:14-30` | menús/popups anidados (SubContent, listbox combobox) | | `toggle-group/context.ts` — propaga `variant`/`size` a los items | (contexto de toggle-group) | solo items de toggle-group | Casos que **deberían** heredar y NO lo hacen: - Un `