perf(eidos): code-split dialog CSS (Phase 1 pilot) + set compound pattern

Pilot for the index.css break-up. Establishes the pattern for compound,
portaled components:
- the `import './x.css'` goes in the ROOT `.svelte` (dialog.svelte); the
  barrel imports the root, so the CSS loads when ANY part mounts.
- portaled content stays styled: recipe ships in dialog's chunk, tokens
  (--dialog-content-bg, …) come from base.css global → verified in
  browser that the portaled [data-dialog-content] keeps bg/radius/
  shadow/padding.

`@layer` evaluated and DISCARDED (evidence-based): 0 current usage, 16
!important that @layer would silently invert, and per-component
[data-*] scoping already prevents cross-component collisions. Kept as a
documented escape hatch.

index.css 94→93 @imports; monolith 814,420→805,692 raw (−8.7 KB) /
−1.1 KB gz. Reworded the policy comment to be list-free (no churn across
the remaining 93). check 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
active-uix
dev 4 months ago
parent b1ab438523
commit eb5e4b1833

@ -62,7 +62,7 @@ monta la página. Ese eje decide la Fase 1 (`@layer` o no).
| Fase | Qué | Riesgo | Verifica |
| --- | --- | --- | --- |
| **0** ✅ | Des-duplicar los 10 (quitar de `index.css`; ya auto-importan) | nulo | hecho — ver resultado |
| **1** | Piloto `dialog` (compuesto + portalizado): dónde va el import · tokens cross-portal · **decidir `@layer`** | medio | build + navegador |
| **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 |
| **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 |
| **4** | `index.css` = foundation-only; medir CSS/página | bajo | build |
@ -82,6 +82,40 @@ chunk carga el recipe). Repetir esta auditoría en cada lote de la Fase 3.
dejan solo su contrato de token de `base.css`; textarea deja solo la
referencia de `words.css`. Build verde, sin acoplamiento roto.
### Decisión `@layer` (Fase 1) — DESCARTADO
Evaluado con evidencia, no en teoría. **No se adopta `@layer`**:
- **0** usos actuales en eidos → sería un sistema nuevo sobre las 104 recipes.
- **16** `!important` en 5 recipes → `@layer` invierte su precedencia (en cascada
con capas, `!important` resuelve en orden de capa **inverso**) ⇒ regresiones
silenciosas a auditar una a una.
- Cada recipe ya scopea a `[data-{component}]` (doctrina CLAUDE.md) → las
colisiones cross-componente a igual especificidad están **estructuralmente
prevenidas**. La única dependencia de orden real (`menu-indicator`, partial
compartido) se resuelve dejándolo en foundation (Fase 2).
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
como **escape hatch documentado** si algún día aparece una colisión que el
scoping no resuelva.
### Fase 1 — resultado medido (piloto `dialog`)
- **Patrón fijado para compuestos**: el `import './x.css'` va en el `.svelte`
**raíz** (`dialog.svelte`). El barrel (`index.ts`) importa la raíz, así que el
CSS carga al usar cualcomponente parte (Trigger, Content portalizado, …).
- `index.css`: 94 → **93** `@import`. Monolito **814 420 → 805 692 raw**
(−8.7 KB) · **108 991 → 107 843 gz** (−1.1 KB).
- Recipe fuera del monolito: `[data-dialog-content]` / `[data-dialog-overlay]`
= **0**; viaja en su chunk. El token `--dialog-content-bg` se queda en
`base.css` (global).
- **Cross-portal verificado en navegador**: el `[data-dialog-content]`
portalizado (fuera de `[data-dialog]`) sigue **completamente estilado**
(bg `oklch(0.285 0 0)`, radius 16px, sombra, padding 20px) — tokens de
`base.css` global + recipe del chunk. El portal no rompe nada.
- `check` 0 errores.
## Metodología
- `npm run build` → `adapter-static`, salida en `build/`.

@ -19,6 +19,7 @@
* </Dialog.Portal>
* </Dialog>
*/
import './dialog.css';
import * as Dialog from '$soma/components/dialog';
import type { DialogProps } from './types';

@ -64,10 +64,9 @@
/*
* Code-split components are intentionally NOT aggregated here: they import
* their own CSS from their `.svelte`, so Vite emits a per-component chunk that
* loads only when the component mounts. Do NOT re-add their `@import` here or
* the CSS double-loads (aggregate + chunk). Currently code-split:
* badge · card · image · password-field · s-text · s-text-virtual-list ·
* scroll-frames · skeleton · spinner · textarea
* loads only when the component mounts. The ABSENCE of a component's `@import`
* below means it is code-split — do NOT re-add it (the CSS would double-load:
* aggregate + chunk). Migration tracker + plan: optimize-bundle.md.
*/
@import './components/box/box.css';
@import './components/flex/flex.css';
@ -98,7 +97,6 @@
@import './components/avatar/avatar.css';
@import './components/breadcrumb/breadcrumb.css';
@import './components/toast/toast.css';
@import './components/dialog/dialog.css';
@import './components/alert-dialog/alert-dialog.css';
@import './components/dropdown-menu/dropdown-menu.css';
@import './components/context-menu/context-menu.css';

Loading…
Cancel
Save

Powered by TurnKey Linux.