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/docs/audit/theming/popover.md

175 lines
14 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.

# popover — alcance de tema: análisis y propuesta
> Generado por `node --import tsx/esm scripts/theming-census.ts --report`.
> Lo **medido** y la **propuesta** se regeneran; el **Veredicto** (§5) se conserva.
> Vista de conjunto: [README](./README.md) · método y protocolo:
> [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7.
- **Medido**: 2026-08-24 · **Alcance**: **67%** — 32 de 48 knobs por token público
- **Knobs de apariencia**: 51 — público 32 · privado 4 · global 12 · literal 0 · sistema 3 · excepción 0 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 46 pública(s) — `trigger-height`, `trigger-padding-inline`, `trigger-border-width`, `trigger-border`, `trigger-radius`, `trigger-bg`, `trigger-fg`, `hover-trigger-bg`, `hover-trigger-border`, `open-trigger-bg`, `open-trigger-border`, `content-z`, `overlay-z`, `overlay-bg`, `trigger-font-size`, `gap`, `max-width`, `min-width-sm`, `min-width-md`, `min-width-lg`, `min-width-xl`, `padding-block-sm`, `padding-inline-sm`, `padding-block-md`, `padding-inline-md`, `padding-block-lg`, `padding-inline-lg`, `padding-block-xl`, `padding-inline-xl`, `font-size`, `font-family`, `font-weight`, `line-height`, `title-font-family`, `title-font-size`, `title-font-weight`, `title-line-height`, `title-fg`, `description-font-family`, `description-font-size`, `description-line-height`, `description-fg`, `radius`, `fg`, `arrow-width`, `arrow-height`
- **Eje `size`**: sí · **ficheros**: `popover.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (12)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `popover.css:247` | `[data-popover-content][data-state='closed'][data-side='top'][data-last-action='saved']` | `border-bottom` | `var(--border-width-medium) solid var(--color-fulfill-element)` |
| 2 | `popover.css:248` | `[data-popover-content][data-state='closed'][data-side='bottom'][data-last-action='saved']` | `border-top` | `var(--border-width-medium) solid var(--color-fulfill-element)` |
| 3 | `popover.css:249` | `[data-popover-content][data-state='closed'][data-side='right'][data-last-action='saved']` | `border-left` | `var(--border-width-medium) solid var(--color-fulfill-element)` |
| 4 | `popover.css:250` | `[data-popover-content][data-state='closed'][data-side='left'][data-last-action='saved']` | `border-right` | `var(--border-width-medium) solid var(--color-fulfill-element)` |
| 5 | `popover.css:252` | `[data-popover-content][data-state='closed'][data-side='top'][data-last-action='failed']` | `border-bottom` | `var(--border-width-medium) solid var(--color-threat-element)` |
| 6 | `popover.css:253` | `[data-popover-content][data-state='closed'][data-side='bottom'][data-last-action='failed']` | `border-top` | `var(--border-width-medium) solid var(--color-threat-element)` |
| 7 | `popover.css:254` | `[data-popover-content][data-state='closed'][data-side='right'][data-last-action='failed']` | `border-left` | `var(--border-width-medium) solid var(--color-threat-element)` |
| 8 | `popover.css:255` | `[data-popover-content][data-state='closed'][data-side='left'][data-last-action='failed']` | `border-right` | `var(--border-width-medium) solid var(--color-threat-element)` |
| 9 | `popover.css:259` | `[data-popover-content][data-state='closed'][data-side='top'][data-last-action='cancelled'], [data-popover-content][data-state='closed'][data-side='top'][data-last-action='dismissed'], [data-popover-content][data-state='closed'][data-side='top'][data-last-action='dismissed-outside']` | `border-bottom` | `var(--border-width-medium) solid var(--color-neutral-element)` |
| 10 | `popover.css:262` | `[data-popover-content][data-state='closed'][data-side='bottom'][data-last-action='cancelled'], [data-popover-content][data-state='closed'][data-side='bottom'][data-last-action='dismissed'], [data-popover-content][data-state='closed'][data-side='bottom'][data-last-action='dismissed-outside']` | `border-top` | `var(--border-width-medium) solid var(--color-neutral-element)` |
| 11 | `popover.css:265` | `[data-popover-content][data-state='closed'][data-side='right'][data-last-action='cancelled'], [data-popover-content][data-state='closed'][data-side='right'][data-last-action='dismissed'], [data-popover-content][data-state='closed'][data-side='right'][data-last-action='dismissed-outside']` | `border-left` | `var(--border-width-medium) solid var(--color-neutral-element)` |
| 12 | `popover.css:268` | `[data-popover-content][data-state='closed'][data-side='left'][data-last-action='cancelled'], [data-popover-content][data-state='closed'][data-side='left'][data-last-action='dismissed'], [data-popover-content][data-state='closed'][data-side='left'][data-last-action='dismissed-outside']` | `border-right` | `var(--border-width-medium) solid var(--color-neutral-element)` |
### 1.2 A través de un privado (4)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `popover.css:95` | `[data-popover-content]` | `inline-size` | `min(var(--_popover-width), var(--_popover-max-width), calc(100vw - var(--space-4)))` |
| 2 | `popover.css:96` | `[data-popover-content]` | `min-inline-size` | `var(--_popover-content-min-width-override, auto)` |
| 3 | `popover.css:98` | `[data-popover-content]` | `block-size` | `var(--_popover-content-height-override, auto)` |
| 4 | `popover.css:99` | `[data-popover-content]` | `min-block-size` | `var(--_popover-content-min-height-override, auto)` |
### 1.3 Literales (0)
_Ninguno._
### 1.4 Excepciones firmadas (0) — fuera del ratio
Literales que llevan su anotación `/* literal: <razón> */` en la propia
declaración: la válvula de recipe-contract §3, la misma que honra
`component-audit`. **Una desviación firmada no es deuda** — se listan para que la
razón se lea, no para acuñarlas.
_Ninguno._
## 2. Sistema transversal (3) — informativo, fuera del ratio
Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `popover.css:44` | `[data-popover-trigger]:not([data-archetype='field-trigger']):focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 2 | `popover.css:208` | `[data-popover-arrow] polygon` | `fill` | `var(--depth-overlay-surface)` |
| 3 | `popover.css:213` | `[data-popover-arrow] path` | `stroke` | `var(--depth-overlay-border)` |
## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_popover-padding-block` | 4 | `var(--popover-padding-block-md)`, `var(--popover-padding-block-sm)`, `var(--popover-padding-block-lg)`, `var(--popover-padding-block-xl)` | public | **sí** |
| `--_popover-padding-inline` | 4 | `var(--popover-padding-inline-md)`, `var(--popover-padding-inline-sm)`, `var(--popover-padding-inline-lg)`, `var(--popover-padding-inline-xl)` | public | **sí** |
| `--_popover-base-width` | 4 | `var(--popover-min-width-md)`, `var(--popover-min-width-sm)`, `var(--popover-min-width-lg)`, `var(--popover-min-width-xl)` | public | **sí** |
| `--_popover-width` | 1 | `var( --_popover-content-width-override, var(--_popover-match-anchor-width, var(--_popover-base-width)) )` | private | no |
| `--_popover-max-width` | 1 | `var(--_popover-content-max-width-override, var(--popover-max-width))` | public | **sí** |
| `--_popover-max-height` | 1 | `var( --_popover-content-max-height-override, var(--floating-available-height, calc(100dvh - var(--space-4))) )` | global | no |
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_popover-content-height-override`, `--_popover-content-max-height-override`, `--_popover-content-max-width-override`, `--_popover-content-min-height-override`, `--_popover-content-min-width-override`, `--_popover-content-width-override`, `--_popover-match-anchor-width`.
## 4. Propuesta de corrección
- **Tiene eje `size`**: los tokens dimensionales van por talla (`{part}-{eje}-{k}`) apuntando al bundle `--size-{k}-*`, nunca al primitivo crudo (theming §5; el guard `recipe-css-contract` prohíbe el primitivo).
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (1)
Valor **verbatim** del CSS de hoy: el default no se mueve, sólo cambia quién
puede moverlo. Nombres derivados de recipe-contract §1 (ejes lógicos, talla
al final) y theming §6.7 (slots de color, modificador delante). Un token con
DOS valores distintos es una colisión de nombre: son dos knobs, o el nombre
no distingue lo que debería — se marca `⚠`.
| token (`--popover-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `content-width` | `root` | `var( --_popover-content-width-override, var(--_popover-match-anchor-width, var(--_popover-base-width)) )` | 1 |
### 4.2 Sin nombre mecánico (15)
- **⚠ decisión: el privado que alimenta este knob no se declara en el CSS (viene de `base.ts` o de un estilo inline) — hay que resolverlo antes de nombrarlo** — 3: `min-inline-size`, `block-size`, `min-block-size`.
- **⚠ decisión: `border-bottom` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 3: `border-bottom`.
- **⚠ decisión: `border-top` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 3: `border-top`.
- **⚠ decisión: `border-left` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 3: `border-left`.
- **⚠ decisión: `border-right` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 3: `border-right`.
### 4.3 Avisos sobre los tokens propuestos (1)
- **el privado `--_popover-width` debe pasar a leer este público (o desaparecer)** — `--popover-content-width`
### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)
- [ ] **Privado que no deriva de un público** — §3 lo marca; el privado debe leer el público o desaparecer.
- [ ] **Velo o acento en el nodo equivocado** (`archetype: 'item'` en un envoltorio, un `background` en shorthand que mata la capa de estado) — se mide desde el píxel hacia arriba.
- [ ] **Doble animación** al mover un sello a una superficie con animación propia — registro de `animationstart`/`animationend`.
- [ ] **Diff de computed = 0** en reposo · hover · abierto · disabled · foco, por talla, antes y después.
- [ ] **Centinela por token nuevo**: valor imposible en el root → el nodo lo sigue. Si no, el token miente.
## 5. Veredicto
<!-- veredicto:start -->
**EJECUTADO 2026-08-22 — 46 % → 67 %**, 11 claves de costura para el
DISPARADOR + una retirada. Lo que queda fuera es UNA cosa, y no es suya.
**NOTA 2026-08-25 — el sobre de disparador pasa a SUELO** (firma, changelog
§55). Las doce claves de disparador + estado descritas abajo no cambian de
nombre ni de número: cambia su ESPECIFICIDAD. Las dos reglas que las consumen
—el reposo y su `:hover`— se emiten desde `:where(...)`, (0,0,0), porque el
propio fichero llama a esta regla _«a baseline button envelope»_ y un baseline a
(0,2,0) le gana a toda receta de huésped a (0,1,0). Medido: **69 de 76**
instancias del catálogo no mueven un valor de receta —el disparador desnudo, que
es para quien se escribió, sigue vestido entero— y las **7** que mueven lo hacen
hacia lo que su propia receta declara. Tercera aplicación de la ley de §12.9
(plano de profundidad) y §53 (escalera de paleta). `[data-state='open']` y
`:focus-visible` se quedan donde estaban: fuera de la firma, en
`next-features.md` §13.
**El disparador genérico es cromo propio**: el que no lleva el arquetipo
`field-trigger` (que tiene su tratamiento aparte) montaba su alto, su
padding, su borde, su radio, su superficie y su tinta sobre primitivos crudos.
Once claves, con dos notas: `trigger-height` lee el BUNDLE de talla
(`--size-md-control-height`) y no el primitivo que el CSS traía, y hover y
abierto tienen CADA UNO su par de claves aunque hoy compartan valor — son dos
estados y un tema puede querer el abierto más marcado.
**`bg` RETIRADO: era un huérfano que el test de huérfanos no veía.** Ninguna
regla CSS lo consumía; el único sitio del corpus que lo nombraba era un
COMENTARIO de `popover-arrow.svelte` («fill: var(--popover-bg)»), y ese test
busca el nombre como texto en los ficheros del componente. La superficie del
panel la pinta el PLANO de profundidad (`--depth-overlay-surface`), que es lo
que un plano debe poseer; el comentario queda corregido.
**Los doce knobs que siguen fuera son la FIRMA DE SALIDA** —`data-last-action`
× 3 intents × 4 lados, tiñendo el borde durante la animación de cierre— y NO
se cosen aquí: la misma matriz está repetida en `drawer` (22 reglas) y
`dialog` (9), leyendo `--color-{fulfill,threat,neutral}-element`. La tabla
morfo→eidos la sitúa como mapeo TRANSVERSAL (« `events[].prewrite` → tinting
the exit anim by cause»), misma fila que los arquetipos: es canal visual de
sema, no cromo de componente. Coserlo tres veces sería triplicar un vocabulario
que debería vivir una sola vez → next-features §13.
**Trece adjudicaciones, todas medidas.** Nueve son partes que la demo no
monta (Title, Description) y dos el overlay opcional: montados en el panel
ABIERTO con los atributos que el CSS selecciona, alcanzan. **Las dos que no
son de montaje**: `font-family` y `line-height` no pueden ganar porque el
plano `overlay` impone la tipografía del panel — el hallazgo §12.9, que
espera firma y afecta a VEINTE componentes. Aquí queda medido otra vez.
**Dos cegueras del instrumento, arregladas**: la página tiene CINCO
disparadores y el guard abría el primero del documento (un ejemplo de más
abajo), no el del escenario → `openWith` apuntando al escenario; y
`content-z` se consume sobre el ENVOLTORIO FLOTANTE
(`:has(> [data-popover-content])`), un nodo sin atributo `data-popover*` que
el filtro no veía: leía muerto moviendo 80 → 4321.
Sonda antes/después: **0 diffs en 420 valores** (8 estados). El escenario monta
UN disparador y el panel portala fuera de él, así que el estado `abierto` es el
que mide el panel: 7 nodos.
<!-- veredicto:end -->

Powered by TurnKey Linux.