uix(dialog): temable — 67 % → 89 %, y el borde dice CÓMO se cerró

Siete claves nuevas (37 → 44), un literal firmado, siete adjudicaciones medidas.
Diff de computed 0 sobre 480 valores en 8 estados.

LAS SIETE SON LAS DOS TINTAS SEMÁNTICAS QUE LE FALTABAN

Cuatro para los tonos EVALUATIVOS (risk-border, risk-title-fg, threat-border,
threat-title-fg): dialog no tiene forward de paleta THM-2 y su trato de tono es
deliberadamente estrecho —el borde y la tinta del TÍTULO, nunca la superficie
del cuerpo, para que el texto largo no compita con la señal—, pero iban a
--color-risk-* a pelo.

Tres para el tinte de SALIDA (saved-border, failed-border, dismissed-border): el
morfo escribe data-last-action antes de que data-state pase a closed, así que el
borde superior dice CÓMO se cerró mientras se va. El tercero cubre cancelled /
dismissed / dismissed-outside, que comparten valor: todo cierre sin resultado.
{tono}-border es el nombre del catálogo (48 claves) y failed-border el de
proof-of-human.

EL 11 % QUE QUEDA SON CUATRO ESCOTILLAS, NO DEUDA

Los --_dialog-content-*-override los escribe el wrapper INLINE desde las props
width / minWidth / maxWidth / height / minHeight. Un público encima lo pisaría el
inline y mentiría — la clase de --_background-gradient-image y del triple de
s-text-virtual-list. El nombre del privado ya lo dice: -override.

⚠ NO se acuñó tipografía de superficie, por §12.9: el contenido lleva
data-depth='modal' y un plano declara font-family / line-height a la misma
especificidad y más tarde en la cascada. Las claves de título y descripción que
ya existían son de PARTES internas, no de la superficie del plano.

SIETE ADJUDICACIONES, DOS DE ELLAS CON SEGUNDA PASADA

Estados que la demo no monta: la hoja inferior, el tamaño full, las ocho celdas
no centradas de la rejilla de posición, el data-position del botón de cierre y
los tres tintes de salida (que sólo existen mientras data-state='closed' y
data-last-action COEXISTEN). Y dos costaron una segunda pasada por el VALOR del
atributo: la rejilla usa top-left / top-right (físicos), no top-start; y el botón
de cierre NO lleva data-position en esta demo aunque todas las reglas que leen
close-inset lo exigen.

VERIFICACIÓN

Sonda antes/después 0 diffs (480 valores · 8 estados · 8 nodos con el diálogo
abierto) · R-5.4 37/44 con las siete adjudicadas, exit 0 · component:audit PASS ·
censo --only 89 % · eidos-lint 15 morfo-backed / 0 invalid / 0 class-hooks ·
rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo conocido) · capturas 2×
· el ledger sigue en sus 63 hunks previos.

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

@ -10,8 +10,8 @@
- **Medido**: 2026-08-23 · **162 recetas** con CSS + **8 componentes sin receta** = 170 fichas, el árbol entero de `eidos/components/` - **Medido**: 2026-08-23 · **162 recetas** con CSS + **8 componentes sin receta** = 170 fichas, el árbol entero de `eidos/components/`
- **La pregunta**: ¿cuánto de la apariencia de cada componente puede cambiar un tema **sin tocar el sistema ni la receta**? - **La pregunta**: ¿cuánto de la apariencia de cada componente puede cambiar un tema **sin tocar el sistema ni la receta**?
- **Alcance global**: **66%** — 3040 de 4578 knobs pasan por un token público del componente - **Alcance global**: **67%** — 3047 de 4577 knobs pasan por un token público del componente
- **Reparto**: público 3040 · privado 317 · global 821 · literal 400 · sistema transversal 509 · excepción firmada 131 _(los dos últimos, fuera del ratio)_ - **Reparto**: público 3047 · privado 317 · global 814 · literal 399 · sistema transversal 509 · excepción firmada 132 _(los dos últimos, fuera del ratio)_
- **Sin token público propio**: 24 · **alcance < 20 %**: 12 · **alcance 100 %**: 28 · **con eje `size`**: 57 - **Sin token público propio**: 24 · **alcance < 20 %**: 12 · **alcance 100 %**: 28 · **con eje `size`**: 57
## Cómo se lee ## Cómo se lee
@ -142,7 +142,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [spinner](./spinner.md) | 64% | 25 | 16 | 5 | 0 | 4 | 0 | 18 | – | | [spinner](./spinner.md) | 64% | 25 | 16 | 5 | 0 | 4 | 0 | 18 | – |
| [tag-group](./tag-group.md) | 65% | 46 | 28 | 14 | 0 | 1 | 3 | 91 | y | | [tag-group](./tag-group.md) | 65% | 46 | 28 | 14 | 0 | 1 | 3 | 91 | y |
| [popover](./popover.md) | 67% | 51 | 32 | 4 | 12 | 0 | 3 | 46 | y | | [popover](./popover.md) | 67% | 51 | 32 | 4 | 12 | 0 | 3 | 46 | y |
| [dialog](./dialog.md) | 67% | 36 | 24 | 4 | 7 | 1 | 0 | 37 | y |
| [spin-field](./spin-field.md) | 67% | 26 | 16 | 0 | 8 | 0 | 2 | 20 | – | | [spin-field](./spin-field.md) | 67% | 26 | 16 | 0 | 8 | 0 | 2 | 20 | – |
| [image-adjustments](./image-adjustments.md) | 67% | 18 | 10 | 0 | 4 | 1 | 3 | 12 | – | | [image-adjustments](./image-adjustments.md) | 67% | 18 | 10 | 0 | 4 | 1 | 3 | 12 | – |
| [virtual-list](./virtual-list.md) | 67% | 13 | 8 | 0 | 0 | 4 | 1 | 12 | – | | [virtual-list](./virtual-list.md) | 67% | 13 | 8 | 0 | 0 | 4 | 1 | 12 | – |
@ -199,6 +198,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [tags-input](./tags-input.md) | 88% | 46 | 37 | 4 | 1 | 0 | 4 | 90 | y | | [tags-input](./tags-input.md) | 88% | 46 | 37 | 4 | 1 | 0 | 4 | 90 | y |
| [gradient-picker](./gradient-picker.md) | 88% | 27 | 23 | 0 | 2 | 1 | 1 | 25 | – | | [gradient-picker](./gradient-picker.md) | 88% | 27 | 23 | 0 | 2 | 1 | 1 | 25 | – |
| [menubar](./menubar.md) | 88% | 27 | 23 | 0 | 3 | 0 | 1 | 35 | y | | [menubar](./menubar.md) | 88% | 27 | 23 | 0 | 3 | 0 | 1 | 35 | y |
| [dialog](./dialog.md) | 89% | 35 | 31 | 4 | 0 | 0 | 0 | 44 | y |
| [drag-drop](./drag-drop.md) | 89% | 16 | 8 | 1 | 0 | 0 | 7 | 9 | – | | [drag-drop](./drag-drop.md) | 89% | 16 | 8 | 1 | 0 | 0 | 7 | 9 | – |
| [chat-typing](./chat-typing.md) | 89% | 9 | 8 | 0 | 1 | 0 | 0 | 7 | – | | [chat-typing](./chat-typing.md) | 89% | 9 | 8 | 0 | 1 | 0 | 0 | 7 | – |
| [chart](./chart.md) | 89% | 95 | 81 | 0 | 0 | 10 | 4 | 45 | – | | [chart](./chart.md) | 89% | 95 | 81 | 0 | 0 | 10 | 4 | 45 | – |

@ -5,24 +5,16 @@
> Vista de conjunto: [README](./README.md) · método y protocolo: > Vista de conjunto: [README](./README.md) · método y protocolo:
> [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7. > [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7.
- **Medido**: 2026-08-23 · **Alcance**: **67%** — 24 de 36 knobs por token público - **Medido**: 2026-08-23 · **Alcance**: **89%** — 31 de 35 knobs por token público
- **Knobs de apariencia**: 36 — público 24 · privado 4 · global 7 · literal 1 · sistema 0 · excepción 0 _(los dos últimos, fuera del ratio)_ - **Knobs de apariencia**: 35 — público 31 · privado 4 · global 0 · literal 0 · sistema 0 · excepción 1 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 37 pública(s) — `overlay-bg`, `overlay-opacity`, `overlay-blur`, `overlay-z`, `content-z`, `content-fg`, `content-radius`, `content-max-height`, `content-max-height-sheet`, `content-width-sm`, `content-width-md`, `content-width-lg`, `content-width-xl`, `content-width-inset`, `content-full-inset`, `content-full-radius`, `content-position-inset`, `content-padding-sm`, `content-padding-md`, `content-padding-lg`, `content-padding-full`, `stack-gap`, `header-gap`, `footer-gap`, `footer-margin-top`, `title-font-family`, `title-font-size`, `title-font-weight`, `title-line-height`, `title-letter-spacing`, `title-fg`, `description-font-family`, `description-font-size`, `description-line-height`, `description-fg`, `close-size`, `close-inset` - **Contrato hoy** (`lib/recipes/base.ts`): 44 pública(s) — `overlay-bg`, `overlay-opacity`, `overlay-blur`, `overlay-z`, `content-z`, `content-fg`, `content-radius`, `content-max-height`, `content-max-height-sheet`, `content-width-sm`, `content-width-md`, `content-width-lg`, `content-width-xl`, `content-width-inset`, `content-full-inset`, `content-full-radius`, `content-position-inset`, `content-padding-sm`, `content-padding-md`, `content-padding-lg`, `content-padding-full`, `stack-gap`, `header-gap`, `footer-gap`, `footer-margin-top`, `title-font-family`, `title-font-size`, `title-font-weight`, `title-line-height`, `title-letter-spacing`, `title-fg`, `description-font-family`, `description-font-size`, `description-line-height`, `description-fg`, `risk-border`, `risk-title-fg`, `threat-border`, `threat-title-fg`, `saved-border`, `failed-border`, `dismissed-border`, `close-size`, `close-inset`
- **Eje `size`**: sí · **ficheros**: `dialog.css` - **Eje `size`**: sí · **ficheros**: `dialog.css`
## 1. Knobs fuera de alcance ## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (7) ### 1.1 Directo a primitivo global (0)
| # | fichero:línea | selector | propiedad | valor | _Ninguno._
| ---: | --- | --- | --- | --- |
| 1 | `dialog.css:213` | `[data-dialog-content][data-color='risk']` | `border-color` | `var(--color-risk-border)` |
| 2 | `dialog.css:217` | `[data-dialog-content][data-color='risk'] [data-dialog-title]` | `color` | `var(--color-risk-text)` |
| 3 | `dialog.css:221` | `[data-dialog-content][data-color='threat']` | `border-color` | `var(--color-threat-border)` |
| 4 | `dialog.css:225` | `[data-dialog-content][data-color='threat'] [data-dialog-title]` | `color` | `var(--color-threat-text)` |
| 5 | `dialog.css:334` | `[data-dialog-content][data-state='closed'][data-last-action='saved']` | `border-top-color` | `var(--color-fulfill-element)` |
| 6 | `dialog.css:337` | `[data-dialog-content][data-state='closed'][data-last-action='failed']` | `border-top-color` | `var(--color-threat-element)` |
| 7 | `dialog.css:342` | `[data-dialog-content][data-state='closed'][data-last-action='cancelled'], [data-dialog-content][data-state='closed'][data-last-action='dismissed'], [data-dialog-content][data-state='closed'][data-last-action='dismissed-outside']` | `border-top-color` | `var(--color-neutral-element)` |
### 1.2 A través de un privado (4) ### 1.2 A través de un privado (4)
@ -33,20 +25,20 @@
| 3 | `dialog.css:66` | `[data-dialog-content]` | `min-height` | `var(--_dialog-content-min-height-override, auto)` | | 3 | `dialog.css:66` | `[data-dialog-content]` | `min-height` | `var(--_dialog-content-min-height-override, auto)` |
| 4 | `dialog.css:184` | `[data-dialog-content][data-size='full']` | `width` | `var(--_dialog-content-width-override, auto)` | | 4 | `dialog.css:184` | `[data-dialog-content][data-size='full']` | `width` | `var(--_dialog-content-width-override, auto)` |
### 1.3 Literales (1) ### 1.3 Literales (0)
| # | fichero:línea | selector | propiedad | valor | _Ninguno._
| ---: | --- | --- | --- | --- |
| 1 | `dialog.css:197` | `[data-dialog-content][data-sheet]` | `width` | `100%` |
### 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 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 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 `component-audit`. **Una desviación firmada no es deuda** — se listan para que la
razón se lea, no para acuñarlas. razón se lea, no para acuñarlas.
_Ninguno._ | # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `dialog.css:197` | `[data-dialog-content][data-sheet]` | `width` | `100%` |
## 2. Sistema transversal (0) — informativo, fuera del ratio ## 2. Sistema transversal (0) — informativo, fuera del ratio
Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2). Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
@ -67,7 +59,7 @@ Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inl
- **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). - **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` (2) ### 4.1 Tokens a declarar en `lib/recipes/base.ts` (0)
Valor **verbatim** del CSS de hoy: el default no se mueve, sólo cambia quién 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 puede moverlo. Nombres derivados de recipe-contract §1 (ejes lógicos, talla
@ -77,21 +69,13 @@ no distingue lo que debería — se marca `⚠`.
| token (`--dialog-…`) | scope TSC | valor propuesto | usos | | token (`--dialog-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: | | --- | --- | --- | ---: |
| `content-border` | `color:risk` | ⚠ `var(--color-risk-border)` / `var(--color-threat-border)` | 2 |
| `content-fg` _(ya existe)_ | `color:risk` | ⚠ `var(--color-risk-text)` / `var(--color-threat-text)` | 2 |
### 4.2 Sin nombre mecánico (8) ### 4.2 Sin nombre mecánico (5)
- **⚠ decisión: `min-width` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `min-width`. - **⚠ decisión: `min-width` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `min-width`.
- **⚠ decisión: `height` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `height`. - **⚠ decisión: `height` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `height`.
- **⚠ decisión: `min-height` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `min-height`. - **⚠ decisión: `min-height` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `min-height`.
- **⚠ decisión: `width` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 2: `width`. - **⚠ decisión: `width` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 2: `width`.
- **⚠ decisión: la propiedad no tiene slot canónico en el vocabulario** — 3: `border-top-color`.
### 4.3 Avisos sobre los tokens propuestos (2)
- **declaración por color: `declarations[]` con scope `color:risk` (tsc.md)** — `--dialog-content-border`, `--dialog-content-fg`
- **declaración por color: `declarations[]` con scope `color:threat` (tsc.md)** — `--dialog-content-border`, `--dialog-content-fg`
### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4) ### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)
@ -105,6 +89,51 @@ no distingue lo que debería — se marca `⚠`.
<!-- veredicto:start --> <!-- veredicto:start -->
_(pendiente — lo escribe el autor; se conserva al regenerar)_ **EJECUTADO 2026-08-23 — 67 % → 89 %.** Siete claves nuevas (37 → 44), un
literal firmado, siete adjudicaciones medidas. Diff de computed **0 sobre 480
valores en 8 estados**.
**Las siete son las dos tintas SEMÁNTICAS que le faltaban.** Cuatro para los
tonos evaluativos (`risk-border`, `risk-title-fg`, `threat-border`,
`threat-title-fg`): dialog no tiene forward de paleta THM-2 y su trato de tono es
deliberadamente estrecho —el borde y la tinta del TÍTULO, nunca la superficie del
cuerpo, para que el texto largo no compita con la señal—, pero iban a
`--color-risk-*` a pelo. Y tres para el tinte de SALIDA (`saved-border`,
`failed-border`, `dismissed-border`): el morfo escribe `data-last-action` antes
de que `data-state` pase a `closed`, así que el borde superior dice CÓMO se cerró
mientras se va. El nombre de la tercera cubre `cancelled` / `dismissed` /
`dismissed-outside`, que comparten valor — todo cierre sin resultado.
`{tono}-border` es el nombre del catálogo (48 claves) y `failed-border` el de
`proof-of-human`.
**El 11 % que queda son los cuatro `--_dialog-content-*-override`**, y no es
deuda: son ESCOTILLAS POR INSTANCIA que el wrapper escribe inline desde las
props `width` / `minWidth` / `maxWidth` / `height` / `minHeight`. Un público
encima lo pisaría el inline y mentiría — la clase de
`--_background-gradient-image` y del triple de `s-text-virtual-list`. El nombre
del privado lo dice: `-override`.
⚠ **No se acuñó tipografía de superficie, por §12.9.** El contenido lleva
`data-depth='modal'` y un plano declara `font-family` / `line-height` con la
misma especificidad y más tarde en la cascada. Las claves de título y descripción
que ya existían son de PARTES internas, no de la superficie del plano, así que no
las toca la firma pendiente.
**Las siete adjudicaciones son estados que la demo no monta**: la hoja inferior,
el tamaño `full`, las ocho celdas no centradas de la rejilla de posición, el
`data-position` del botón de cierre y los tres tintes de salida (que sólo existen
en la ventana en que `data-state='closed'` y `data-last-action` COEXISTEN).
Todas forzadas sobre el nodo real y medidas.
⚠ **Dos de ellas costaron una segunda pasada por el valor del atributo**: la
rejilla de posición usa `top-left` / `top-right` (físicos), no `top-start`, y el
botón de cierre **no lleva `data-position` en esta demo**, aunque todas las
reglas que leen `close-inset` lo exigen. Forzar el atributo correcto las mueve
las dos.
**Verificación**: sonda antes/después **0 diffs** (480 valores · 8 estados · 8
nodos con el diálogo abierto) · R-5.4 **37/44** con las siete adjudicadas, exit 0
· `component:audit` PASS · censo 89 % con las cuatro escotillas explicadas ·
`eidos-lint` 15 morfo-backed / **0 invalid, 0 class-hooks** · capturas 2×.
<!-- veredicto:end --> <!-- veredicto:end -->

@ -77,6 +77,26 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'radius-sm': 'only the step in force paints; forced data-rounded=sm → reaches (4px → 37px)', 'radius-sm': 'only the step in force paints; forced data-rounded=sm → reaches (4px → 37px)',
'radius-lg': 'only the step in force paints; forced data-rounded=lg → reaches (10px → 37px)' 'radius-lg': 'only the step in force paints; forced data-rounded=lg → reaches (10px → 37px)'
}, },
dialog: {
// 2026-08-23. Seven tokens of states the demo does not mount: it shows a
// middle-centred `md` dialog, open and neutral. All forced on the real
// content node, transitions frozen.
'content-max-height-sheet':
'only under [data-sheet] (the wrapper sets it below the sm breakpoint); forced -> reaches (850px -> 377px)',
'content-full-inset': "only under [data-size='full']; forced -> reaches (12px -> 37px)",
'content-position-inset':
'only on the 8 non-centred cells of the position grid; forced data-position=top-left -> reaches (16px -> 37px)',
'close-inset':
'the close button carries NO data-position in this demo, and every rule that reads it needs one; forced data-position=top-right -> reaches (12px -> 37px)',
// The exit tint: the morfo writes `data-last-action` before `data-state`
// flips to closed, so both attributes only coexist WHILE the dialog leaves.
'saved-border':
'only during the exit, with [data-state=closed][data-last-action=saved]; forced -> reaches (oklch(0.9582 0.0223 155) -> rgb(1,2,3))',
'failed-border':
'same exit window; forced -> reaches (oklch(0.9555 0.0207 13.8) -> rgb(1,2,3))',
'dismissed-border':
'same exit window (cancelled / dismissed / dismissed-outside share it); forced -> reaches (oklch(0.9551 0 0) -> rgb(1,2,3))'
},
'pin-input': { 'pin-input': {
// 2026-08-23. Six STATE tokens: the demo shows an empty, editable, valid // 2026-08-23. Six STATE tokens: the demo shows an empty, editable, valid
// pin. Forced on the real nodes — note WHERE: `filled` and `active` are // pin. Forced on the real nodes — note WHERE: `filled` and `active` are

@ -50,6 +50,44 @@ attached, sin `Root` ni `Provider` publico.
Partes publicas: `Trigger`, `Portal`, `Overlay`, `Content`, `Title`, Partes publicas: `Trigger`, `Portal`, `Overlay`, `Content`, `Title`,
`Description`, `Close`, `Header`, `Footer`. `Description`, `Close`, `Header`, `Footer`.
## Talla y tema
**44 claves públicas** en `lib/recipes/base.ts`: el velo
(`overlay-{bg,opacity,blur,z}`), la caja (`content-{z,fg,radius,max-height,
width-*,padding-*,full-*,position-inset}`), los ritmos (`stack-gap`,
`header-gap`, `footer-gap`, `footer-margin-top`), la tipografía del título y de
la descripción, el botón de cierre y **las siete que entraron el 2026-08-23**.
La pestaña **Tokens** de su demo las resuelve en vivo.
- **`risk-border` · `risk-title-fg` · `threat-border` · `threat-title-fg`** — los
dos tonos EVALUATIVOS. Dialog no tiene forward de paleta THM-2: sólo `risk` y
`threat` reciben trato, y es deliberadamente estrecho (el borde y la tinta del
TÍTULO, nunca la superficie del cuerpo, para que el texto largo no compita con
la señal). Iban a `--color-risk-*` / `--color-threat-*` a pelo.
- **`saved-border` · `failed-border` · `dismissed-border`** — el tinte de SALIDA.
El morfo escribe `data-last-action` antes de que `data-state` pase a `closed`,
así que el borde superior dice CÓMO se cerró mientras se va. El tercero cubre
`cancelled`, `dismissed` y `dismissed-outside`, que comparten valor: todo
cierre sin resultado.
**Lo que queda fuera, y por qué**: los cuatro `--_dialog-content-*-override` son
ESCOTILLAS POR INSTANCIA — el wrapper las escribe inline desde las props
`width` / `minWidth` / `maxWidth` / `height` / `minHeight`, así que un público
encima lo pisaría el inline. Y el `width: 100%` de la hoja inferior es
identidad, firmado en su declaración.
⚠ **La tipografía del panel NO se toca hasta la firma §12.9.** El contenido
lleva `data-depth='modal'`, y un plano de profundidad declara `font-family` y
`line-height` con la misma especificidad y más tarde en la cascada: lo que se
acuñe ahí puede leer muerto. Las claves de título y descripción que ya existen
son de PARTES internas, no de la superficie del plano.
**Lo que el guard da por silencioso** (`theming-sentinel-exceptions.ts`): siete
estados que la demo no monta —la hoja, el tamaño `full`, las ocho celdas no
centradas de la rejilla de posición, el `data-position` del botón de cierre y
los tres tintes de salida—, todos medidos forzando el atributo sobre el nodo
real.
## Comparativa ## Comparativa
| Capacidad | Air | Eidos/Soma | Radix | Bits UI | Ark UI | shadcn-svelte | | Capacidad | Air | Eidos/Soma | Radix | Bits UI | Ark UI | shadcn-svelte |

@ -194,7 +194,7 @@
top: auto; top: auto;
bottom: 0; bottom: 0;
translate: none; translate: none;
width: 100%; width: 100%; /* literal: identity — a bottom sheet spans the viewport, by definition */
max-height: var(--dialog-content-max-height-sheet); max-height: var(--dialog-content-max-height-sheet);
border-radius: var(--dialog-content-radius) var(--dialog-content-radius) 0 0; border-radius: var(--dialog-content-radius) var(--dialog-content-radius) 0 0;
--_dialog-padding: var(--dialog-content-padding-sm); --_dialog-padding: var(--dialog-content-padding-sm);
@ -210,19 +210,19 @@
/* neutral = the plane's own border (`--depth-modal-border`); no override needed. */ /* neutral = the plane's own border (`--depth-modal-border`); no override needed. */
[data-dialog-content][data-color='risk'] { [data-dialog-content][data-color='risk'] {
border-color: var(--color-risk-border); border-color: var(--dialog-risk-border);
} }
[data-dialog-content][data-color='risk'] [data-dialog-title] { [data-dialog-content][data-color='risk'] [data-dialog-title] {
color: var(--color-risk-text); color: var(--dialog-risk-title-fg);
} }
[data-dialog-content][data-color='threat'] { [data-dialog-content][data-color='threat'] {
border-color: var(--color-threat-border); border-color: var(--dialog-threat-border);
} }
[data-dialog-content][data-color='threat'] [data-dialog-title] { [data-dialog-content][data-color='threat'] [data-dialog-title] {
color: var(--color-threat-text); color: var(--dialog-threat-title-fg);
} }
/* ── Layout shells (eidos-only, no morfo backing) ──────────────────────── */ /* ── Layout shells (eidos-only, no morfo backing) ──────────────────────── */
@ -331,13 +331,13 @@
* coexist during the exit animation; CSS tints the border-top by causa. * coexist during the exit animation; CSS tints the border-top by causa.
*/ */
[data-dialog-content][data-state='closed'][data-last-action='saved'] { [data-dialog-content][data-state='closed'][data-last-action='saved'] {
border-top-color: var(--color-fulfill-element); border-top-color: var(--dialog-saved-border);
} }
[data-dialog-content][data-state='closed'][data-last-action='failed'] { [data-dialog-content][data-state='closed'][data-last-action='failed'] {
border-top-color: var(--color-threat-element); border-top-color: var(--dialog-failed-border);
} }
[data-dialog-content][data-state='closed'][data-last-action='cancelled'], [data-dialog-content][data-state='closed'][data-last-action='cancelled'],
[data-dialog-content][data-state='closed'][data-last-action='dismissed'], [data-dialog-content][data-state='closed'][data-last-action='dismissed'],
[data-dialog-content][data-state='closed'][data-last-action='dismissed-outside'] { [data-dialog-content][data-state='closed'][data-last-action='dismissed-outside'] {
border-top-color: var(--color-neutral-element); border-top-color: var(--dialog-dismissed-border);
} }

@ -1166,6 +1166,13 @@
--dialog-description-font-size: var(--size-sm-font-size); --dialog-description-font-size: var(--size-sm-font-size);
--dialog-description-line-height: var(--leading-ui); --dialog-description-line-height: var(--leading-ui);
--dialog-description-fg: var(--color-content-secondary); --dialog-description-fg: var(--color-content-secondary);
--dialog-risk-border: var(--color-risk-border);
--dialog-risk-title-fg: var(--color-risk-text);
--dialog-threat-border: var(--color-threat-border);
--dialog-threat-title-fg: var(--color-threat-text);
--dialog-saved-border: var(--color-fulfill-element);
--dialog-failed-border: var(--color-threat-element);
--dialog-dismissed-border: var(--color-neutral-element);
--dialog-close-size: var(--size-sm-control-height); --dialog-close-size: var(--size-sm-control-height);
--dialog-close-inset: var(--space-3); --dialog-close-inset: var(--space-3);
--field-gap-xs: var(--space-1); --field-gap-xs: var(--space-1);

@ -1181,6 +1181,24 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
// (bg / hover / focus-ring / disabled / radius). Only the dialog-specific // (bg / hover / focus-ring / disabled / radius). Only the dialog-specific
// floating geometry stays here: `close-size` reserves header room beside a // floating geometry stays here: `close-size` reserves header room beside a
// floating close, `close-inset` is its corner offset. // floating close, `close-inset` is its corner offset.
// The two EVALUATIVE tones. Dialog has no THM-2 palette forward: only
// `risk` and `threat` get a treatment, and it is deliberately narrow —
// the border and the TITLE's ink, never the body surface, so long-form
// content does not compete with the signal. `{tone}-border` is the
// catalogue's name (48 keys across badge, card…); the title pair composes
// modifier + part + slot, like `radio-group.disabled-item-label-fg`.
'risk-border': 'var(--color-risk-border)',
'risk-title-fg': 'var(--color-risk-text)',
'threat-border': 'var(--color-threat-border)',
'threat-title-fg': 'var(--color-threat-text)',
// The EXIT tint: the morfo writes `data-last-action` before `data-state`
// flips to closed, so the border-top says HOW it closed while it leaves.
// `failed-border` is `proof-of-human`'s name for the same idea. The third
// covers cancelled / dismissed / dismissed-outside, which share a value:
// every close with no outcome.
'saved-border': 'var(--color-fulfill-element)',
'failed-border': 'var(--color-threat-element)',
'dismissed-border': 'var(--color-neutral-element)',
'close-size': 'var(--size-sm-control-height)', 'close-size': 'var(--size-sm-control-height)',
'close-inset': 'var(--space-3)' 'close-inset': 'var(--space-3)'
}, },

@ -8,10 +8,11 @@
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { dialogMorfo } from '@/uix/morfo/components/dialog'; import { dialogMorfo } from '@/uix/morfo/components/dialog';
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix(); 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'); let tab = $state<Tab>('live');
type DialogIntent = 'neutral' | 'risk' | 'threat'; type DialogIntent = 'neutral' | 'risk' | 'threat';
@ -248,6 +249,9 @@
<span data-uix-tab-count>{events.length}</span> <span data-uix-tab-count>{events.length}</span>
</button> </button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</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 <span data-uix-tab-count>44</span>
</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button> <button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div> </div>
@ -649,6 +653,10 @@
</section> </section>
{/if} {/if}
{#if tab === 'tokens'}
<TokensPanel component="dialog" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'} {#if tab === 'recipe'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2> <h2 data-uix-section-title>Eidos recipe</h2>

Loading…
Cancel
Save

Powered by TurnKey Linux.