From eb5e4b183341aaf9b57f4a70df5861a383190191 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 6 Jun 2026 01:14:27 +0200 Subject: [PATCH] perf(eidos): code-split dialog CSS (Phase 1 pilot) + set compound pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- optimize-bundle.md | 36 ++++++++++++++++++- src/uix/eidos/components/dialog/dialog.svelte | 1 + src/uix/eidos/index.css | 8 ++--- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/optimize-bundle.md b/optimize-bundle.md index 47c2854f1..9007b5661 100644 --- a/optimize-bundle.md +++ b/optimize-bundle.md @@ -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/`. diff --git a/src/uix/eidos/components/dialog/dialog.svelte b/src/uix/eidos/components/dialog/dialog.svelte index ffa31f0c2..6ebb8a039 100644 --- a/src/uix/eidos/components/dialog/dialog.svelte +++ b/src/uix/eidos/components/dialog/dialog.svelte @@ -19,6 +19,7 @@ * * */ + import './dialog.css'; import * as Dialog from '$soma/components/dialog'; import type { DialogProps } from './types'; diff --git a/src/uix/eidos/index.css b/src/uix/eidos/index.css index a9f87fbda..19ea9aa3c 100644 --- a/src/uix/eidos/index.css +++ b/src/uix/eidos/index.css @@ -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';