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

136 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Plan de corrección — herencia / escala (Eidos) · **v2, corregido por diseño + referencias**
> **Compañero de** [`inherit_audit.md`](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](https://www.radix-ui.com/themes/docs/theme/spacing)). | `--scaling` idéntico. | ✅ validado; solo higiene (literales). |
| **densidad** (R2) | M3/Carbon/Fluent: altura/spacing cambian, **texto estable** ([m3/density](https://m3.material.io/foundations/layout/understanding-layout/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](https://chakra-ui.com/docs/theming/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](https://ant.design/components/config-provider/)). 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](https://www.radix-ui.com/themes/docs/theme/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](https://developer.apple.com/documentation/swiftui/concentricrectangle)); canon web idéntico ([Cloud Four](https://cloudfour.com/thinks/the-math-behind-nesting-rounded-corners/)). | `[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.