13 KiB
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 · método y protocolo:PLAN-theming.md§1, §2, §7.
- Medido: 2026-08-23 · 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 guardrecipe-css-contractprohí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.tso 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:
1es 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-bgdebe 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, unbackgrounden 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
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 deAvatarGroup. 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:rootdonde escribe un tema. Medido: desde el asiento del tema,37pxno 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-overlapy 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-maxINLINE y la receta declaraba su default99; no lo leía nadie. El tope se aplica condata-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+3del grupo sigue exactamente donde estaba.- el respaldo
, whitede--_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.4contra--font-line-height-sm). Comprobado en la rama custom: la tinta sigue computandorgb(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í:
- 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 esfg, nocolor. 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. - El barrido de tallas del centinela no llega a
xxl; añadirlo dejaría STALE las seis excepciones quemetricsescribió por lo mismo, así que se respeta el precedente y se adjudica.