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/badge.md

184 lines
12 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.

# badge — 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-25 · **Alcance**: **100%** — 15 de 15 knobs por token público
- **Knobs de apariencia**: 17 — público 15 · privado 0 · global 0 · literal 0 · sistema 0 · excepción 5 · estructural 0 · puente 2 · canal 0 _(los cinco últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 71 pública(s) — `padding-inline-xs`, `padding-inline-sm`, `padding-inline-md`, `padding-inline-lg`, `gap-xs`, `gap-sm`, `gap-md`, `gap-lg`, `font-family`, `font-size-xs`, `font-size-sm`, `font-size-md`, `font-size-lg`, `font-weight`, `line-height`, `letter-spacing`, `border-width`, `radius-sm`, `radius-md`, `radius-lg`, `radius-full`, `min-height-xs`, `min-height-sm`, `min-height-md`, `min-height-lg`, `min-height`, `dot-radius`, `dot-size`, `icon-size`, `remove-size`, `remove-margin-start`, `primary-solid`, `primary-text`, `primary-contrast`, `secondary-solid`, `secondary-text`, `secondary-contrast`, `neutral-solid`, `neutral-text`, `neutral-contrast`, `affirm-solid`, `affirm-text`, `affirm-contrast`, `fulfill-track`, `fulfill-border`, `fulfill-solid`, `fulfill-text`, `fulfill-contrast`, `risk-solid`, `risk-text`, `risk-contrast`, `threat-solid`, `threat-text`, `threat-contrast`, `loss-track`, `loss-border`, `loss-solid`, `loss-text`, `loss-contrast`, `primary-track`, `primary-border`, `secondary-track`, `secondary-border`, `neutral-track`, `neutral-border`, `affirm-track`, `affirm-border`, `risk-track`, `risk-border`, `threat-track`, `threat-border` · 5 privada(s) forward — `_palette-track`, `_palette-border`, `_palette-solid`, `_palette-text`, `_palette-contrast`
- **Eje `size`**: sí · **ficheros**: `badge.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (0)
_Ninguno._
### 1.2 A través de un privado (0)
Sólo el **residuo**: el puente de paleta (§2-ter) y el canal de valor
(§2-quater) salen aparte, porque no son deuda ni tienen nombre que acuñar.
_Ninguno._
### 1.3 Literales (0)
_Ninguno._
### 1.4 Excepciones firmadas (5) — 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.
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `badge.css:58` | `[data-badge]` | `background` | `var(--_badge-bg)` |
| 2 | `badge.css:59` | `[data-badge]` | `color` | `var(--_badge-fg)` |
| 3 | `badge.css:131` | `[data-badge][data-gradient][data-variant='solid']` | `background-image` | `var(--_badge-fill-finish)` |
| 4 | `badge.css:179` | `[data-badge-icon] :is(svg, [data-svg], [data-icon])` | `inline-size` | `100%` |
| 5 | `badge.css:180` | `[data-badge-icon] :is(svg, [data-svg], [data-icon])` | `block-size` | `100%` |
## 2. Sistema transversal (0) — informativo, fuera del ratio
Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
_Ninguno._
## 2-ter. Puente de paleta THM-2 (2) — fuera del ratio
La receta lee `var(--_badge-palette-{slot})`, que **lo escribe el forward** de la
cascada de paleta bajo `[data-badge]:where([data-color], [data-color-custom])`
(`renderRecipePaletteForward`, firma B′). Es el MECANISMO de la paleta por
instancia, y se alcanza **dos veces**: por la capa compartida `--palette-*` y
por los tonos públicos del propio componente. **No se acuña**: un público
encima dejaría que un tema lo fijara y matara en silencio el `color=` de cada
instancia (veredictos §5 de `card`, `tags-input`, `avatar`).
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `badge.css:158` | `[data-badge-dot]` | `background` | `var(--_badge-palette-solid)` |
| 2 | `badge.css:164` | `[data-badge][data-variant='solid'] [data-badge-dot]` | `background` | `var(--_badge-palette-contrast)` |
## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_badge-padding-inline` | 4 | `var(--badge-padding-inline-md)`, `var(--badge-padding-inline-xs)`, `var(--badge-padding-inline-sm)`, `var(--badge-padding-inline-lg)` | public | **sí** |
| `--_badge-gap` | 4 | `var(--badge-gap-md)`, `var(--badge-gap-xs)`, `var(--badge-gap-sm)`, `var(--badge-gap-lg)` | public | **sí** |
| `--_badge-font-size` | 4 | `var(--badge-font-size-md)`, `var(--badge-font-size-xs)`, `var(--badge-font-size-sm)`, `var(--badge-font-size-lg)` | public | **sí** |
| `--_badge-radius` | 5 | `var(--badge-radius-full)`, `var(--badge-radius-sm)`, `var(--badge-radius-md)`, `var(--badge-radius-lg)` | public | **sí** |
| `--_badge-bg` | 5 | `var(--_badge-palette-track)`, `var(--_badge-palette-solid)`, `transparent` | literal, private | no |
| `--_badge-fg` | 5 | `var(--_badge-palette-text)`, `var(--_badge-finish-ink, var(--_badge-palette-contrast))` | private | no |
| `--_badge-border-color` | 5 | `transparent`, `var(--_badge-palette-border)` | literal, private | no |
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_badge-fill-finish`, `--_badge-finish-ink`, `--_badge-palette-border`, `--_badge-palette-contrast`, `--_badge-palette-solid`, `--_badge-palette-text`, `--_badge-palette-track`.
## 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` (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 (`--badge-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `bg` | `root` | `var(--_badge-bg)` | 1 |
| `fg` | `root` | `var(--_badge-fg)` | 1 |
| `solid-bg-image` | `root` | `var(--_badge-fill-finish)` | 1 |
### 4.2 Sin nombre mecánico (2)
- **⚠ 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`, `block-size`.
### 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-23 — 59 % → 75 %.** Seis claves nuevas (65 → 71), dos
literales firmados, cinco privados adjudicados. Diff de computed **0 sobre 992
valores en 7 estados**.
**La ALTURA del chip era el hueco.** Badge monta su altura en la escala de
CONTROL a propósito —para que `md` signifique lo mismo lleve o no un control
dentro (el ✕ removible es un `IconButton`: medido, el mismo badge `md` daba 30px
sin él y 46px con él)— pero lo hacía desde un privado que leía
`--control-height-*` a pelo, con cuatro fuentes por talla. Eso es una ESCALA, no
un conmutador: sube al TSC como `min-height-{xs,sm,md,lg}` + el resuelto, el
mismo molde que sus vecinas `padding-inline` / `gap` / `font-size` ya tenían.
⚠⚠ **El NOMBRE estuvo mal unas horas.** Salió como `min-block-size-{k}` —la
propiedad CSS— cuando la ranura del vocabulario para ella es `height`: lo dice
recipe-contract §1 («Control height per size → `control-height-{size}`, o
`{part}-height-{size}`») y lo dice el propio censo, que mapea
`min-block-size → height` en su tabla de propiedades. Medido en el catálogo:
**24 componentes dimensionan por `…-height-{k}`, y `min-block-size-{k}` existía
sólo aquí**. El gemelo de raíz con esta forma exacta —por talla, sobre
`--size-{k}-control-height`— es `proof-of-human.min-height-{k}`. **Renombrado a
`min-height-{k}` + `min-height` el mismo día** (diff 0, guard 29/71 igual,
`--names` 0 desviadas). R-5.3 no lo cazó: guarda la tinta y el modificador, no
el vocabulario DIMENSIONAL — anotado en §13.
⚠ **Y al entrar en el contrato, la suite lo corrigió**: el valor verbatim del
CSS era el primitivo CRUDO (`--control-height-{k}`), y `recipe-css-contract`
exige la coordenada del BUNDLE (`--size-{k}-control-height`, theming §5). Es la
trampa que el handoff nombra —«la propuesta hereda el incumplimiento del CSS de
partida»— y que ya había mordido a `code-block`: **subir un privado al contrato
somete su valor a reglas que en el CSS no se le aplicaban**. Corregido, diff
sigue en 0.
**`dot-radius`** es el par que a `dot-size` le faltaba. Redondo es el DEFECTO,
no la definición: `knob.indicator-radius` vale `--radius-sm`, así que el
catálogo ya trata el radio de un marcador como knob.
**Los cinco privados que quedan son las tres clases de F2-B, en un solo
componente**: `--_badge-bg` / `--_badge-fg` son un CONMUTADOR (cambian de fuente
con la variante — track, solid, transparent — y aplanarlos obligaría a duplicar
cada regla), `--_badge-fill-finish` es el canal que el generador de degradados
deriva de la paleta de ESTA instancia, y `--_badge-palette-*` son forwards THM-2
que la capa de color alimenta por instancia: un público encima dejaría que un
tema los fijara y matara el `color=` de cada chip.
**Las 40 claves de tono no mueven nada, y ya sabíamos por qué.** El bloque
genérico `[data-badge][data-color]` del forward de paleta se emite EL ÚLTIMO y
gana por orden a igual especificidad, así que la pintura sale del `--palette-*`
global. Es el hallazgo de cascada de paleta medido en `button`, `badge` y
`callout` el mismo día (§13, pendiente de firma) y entra en el ledger como
EXCEPCIÓN POR PATRÓN — una razón arquitectónica escrita una vez, no cuarenta
accidentes. Con ellas y los dos pasos de `radius` adjudicados (medidos forzando
`data-rounded`: 4px → 37px y 10px → 37px), el guard queda en 29/71 y limpio.
**El instrumento no veía tres de sus cinco partes.** Dot, icon y remove son
interruptores OPT-IN apagados por defecto: la sonda medía **2 nodos** —raíz y
etiqueta— y ninguna de las partes que la receta pinta. Encendidos, 5. Es la
misma clase que `background` (capas opt-in) y se resuelve igual.
⚠ **`component:audit` da NEEDS-WORK, y es PREEXISTENTE**: `R-1.5`, sin
tratamiento de foco en la receta (`grep focus-visible` = 0 tanto en HEAD como
después de este commit). El ✕ removible trae el suyo del `IconButton` que
compone; el chip en sí no es focalizable. No se toca aquí: darle foco mueve
píxel y es decisión de diseño.
**Verificación**: sonda antes/después **0 diffs** (992 valores · 7 estados · 5
nodos con las tres partes encendidas) · R-5.4 29/71, el resto adjudicado (dos
entradas propias + una excepción por patrón), cero STALE · censo 75 % ·
`eidos-lint` 5 morfo-backed / 14 eidos-only / **0 invalid, 0 class-hooks** ·
`rtl:check` 0 · `docs:check` 0.
<!-- veredicto:end -->

Powered by TurnKey Linux.