uix(toast): temable — 98 % → 100 %, y el plano se lleva la TARJETA ENTERA

El contrato ya tenía sus 85 claves y `global` era CERO: no se acuña ninguna.
Lo único fuera de alcance era un literal, y era identidad.

Lo que entra:

- `opacity: 1` de la tarjeta firmado con su anotación `/* literal: */`
  (recipe-contract §3): es el polo EN REPOSO del par abierto/cerrado, y el
  que lleva tokens es el otro (`closed-offset`, `closed-scale`). «Presente
  del todo» no tiene un segundo valor que sostener. 98 % → 100 %.
- Guard R-5.4: entrada nueva en `COMPONENT_OVERRIDES` que dispara la tarjeta
  con el botón del demo — y que DELIBERADAMENTE no pone `openMarker`. Una
  tarjeta se auto-descarta a los 5 s, así que «¿ya está abierta?» no tiene
  respuesta útil: con `openMarker` tres corridas sobre código idéntico
  discreparon en QUÉ token leía muerto. Sin él, el guard cae al marcador por
  defecto (una parte que este componente no tiene) y dispara una tarjeta
  nueva antes de CADA token: **51/85 en tres corridas seguidas**, determinista.
- Ledger: 34 adjudicaciones, ninguna retirada, todas MEDIDAS.
- La sonda aprende a disparar el toast (`DEMO_VARIANTS`): medía UN nodo (el
  viewport) porque un toast no EXISTE hasta que algo lo lanza. Ahora 8.

EL HALLAZGO — §12.9 con una cara nueva y con coste de PRODUCTO:

`toast-item.svelte` sella `data-depth='overlay'`, y el plano declara
`background`, el ATAJO `border` y `box-shadow` con la misma especificidad
(0,1,0) y más tarde. Medido sobre la tarjeta real con transiciones congeladas:
escribir `--toast-{tono}-surface` / `-border`, `--toast-provider-border-width`,
`--toast-accent-width` o `--toast-provider-shadow` no mueve NADA; escribir
`--depth-overlay-surface` / `-border` / `-shadow` y `--border-width` en la
misma raíz lo repinta todo. Son 21 claves de 85, y a diferencia de `tooltip`
esto se VE: un toast `risk` y uno `fulfill` visten la misma tarjeta gris, y la
franja de acento —un `border-inline-start` que el atajo posterior pisa en los
cuatro lados— no aparece nunca en reposo.

Matiz que sólo aparece midiendo: los seis `{tono}-accent` NO están muertos.
Bajo `[data-loading]` la regla del pulso es (0,2,0) y su `@keyframes` gana a
toda declaración normal — 100 fotogramas del pulso real: sin escribir, el
color interpolado corre en oklab L 0.5032..0.8514; con el token en rgb(1,2,3),
0.0823..0.5032. La misma clave miente en reposo y dice la verdad en animación.

§12.9 sigue SIN FIRMAR, así que las 21 se ADJUDICAN con `PLANE_SUPERSEDED` y
la incidencia se registra en `next-features.md` §12. No se toca desde este eje.

Las 13 restantes: la pareja de transición (el guard congela `transition`),
`closed-offset` / `closed-scale` (estado de salida + `transform`, que el guard
no fotografía), los tres del anillo de foco de la acción (el guard hace
`blur`), los cinco `{tono}-action-hover-bg` (el guard estampa el tono en su
pasada estática y no en la de hover) y `provider-max-width`, cuyo centinela de
1234px se capa contra el `min(100%, …)` — medido desde el otro lado, 120px
encoge la tarjeta de 420 a 280.

Censo: 98 % → 100 % (45 knobs · 45 públicos · 0 globales · 0 privados ·
1 excepción firmada). Global 68 %, al 100 % 42 → 45. Sin cambios en
`recipes/base.ts` ni en `generated/base.css`.

Diff de computed: **0 diffs sobre 1.760 valores en 7 estados** (8 nodos en
reposo, 7 en hover). Capturas 2× de la tarjeta byte a byte idénticas — y van
por un script propio, porque su viewport es `position: fixed` y cae fuera del
recorte del escenario que usa `__shot`.

Más README con su sección «Talla y tema», pestaña `Tokens` en la demo (85
claves, verificada en el navegador) y ficha con veredicto §5.

Guards: centinela 51/85 verde (exit 0, sin STALE) · `component-audit --only
toast` PASS · `eidos-lint toast` 0 invalid (24 morfo-backed, 6 eidos-only) ·
`vitest run src/uix/eidos` 35/36 (el rojo conocido es `skin-media-player`) ·
`rtl:check` 0 · `docs:check` 0 · `check` COMPLETED sin un solo error en
ficheros de toast · prettier limpio sobre el contenido normalizado a LF.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent 868bd235bf
commit b8eccc1c4f

@ -5,8 +5,8 @@
> 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**: **98%** — 45 de 46 knobs por token público
- **Knobs de apariencia**: 46 — público 45 · privado 0 · global 0 · literal 1 · sistema 0 · excepción 0 _(los dos últimos, fuera del ratio)_
- **Medido**: 2026-08-24 · **Alcance**: **100%** — 45 de 45 knobs por token público
- **Knobs de apariencia**: 45 — público 45 · privado 0 · global 0 · literal 0 · sistema 0 · excepción 1 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 85 pública(s) — `toaster-z`, `toaster-inset`, `toaster-gap`, `toaster-width`, `provider-min-width`, `provider-max-width`, `provider-gap`, `provider-stack-gap`, `provider-padding-inline`, `provider-padding-block`, `provider-border-width`, `provider-radius`, `provider-shadow`, `accent-width`, `transition-duration`, `transition-ease`, `closed-offset`, `closed-scale`, `status-size`, `status-radius`, `title-font-family`, `title-font-size`, `title-font-weight`, `title-line-height`, `title-fg`, `message-fg`, `description-font-family`, `description-font-size`, `description-line-height`, `description-fg`, `action-gap`, `action-padding-inline`, `action-padding-block`, `action-margin-top`, `action-border-width`, `action-radius`, `action-font-family`, `action-font-size`, `action-font-weight`, `action-line-height`, `action-focus-ring-offset`, `action-focus-ring-width`, `action-focus-ring-color`, `default-surface`, `default-border`, `default-accent`, `default-status-bg`, `default-status-fg`, `default-action-fg`, `default-action-hover-bg`, `affirm-surface`, `affirm-border`, `affirm-accent`, `affirm-status-bg`, `affirm-status-fg`, `affirm-action-fg`, `affirm-action-hover-bg`, `fulfill-surface`, `fulfill-border`, `fulfill-accent`, `fulfill-status-bg`, `fulfill-status-fg`, `fulfill-action-fg`, `fulfill-action-hover-bg`, `risk-surface`, `risk-border`, `risk-accent`, `risk-status-bg`, `risk-status-fg`, `risk-action-fg`, `risk-action-hover-bg`, `threat-surface`, `threat-border`, `threat-accent`, `threat-status-bg`, `threat-status-fg`, `threat-action-fg`, `threat-action-hover-bg`, `loss-surface`, `loss-border`, `loss-accent`, `loss-status-bg`, `loss-status-fg`, `loss-action-fg`, `loss-action-hover-bg`
- **Eje `size`**: no · **ficheros**: `toast.css`
@ -20,20 +20,21 @@ _Ninguno._
_Ninguno._
### 1.3 Literales (1)
### 1.3 Literales (0)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `toast.css:101` | `[data-toast-item]` | `opacity` | `1` |
_Ninguno._
### 1.4 Excepciones firmadas (0) — fuera del ratio
### 1.4 Excepciones firmadas (1) — 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.
_Ninguno._
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `toast.css:101` | `[data-toast-item]` | `opacity` | `1` |
## 2. Sistema transversal (0) — informativo, fuera del ratio
Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
@ -65,10 +66,6 @@ no distingue lo que debería — se marca `⚠`.
| token (`--toast-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
### 4.2 Sin nombre mecánico (1)
- **⚠ 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** — 1: `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.
@ -81,6 +78,62 @@ no distingue lo que debería — se marca `⚠`.
<!-- veredicto:start -->
_(pendiente — lo escribe el autor; se conserva al regenerar)_
**Medido y cerrado el 2026-08-24 — 98 % → 100 %, con UNA firma y cero claves
acuñadas.** El contrato ya tenía sus 85 y no había nada que coser: `global` era
cero antes de empezar y el único knob fuera de alcance era un literal.
**El literal es identidad, no deuda.** `opacity: 1` en `[data-toast-item]` es el
polo EN REPOSO del par abierto/cerrado, y el que lleva tokens es el otro
(`[data-state='closed']` → `closed-offset`, `closed-scale`). «Presente del todo»
no tiene un segundo valor que sostener. Firmado con su anotación `literal:`
(recipe-contract §3), sale del ratio. La propuesta §4.2 lo insinuaba y aquí se
resuelve con la válvula del canon, no con una clave.
**El hallazgo del componente es §12.9 con una cara nueva: en una tarjeta
PORTALADA el plano `overlay` no se lleva sólo la tipografía — se lleva la
TARJETA ENTERA.** `toast-item.svelte` sella `data-depth='overlay'`, y
`[data-depth='overlay']` declara `background`, el ATAJO `border` y `box-shadow`
con la misma especificidad (0,1,0) y más tarde en la cascada. Medido el
2026-08-24 sobre la tarjeta real con las transiciones congeladas
(`scripts/__dbg-toast-plane.mjs`): escribir `--toast-risk-surface`,
`--toast-*-border`, `--toast-provider-border-width`, `--toast-accent-width` o
`--toast-provider-shadow` **no mueve nada** (el fondo se queda en
`oklch(0.931 0 0)`, el borde en `oklch(0.8853 0 0) 1px`); escribir
`--depth-overlay-surface` / `-border` / `-shadow` y `--border-width` en la misma
raíz lo repinta todo (`rgb(4, 5, 6)`, 999px, el anillo de 9px). Son **21 claves
de 85**, y a diferencia de `tooltip` esto TIENE consecuencia visible: **un toast
`risk` y uno `fulfill` visten la misma tarjeta gris** — sólo el disco de estado
y la tinta de la acción llevan la intención, y la franja de acento
(`border-inline-start`, un longhand que el atajo posterior pisa en los cuatro
lados) no se ve en reposo. Es la incidencia §12.9, **pendiente de firma**: se
mide, se adjudica con la constante `PLANE_SUPERSEDED` del ledger y **no se toca
desde este eje** — arreglarla mueve píxel en veinte componentes.
**Matiz que sólo aparece midiendo: los seis `{tono}-accent` NO están muertos.**
El plano los tapa en reposo, pero bajo `[data-toast-item][data-loading]` la
regla del pulso es (0,2,0) y su `@keyframes` gana a cualquier declaración
normal. Medido sobre 100 fotogramas del pulso real: sin escribir, el color
interpolado del borde corre en oklab L 0.5032..0.8514; con
`--toast-default-accent: rgb(1, 2, 3)`, 0.0823..0.5032.
**Guard R-5.4: 51/85 mueven, 34 adjudicados, ninguno retirado.** Además de las
21 del plano: la pareja de transición (el guard congela `transition`),
`closed-offset` / `closed-scale` (estado de salida + `transform`, que el guard
no fotografía), los tres del anillo de foco de la acción (el guard hace `blur`),
los cinco `{tono}-action-hover-bg` (el guard estampa el tono en su pasada
estática y no en la de hover) y `provider-max-width`, cuyo valor centinela de
1234px se capa contra el `min(100%, …)` — medido desde el otro lado, 120px
encoge la tarjeta de 420 a 280.
**Y una lección de instrumento**: este componente es el primero donde **poner
`openMarker` EMPEORA la medición**. Una tarjeta se auto-descarta a los 5 s, así
que «¿ya está abierta?» no tiene respuesta útil: con `openMarker` tres corridas
sobre código idéntico discreparon en QUÉ token leía muerto. Sin él, el guard cae
al marcador por defecto (`[data-toast-content]`, una parte que este componente no
tiene) y **dispara una tarjeta nueva antes de CADA token** — 51/85 en tres
corridas seguidas, determinista.
**Techo: 100 %.** `Toast.Close` no aporta claves porque compone el `IconButton`
canónico — ceder el cromo al compuesto sale gratis, el precedente `toolbar`.
<!-- veredicto:end -->

@ -1292,6 +1292,31 @@ Lo que `chart` destapó (2026-08-22):
La decisión es la de §12.9 —qué manda, el plano o la receta— y aquí ya no es
sólo tipografía: es la superficie.
- **⚠⚠ Y EN `toast` EL PLANO SE LLEVA LA TARJETA ENTERA — 21 claves, y esta vez
con consecuencia de PRODUCTO** (medido 2026-08-24). Misma mecánica que
`tooltip`, un piso más arriba: `toast-item.svelte` sella `data-depth='overlay'`
y el plano declara `background`, el **ATAJO `border`** y `box-shadow` a la misma
(0,1,0) y más tarde. Medido sobre la tarjeta real con transiciones congeladas
(`scripts/__dbg-toast-plane.mjs`): `--toast-{default,affirm,fulfill,risk,threat,loss}-surface`
y `-border` (12), `--toast-provider-border-width`, `--toast-accent-width` y
`--toast-provider-shadow` no mueven nada; `--depth-overlay-surface` / `-border`
/ `-shadow` y `--border-width` en la misma raíz lo repintan todo.
**Consecuencia visual**: un toast `risk` y uno `fulfill` visten **la misma
tarjeta gris**. Sólo el disco de estado y la tinta de la acción llevan la
intención, porque viven en OTROS nodos. Y la franja de acento —el rasgo que
identifica la intención de un vistazo— es un `border-inline-start`, un longhand
que el atajo posterior pisa en los cuatro lados: no se ve **nunca** en reposo.
Es la primera vez que §12.9 tiene coste de producto y no sólo de contrato.
**Matiz medido, que conviene a la firma**: los seis `{tono}-accent` SÍ alcanzan
bajo `[data-toast-item][data-loading]`, donde la regla del pulso es (0,2,0) y su
`@keyframes` gana a toda declaración normal (100 fotogramas: sin escribir, el
color interpolado corre en oklab L 0.5032..0.8514; con el token en `rgb(1,2,3)`,
0.0823..0.5032). O sea: el plano gana en reposo y pierde en animación, así que
la misma clave miente y dice la verdad según el estado. Las 21 quedan
ADJUDICADAS con `PLANE_SUPERSEDED`, no retiradas.
- **El censo pierde el eje `size` DE PREMIO por aplicar bien el protocolo**
(medido 2026-08-23 en `navigation-menu`). `hasSize` se decide grepeando
`data-size` en el CSS de la receta (`theming-census.ts:287`), y el paso §7.4

@ -190,7 +190,15 @@ const DEMO_VARIANTS: Record<string, string[]> = {
// hidden` and the avatars slot unmounted.
'chat-typing': [
'[data-uix-control]:has([data-uix-control-label]:text-is("typers")) [data-uix-chip]:text-is("Ada")'
]
],
// A toast does not EXIST until something fires it: the stage mounts an empty
// `<Toaster>` and the probe measured ONE node (the viewport). The play button
// of the demo is the only way in, and it has to be re-pressed after every
// reload — which is exactly what this list is for. Its 5 s auto-dismiss is
// what makes the click go here and not in a one-off script: each pass fires
// a fresh card, and the hover pass PAUSES the timer on pointerenter (soma's
// own behaviour), so the card survives the slowest pass of the run.
toast: ['[data-uix-play]']
};
async function main() {

@ -1325,6 +1325,41 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'focus-reset-radius':
'the corner only rounds under :focus-visible and the guard blurs the active element on purpose (the F2-A fix for click-focus poisoning); focused the real reset from script and confirmed :focus-visible matched -> reaches (4px -> 1234px). Same cause and same shape as rating-group.focus-radius'
},
toast: {
// 2026-08-24. Thirty-four, and the biggest group is §12.9 with a face
// nobody had seen yet: on a PORTALED card the overlay plane does not only
// take the typography, it takes the whole CARD. Measured on the real item
// with transitions frozen (`scripts/__dbg-toast-plane.mjs`): writing the
// component's surface / border / accent / border-width / shadow moves
// NOTHING (bg stays oklch(0.931 0 0), border oklch(0.8853 0 0) 1px), while
// --depth-overlay-surface / -border / -shadow / --border-width on the same
// root repaint every one of them (rgb(4, 5, 6), 999px, the 9px ring). The
// tone keys ride the PATTERN entries below.
'provider-border-width':
PLANE_SUPERSEDED +
' — the plane writes the `border` SHORTHAND, so its `var(--border-width)` beats the card`s own `border: var(--toast-provider-border-width) solid …`. Measured 2026-08-24: 1234px on the token leaves borderTopWidth at 1px; 999px on --border-width moves it',
'accent-width':
PLANE_SUPERSEDED +
' — same shorthand, and this is the one that COSTS something visible: the accent stripe is a `border-inline-start` longhand, which the later shorthand overwrites on all four sides. Measured 2026-08-24: borderInlineStartWidth stays 1px under a 1234px write and follows --border-width instead',
'provider-shadow':
PLANE_SUPERSEDED +
' — and here it costs nothing at all: the key`s default IS `var(--depth-overlay-shadow), var(--depth-overlay-halo)`, the very value the plane paints, so the computed result is identical either way. Measured 2026-08-24: a 7px sentinel ring does not appear, the plane`s 9px one does',
'transition-duration':
'the guard freezes transitions and this token IS the transition; measured unfrozen -> reaches (0.12s -> 11.5s on the two transitioned properties of the card)',
'transition-ease':
'same freeze; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(3))',
'closed-offset':
'TWO reasons: the rule is `[data-toast-item][data-state=closed]` and a card only wears that state on its way OUT (the guard measures a card it has just fired), and it paints through `transform`, which the guard does not snapshot (the blind spot already adjudicated on background / rating-group / card / fab). Measured 2026-08-24 forcing data-state=closed on the real card with transitions frozen -> reaches (matrix(0.98, 0, 0, 0.98, 0, 10) -> matrix(0.123, 0, 0, 0.123, 0, 1234))',
'closed-scale': 'same rule, same property, same measurement -> reaches (0.98 -> 0.123)',
'action-focus-ring-offset':
'the ring only paints under `[data-toast-action]:focus-visible` and the guard blurs the active element on purpose; walked the focus onto the real Undo button with the keyboard, confirmed :focus-visible matched -> reaches (the inner spread goes 1px -> 999px)',
'action-focus-ring-width':
'same focus ring; measured focused -> reaches (the outer spread follows offset + width)',
'action-focus-ring-color':
'same focus ring; measured focused -> reaches (color(srgb 0.745076 …) -> rgb(1, 2, 3))',
'provider-max-width':
'the card reads `inline-size: min(100%, var(--toast-provider-max-width))`, so the guard`s 1234px is CAPPED by the other half of the min() and nothing moves — the known blind spot of a sentinel value that is too big. Measured 2026-08-24 from the other side, which is the only side that can move it: 120px -> the card narrows from 420px to 280px (its min-inline-size floor), so the token reaches'
},
tabs: {
// 2026-08-24. Nine, and NOT ONE of them is a token that lies: four are the
// transition the guard freezes, three the focus ring the guard blurs, one
@ -1468,6 +1503,25 @@ export const SENTINEL_PATTERN_EXCEPTIONS: Array<{
PALETTE_SUPERSEDED +
' — the eight tones x three slots, 24 of its 91 keys. Measured 2026-08-24 on the real tag forced into data-state=active with transitions frozen: with data-color=primary (the wrapper resolves `color` and stamps it on EVERY instance, default included) --tags-input-primary-border moves NOTHING (the tag border stays oklch(0.8043 0.0918 309.69)) while --palette-border on the same node repaints it (rgb(4, 5, 6)); strip data-color and --tags-input-primary-border paints again (rgb(1, 2, 3)). The generic `[data-tags-input][data-color], [data-tags-input][data-color-custom]` block is emitted LAST at the same (0,2,0) as the eight per-tone blocks. The three `*-text` slots carry a SECOND cause on top: even unsuperseded they cannot tint the item, because the archetype ink beats them (see the item-fg entry above)'
},
{
component: 'toast',
pattern: /^(default|affirm|fulfill|risk|threat|loss)-(surface|border)$/,
reason:
PLANE_SUPERSEDED +
' — the six intents x two slots, 12 of its 85 keys, and this is §12.9 on a PORTALED card: the item carries data-depth=overlay and the plane declares `background` and the `border` shorthand at the same (0,1,0) and later, so the whole per-intent CARD is painted by the plane. Measured 2026-08-24 on the real item with transitions frozen: --toast-risk-surface / -border move nothing (bg stays oklch(0.931 0 0)) while --depth-overlay-surface / -border on the same root repaint it (rgb(4, 5, 6)). Visible consequence, registered in next-features §12.9 and NOT fixed from this axis: a risk toast and a fulfill toast wear the same grey card; only the status disc and the action ink carry the intent'
},
{
component: 'toast',
pattern: /^(default|affirm|fulfill|risk|threat|loss)-accent$/,
reason:
'SUPERSEDED by the depth plane AT REST (the accent is a `border-inline-start` longhand and the plane`s later `border` shorthand overwrites all four sides — §12.9, same measurement as the surface/border pattern), but NOT dead: under `[data-toast-item][data-loading]` the pulse rule is (0,2,0) and its @keyframes outranks every normal declaration. Measured 2026-08-24 over 100 frames of the real pulse: without the write the interpolated border-inline-start-color runs oklab L 0.5032..0.8514; with --toast-default-accent=rgb(1, 2, 3) it runs 0.0823..0.5032 — the token reaches, in the one state where the plane cannot reach it'
},
{
component: 'toast',
pattern: /^(affirm|fulfill|risk|threat|loss)-action-hover-bg$/,
reason:
'the tone is what the guard cannot combine with the hover: its static pass stamps data-intent to test a tone key, and its hover pass does not — so only `default-action-hover-bg` (the untinted card, which needs no stamp) reads alive. Measured 2026-08-24 with the demo`s own `risk` chip and a real pointer on the Undo button, transitions frozen -> reaches (oklch(0.9923 0.0025 48.72) -> rgb(1, 2, 3))'
},
{
component: 'select',
pattern: /^(primary|secondary|neutral|affirm|fulfill|risk|threat|loss)-(track|border|text)$/,

@ -656,6 +656,19 @@ const COMPONENT_OVERRIDES: Record<
// tablist is the only shape that gives the rail a WIDTH, and the disabled
// opacity floor needs a trigger wearing the state (the radio-group precedent
// — an extra surface instead of three ledger lines).
// A toast does not EXIST until something fires it: the stage mounts an empty
// `<Toaster>`, there is no `[data-toast-trigger]` for the default opener to
// find, and the guard measured the viewport alone. The demo's play button is
// the way in — and this entry deliberately does NOT set `openMarker`, which
// is the opposite of every other portaled component here. A card
// auto-dismisses after 5 s, so "is it already open?" has no useful answer:
// with `openMarker: '[data-toast-item]'` the run was NON-DETERMINISTIC (three
// runs on identical code disagreed on WHICH token read dead — the card
// expired mid-measure). Falling through to the default marker
// (`[data-toast-content]`, a part this component does not have) makes the
// re-open fire a FRESH card before EVERY token, which is the only way to
// outrun the timer.
toast: { openWith: ['[data-uix-play]'] },
tabs: {
sweepAttr: { attr: 'data-variant', values: ['line', 'surface', 'pills', 'segmented'] },
urls: [

@ -87,6 +87,54 @@ Sources: [Radix](https://www.radix-ui.com/primitives/docs/components/toast),
- Eidos owns status glyphs, placement/gap styling, `data-icon-only` close
styling and card layout.
## Talla y tema
**85 claves públicas** en `lib/recipes/base.ts`, y la pestaña **Tokens** de su
demo las resuelve en vivo. No hay eje `size`: un toast es una talla. El reparto:
| grupo | claves | notas |
| --- | ---: | --- |
| Viewport | `toaster-{z,inset,gap,width}` | la esquina fija y el hueco de la pila |
| Tarjeta | `provider-{min-width,max-width,gap,stack-gap,padding-inline,padding-block,border-width,radius,shadow}`, `accent-width` | |
| Movimiento | `transition-{duration,ease}`, `closed-{offset,scale}` | `closed-*` son la pose de SALIDA |
| Disco de estado | `status-{size,radius}` | |
| Texto | `title-{font-family,font-size,font-weight,line-height,fg}`, `description-{font-family,font-size,line-height,fg}`, `message-fg` | la COSTURA: cada uno lee su `--style-*` / `--size-*` por default |
| Acción | `action-{gap,padding-inline,padding-block,margin-top,border-width,radius,font-family,font-size,font-weight,line-height}`, `action-focus-ring-{offset,width,color}` | el trío de foco ESPEJA la familia del sistema — exento de la gramática de tinta |
| Intención × 6 | `{default,affirm,fulfill,risk,threat,loss}-{surface,border,accent,status-bg,status-fg,action-fg,action-hover-bg}` | 42 claves: seis intenciones por siete ranuras |
`Toast.Close` **no tiene claves**: compone el `<IconButton rounded="full">`
canónico, así que su cromo vive en `button.css`. Cederlo sale gratis (el
precedente `toolbar`), y por eso el bloque `close-*` se retiró en su día.
Lo que entró el **2026-08-24** (98 % → 100 %): **una sola firma**, no una clave.
El `opacity: 1` de la tarjeta es **identidad** — el polo EN REPOSO del par
abierto/cerrado, donde el que lleva tokens es `closed` (`closed-offset`,
`closed-scale`); «presente del todo» no tiene un segundo valor que sostener.
Anotado con su `/* literal: */` (recipe-contract §3), sale del ratio.
**Lo que el guard R-5.4 da por silencioso: 34 de 85, y 21 son §12.9.** Medido el
2026-08-24 sobre la tarjeta real con las transiciones congeladas: la tarjeta
lleva `data-depth='overlay'`, y el plano declara `background`, el ATAJO `border`
y `box-shadow` con la misma especificidad (0,1,0) y **más tarde**, así que
**gana**. Escribir `--toast-risk-surface` / `-border` no mueve nada; escribir
`--depth-overlay-surface` / `-border` en la misma raíz lo repinta todo. Con la
consecuencia visible de que **un toast `risk` y uno `fulfill` visten la misma
tarjeta gris** — sólo el disco de estado y la tinta de la acción llevan la
intención. Es la incidencia §12.9, **pendiente de firma**: se mide, se registra
y no se toca desde este eje. El resto son límites del instrumento, todos
medidos: la pareja de transición (el guard congela `transition`), `closed-*`
(estado de salida + `transform`, que el guard no fotografía), el anillo de foco
de la acción (el guard hace `blur`), los cinco `{tono}-action-hover-bg` (el
guard no combina tono con hover) y `provider-max-width`, cuyo valor centinela se
capa contra el `min(100%, …)` — medido desde el otro lado, 120px encoge la
tarjeta de 420 a 280.
**Los seis `{tono}-accent` NO están muertos**: el plano los tapa en reposo, pero
bajo `[data-loading]` la regla del pulso es (0,2,0) y su `@keyframes` gana a
cualquier declaración normal. Medido sobre 100 fotogramas del pulso real: sin
escribir, el color interpolado corre en oklab L 0.5032..0.8514; con el token en
`rgb(1, 2, 3)`, 0.0823..0.5032.
## Baseline
Referencia local: Soma + Morfo + Eidos. La capa visual de Eidos

@ -98,7 +98,7 @@
box-shadow: var(--toast-provider-shadow);
color: var(--toast-message-fg);
pointer-events: auto;
opacity: 1;
opacity: 1; /* literal: identity — the RESTING pole of the open/closed pair below, where `closed` is the one that carries tokens (`closed-offset`, `closed-scale`); "fully present" has no second value to hold */
transform: translateY(0) scale(1);
transition:
opacity var(--toast-transition-duration) var(--toast-transition-ease),

@ -9,10 +9,12 @@
import { toastMorfo } from '@/uix/morfo/components/toast';
import { getActiveUix } from '$active-uix';
import { INTENTS, type Intent } from '$uix/intent';
import TokensPanel from '../../lib/TokensPanel.svelte';
import TokensTabCount from '../../lib/TokensTabCount.svelte';
const uix = getActiveUix();
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens' | 'a11y';
let tab = $state<Tab>('live');
// ── Live state ────────────────────────────────────────────────────────
@ -211,6 +213,9 @@
<span data-uix-tab-count>{events.length}</span>
</button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <TokensTabCount component="toast" />
</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div>
@ -546,6 +551,10 @@
</section>
{/if}
{#if tab === 'tokens'}
<TokensPanel component="toast" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'}
<section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2>

Loading…
Cancel
Save

Powered by TurnKey Linux.