From bb89174a8bb7e1e489f5bef156b09fc50c60d9e1 Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 23 Aug 2026 21:00:52 +0200 Subject: [PATCH] =?UTF-8?q?uix(dialog):=20temable=20=E2=80=94=2067=20%=20?= =?UTF-8?q?=E2=86=92=2089=20%,=20y=20el=20borde=20dice=20C=C3=93MO=20se=20?= =?UTF-8?q?cerr=C3=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/audit/theming/README.md | 6 +- docs/audit/theming/dialog.md | 89 ++++++++++++------- scripts/theming-sentinel-exceptions.ts | 20 +++++ src/uix/eidos/components/dialog/README.md | 38 ++++++++ src/uix/eidos/components/dialog/dialog.css | 16 ++-- src/uix/eidos/generated/base.css | 7 ++ src/uix/eidos/lib/recipes/base.ts | 18 ++++ web/routes/uix/components/dialog/+page.svelte | 10 ++- 8 files changed, 162 insertions(+), 42 deletions(-) diff --git a/docs/audit/theming/README.md b/docs/audit/theming/README.md index 349befabf..8b3c37a80 100644 --- a/docs/audit/theming/README.md +++ b/docs/audit/theming/README.md @@ -10,8 +10,8 @@ - **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**? -- **Alcance global**: **66%** — 3040 de 4578 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)_ +- **Alcance global**: **67%** — 3047 de 4577 knobs pasan por un token público del componente +- **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 ## 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 | – | | [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 | -| [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 | – | | [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 | – | @@ -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 | | [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 | +| [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 | – | | [chat-typing](./chat-typing.md) | 89% | 9 | 8 | 0 | 1 | 0 | 0 | 7 | – | | [chart](./chart.md) | 89% | 95 | 81 | 0 | 0 | 10 | 4 | 45 | – | diff --git a/docs/audit/theming/dialog.md b/docs/audit/theming/dialog.md index ff3082db1..0f63c515e 100644 --- a/docs/audit/theming/dialog.md +++ b/docs/audit/theming/dialog.md @@ -5,24 +5,16 @@ > Vista de conjunto: [README](./README.md) · método y protocolo: > [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7. -- **Medido**: 2026-08-23 · **Alcance**: **67%** — 24 de 36 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)_ -- **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` +- **Medido**: 2026-08-23 · **Alcance**: **89%** — 31 de 35 knobs por token público +- **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`): 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` ## 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 | -| ---: | --- | --- | --- | --- | -| 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)` | +_Ninguno._ ### 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)` | | 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 | -| ---: | --- | --- | --- | --- | -| 1 | `dialog.css:197` | `[data-dialog-content][data-sheet]` | `width` | `100%` | +_Ninguno._ -### 1.4 Excepciones firmadas (0) — fuera del ratio +### 1.4 Excepciones firmadas (1) — fuera del ratio Literales que llevan su anotación `/* literal: */` 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 | `dialog.css:197` | `[data-dialog-content][data-sheet]` | `width` | `100%` | ## 2. Sistema transversal (0) — informativo, fuera del ratio 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). -### 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 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 | | --- | --- | --- | ---: | -| `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: `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: `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) @@ -105,6 +89,51 @@ no distingue lo que debería — se marca `⚠`. -_(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×. diff --git a/scripts/theming-sentinel-exceptions.ts b/scripts/theming-sentinel-exceptions.ts index e78df458f..477d69a9a 100644 --- a/scripts/theming-sentinel-exceptions.ts +++ b/scripts/theming-sentinel-exceptions.ts @@ -77,6 +77,26 @@ export const SENTINEL_EXCEPTIONS: Record> = { '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)' }, + 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': { // 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 diff --git a/src/uix/eidos/components/dialog/README.md b/src/uix/eidos/components/dialog/README.md index adb5fd3c4..c6543e2c0 100644 --- a/src/uix/eidos/components/dialog/README.md +++ b/src/uix/eidos/components/dialog/README.md @@ -50,6 +50,44 @@ attached, sin `Root` ni `Provider` publico. Partes publicas: `Trigger`, `Portal`, `Overlay`, `Content`, `Title`, `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 | Capacidad | Air | Eidos/Soma | Radix | Bits UI | Ark UI | shadcn-svelte | diff --git a/src/uix/eidos/components/dialog/dialog.css b/src/uix/eidos/components/dialog/dialog.css index 8129d0c7a..4c9156646 100644 --- a/src/uix/eidos/components/dialog/dialog.css +++ b/src/uix/eidos/components/dialog/dialog.css @@ -194,7 +194,7 @@ top: auto; bottom: 0; translate: none; - width: 100%; + width: 100%; /* literal: identity — a bottom sheet spans the viewport, by definition */ max-height: var(--dialog-content-max-height-sheet); border-radius: var(--dialog-content-radius) var(--dialog-content-radius) 0 0; --_dialog-padding: var(--dialog-content-padding-sm); @@ -210,19 +210,19 @@ /* neutral = the plane's own border (`--depth-modal-border`); no override needed. */ [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] { - color: var(--color-risk-text); + color: var(--dialog-risk-title-fg); } [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] { - color: var(--color-threat-text); + color: var(--dialog-threat-title-fg); } /* ── Layout shells (eidos-only, no morfo backing) ──────────────────────── */ @@ -331,13 +331,13 @@ * coexist during the exit animation; CSS tints the border-top by causa. */ [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'] { - 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='dismissed'], [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); } diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index a2915cb1b..c57e867ca 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -1166,6 +1166,13 @@ --dialog-description-font-size: var(--size-sm-font-size); --dialog-description-line-height: var(--leading-ui); --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-inset: var(--space-3); --field-gap-xs: var(--space-1); diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index 115ff5f22..23b9de308 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -1181,6 +1181,24 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({ // (bg / hover / focus-ring / disabled / radius). Only the dialog-specific // floating geometry stays here: `close-size` reserves header room beside a // 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-inset': 'var(--space-3)' }, diff --git a/web/routes/uix/components/dialog/+page.svelte b/web/routes/uix/components/dialog/+page.svelte index 27d6fe4b8..756f05b34 100644 --- a/web/routes/uix/components/dialog/+page.svelte +++ b/web/routes/uix/components/dialog/+page.svelte @@ -8,10 +8,11 @@ import { compileMorfo } from '$uix/morfo'; import { dialogMorfo } from '@/uix/morfo/components/dialog'; import { getActiveUix } from '$active-uix'; + import TokensPanel from '../../lib/TokensPanel.svelte'; const uix = getActiveUix(); - type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; + type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens' | 'a11y'; let tab = $state('live'); type DialogIntent = 'neutral' | 'risk' | 'threat'; @@ -248,6 +249,9 @@ {events.length} + @@ -649,6 +653,10 @@ {/if} + {#if tab === 'tokens'} + + {/if} + {#if tab === 'recipe'}

Eidos recipe