12 KiB
Plan de corrección — herencia / escala (Eidos) · v2, corregido por diseño + referencias
Compañero de
inherit_audit.md. · Fecha: 2026-06-27 · Excluidos:words/**,palabras/**,chronos/**(WIP).Esta v2 reemplaza la doctrina de radio de la v1. La v1 acoplaba el radio al
sizedel control (sesgo de statu quo, anclado entoggle). La investigación de 10 frameworks de referencia (Radix, Material 3, Apple HIG, Ant, MUI, Tailwind, Carbon, Fluent, Chakra, Mantine — verificada adversarialmente) demostró que eso es un outlier: nadie escala el radio sin tope con el size. La doctrina correcta, decidida por el usuario, es el modelo Radix (radio = eje propio, desacoplado del size) + herencia concéntrica del padre (modelo Apple).Cada decisión va con cita de diseño (
THEMING.md/RFC) o de referencia (URL).
0. Doctrina raíz (corregida)
Un componente deriva su GEOMETRÍA + TIPO + ICONO del primitivo de size
--size-{k}-*(que compone densidad×scaling); si no fija su size, HEREDA el del padre (publishers deliberados, cadena Ant). El RADIO es un eje propio —default por arquetipo × factor de temaradius× overriderounded— DESACOPLADO del size, y se HEREDA del padre de forma CONCÉNTRICA cuando anida (inner = max(0, outer − gap), modelo Apple).
Decisiones cerradas:
- ✅
--size-md-font-size = 16px(1:1; el texto de control sigue la escala tipográfica — campo "font-in-size" de Chakra/Ant/Radix). El compact-md 14px queda muerto. - ✅ Radio = modelo Radix (Opción A): eje propio, desacoplado del
size.
Reencuadre clave: tu regla 5 ("las esquinas heredan del padre; los radios concéntricos") no pide "el radio escala con el size propio" (eso era R4, ya cubierto por el size recipe). Pide herencia del padre + concentricidad — que es exactamente el modelo de Apple (ContainerRelativeShape/ConcentricRectangle) y el helper [data-shape-nest] que eidos ya tiene. El falso problema "cap-at-md vs full-scale" desaparece: el radio no es función del size.
1. Qué fijan el diseño + las referencias (resumen citado)
| Eje | Referencia (cita) | eidos hoy | Acción |
|---|---|---|---|
| scaling (R1) | Radix scaling 90-110% escala todo incl. tipo (radix/spacing). |
--scaling idéntico. |
✅ validado; solo higiene (literales). |
| densidad (R2) | M3/Carbon/Fluent: altura/spacing cambian, texto estable (m3/density). | density compact/comfortable/spacious, texto estable. | ✅ validado. eidos por delante: tener densidad Y scaling es la unión que ningún referente ofrece. |
| size → internos (R4) | Chakra/Ant/Radix meten font en el size recipe (chakra/sizes). | recipes por-size (font incl.). | ✅ 1:1 (md=16) confirma el campo. |
| heredar size (R3) | Ant ConfigProvider.componentSize global; cadena explícito > grupo > contexto > md (ant/config). Publishers = contenedores concretos (Form, input-group, group). |
hardcode md; solo 3 contextos ad-hoc. |
WS-A. |
| radio (R5.a/b) | Radix: radio = factor de tema × paso por rol, independiente del size; full→pill, checkbox capado (radix/radius). Ningún referente escala radio sin tope. |
3 filosofías; STATIC_SIZE lo escala 7 pasos (outlier). |
WS-B (modelo Radix). |
| esquinas concéntricas (R5) | Apple inner = max(0, outer − gap), hijo deriva del padre (apple/ConcentricRectangle); canon web idéntico (Cloud Four). |
[data-shape-nest] = ese modelo, reference-grade, pero solo 5/80. |
WS-C. |
2. Workstreams
WS-A — Herencia ambiental de size (R3) · esfuerzo L · pilot Form→Field
Verificado. Todo wrapper resuelve con fallback literal 'md' (eidos.resolve(size,'md'), active-eidos.svelte.ts:540), sin mirar al padre. Solo 3 contextos ad-hoc (dialog/context.ts, lib/list-surface-context.ts, toggle-group/context.ts). 124 archivos usan el idioma.
Diseño (modelo Ant, no "toda caja emite"):
- Nuevo
src/uix/eidos/lib/ui-size-context.ts(espejo delist-surface-context.ts, getter reactivo) +clampSize<S>(size, allowed, fallback)para el subset por componente. - Publishers DELIBERADOS (no cualquier caja):
Form, los grupos (button-group/toggle-group ya), overlays (Dialog/Drawer/Popover content como provider de su propio size), y un provider global tipoConfigProvider.componentSize(la superficie "size por defecto de la app"). Esto es lo que las referencias hacen; un<Button>cualquiera no debe rebroadcast su size a descendientes no relacionados. - Cadena de prioridad (Ant, documentar):
prop explícita > grupo/compound más cercano > provider ambiental > 'md'. Reader:eidos.resolve(size ?? group?.size ?? ambient?.getSize(), 'md')— la ambiental como fallback deresolve, nunca comosize ?? ctx(romperíaresolveResponsivePropen los 124 sitios). - Cambio de API: lectores pasan de
size = 'md'asize?: Size(defaultundefined) para detectar "no fijado". (sign-off) - Cap-at-md (
THEMING.md:560) aplica a su ámbito real —derivación contenedor→parte en overlays (un dialogfullno engorda su Close)— no necesariamente a la herencia en-flujo (<Form size="lg">probablemente quiere controleslg). Política por tipo de publisher. (sign-off) - Portal-reset: los roots de overlay resetean el contexto a su propio size (no filtran el del Form ancestro al panel portalizado). (recomendado: sí)
Pilot: Form SET + Field/Checkbox READ; verificar en navegador <Form size="lg"> → controles lg. Luego el resto.
WS-B — Radio = modelo Radix (R5.a/b) · esfuerzo M · pilot button + card
El radio es un eje propio, desacoplado del size. Cuatro piezas (Radix verbatim):
- Default por arquetipo/rol — cada componente tiene un radio de reposo por su arquetipo (control, chip, panel…), no por su size. Encaja con el vocabulario de arquetipos ya existente (
ARCHETYPE_COHERENCE_AUDIT). - × factor de tema
radius(none|small|medium|large|full) — verificar si existe; si no, añadir un--radius-factorglobal à la Radix (--radius-factor0/0.75/1/1.5/1.5 sobre la escala--radius-*). Es la perilla de tema que falta. - Override
roundedper-instancia —button/cardya lo tienen (button.css:216-220,card.css:133-134); generalizarlo como la jaula abierta (= propradiusde Radix/Mantine/Chakra). full/pill = cápsula (caso especial size-aware, autocapado: ½ altura / 9999px) con caps por parte (checkbox nunca totalmente redondo, modelo Radix).
Migración (DES-acoplar, lo contrario de la v1):
- Quitar el rebinding de radio de las cascadas
[data-size]— empezando portoggle(toggle.css:64-98rebindea--_toggle-radiuspor size: ese era el outlier, se elimina). El radio pasa a venir del default de arquetipo + factor +rounded. - Sacar el radio del bundle de size:
STATIC_SIZE.radius/--size-{k}-radiusdejan de ser fuente del radio (eran el outlier). Se repurposan como tabla de default-por-arquetipo o se podan. - Unifica las 3 filosofías bajo la de Radix;
button/cardquedan como la referencia (ya casi lo eran).
Pilot: button + card (los más cercanos a Radix). Verificar esquinas a xs..xl + que rounded/full siguen ganando. Es cambio visual → screenshots por size. (sign-off del look)
WS-C — Esquinas concéntricas heredadas del padre (R5) · esfuerzo M · pilot dropdown-menu
Verificado. El helper [data-shape-nest] { border-radius: max(0px, calc(var(--shape-outer-radius) − var(--shape-nest-gap))) } (render-css.ts:1153-1159) es el modelo de Apple, reference-grade. Adoptan 5; debería ser ~20-40 (los que de verdad anidan una superficie redondeada en un padre redondeado con padding).
Diseño: dos partes — el padre SETea --shape-outer-radius (+ --shape-nest-gap = su padding); el hijo añade data-shape-nest y borra su border-radius propio. Es la herencia del padre que pide tu regla 5, y compone con WS-B (un parte usa concéntrico cuando anida; el default de arquetipo cuando no).
Adoptar (los que anidan de verdad): items de dropdown-menu/context-menu/listbox/navigation-menu (in-bar), cards en cards, contenido interno de paneles, etc. Excluir: flush/gap-cero (radio compartido legítimo), y padres con radio full/9999px (SHAPE_ENGINE_RFC §5: el cálculo es ilegible a 9999 → excluir dialog/drawer full/xl).
Pilot: dropdown-menu (espejar command.css:230-232). Verificar concéntrico en navegador. Luego el resto, uno a uno.
WS-D — Consumir el bundle de size (geometría/tipo/icono) + fijar font · esfuerzo S→M
Verificado. El bundle --size-{k}-* se emite (render-css.ts:1328) con 0 consumidores; cada recipe re-declara el mapeo (≈417 tokens). THEMING.md:615-616 dice que consumirlo es el follow-up previsto.
- Inmediato:
STATIC_SIZE.fontSize→ 1:1 (md:'sm'→'md', etc.,static.ts:423); actualizar test + regengenerated/base.css. Mata la mentira 14px. - Consumir en los recipes los slots control-height / font-size / icon-size / padding / gap (= "size recipe" de Chakra; mata la duplicación). Recipes que se desvían a propósito (ladder de px de
button) quedan explícitos. - El slot
radiusSALE del bundle (WS-B): el radio ya no es size-keyed.
WS-E — Densidad + scaling: validar + higiene · esfuerzo S
eidos está alineado y por delante (ambos ejes; ningún referente tiene los dos). Acción = documentar + higiene:
- Documentar que densidad = texto estable y scaling = escala texto (mantener ortogonales), con pisos de a11y (targets ≥ tamaño mínimo) — recomendación M3/Carbon.
- Tokenizar literales residuales:
carouselindicadores0.375rem…→ tokens (carousel.css:128-142); revisar grosor de barraprogress/meter(base.ts:1030).
3. Guards de CI (extender recipe-css-contract.test.ts)
- (WS-A) todo wrapper con prop
sizeresuelve vía la cadena ambiental (no'md'literal suelto). - (WS-B) ningún recipe rebindea
radiusen sus bloques[data-size](el radio está desacoplado del size — invierte el guard equivocado de la v1). - (WS-C) un hijo redondeado anidado en un padre redondeado con padding usa
data-shape-nesto está en la lista de exención (flush / pills /full). - (WS-D/E) sin literales px/rem en tokens de geometría/tipo de recipe; todo
--size-{k}-{slot}emitido tiene ≥1 consumidor (o se poda).
4. Secuencia (pilot-first siempre; nunca cascada)
WS-D paso 1 (font 1:1, trivial) → WS-B (radio Radix, pilot button/card) → WS-C (concéntrico, pilot dropdown-menu) → WS-A (size ambiental, pilot Form→Field) → WS-D pasos 2-3 (consumir bundle) → WS-E (docs+higiene).
B y C son R5 (van juntos: B fija el default de arquetipo, C la herencia concéntrica). A es independiente (R3) y puede solaparse. D-paso-1 primero por trivial; D-pasos-2-3 tras B (que saca el radio del bundle).
5. Verificación
- Tipos/lógica:
npm run check+npx vitest run src/uix/eidos(+ scope del componente). - Visual (obligatorio en B y C): screenshot/preview por size; el radio es visible y
npm run checkno lo valida. Medir, no ojímetro (precedente toggle-group: computed values bit-a-bit). - Pilot-first: un componente, verificar en navegador, luego propagar.
6. Sign-offs restantes (tuyos)
size?: Size(defaultundefined) en lectores — cambio de API pública.- Provider global
componentSize(à la Ant ConfigProvider) además de los publishers por-contenedor — ¿lo incluimos como superficie "size por defecto de la app"? - Cap-at-md: ¿solo overlays (un
fullno engorda su parte) y la herencia en-flujo (Form size="lg") sigue 1:1? (recomendado). - Factor de tema
radius(none…full, à la Radix): ¿lo añadimos si no existe? (recomendado — es la perilla que cierra el modelo Radix). - Cambio visual de WS-B (esquinas de button/card dejan de moverse con el size; pasan a default-de-arquetipo +
rounded).