You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/inherit_fix_plan.md

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 size del control (sesgo de statu quo, anclado en toggle). 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 tema radius × override rounded— 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"):

  1. Nuevo src/uix/eidos/lib/ui-size-context.ts (espejo de list-surface-context.ts, getter reactivo) + clampSize<S>(size, allowed, fallback) para el subset por componente.
  2. 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 tipo ConfigProvider.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.
  3. 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 de resolve, nunca como size ?? ctx (rompería resolveResponsiveProp en los 124 sitios).
  4. Cambio de API: lectores pasan de size = 'md' a size?: Size (default undefined) para detectar "no fijado". (sign-off)
  5. Cap-at-md (THEMING.md:560) aplica a su ámbito real —derivación contenedor→parte en overlays (un dialog full no engorda su Close)— no necesariamente a la herencia en-flujo (<Form size="lg"> probablemente quiere controles lg). Política por tipo de publisher. (sign-off)
  6. 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):

  1. 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).
  2. × factor de tema radius (none|small|medium|large|full) — verificar si existe; si no, añadir un --radius-factor global à la Radix (--radius-factor 0/0.75/1/1.5/1.5 sobre la escala --radius-*). Es la perilla de tema que falta.
  3. Override rounded per-instancia — button/card ya lo tienen (button.css:216-220, card.css:133-134); generalizarlo como la jaula abierta (= prop radius de Radix/Mantine/Chakra).
  4. 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 por toggle (toggle.css:64-98 rebindea --_toggle-radius por 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}-radius dejan 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/card quedan 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.

  1. Inmediato: STATIC_SIZE.fontSize → 1:1 (md:'sm'→'md', etc., static.ts:423); actualizar test + regen generated/base.css. Mata la mentira 14px.
  2. 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.
  3. El slot radius SALE 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: carousel indicadores 0.375rem… → tokens (carousel.css:128-142); revisar grosor de barra progress/meter (base.ts:1030).

3. Guards de CI (extender recipe-css-contract.test.ts)

  1. (WS-A) todo wrapper con prop size resuelve vía la cadena ambiental (no 'md' literal suelto).
  2. (WS-B) ningún recipe rebindea radius en sus bloques [data-size] (el radio está desacoplado del size — invierte el guard equivocado de la v1).
  3. (WS-C) un hijo redondeado anidado en un padre redondeado con padding usa data-shape-nest o está en la lista de exención (flush / pills / full).
  4. (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 check no 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)

  1. size?: Size (default undefined) en lectores — cambio de API pública.
  2. Provider global componentSize (à la Ant ConfigProvider) además de los publishers por-contenedor — ¿lo incluimos como superficie "size por defecto de la app"?
  3. Cap-at-md: ¿solo overlays (un full no engorda su parte) y la herencia en-flujo (Form size="lg") sigue 1:1? (recomendado).
  4. Factor de tema radius (none…full, à la Radix): ¿lo añadimos si no existe? (recomendado — es la perilla que cierra el modelo Radix).
  5. Cambio visual de WS-B (esquinas de button/card dejan de moverse con el size; pasan a default-de-arquetipo + rounded).

Powered by TurnKey Linux.