13 KiB
chat-message — 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-24 · Alcance: 100% — 71 de 71 knobs por token público
- Knobs de apariencia: 78 — público 71 · privado 0 · global 0 · literal 0 · sistema 7 · excepción 4 (los dos últimos, fuera del ratio)
- Contrato hoy (
lib/recipes/base.ts): 81 pública(s) —gap,row-gap-run,row-gap-group,focus-radius,gutter-inline-size,bubble-padding-inline-sm,bubble-padding-inline-md,bubble-padding-inline-lg,bubble-padding-block-sm,bubble-padding-block-md,bubble-padding-block-lg,bubble-radius,bubble-radius-run,bubble-tail-radius,bubble-max-inline-size,bubble-font-size-sm,bubble-font-size-md,bubble-font-size-lg,bubble-line-height,bubble-bg-in,bubble-fg-in,bubble-bg-out,bubble-fg-out,bubble-shadow,header-gap,header-font-size,header-font-weight,header-fg,meta-gap,meta-font-size,meta-fg,status-size,status-fg,read-status-fg,failed-status-fg,read-by-gap,read-by-font-size,read-by-fg,read-by-margin,emphasize-bg,emphasize-radius,emphasize-duration,reply-padding-inline,reply-padding-block,reply-accent-size,reply-radius,reply-bg,reply-fg,reply-accent,reply-font-size,reaction-gap,reaction-inner-gap,reaction-height,reaction-padding-inline,reaction-radius,reaction-font-size,reaction-bg,reaction-fg,reaction-border,reaction-shadow,reaction-overlap,reaction-pressed-bg,reaction-pressed-border,reaction-pressed-fg,quick-react-gap,quick-react-pad,quick-react-size,quick-react-radius,quick-react-emoji-size,quick-react-hover-bg,quick-react-hover-scale,actions-bg,actions-border,actions-shadow,actions-radius,actions-padding,mentioned-bg,mentioned-fg,mentioned-accent,actions-gap,actions-offset - Eje
size: sí · ficheros:chat-message.css
1. Knobs fuera de alcance
1.1 Directo a primitivo global (0)
Ninguno.
1.2 A través de un privado (0)
Ninguno.
1.3 Literales (0)
Ninguno.
1.4 Excepciones firmadas (4) — 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 | chat-message.css:371 |
[data-popover-content][data-chat-message-quick-reactions] |
inline-size |
max-content |
| 2 | chat-message.css:401 |
[data-chat-message-quick-react] |
line-height |
1 |
| 3 | chat-message.css:470 |
[data-chat-message]:hover [data-chat-message-actions], [data-chat-message]:focus-within [data-chat-message-actions] |
opacity |
1 |
| 4 | chat-message.css:476 |
[data-chat-message-actions] |
opacity |
1 |
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 | chat-message.css:36 |
[data-chat-message]:focus-visible |
outline |
var(--focus-ring-width) solid var(--focus-ring-color) |
| 2 | chat-message.css:270 |
[data-chat-message][data-delivery='sending'] [data-chat-message-bubble] |
opacity |
var(--opacity-muted) |
| 3 | chat-message.css:296 |
[data-chat-message-reply]:hover |
background-image |
linear-gradient(var(--state-hover), var(--state-hover)) |
| 4 | chat-message.css:300 |
[data-chat-message-reply]:focus-visible |
outline |
var(--focus-ring-width) solid var(--focus-ring-color) |
| 5 | chat-message.css:342 |
[data-chat-message-reaction]:hover, [data-chat-message-reaction-add]:hover |
background-image |
linear-gradient(var(--state-hover), var(--state-hover)) |
| 6 | chat-message.css:347 |
[data-chat-message-reaction]:focus-visible, [data-chat-message-reaction-add]:focus-visible |
outline |
var(--focus-ring-width) solid var(--focus-ring-color) |
| 7 | chat-message.css:416 |
[data-chat-message-quick-react]:focus-visible |
outline |
var(--focus-ring-width) solid var(--focus-ring-color) |
3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
|---|---|---|---|---|
--_chat-message-bubble-bg |
2 | var(--chat-message-bubble-bg-in), var(--chat-message-bubble-bg-out) |
public | sí |
--_chat-message-bubble-fg |
2 | var(--chat-message-bubble-fg-in), var(--chat-message-bubble-fg-out) |
public | sí |
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 (--chat-message-…) |
scope TSC | valor propuesto | usos |
|---|---|---|---|
quick-reactions-width |
root |
max-content |
1 |
4.2 Sin nombre mecánico (3)
- ⚠ 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 — 3:line-height,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, 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-24 — 84 % → 100 %. Contrato 75 → 81 claves; censo
público 71 · global 0 · literal 0 · sistema 7 · excepción 4. Diff de computed
VACÍO (4.192 valores, 8 estados, 16 nodos) y capturas 2× idénticas byte a
byte, en reposo y con la barra tapback abierta. Centinela 77/81, cuatro
adjudicadas. component-audit PASS · eidos-lint 0 invalid · rtl:check 0 ·
docs:check 0 · check sin errores del componente.
Otra vez, lo caro fue VER. El guard daba 45/75 antes de tocar una línea
y las treinta «muertas» estaban las treinta VIVAS: una fila de chat es una PILA
de superficies opt-in (reply citado, read-by, estado de entrega, mención) y de
ejes de PRESENTACIÓN (run, direction, emphasized) que la demo arranca en
su posición más pobre. Con las cuatro superficies encendidas por prepareWith
y los cinco ejes barridos como producto: 71/75 antes de acuñar nada. La
sonda pasó de 12 a 16 nodos en reposo y a 24 con la barra abierta.
Lo que entra (6 claves, la COSTURA de los seis globales): focus-radius,
mentioned-fg, reply-fg, reaction-fg, reaction-inner-gap y
quick-react-radius. El valor sigue siendo el del sistema; el knob pasa a ser
del componente.
Los nombres salen del CATÁLOGO. La ficha proponía radius (colisiona con
emphasize-radius, que ES la esquina en reposo de la MISMA fila: la de foco
lleva su modificador delante, focus-radius), bubble-fg (el knob es de la
MENCIÓN — mentioned-bg / mentioned-accent ya existen, faltaba
mentioned-fg), reaction-width + reaction-height para el glifo (dos
literales de una regla muerta, y reaction-height ya existe con otro sentido:
la altura del CHIP) y quick-reactions-width para un max-content que es
identidad.
Y el caso que obligó a decidir: reaction-gap ya existía con otro sentido.
La receta la pinta en la fila reactions (el hueco ENTRE chips) mientras el
knob nuevo es el ritmo INTERNO del chip — el emoji y su cuenta. Mismo valor
(--space-1), dos papeles: un chip más denso no es una fila más apretada.
Reusar la clave habría cambiado en silencio lo que mueve un tema que ya la usa,
así que entra como reaction-inner-gap y reaction-gap se queda como está. El
resto del bloque {parte}-gap de esta receta (meta-gap, read-by-gap,
header-gap, actions-gap) significa «dentro de la parte»: la anómala es la
vieja, y renombrarla es churn con riesgo.
Lo que SALE: una regla que no ha pintado nunca.
[data-chat-message-reaction] svg, [data-chat-message-reaction-add] svg fijaba
1.1em en los dos ejes «porque un svg de sólo viewBox computa 0×0». El glifo
por defecto es el Icon compuesto, que emite
style="width: var(--icon-size-sm); height: …" INLINE — y un estilo en línea
gana a todo selector. Medido: 16 px (el paso sm del icono), no los 15,4 px
que 1.1em daría sobre los 14 px heredados. La otra mitad del selector no tiene
nodo: los chips llevan un emoji de texto. Retirada: 0 diffs. Tercera vez en
la familia (chat-log, chat-composer, y aquí).
Identidades firmadas (4, fuera del ratio): inline-size: max-content de la
barra tapback (la barra ES su fila de emoji), line-height: 1 de la celda (la
celda es la caja del glifo) y los dos opacity: 1 de la pastilla de acciones
(el final del fundido 0 → 1).
Adjudicadas (4), todas límites del INSTRUMENTO y medidas a mano:
bubble-tail-radius— el guard fotografía UNA esquina (borderTopLeftRadius) y la cola es la de ABAJO (border-end-startenin,border-end-endenout). Medido en las 8 combinaciones dirección × run: 4 px → 1234 px ensoloylasten las dos direcciones. Su hermanabubble-radius-runlee viva porquemiddleaplana la esquina START, que sí está en la lista.emphasize-duration— ES la transición que el guard congela para poder medir todo lo demás. Sin congelar: 0,24 s → 4,321 s.focus-radius— la fila sólo es tabulable compuesta DENTRO de un Feed (soma fusiona la identidad deFeed.Articlecuando hay un Feed ancestro) y su propia demo la monta suelta, así que:focus-visibleno tiene nodo ahí. Medido en/uix/components/chat-log, donde las filas SÍ son artículos de feed (tabindex=0): reposo 6 px (la esquina deemphasize), enfocada 4 px → 1234 px, por foco programático y por recorrido real con Tab.quick-react-hover-scale— dos límites a la vez:transformno está en la lista de propiedades del guard (el punto ciego ya adjudicado enbackground,rating-groupycard) y la celda es un botón PORTALADO con regla:hover. Con puntero real y transiciones congeladas:matrix(1.18…)→matrix(7.77…).
Defecto real encontrado y NO corregido aquí (mueve píxel): el chip de añadir
reacción lleva también data-popover-trigger, y
[data-popover-trigger]:not([data-archetype='field-trigger']) pesa (0,2,0)
contra los (0,1,0) de [data-chat-message-reaction-add] — así que su fondo, su
borde, su radio, su tinta, su altura, su padding y su tamaño de letra los pinta
popover.css. Se ve en la captura: los dos chips de reacción son píldoras
redondas y el de añadir es un cuadrado gris. Es la clase gradient-picker (36
de 45) / emoji-picker (3). Los reaction-* NO mienten —alcanzan sobre los
chips normales—, así que no hay nada que retirar; lo que hay es una decisión de
diseño pendiente. Registrado, no tocado (D-TH.5).
D-TH.5 — CERRADO 2026-08-25 por la firma «el sobre de trigger de popover es el
SUELO» (changelog §55). Se resolvió donde estaba el defecto, no aquí: el sobre
de popover.css bajó a :where(...) (0,0,0) —tercera aplicación de la ley de
§12.9 y §53— y el bloque de esta receta gana ya sus siete propiedades. El chip
mide 34×26, radio 9999px, fondo oklch(0.9911 0 0), 12px de letra, 8px de
padding: la misma píldora que sus hermanas. Cero claves retiradas y cero
añadidas — el número del componente no se mueve, el centinela sigue en 77/81 con
las mismas cuatro adjudicadas.
Lo que queda fuera y por qué: cuatro identidades firmadas (§1.4) y siete
knobs de sistema transversal (§2, anillo de foco × 4, velo de estado × 2,
--opacity-muted del sending). No hay deuda: el componente está CERRADO en su
número.