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

181 lines
13 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.

# avatar — 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**: **90%** — 27 de 30 knobs por token público
- **Knobs de apariencia**: 30 — público 27 · privado 3 · global 0 · literal 0 · sistema 0 · excepción 6 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 88 pública(s) — `size-xs`, `size-sm`, `size-md`, `size-lg`, `size-xl`, `size-xxl`, `font-family`, `font-size-xs`, `font-size-sm`, `font-size-md`, `font-size-lg`, `font-size-xl`, `font-size-xxl`, `font-weight`, `radius-full`, `radius-md`, `radius-sm`, `radius-none`, `border-width`, `primary-solid-bg`, `primary-solid-fg`, `primary-soft-bg`, `primary-soft-fg`, `primary-outline-border`, `primary-outline-fg`, `secondary-solid-bg`, `secondary-solid-fg`, `secondary-soft-bg`, `secondary-soft-fg`, `secondary-outline-border`, `secondary-outline-fg`, `neutral-solid-bg`, `neutral-solid-fg`, `neutral-soft-bg`, `neutral-soft-fg`, `neutral-outline-border`, `neutral-outline-fg`, `affirm-solid-bg`, `affirm-solid-fg`, `affirm-soft-bg`, `affirm-soft-fg`, `affirm-outline-border`, `affirm-outline-fg`, `fulfill-solid-bg`, `fulfill-solid-fg`, `fulfill-soft-bg`, `fulfill-soft-fg`, `fulfill-outline-border`, `fulfill-outline-fg`, `risk-solid-bg`, `risk-solid-fg`, `risk-soft-bg`, `risk-soft-fg`, `risk-outline-border`, `risk-outline-fg`, `threat-solid-bg`, `threat-solid-fg`, `threat-soft-bg`, `threat-soft-fg`, `threat-outline-border`, `threat-outline-fg`, `loss-solid-bg`, `loss-solid-fg`, `loss-soft-bg`, `loss-soft-fg`, `loss-outline-border`, `loss-outline-fg`, `ring-width-sm`, `ring-width-md`, `ring-width-lg`, `ring-color-custom`, `badge-border-width`, `badge-color-custom`, `badge-color-custom-contrast`, `group-gap-xs`, `group-gap-sm`, `group-gap-md`, `group-gap-lg`, `group-overlap-xs`, `group-overlap-sm`, `group-overlap-md`, `group-overlap-lg`, `group-overlap-xl`, `group-overlap-xxl`, `group-carve-width`, `group-carve-color`, `group-reverse-z-max`, `overflow-font-weight` · 11 privada(s) forward — `_palette-solid`, `_palette-surface`, `_palette-contrast`, `_palette-text`, `_palette-border`, `_bg`, `_fg`, `_border`, `_badge-bg`, `_badge-fg`, `_badge-border`
- **Eje `size`**: sí · **ficheros**: `avatar.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (0)
_Ninguno._
### 1.2 A través de un privado (3)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `avatar.css:38` | `[data-avatar]` | `background` | `var(--_avatar-bg)` |
| 2 | `avatar.css:39` | `[data-avatar]` | `color` | `var(--_avatar-fg)` |
| 3 | `avatar.css:224` | `[data-avatar-badge]` | `background` | `var(--_avatar-badge-bg)` |
### 1.3 Literales (0)
_Ninguno._
### 1.4 Excepciones firmadas (6) — 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 | `avatar.css:98` | `[data-avatar-image]` | `inline-size` | `100%` |
| 2 | `avatar.css:99` | `[data-avatar-image]` | `block-size` | `100%` |
| 3 | `avatar.css:117` | `[data-avatar-fallback]` | `inline-size` | `100%` |
| 4 | `avatar.css:118` | `[data-avatar-fallback]` | `block-size` | `100%` |
| 5 | `avatar.css:123` | `[data-avatar-fallback]` | `line-height` | `1` |
| 6 | `avatar.css:231` | `[data-avatar-badge]` | `line-height` | `1` |
## 2. Sistema transversal (0) — informativo, fuera del ratio
Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
_Ninguno._
## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_avatar-size` | 7 | `var(--avatar-size-md)`, `var(--avatar-size-xs)`, `var(--avatar-size-sm)`, `var(--avatar-size-lg)`, `var(--avatar-size-xl)`, `var(--avatar-size-xxl)` | public | **sí** |
| `--_avatar-font-size` | 7 | `var(--avatar-font-size-md)`, `var(--avatar-font-size-xs)`, `var(--avatar-font-size-sm)`, `var(--avatar-font-size-lg)`, `var(--avatar-font-size-xl)`, `var(--avatar-font-size-xxl)` | public | **sí** |
| `--_avatar-radius` | 5 | `var(--avatar-radius-full)`, `var(--avatar-radius-md)`, `var(--avatar-radius-sm)`, `var(--avatar-radius-none)` | public | **sí** |
| `--_avatar-ring-color` | 10 | `var(--avatar-neutral-solid-bg)`, `var(--avatar-primary-solid-bg)`, `var(--avatar-secondary-solid-bg)`, `var(--avatar-affirm-solid-bg)`, `var(--avatar-fulfill-solid-bg)`, `var(--avatar-risk-solid-bg)` …(+3) | public | **sí** |
| `--_avatar-ring-width` | 4 | `var(--avatar-ring-width-md)`, `var(--avatar-ring-width-sm)`, `var(--avatar-ring-width-lg)` | public | **sí** |
| `--_avatar-badge-size` | 1 | `calc(var(--_avatar-size) * 0.3)` | private | no |
| `--_avatar-badge-dot-size` | 1 | `calc(var(--_avatar-size) * 0.26)` | private | no |
| `--_avatar-badge-flip` | 2 | `1`, `-1` | literal | no |
| `--_avatar-badge-bg` | 3 | `var(--avatar-badge-color-custom)`, `color-mix( in srgb, var(--avatar-badge-color-custom) 18%, transparent )`, `var(--color-surface-default)` | global, public | no |
| `--_avatar-badge-fg` | 3 | `var(--avatar-badge-color-custom-contrast)`, `var(--avatar-badge-color-custom)` | public | **sí** |
| `--_avatar-badge-border` | 1 | `var(--avatar-badge-color-custom)` | public | **sí** |
| `--_avatar-group-overlap` | 7 | `var(--avatar-group-overlap-md)`, `var(--avatar-group-overlap-xs)`, `var(--avatar-group-overlap-sm)`, `var(--avatar-group-overlap-lg)`, `var(--avatar-group-overlap-xl)`, `var(--avatar-group-overlap-xxl)` | public | **sí** |
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_avatar-bg`, `--_avatar-border`, `--_avatar-fg`.
## 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 (`--avatar-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `badge-bg` | `root` | ⚠ `var(--avatar-badge-color-custom)` / `color-mix( in srgb, var(--avatar-badge-color-custom) 18%, transparent )` / `var(--color-surface-default)` | 3 |
### 4.2 Sin nombre mecánico (8)
- **⚠ 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** — 2: `background`, `color`.
- **⚠ 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** — 4: `inline-size`, `block-size`.
- **⚠ 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** — 2: `line-height`.
### 4.3 Avisos sobre los tokens propuestos (1)
- **el privado `--_avatar-badge-bg` debe pasar a leer este público (o desaparecer)** — `--avatar-badge-bg`
### 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 — 75 % → 90 %, contrato 88 claves, centinela 83/88 (5
adjudicadas).** Diff de computed **0** en tres bases (demo de `avatar` con
insignia + anillo: 576 valores · la misma en modo `fallback`: 384 · demo de
`AvatarGroup`: 3.072) y las dos capturas 2× **idénticas al byte**.
**Lo primero, porque contaminaba todo lo demás: su contrato existía y ningún
instrumento lo veía.** La entrada `avatar` de `base.ts` es la ÚNICA construida
por una IIFE (un helper local genera sus 24 ámbitos compuestos), así que su mapa
vive un tabulador más adentro, en el `return {`. El censo la leía como «sin
entrada en `base.ts`» —84 claves invisibles— y el centinela **moría** con `no
recipe block for avatar`: el componente no se podía medir. Los dos lectores
leen ya la IIFE (dedentan el `return`). Sin eso, el gate de este componente no
significaba nada.
**Lo que se acuñó (6) y por qué:**
- **`group-overlap-{xs..xxl}`** — la escala de solape de `AvatarGroup`. Había
UNA clave, `--avatar-group-overlap`, y la receta la **re-declaraba** en seis
bloques `[data-size]`: sentada en el elemento, ganaba siempre al `:root` donde
escribe un tema. **Medido**: desde el asiento del tema, `37px` no movía el
margen ni un píxel; escrita sobre el nodo, sí. Es un **falso positivo del
centinela** (escribe el token también sobre cada nodo del componente, y para
una propiedad personalizada que la receta re-declara EN EL ELEMENTO ese
inline sí gana), y queda anotado como límite del instrumento. Ahora el paso
viaja por `--_avatar-group-overlap` y los seis alcanzan desde `:root` (−8,4 ·
−11,2 · −14 · −16,8 · −22,4 · −33,6 px → 37 px, uno a uno).
**Lo que se retiró (2 declaraciones muertas, diff 0 las dos):**
- **`group-max`** — el envoltorio escribía `--avatar-group-max` INLINE y la
receta declaraba su default `99`; **no lo leía nadie**. El tope se aplica con
`data-has-max` + `:nth-child(n + M)` porque una variable no entra en
`:nth-child()` — lo dice el propio comentario del CSS. Retirada de los dos
sitios: el `+3` del grupo sigue exactamente donde estaba.
- **el respaldo `, white`** de `--_avatar-badge-fg`: el contrato ya declara
`--avatar-badge-color-custom-contrast: white`, así que el respaldo era
inalcanzable y sólo podía envejecer contra su token (la clase del `, 1.4`
contra `--font-line-height-sm`). Comprobado en la rama custom: la tinta sigue
computando `rgb(255, 255, 255)`.
**Seis literales firmados** (salen del ratio, clase `exception`): los cuatro
`100 %` de `Image` y `Fallback` son IDENTIDAD —la parte ES la superficie del
avatar, no una talla propia— y los dos `line-height: 1` mantienen el glifo
centrado por la caja flex; con interlineado se descentra.
**Lo que se queda fuera, y por qué (los 3 privados, el techo real es 90 %):**
`--_avatar-bg`, `--_avatar-fg` y `--_avatar-badge-bg` son un **CONMUTADOR** —
cambian de fuente con la variante (`solid` · `soft` · `outline`) y su valor sale
del forward de paleta THM-2 que la capa de color alimenta por instancia desde
`[data-color]`. Un público encima dejaría que un tema los fijara y matara el
`color=` de cada avatar (la razón de §3.pre del handoff). Aplanarlos obligaría
además a duplicar cada regla por tono: 24 combinaciones.
**Las cinco adjudicaciones del centinela** (todas medidas a mano sobre el nodo
REAL, con transiciones congeladas): `size-xxl` y `font-size-xxl` viven en el
paso `xxl`, que **el barrido de tallas del guard no alcanza** (para en `xl` — el
mismo límite que `metrics` ya registró); `radius-none` y `ring-width-sm` son
«sólo el paso en vigor pinta»; `group-overlap-xxl` junta las dos cosas.
**Defectos reales que NO se arreglan aquí:**
1. **R-5.3, cuatro claves fuera de la gramática, PREEXISTENTES**:
`ring-color-custom`, `badge-color-custom`, `badge-color-custom-contrast` (y
la cuarta que el audit trunca). La tinta es `fg`, no `color`. Son los
canales de valor que el envoltorio escribe INLINE, así que renombrarlos
cambia el contrato público de tres escotillas — decisión, no ejecución:
queda listado, el audit sigue en NEEDS-WORK por esto y sólo por esto.
2. El barrido de tallas del centinela **no llega a `xxl`**; añadirlo dejaría
STALE las seis excepciones que `metrics` escribió por lo mismo, así que se
respeta el precedente y se adjudica.
<!-- veredicto:end -->

Powered by TurnKey Linux.