@ -19,7 +19,7 @@ excediendo** — y es un problema *estructural* (un agregado monolítico), no de
| JS por página | ✅ Sano | entry 10.4 KB gz · runtime ~35 KB gz · componente mediana 7.3 KB gz |
| JS por página | ✅ Sano | entry 10.4 KB gz · runtime ~35 KB gz · componente mediana 7.3 KB gz |
| CSS por página | ⚠️ Exceso | **113.5 KB gz / 851 KB raw** en *toda* página `/uix` |
| CSS por página | ⚠️ Exceso | **113.5 KB gz / 851 KB raw** en *toda* página `/uix` |
| Code-splitting JS | ✅ Correcto | color engine, sema y componentes se cargan bajo demanda |
| Code-splitting JS | ✅ Correcto | color engine, sema y componentes se cargan bajo demanda |
| Code-splitting CSS | ⏳ En progreso | Fase 0 hecha (− 4.5 KB gz/página, doble-carga eliminada); 94 recipes aún en el agregado |
| Code-splitting CSS | ✅ Resuelto | 93 recipes code-split; index.css foundation-only. **113.5 → 54.4 KB gz/página** (− 59 KB). Pendiente: Fase 5 (podar base.css) |
---
---
@ -63,9 +63,9 @@ monta la página. Ese eje decide la Fase 1 (`@layer` o no).
| --- | --- | --- | --- |
| --- | --- | --- | --- |
| **0** ✅ | Des-duplicar los 10 (quitar de `index.css` ; ya auto-importan) | nulo | hecho — ver resultado |
| **0** ✅ | Des-duplicar los 10 (quitar de `index.css` ; ya auto-importan) | nulo | hecho — ver resultado |
| **1** ✅ | Piloto `dialog` (compuesto + portalizado): import en la raíz · tokens cross-portal OK · ** `@layer` descartado** | medio | hecho — ver resultado |
| **1** ✅ | Piloto `dialog` (compuesto + portalizado): import en la raíz · tokens cross-portal OK · ** `@layer` descartado** | medio | hecho — ver resultado |
| **2** | Partials compartidos (`menu-indicator`) + primitivas layout se quedan en foundation; auditar colisiones a igual especificidad | medio | eidos-lint |
| **2** ✅ | Partials compartidos (`menu-indicator` order-independent por especificidad) + primitivas layout en foundation | medio | hecho |
| **3** | Propagar a los ~94 restantes **por lotes** por familia (overlays·pickers·menús·forms·data·tipografía); nunca en cascada | medio | build+lint+visual/lote |
| **3** ✅ | Propagar a los 83 restantes **por lotes** por familia; couplings arreglados (toggle-group, pickers) | medio | hecho |
| **4** | `index.css` = foundation-only; medir CSS/página | bajo | build |
| **4** ✅ | `index.css` = foundation-only (− 59 KB gz/página) | bajo | hecho |
| **5** | (track aparte) podar `base.css` a roles tematizados + calendarios no-gregorianos dynamic import | alto | build |
| **5** | (track aparte) podar `base.css` a roles tematizados + calendarios no-gregorianos dynamic import | alto | build |
**Cross-coupling a vigilar** (recipes agregados que referencian un componente
**Cross-coupling a vigilar** (recipes agregados que referencian un componente
@ -93,7 +93,7 @@ Evaluado con evidencia, no en teoría. **No se adopta `@layer`**:
- Cada recipe ya scopea a `[data-{component}]` (doctrina CLAUDE.md) → las
- Cada recipe ya scopea a `[data-{component}]` (doctrina CLAUDE.md) → las
colisiones cross-componente a igual especificidad están **estructuralmente
colisiones cross-componente a igual especificidad están **estructuralmente
prevenidas**. La única dependencia de orden real (`menu-indicator`, partial
prevenidas**. La única dependencia de orden real (`menu-indicator`, partial
compartido) se resuelve dejándolo en foundation (Fase 2) .
compartido) se resolvió por **especificidad** (Fase 2), no por orden .
El scoping por `data-*` ya da el determinismo que daría `@layer` , sin su coste
El scoping por `data-*` ya da el determinismo que daría `@layer` , sin su coste
(envolver 104 ficheros) ni su riesgo (invertir 16 `!important` ). `@layer` queda
(envolver 104 ficheros) ni su riesgo (invertir 16 `!important` ). `@layer` queda
@ -114,6 +114,54 @@ scoping no resuelva.
portalizado (fuera de `[data-dialog]` ) sigue **completamente estilado**
portalizado (fuera de `[data-dialog]` ) sigue **completamente estilado**
(bg `oklch(0.285 0 0)` , radius 16px, sombra, padding 20px) — tokens de
(bg `oklch(0.285 0 0)` , radius 16px, sombra, padding 20px) — tokens de
`base.css` global + recipe del chunk. El portal no rompe nada.
`base.css` global + recipe del chunk. El portal no rompe nada.
### Fases 2– 4 — barrido completo (COMPLETADO)
83 recipes splitteados en 5 lotes (build+navegador+commit por lote). `index.css`
quedó **foundation-only** : `base.css` + `archetypes` + `events` + las 10
primitivas de layout (box/flex/grid/stack/group/wrap/container/section/aspect-
ratio/auto-grid — se quedan: uso ubicuo + las recipes layerean sobre ellas).
| Lote | Qué | Δ monolito (gz) |
| --- | --- | --- |
| 1 (35) | tipografía·inline·overlays·feedback·misc | 107.8 → 90.4 KB |
| 2 (7) | menús + `menu-indicator` order-independent | 90.4 → 86.5 KB |
| 3 (15) | form controls | 86.5 → 74.7 KB |
| 4 (16) | familia de pickers | 74.7 → 63.5 KB |
| 5 (10) | data·service·`words` | 63.5 → **54.4 KB** |
**Resultado final: monolito 850 879 → 335 100 raw · 113 489 → 54 429 gz** —
**− 515 KB raw / − 59 KB gz en TODA página**. Una página de 5 componentes ahora
paga ~54 KB foundation + sus chunks (dialog 1.6 KB, toggle 0.45 KB, …) en vez de
113.5 KB fijos. El piso = `base.css` (52 KB gz), tal como predijo la auditoría.
**Patrón de acoplamiento (lección clave)** — un compuesto que **renderiza el
markup** de otro componente (vía partes soma o `import type` ) pero **no monta el
componente eidos** no arrastra su recipe. Hay que importarla explícitamente (leaf
primero, para que el compuesto la pueda sobreescribir). Casos encontrados y
arreglados, verificados en navegador:
- `toggle-group` → `toggle` (los ítems son DOM-equivalentes a `<Toggle>` por
identidad estructural). Sin el fix: botones crudos (bg `#f0f0f0` , borde outset).
- `date-picker` → `calendar` + `month-grid` + `year-grid` (vistas con `import
type`). Sin el fix: calendario en `display:table` crudo.
- `date-range-picker` → `range-calendar` ; `date-range-field` → `date-field` ;
`time-range-field` → `time-field` .
- `color/time/time-range-picker` → `slider` (usan partes soma Slider). Sin el fix:
thumb 0px.
- `picker-shell` → `popover` (contenido en un Popover soma).
Un compuesto que **monta** el componente eidos (p. ej. `words` monta Button/Icon/
NumberField/ColorPicker/TextArea) NO necesita el import — el recipe llega solo.
> **Coste residual aceptado** : si una página usa a la vez un compuesto y el leaf
> standalone (p. ej. date-picker + un Calendar suelto), el recipe del leaf viaja
> en 2 chunks (doble-carga menor, combo raro). Eliminarlo requeriría que el
> compuesto montara el componente eidos en vez de markup soma — refactor de
> componente, fuera del alcance de este split.
`@layer` siguió sin necesitarse: cero colisiones cross-componente en todo el
barrido (el scoping `[data-{component}]` lo confirma en la práctica).
- `check` 0 errores.
- `check` 0 errores.
## Metodología
## Metodología