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/select.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.

# select — 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-23 · **Alcance**: **92%** — 57 de 62 knobs por token público
- **Knobs de apariencia**: 69 — público 57 · privado 5 · global 0 · literal 0 · sistema 7 · excepción 3 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 103 pública(s) — `stack-gap`, `font-family`, `font-size-xs`, `font-size-sm`, `font-size-md`, `font-size-lg`, `font-size-xl`, `line-height`, `fg`, `primary-text`, `secondary-text`, `neutral-text`, `affirm-text`, `fulfill-track`, `fulfill-border`, `fulfill-text`, `risk-text`, `threat-text`, `loss-track`, `loss-border`, `loss-text`, `trigger-height-xs`, `trigger-height-sm`, `trigger-height-md`, `trigger-height-lg`, `trigger-height-xl`, `trigger-padding-inline-xs`, `trigger-padding-inline-sm`, `trigger-padding-inline-md`, `trigger-padding-inline-lg`, `trigger-padding-inline-xl`, `trigger-gap-xs`, `trigger-gap-sm`, `trigger-gap-md`, `trigger-gap-lg`, `trigger-gap-xl`, `trigger-border-width`, `trigger-border`, `hover-trigger-border`, `trigger-radius`, `trigger-bg`, `trigger-shadow`, `trigger-fg`, `trigger-indicator-fg`, `trigger-indicator-size`, `placeholder-fg`, `disabled-border`, `disabled-bg`, `disabled-fg`, `invalid-border`, `content-z`, `content-padding-block-xs`, `content-padding-block-sm`, `content-padding-block-md`, `content-padding-block-lg`, `content-padding-block-xl`, `content-padding-inline-xs`, `content-padding-inline-sm`, `content-padding-inline-md`, `content-padding-inline-lg`, `content-padding-inline-xl`, `content-min-width`, `content-max-width`, `content-max-height`, `content-radius`, `content-fg`, `open-duration`, `close-duration`, `viewport-gap`, `viewport-padding`, `group-gap`, `group-padding-block`, `group-heading-padding-block`, `group-heading-padding-inline`, `group-heading-font-family`, `group-heading-font-size`, `group-heading-font-weight`, `group-heading-letter-spacing`, `group-heading-fg`, `separator-size`, `separator-margin-block`, `separator-bg`, `item-gap`, `item-padding-inline`, `item-fg`, `item-indicator-size`, `item-description-mt`, `item-description-fg`, `item-description-font-size`, `transition-duration`, `transition-ease`, `primary-track`, `primary-border`, `secondary-track`, `secondary-border`, `neutral-track`, `neutral-border`, `affirm-track`, `affirm-border`, `risk-track`, `risk-border`, `threat-track`, `threat-border` · 3 privada(s) forward — `_palette-track`, `_palette-border`, `_palette-text`
- **Eje `size`**: sí · **ficheros**: `select.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (3)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `select.css:131` | `[data-select-trigger][data-invalid]` | `border-color` | `var(--color-risk-border)` |
| 2 | `select.css:136` | `[data-select-trigger][data-invalid]:focus-visible, [data-select-trigger][data-invalid][data-state='open']` | `border-color` | `var(--color-risk-border)` |
| 3 | `select.css:277` | `[data-select-group]` | `gap` | `var(--space-1)` |
### 1.2 A través de un privado (5)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `select.css:101` | `[data-select-trigger][data-state='open']` | `border-color` | `var(--_select-palette-border)` |
| 2 | `select.css:107` | `[data-select-trigger]:focus-visible` | `border-color` | `var(--_select-palette-border)` |
| 3 | `select.css:169` | `[data-select-trigger][data-state='open'] [data-select-indicator]` | `color` | `var(--_select-palette-text)` |
| 4 | `select.css:319` | `[data-select-item][data-state='checked']` | `background` | `var(--_select-palette-track)` |
| 5 | `select.css:320` | `[data-select-item][data-state='checked']` | `color` | `var(--_select-palette-text)` |
### 1.3 Literales (3)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `select.css:18` | `[data-select]` | `inline-size` | `100%` |
| 2 | `select.css:35` | `[data-select-trigger]` | `inline-size` | `100%` |
| 3 | `select.css:359` | `[data-select-item][data-state='checked'] [data-select-item-indicator]` | `opacity` | `1` |
### 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 (7) — 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 | `select.css:97` | `[data-select-trigger]:hover:not(:disabled):not([data-disabled])` | `background-image` | `linear-gradient(var(--state-hover), var(--state-hover))` |
| 2 | `select.css:102` | `[data-select-trigger][data-state='open']` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 3 | `select.css:108` | `[data-select-trigger]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 4 | `select.css:335` | `[data-select-item-description]` | `opacity` | `var(--opacity-subtle)` |
| 5 | `select.css:340` | `[data-select-item][data-state='checked'] [data-select-item-description]` | `opacity` | `var(--opacity-subtle)` |
| 6 | `select.css:369` | `[data-select-arrow] polygon` | `fill` | `var(--depth-overlay-surface)` |
| 7 | `select.css:374` | `[data-select-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? |
| --- | ---: | --- | --- | :-: |
| `--_select-trigger-height` | 5 | `var(--select-trigger-height-md)`, `var(--select-trigger-height-xs)`, `var(--select-trigger-height-sm)`, `var(--select-trigger-height-lg)`, `var(--select-trigger-height-xl)` | public | **sí** |
| `--_select-trigger-padding-inline` | 5 | `var(--select-trigger-padding-inline-md)`, `var(--select-trigger-padding-inline-xs)`, `var(--select-trigger-padding-inline-sm)`, `var(--select-trigger-padding-inline-lg)`, `var(--select-trigger-padding-inline-xl)` | public | **sí** |
| `--_select-trigger-gap` | 5 | `var(--select-trigger-gap-md)`, `var(--select-trigger-gap-xs)`, `var(--select-trigger-gap-sm)`, `var(--select-trigger-gap-lg)`, `var(--select-trigger-gap-xl)` | public | **sí** |
| `--_select-font-size` | 10 | `var(--select-font-size-md)`, `var(--select-font-size-xs)`, `var(--select-font-size-sm)`, `var(--select-font-size-lg)`, `var(--select-font-size-xl)` | public | **sí** |
| `--_select-content-padding-block` | 5 | `var(--select-content-padding-block-md)`, `var(--select-content-padding-block-xs)`, `var(--select-content-padding-block-sm)`, `var(--select-content-padding-block-lg)`, `var(--select-content-padding-block-xl)` | public | **sí** |
| `--_select-content-padding-inline` | 5 | `var(--select-content-padding-inline-md)`, `var(--select-content-padding-inline-xs)`, `var(--select-content-padding-inline-sm)`, `var(--select-content-padding-inline-lg)`, `var(--select-content-padding-inline-xl)` | public | **sí** |
| `--_select-content-width` | 1 | `var(--_select-match-anchor-width, var(--select-content-min-width))` | public | **sí** |
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_select-match-anchor-width`, `--_select-palette-border`, `--_select-palette-text`, `--_select-palette-track`.
## 4. Propuesta de corrección
- **Consume la capa compartida `list-surface`.** Un eje que la capa posee se consume como `var(--_x, var(--x))`; el consumidor **no acuña** `--select-{eje}` para él — sería un vocabulario paralelo (README de `eidos/components`, «Capas compartidas» regla 2).
- **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` (3)
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 (`--select-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `invalid-trigger-border` | `root` | `var(--color-risk-border)` | 1 |
| `open-invalid-trigger-border` | `root` | `var(--color-risk-border)` | 1 |
| `group-gap` | `root` | `var(--space-1)` | 1 |
### 4.2 Sin nombre mecánico (8)
- **⚠ decisión: `100%` es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar** — 2: `inline-size`.
- **⚠ 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** — 5: `border-color`, `color`, `background`.
- **⚠ decisión: `1` es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar** — 1: `opacity`.
### 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-24 — 83 % → 92 %.** Dos claves acuñadas, tres literales
firmados, 34 tokens adjudicados y CERO cambios de píxel (sonda: 2.176 valores
computados en 8 estados, **0 diffs**; capturas 2× antes/después **idénticas byte
a byte**, con el panel cerrado y abierto).
**Lo acuñado (2, la COSTURA de PLAN §2-A):**
- **`invalid-border`** — las tres declaraciones del estado inválido (borde en
reposo, borde con foco/abierto y el anillo `box-shadow` que lo dobla) leían
`var(--color-risk-border)` **a pelo**. Es UNA clave, no dos: la §4 generada
proponía `invalid-trigger-border` + `open-invalid-trigger-border`, y las filas
agrupadas por VALOR son una sola. Nombre bare como sus vecinas `disabled-*`
(las ranuras de estado de esta receta son las del trigger) y como las siete
`invalid-border` del catálogo.
- **`group-gap`** — el hueco entre filas de un grupo iba a `var(--space-1)`
directo. Nombre CONTADO en `base.ts` antes de acuñarlo: `toolbar.group-gap`.
**Los tres literales firmados** (§1.3 → §1.4, fuera del ratio): los dos
`inline-size: 100%` (identidad: el select ES la anchura de su contenedor, el
trigger ES la caja del select) y el `opacity: 1` del check revelado, que deshace
el `0` de la regla base. Precedentes: `avatar`, `background`, `image`, `tooltip`.
**Lo que queda fuera y por qué (5 privados, techo del componente):** son los
**forwards de paleta THM-2** (`--_select-palette-{track,border,text}`) leídos a
pelo por las reglas de abierto / foco / checked. No se acuñan: un público encima
dejaría que un tema los fijara y matara en silencio el `color=` de cada
instancia. **El componente está CERRADO en su 92 %.**
**La §4 generada falló, otra vez, de las cuatro maneras conocidas**: infló
(2 claves para 1 concepto), y su §4.2 daba los cinco knobs de paleta como
«decisión pendiente» cuando la doctrina THM-2 ya los resuelve.
### Lo que enseñó — el PORTAL parte el instrumento en dos
1. **El guard tiene que mantener el panel ABIERTO para medir treinta tokens, y
el estado abierto TAPA los de reposo.** `trigger-border` y
`trigger-indicator-fg` leían muertos con el panel abierto y **mueven con el
panel cerrado** (medido). No es un token que miente: es un instrumento que
sólo puede estar en un estado a la vez.
2. **Tres knobs viven en nodos SIN `data-select-*`**: la `z` del panel está en el
`[data-floating-wrapper]` que soma porta (el caso `popover.content-z`), y el
ritmo del viewport —hueco entre opciones y respiro final— lo pinta el
`ScrollArea` compuesto. Con `extraNodes` pasaron de muertos a vivos (65/101 →
69/103). **Cuenta los nodos: 4 en reposo, 42 con el panel abierto.**
3. **La cascada de paleta anula los 24 tonos, y aquí en su forma MULTI-PARTE.**
El bloque genérico `[data-select-trigger][data-color], [data-select-content][data-color]`
se emite el último y lee `var(--palette-track, …)`; medido sobre el trigger
abierto: `--select-primary-border` no mueve nada, `--palette-border` sobre el
mismo nodo repinta. Es la incidencia de las 419 claves, con dos raíces en vez
de una porque el panel se porta fuera.
4. **⚠ `separator-size` alcanza y AUN ASÍ no pinta.** El separador está en la
columna flex del ScrollArea con `flex-shrink` 1 y el panel desborda
(scrollHeight 670 vs clientHeight 310): su `block-size: 1px` computa **0px** y
ningún valor del token lo mueve; con `flex-shrink: 0` sobre el mismo nodo el
token sigue (1px → 9px). **Un token vivo sobre un nodo aplastado lee igual que
uno muerto.** Defecto de píxel → `next-features` §13, no se arregla aquí.
5. **§12.9 respetado**: el contenido lleva `data-depth=overlay`, así que NO se
acuñó ni `font-family` ni `line-height` para el panel (las que ya existen
sirven a la raíz y al trigger, que no son overlay).
<!-- veredicto:end -->

Powered by TurnKey Linux.