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/chat-message.md

186 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.

# 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](./README.md) · método y protocolo:
> [`PLAN-theming.md`](../../process/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 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 (`--chat-message-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `quick-reactions-width` | `root` | `max-content` | 1 |
### 4.2 Sin nombre mecánico (3)
- **⚠ 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** — 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, 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 — 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**:
1. `bubble-tail-radius` — el guard fotografía UNA esquina
(`borderTopLeftRadius`) y la cola es la de ABAJO (`border-end-start` en `in`,
`border-end-end` en `out`). Medido en las 8 combinaciones dirección × run:
4 px → 1234 px en `solo` y `last` en las dos direcciones. Su hermana
`bubble-radius-run` lee viva porque `middle` aplana la esquina START, que sí
está en la lista.
2. `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.
3. `focus-radius` — la fila sólo es tabulable compuesta DENTRO de un Feed (soma
fusiona la identidad de `Feed.Article` cuando hay un Feed ancestro) y su
propia demo la monta suelta, así que `:focus-visible` no 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 de `emphasize`), enfocada 4 px →
1234 px, por foco programático y por recorrido real con Tab.
4. `quick-react-hover-scale` — dos límites a la vez: `transform` no está en la
lista de propiedades del guard (el punto ciego ya adjudicado en `background`,
`rating-group` y `card`) 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).
**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.
<!-- veredicto:end -->

Powered by TurnKey Linux.