|
|
|
|
@ -0,0 +1,374 @@
|
|
|
|
|
# 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(<size> * 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 `<Select>` / `<Switch>` / `<Checkbox>` junto a hermanos `lg` o dentro de un
|
|
|
|
|
bloque sizeado: salen `md`.
|
|
|
|
|
- `<Form size="lg">` **no** propaga el size a los `<Field>` / controles anidados
|
|
|
|
|
por contexto ambiental — el recipe `form` solo sizea su propio gap + sus
|
|
|
|
|
acciones (`form.css:25-51,118-137`), no inyecta un `size` a Fields arbitrarios.
|
|
|
|
|
- `Dialog.*` / `Drawer.*` solo propagan al Close; el resto de partes internas no.
|
|
|
|
|
|
|
|
|
|
**Causa raíz:** no hay un token/contexto `--ui-size` ambiental ni un
|
|
|
|
|
`SizeContext` genérico que un control lea cuando su prop `size` es `undefined`.
|
|
|
|
|
El default es un literal `'md'`, no `inherit`.
|
|
|
|
|
|
|
|
|
|
### R4 — Escalado interno con el `size` propio · 🟡 parcial · severidad: MEDIA
|
|
|
|
|
|
|
|
|
|
**Lo que SÍ escala** (verificado en ~35 componentes): por cada `[data-size='X']`
|
|
|
|
|
el componente rebindea **altura, padding, gap, font-size, icon-size** y sus
|
|
|
|
|
dimensiones específicas (track/thumb/indicator/control-size/swatch/preview/day-size…).
|
|
|
|
|
Ejemplos confirmados: `button.css:111-154`, `field.css:56-78`, `select.css:55-80,198-220`,
|
|
|
|
|
`switch.css:35-61`, `slider.css:10-36`, `radio-group.css:96-114,212-230`,
|
|
|
|
|
`tabs.css:23-42`, `tag-group.css:20-41`, `toolbar.css:30-55`, `pagination.css:15-34`,
|
|
|
|
|
`accordion.css:42-150`, `calendar.css:28-47`, `card.css:95-129`, `checkbox.css:34-62`,
|
|
|
|
|
`combobox.css:109-135,293-315`, `feed.css:45-68`, `editable.css:23-49`,
|
|
|
|
|
`file-upload.css:17-31`, `avatar.css:50-73`, `banner.css:38-54`, `tooltip.css:46-56`.
|
|
|
|
|
→ **La tipografía interna y la geometría de sub-controles escalan correctamente.**
|
|
|
|
|
|
|
|
|
|
**Lo que NO escala con el `size`:**
|
|
|
|
|
1. **El radio** — ausente de casi todas las cascadas `[data-size]` (ver R5). Solo
|
|
|
|
|
`toggle` lo rebindea.
|
|
|
|
|
2. **Decoraciones con literal** — `carousel` puntos en rem (R1).
|
|
|
|
|
|
|
|
|
|
### R5 — Radio / forma · ❌ incumplida · severidad: ALTA
|
|
|
|
|
|
|
|
|
|
**5.a — El radio NO escala con el `size`.** En casi todos los componentes el radio
|
|
|
|
|
es un token **fijo**, idéntico en todas las tallas:
|
|
|
|
|
|
|
|
|
|
| Componente | Radio | Evidencia |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| `field` (toda la familia de campos) | `var(--field-control-radius)` único | `field.css:191` |
|
|
|
|
|
| `select` | `--select-trigger-radius` / `--select-content-radius` fijos | `select.css:39,189` |
|
|
|
|
|
| `tabs` | `--tabs-trigger/content/list/pills-*-radius` fijos | `tabs.css:72,115,166,174,198` |
|
|
|
|
|
| `pagination` | `--pagination-control-radius` fijo | `pagination.css:52` |
|
|
|
|
|
| `tag-group` | `--tag-group-item-radius` fijo | `tag-group.css:70` |
|
|
|
|
|
| `stepper`, `toolbar`, `combobox`, `tooltip`, `listbox`, menús, calendars | `var(--radius-*)` fijo | grep `border-radius: var(--radius-` |
|
|
|
|
|
|
|
|
|
|
**Único componente que acopla radio↔size:** `toggle` —
|
|
|
|
|
`--_toggle-radius: var(--toggle-radius-{size})` rebindeado por `[data-size]`
|
|
|
|
|
(`toggle.css:7,70,79,88,97`). Es el patrón correcto según `STATIC_SIZE`.
|
|
|
|
|
|
|
|
|
|
**5.b — Incoherencia de filosofía: 3 modelos de radio coexisten sin doctrina.**
|
|
|
|
|
- **size-coupled:** `toggle` (radio sigue el size).
|
|
|
|
|
- **prop `rounded` independiente:** `button` y `card` declaran `--{c}-radius-{sm..xl}`
|
|
|
|
|
pero los cablean a `data-rounded` (default `md`), **no** a `data-size`
|
|
|
|
|
(`button.css:27,216-220`; `card.css:40,133-134`). → un `<Button size="xl">`
|
|
|
|
|
conserva esquinas `md`.
|
|
|
|
|
- **token fijo único:** todos los demás (sin `rounded`, sin acoplar a size).
|
|
|
|
|
|
|
|
|
|
El primitivo canónico que zanjaría esto — el campo `radius` por size de
|
|
|
|
|
`STATIC_SIZE` y el token `--size-{k}-radius` (`render-css.ts:1344`) — **está
|
|
|
|
|
huérfano (0 consumidores)** (`THEMING.md:605`). El contrato existe pero nadie lo usa.
|
|
|
|
|
|
|
|
|
|
**5.c — Radios concéntricos: adopción casi nula.** `[data-shape-nest]` /
|
|
|
|
|
`--shape-outer-radius` solo aparece en **11 archivos** (card-group, combobox,
|
|
|
|
|
select, command, menubar + sus items). Las superficies anidadas más comunes —
|
|
|
|
|
campo dentro de `Form`, card dentro de panel, contenido de `Dialog`/`Drawer`,
|
|
|
|
|
calendar dentro de un picker, items de la mayoría de menús — **no** derivan su
|
|
|
|
|
radio del padre. El concéntrico es opt-in por diseño (`SHAPE_ENGINE_RFC.md:80`),
|
|
|
|
|
pero su penetración real es marginal.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 3. Incoherencias estructurales transversales
|
|
|
|
|
|
|
|
|
|
1. **Bundle `--size-*` huérfano + contradictorio.** La foundation emite
|
|
|
|
|
`--size-md-font-size: 14px` (`appendSizeDeclarations`), pero la regla viva es
|
|
|
|
|
el **1:1** (`md = 16px`) re-declarado en cada recipe (`THEMING.md:605-606`).
|
|
|
|
|
El sistema mantiene un contrato de tamaño que (a) nadie consume y (b) miente
|
|
|
|
|
sobre los valores actuales. Es deuda que invita a drift.
|
|
|
|
|
2. **Re-declaración del mapeo size→token en cada recipe.** Como nadie consume
|
|
|
|
|
`--size-{k}-*`, cada componente repite a mano `height/px/gap/font/radius` por
|
|
|
|
|
size (decenas de tokens × 80 componentes). Un solo punto de verdad (consumir
|
|
|
|
|
el bundle) eliminaría la duplicación — hoy el único guard contra drift es el
|
|
|
|
|
test de "no literales px en font/icon" (`THEMING.md:608-611`), que **no cubre
|
|
|
|
|
padding/gap/radio**.
|
|
|
|
|
3. **Sin herencia ambiental de size (R3)** ni de **forma** (R5.c): los dos ejes
|
|
|
|
|
que el usuario espera "heredables por defecto" son justo los que el sistema
|
|
|
|
|
trata como per-instancia con default literal.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 4. Matriz por componente
|
|
|
|
|
|
|
|
|
|
Leyenda: ✅ cumple · 🟡 parcial · ❌ incumple · `—` no aplica (sin eje de
|
|
|
|
|
`size`/densidad). R1=scaling · R2=densidad · R3=hereda size del padre ·
|
|
|
|
|
R4=escalado interno con size · R5=radio escala/concéntrico.
|
|
|
|
|
|
|
|
|
|
### 4.1 Controles e inputs (tienen `size`)
|
|
|
|
|
|
|
|
|
|
| Componente | R1 | R2 | R3 | R4 | R5 | Notas |
|
|
|
|
|
|---|---|---|---|---|---|---|
|
|
|
|
|
| `toggle` | ✅ | ✅ | ❌ md | ✅ | ✅ | **Referencia**: radio acoplado a size. |
|
|
|
|
|
| `button` | ✅ | ✅ | ❌ md | 🟡 | ❌ | radio por `rounded`, no size. |
|
|
|
|
|
| `badge` | ✅ | ✅ | ❌ md | 🟡 | ❌ | radio fijo / `rounded`. |
|
|
|
|
|
| `checkbox` | ✅ | ✅ | ❌ md | ✅ | ❌ | radio del box fijo. |
|
|
|
|
|
| `radio-group` | ✅ | ✅ | ❌ md | ✅ | ❌ | indicador circular (radio N/A), pero item radio fijo. |
|
|
|
|
|
| `switch` | ✅ | ✅ | ❌ md | ✅ | ❌ | track/thumb son pills (radio full, OK). |
|
|
|
|
|
| `slider` | ✅ | ✅ | ❌ md | ✅ | ❌ | track/thumb pills (OK). |
|
|
|
|
|
| `field` (genérico) | ✅ | ✅ | ❌ md | ✅ | ❌ | `--field-control-radius` único. |
|
|
|
|
|
| `spin/number/css-field` | ✅ | ✅ | ❌ md | ✅ | ❌ | comparten superficie spin-field. |
|
|
|
|
|
| `date/time/color-field` | ✅ | ✅ | ❌ md | ✅ | ❌ | segmentos + swatch escalan; radio fijo. |
|
|
|
|
|
| `search/password-field` | ✅ | ✅ | ❌ md | ✅ | ❌ | radio fijo. |
|
|
|
|
|
| `pin-input` | ✅ | ✅ | ❌ md | ✅ | ❌ | celdas: font escala (`cell-font-size-*`), radio fijo. |
|
|
|
|
|
| `textarea` | ✅ | ✅ | ❌ md | ✅ | ❌ | radio fijo. |
|
|
|
|
|
| `tags-input` | ✅ | ✅ | ❌ md | ✅ | ❌ | radio fijo. |
|
|
|
|
|
| `editable` | ✅ | ✅ | ❌ md | ✅ | ❌ | radio fijo. |
|
|
|
|
|
| `select` | ✅ | ✅ | ❌ md | ✅ | ❌ | trigger+content escalan; radios fijos. |
|
|
|
|
|
| `combobox` | ✅ | ✅ | ❌ md | ✅ | ❌ | usa concéntrico en items (`data-shape-nest`). |
|
|
|
|
|
| `rating-group` | ✅ | ✅ | ❌ md | ✅ | — | iconos (estrellas); radio N/A. |
|
|
|
|
|
| `stepper` | ✅ | ✅ | ❌ md | ✅ | ❌ | indicador/trigger/content radio fijo. |
|
|
|
|
|
|
|
|
|
|
### 4.2 Acciones compuestas (composición sobre Button/Toggle)
|
|
|
|
|
|
|
|
|
|
| Componente | R1 | R2 | R3 | R4 | R5 | Notas |
|
|
|
|
|
|---|---|---|---|---|---|---|
|
|
|
|
|
| `toggle-group` | ✅ | ✅ | 🟡 ctx | ✅ | ❌ | hereda size/variant del grupo (contexto) — **único buen caso de R3 en controles**; items son Toggle → radio podría seguir el size pero se aplana en grupo. |
|
|
|
|
|
| `button-group` | ✅ | ✅ | ❌ md | 🟡 | ❌ | radio en extremos via Button. |
|
|
|
|
|
| `split-button` | ✅ | ✅ | ❌ md | 🟡 | ❌ | compone Button. |
|
|
|
|
|
| `fab` | ✅ | ✅ | ❌ md | ✅ | ❌ | redondo (radio full, OK). |
|
|
|
|
|
| `menu-dial` / `onion-menu` | ✅ | ✅ | ❌ md | ✅ | — | radial; radio full. |
|
|
|
|
|
|
|
|
|
|
### 4.3 Overlays y superficies (size = densidad/anchura del panel)
|
|
|
|
|
|
|
|
|
|
| Componente | R1 | R2 | R3 | R4 | R5 | Notas |
|
|
|
|
|
|---|---|---|---|---|---|---|
|
|
|
|
|
| `dialog` | ✅ | ✅ | 🟡 Close | ✅ | 🟡 | propaga size al Close (ctx). Radio panel fijo (en `full` lo cambia, `dialog.css:177`). |
|
|
|
|
|
| `drawer` | ✅ | ✅ | ❌ | ✅ | 🟡 | width/height/padding por size; radio fijo. |
|
|
|
|
|
| `popover` | ✅ | ✅ | ❌ md | 🟡 | ❌ | radio `--radius-md` fijo. |
|
|
|
|
|
| `tooltip` | ✅ | ✅ | ❌ md | ✅ | ❌ | py/px/font por size; radio fijo. |
|
|
|
|
|
| `dropdown/context-menu` | ✅ | ✅ | 🟡 ctx | 🟡 | ❌ | list-surface ctx para anidados; usan `data-shape-nest` en items. |
|
|
|
|
|
| `menubar` / `navigation-menu` | ✅ | ✅ | 🟡 ctx | 🟡 | ❌ | concéntrico parcial. |
|
|
|
|
|
| `command` | ✅ | ✅ | ❌ md | ✅ | ❌ | input/item escalan; concéntrico en items. |
|
|
|
|
|
| `listbox` | ✅ | ✅ | 🟡 ctx | 🟡 | ❌ | radio fijo. |
|
|
|
|
|
| `tabs` | ✅ | ✅ | ❌ md | ✅ | ❌ | trigger/content/list/pills radio TODO fijo. |
|
|
|
|
|
| `toolbar` | ✅ | ✅ | ❌ md | ✅ | ❌ | controles escalan; radios fijos. |
|
|
|
|
|
| `banner` / `toast` / `announce` | ✅ | ✅ | ❌ md | ✅ | ❌ | radio fijo. |
|
|
|
|
|
|
|
|
|
|
### 4.4 Pickers y calendarios
|
|
|
|
|
|
|
|
|
|
| Componente | R1 | R2 | R3 | R4 | R5 | Notas |
|
|
|
|
|
|---|---|---|---|---|---|---|
|
|
|
|
|
| `calendar` / `range-calendar` | ✅ | ✅ | ❌ md | ✅ | ❌ | padding/control/day/font por size; celdas radio fijo. |
|
|
|
|
|
| `month-grid` / `year-grid` | ✅ | ✅ | ❌ md | ✅ | ❌ | escalan font/celda. |
|
|
|
|
|
| `date/time/date-range/time-range-picker` | ✅ | ✅ | ❌ md | ✅ | ❌ | reusan tokens de field+calendar por size. |
|
|
|
|
|
| `color-picker` | ✅ | ✅ | ❌ md | ✅ | ❌ | trigger/content/swatch por size. |
|
|
|
|
|
| `gradient-builder` / `gradient-picker` | ✅ | ✅ | ❌ md | 🟡 | ❌ | (nuevos, sin commitear) radio fijo. |
|
|
|
|
|
|
|
|
|
|
### 4.5 Datos / navegación / feedback
|
|
|
|
|
|
|
|
|
|
| Componente | R1 | R2 | R3 | R4 | R5 | Notas |
|
|
|
|
|
|---|---|---|---|---|---|---|
|
|
|
|
|
| `accordion` | ✅ | ✅ | ❌ md | ✅ | ❌ | trigger/content/indicator por size (incl. `full`); radio item fijo. |
|
|
|
|
|
| `breadcrumb` | ✅ | ✅ | ❌ md | ✅ | — | solo gap+font (sin radio relevante). |
|
|
|
|
|
| `pagination` | ✅ | ✅ | ❌ md | ✅ | ❌ | control radio fijo. |
|
|
|
|
|
| `tag-group` | ✅ | ✅ | ❌ md | ✅ | ❌ | item radio fijo. |
|
|
|
|
|
| `tabs` | (ver 4.3) | | | | | |
|
|
|
|
|
| `table` / `tree-view` / `tree-grid` / `grid-list` | ✅ | ✅ | ❌ md | ✅ | ❌ | densidad de fila por size; radios de celda/chip fijos. |
|
|
|
|
|
| `feed` | ✅ | ✅ | ❌ md | ✅ | — | padding/indent/font por size. |
|
|
|
|
|
| `carousel` | 🟡 | 🟡 | ❌ md | 🟡 | — | **indicadores en rem literal** (`carousel.css:128-142`). |
|
|
|
|
|
| `virtual-list` / `virtual-grid` | ✅ | ✅ | ❌ md | ✅ | — | densidad por size. |
|
|
|
|
|
| `progress` / `meter` | 🟡 | 🟡 | ❌ md | ✅ | — | **grosor de barra px fijo** (`base.ts:1030`); radio full (OK). |
|
|
|
|
|
| `spinner` / `skeleton` | ✅ | ✅ | ❌ md | ✅ | — | radio circular/sm. |
|
|
|
|
|
| `avatar` | ✅ | ✅ | ❌ md | ✅ | 🟡 | tamaño+font por size; radio via `data-radius`, no size. |
|
|
|
|
|
| `card` | ✅ | ✅ | ❌ md | ✅ | ❌ | título/desc/body+padding por size; radio por `rounded`. |
|
|
|
|
|
| `card-group` | ✅ | ✅ | ❌ md | ✅ | 🟡 | usa `data-shape-nest` (concéntrico). |
|
|
|
|
|
| `metrics` / `timeline` | ✅ | ✅ | ❌ md | ✅ | ❌ | radios fijos. |
|
|
|
|
|
| `file-upload` | ✅ | ✅ | ❌ md | ✅ | ❌ | gap/padding/control/preview por size. |
|
|
|
|
|
| `form` | ✅ | ✅ | ❌ md | 🟡 | — | sizea gap+acciones; **no propaga size a Fields** (R3). |
|
|
|
|
|
|
|
|
|
|
### 4.6 Primitivas de layout / tipografía (sin eje `size`-densidad)
|
|
|
|
|
|
|
|
|
|
`box · stack · flex · grid · wrap · group · float · aspect-ratio · auto-grid ·
|
|
|
|
|
container · section` (layout) y `text · heading · display · code · code-block ·
|
|
|
|
|
kbd · mark · highlight · link · separator` (tipografía/inline):
|
|
|
|
|
|
|
|
|
|
- **R1/R2:** ✅ — escalan vía `var(--space-*)` (layout) y `var(--font-size-*)` /
|
|
|
|
|
`var(--style-*)` (tipografía), todos scaling/densidad-coupled.
|
|
|
|
|
- **R3/R4/R5:** `—` — no exponen `size` discreto de control (excepto `container`
|
|
|
|
|
default `xl` / `section` default `lg`, que son anchuras de layout, no densidad).
|
|
|
|
|
- Radio relevante solo en `code-block`/`kbd`/`mark`/`link` → `--radius-sm/md`
|
|
|
|
|
fijo (aceptable: no tienen eje de size).
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 5. Recomendaciones priorizadas
|
|
|
|
|
|
|
|
|
|
### P0 — Herencia ambiental de `size` (cierra R3)
|
|
|
|
|
Introducir un **contexto/cascada de size ambiental** que un control lea cuando su
|
|
|
|
|
prop `size` es `undefined`, en lugar de hardcodear `'md'`. Dos vías:
|
|
|
|
|
- **Contexto Svelte genérico** `SizeContext` (como `list-surface-context.ts` pero
|
|
|
|
|
universal), set por contenedores sizeados (`Form`, `Card`, `Toolbar`, `Dialog`…)
|
|
|
|
|
y leído por cada wrapper: `size = props.size ?? ctx?.size ?? 'md'`.
|
|
|
|
|
- O un token CSS heredable `--ui-size` + `data-size` que cascadee por DOM (cuando
|
|
|
|
|
no haya portal de por medio).
|
|
|
|
|
Aplicar primero a `Form → Field/controles` (el caso más esperado) reutilizando la
|
|
|
|
|
norma cap-en-`md` ya documentada (`THEMING.md:560-579`).
|
|
|
|
|
|
|
|
|
|
### P1 — Acoplar el radio al `size` (cierra R5.a / R4-radio)
|
|
|
|
|
Decidir **una** doctrina de radio y aplicarla:
|
|
|
|
|
- **Opción canónica (recomendada):** consumir el `radius` por size de
|
|
|
|
|
`STATIC_SIZE` — añadir `--_{c}-radius` a cada cascada `[data-size]` como hace
|
|
|
|
|
`toggle`. Resucita el contrato `--size-{k}-radius` hoy huérfano.
|
|
|
|
|
- Mantener `rounded` como **override** explícito sobre ese default por size
|
|
|
|
|
(no como sustituto), unificando `button`/`card` con el resto.
|
|
|
|
|
|
|
|
|
|
### P1 — Eliminar el bundle `--size-*` huérfano o forzar su consumo
|
|
|
|
|
O bien (a) los recipes **consumen** `--size-{k}-*` en vez de re-declarar el mapeo
|
|
|
|
|
(un punto de verdad, mata el drift y la mentira del `14px`), o bien (b) se poda el
|
|
|
|
|
bundle y se documenta que el canon vivo es el 1:1 por recipe. Hoy coexisten ambos
|
|
|
|
|
y se contradicen (`THEMING.md:605`).
|
|
|
|
|
|
|
|
|
|
### P2 — Ampliar radios concéntricos (R5.c)
|
|
|
|
|
Extender `[data-shape-nest]` a las superficies anidadas obvias: contenido de
|
|
|
|
|
`Dialog`/`Drawer`, `Field` dentro de `Form`, calendar dentro de los pickers,
|
|
|
|
|
items de menús que aún no lo usan.
|
|
|
|
|
|
|
|
|
|
### P3 — Tokenizar los literales residuales (R1/R2)
|
|
|
|
|
- `carousel` indicadores: `0.375rem…0.75rem` → tokens (`--icon-size-*` o
|
|
|
|
|
`--space-*`) para que sigan `--scaling`/densidad (`carousel.css:128-142`).
|
|
|
|
|
- Evaluar grosor de barra `progress`/`meter` (`base.ts:1030`) — si debe seguir el
|
|
|
|
|
zoom, pasarlo a un token escalado.
|
|
|
|
|
|
|
|
|
|
### P3 — Guard de coherencia ampliado
|
|
|
|
|
El test actual solo prohíbe literales px en `font-size`/`icon-size`
|
|
|
|
|
(`THEMING.md:608`). Ampliarlo a `padding`/`gap`/`radius` de recipe y a una regla
|
|
|
|
|
"si hay `[data-size]`, debe rebindear el radio" cerraría R4/R5 a nivel de CI.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 6. Resumen de severidad
|
|
|
|
|
|
|
|
|
|
| Hallazgo | Regla | Severidad | Amplitud |
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
| Sin herencia ambiental de `size` (default literal `md`) | R3 | **ALTA** | ~todos los componentes |
|
|
|
|
|
| Radio desacoplado del `size` (solo `toggle` lo acopla) | R5.a / R4 | **ALTA** | ~todos los controles/superficies |
|
|
|
|
|
| 3 filosofías de radio sin doctrina única | R5.b | **ALTA** (coherencia) | toggle vs button/card vs resto |
|
|
|
|
|
| Bundle `--size-*` huérfano y contradictorio | transversal | MEDIA | foundation + 80 recipes |
|
|
|
|
|
| Concéntrico casi sin adoptar | R5.c | MEDIA | superficies anidadas |
|
|
|
|
|
| Literales rem/px (carousel, barras) | R1/R2 | BAJA | 2–3 componentes |
|
|
|
|
|
|
|
|
|
|
**Conclusión:** el sistema es **fuerte** en R1/R2 (la tokenización fuerza
|
|
|
|
|
scaling+densidad casi gratis) y **débil** justo donde el usuario apunta: la
|
|
|
|
|
**herencia** (R3 inexistente, R5.c marginal) y la **coherencia del radio con el
|
|
|
|
|
size** (R5.a/R4, con una inconsistencia de fondo entre `toggle`, `button/card` y
|
|
|
|
|
el resto). Las cuatro acciones P0–P1 resuelven el 80% del problema reutilizando
|
|
|
|
|
mecanismos que el framework ya tiene a medio cablear (el contexto de size de
|
|
|
|
|
`dialog`, el `radius` por size de `STATIC_SIZE`, el patrón `toggle`).
|