From 2117d7422fc2d0469e7b17599f1d937b51ab8746 Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 23 Aug 2026 22:41:20 +0200 Subject: [PATCH] =?UTF-8?q?uix(editable):=20temable=20=E2=80=94=2075=20%?= =?UTF-8?q?=20=E2=86=92=2082=20%,=20y=20el=20guard=20CERRABA=20la=20superf?= =?UTF-8?q?icie=20que=20med=C3=ADa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Protocolo PLAN-theming §7 sobre `editable`. Censo 75 % → **82 %**, contrato 56 claves, centinela **42/56** con 14 adjudicadas. El default NO se mueve. ## Lo que costó medirlo: dos superficies y una apertura frágil al revés - **Preview e input comparten celda de rejilla y se excluyen** (`display: none`), igual que los triggers (Edit en reposo, Save/Cancel editando): una pasada deja fuera la mitad de la receta. Todo se midió DOS veces. - **Salir del modo edición es lo que hace un `blur`** —da igual lo que diga `submitMode`, esa opción sólo decide si el valor se compromete— y el guard blurea justo después de abrir. Abriendo con clic en el trigger, la superficie ya estaba cerrada cuando se medía: `submit-fg` leía muerto y mueve perfectamente abierta (`rgb(255,255,255)` → `rgb(1,2,3)`). Se abre por FOCO, con la demo en `activationMode=focus`, que es la apertura que el guard no deshace. Misma ley que `navigation-menu`, con el gesto invertido. ## Lo que entra al contrato (1 clave) `disabled-fg` — la tinta apagada de TODO el componente: preview, input y los tres triggers comparten UNA regla, así que el nombre desnudo del catálogo es el que toca (`label` y `select` ya lo llevan con este mismo valor). Era el único knob que iba a un primitivo a pelo, y encima con un respaldo escrito a mano —`var(--color-content-disabled, var(--color-content-muted))`— que estaba MUERTO: el token sí está declarado, así que el respaldo no podía pintar nunca. Retirado con él. ## Lo que sale (1 clave huérfana) `control-border`: ninguna regla lo consumía. El borde en reposo del control es `transparent` por diseño y el del input es el acento (`--_editable-palette-border`), así que esa clave pública prometía un knob que no existe — la deuda INVERSA del eje. Diff 0, y el centinela lo confirmaba muerto. ## Dos literales de identidad firmados El `fit-content` del preview (es tan ancho como el texto que enseña; la ficha proponía acuñarlo como `preview-width`, que sería acuñar la definición de la parte) y el `100 %` del input (llena la celda que comparte con el preview). ## Lo que se queda privado (el techo honesto es 82 %) Los SIETE knobs por privado son el puente de paleta THM-2 (`--_editable-palette-{solid,border,solid-hover}`), que la capa compartida alimenta por instancia desde `[data-color]`: un público encima dejaría que un tema lo fijara y matara el `color=` de cada instancia. Otros cuatro son sistema transversal (anillo de foco y velo de hover), ya fuera del ratio. ## Las catorce adjudicaciones, en dos razones — y una tercera que casi se cuela - El par `transition-*` **ES** la transición que el guard congela; medido sin congelar: `0.12s → 4.321s` y `cubic-bezier(0.4, 0, 0.2, 1) → steps(3)`. - Las doce claves de tono son la cascada de paleta, por PATRÓN y con el mecanismo a la vista: el nodo lleva `--palette-solid` de la capa por instancia, el bloque genérico `[data-editable][data-color]` —emitido EL ÚLTIMO, misma especificidad— lo lee, y el respaldo que nombra la clave del componente no llega nunca. Quítale `data-color` a la misma instancia y `--editable-primary-solid` vuelve a pintar (`rgb(1, 2, 3)`). - ⚠ **`risk-border` NO cae en ese patrón**: las tres reglas de inválido la leen DIRECTAMENTE, así que su silencio tiene otra causa —la demo es válida— y entrada propia: forzando `data-invalid` alcanza en el input Y en el control. Una excepción por patrón que engulle una clave con vida propia es una adjudicación falsa. ## Artefactos - Sonda: **0 diffs** en las dos caras — modo preview (1.696 valores, 7 estados) y modo edición (2.528 valores, 8 estados). - Captura 2× del reposo idéntica al byte. La de edición NO es comparable por bytes: la traza de la demo imprime un TIMESTAMP, así que dos corridas del mismo código difieren (comprobado antes de creerme el diff). Revisada a ojo. - `component-audit --only editable`: **PASS** · `eidos-lint` 0 invalid · `vitest src/uix/eidos` sin rojos nuevos (el conocido `skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `npm run check` COMPLETED, 0 errores en `editable`. - README «Talla y tema» + pestaña `Tokens` (56 filas, verificada en el navegador con `networkidle` y clic real). ## Defecto anotado, NO arreglado El tinte inválido ES la clave de tono (`--editable-risk-border`): dos knobs con una sola llave — un tema que quiera otro color de error mueve también las instancias `color="risk"`. El arreglo es una clave de estado por parte (`invalid-input-border` / `invalid-control-border`, la forma que `switch` acuñó el mismo día) y es contrato: se lista. Co-Authored-By: Claude Opus 5 --- docs/audit/theming/editable.md | 105 +++++++++++++++--- docs/process/PLAN-theming.md | 22 ++++ scripts/theming-sentinel-exceptions.ts | 20 ++++ scripts/theming-sentinel.ts | 16 +++ src/uix/eidos/components/editable/README.md | 38 +++++++ .../eidos/components/editable/editable.css | 6 +- src/uix/eidos/generated/base.css | 2 +- src/uix/eidos/lib/recipes/base.ts | 7 +- .../uix/components/editable/+page.svelte | 10 +- 9 files changed, 203 insertions(+), 23 deletions(-) diff --git a/docs/audit/theming/editable.md b/docs/audit/theming/editable.md index 3f715534b..bc32371cb 100644 --- a/docs/audit/theming/editable.md +++ b/docs/audit/theming/editable.md @@ -5,18 +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**: **75%** — 30 de 40 knobs por token público -- **Knobs de apariencia**: 44 — público 30 · privado 7 · global 1 · literal 2 · sistema 4 · excepción 0 _(los dos últimos, fuera del ratio)_ -- **Contrato hoy** (`lib/recipes/base.ts`): 56 pública(s) — `gap-xs`, `gap-sm`, `gap-md`, `gap-lg`, `gap-xl`, `control-height-xs`, `control-height-sm`, `control-height-md`, `control-height-lg`, `control-height-xl`, `control-padding-inline-xs`, `control-padding-inline-sm`, `control-padding-inline-md`, `control-padding-inline-lg`, `control-padding-inline-xl`, `font-family`, `font-size-xs`, `font-size-sm`, `font-size-md`, `font-size-lg`, `font-size-xl`, `line-height`, `fg`, `area-min-inline-size`, `control-radius`, `control-border-width`, `control-border`, `hover-control-border`, `control-bg`, `control-shadow`, `placeholder-fg`, `trigger-gap`, `trigger-padding-inline`, `trigger-radius`, `trigger-border-width`, `trigger-border`, `hover-trigger-border`, `trigger-bg`, `trigger-fg`, `hover-trigger-fg`, `submit-fg`, `transition-duration`, `transition-ease`, `disabled-opacity`, `primary-solid`, `secondary-solid`, `neutral-solid`, `affirm-solid`, `risk-solid`, `threat-solid`, `primary-border`, `secondary-border`, `neutral-border`, `affirm-border`, `risk-border`, `threat-border` · 3 privada(s) forward — `_palette-solid`, `_palette-border`, `_palette-solid-hover` +- **Medido**: 2026-08-23 · **Alcance**: **82%** — 31 de 38 knobs por token público +- **Knobs de apariencia**: 42 — público 31 · privado 7 · global 0 · literal 0 · sistema 4 · excepción 2 _(los dos últimos, fuera del ratio)_ +- **Contrato hoy** (`lib/recipes/base.ts`): 56 pública(s) — `gap-xs`, `gap-sm`, `gap-md`, `gap-lg`, `gap-xl`, `control-height-xs`, `control-height-sm`, `control-height-md`, `control-height-lg`, `control-height-xl`, `control-padding-inline-xs`, `control-padding-inline-sm`, `control-padding-inline-md`, `control-padding-inline-lg`, `control-padding-inline-xl`, `font-family`, `font-size-xs`, `font-size-sm`, `font-size-md`, `font-size-lg`, `font-size-xl`, `line-height`, `fg`, `area-min-inline-size`, `control-radius`, `control-border-width`, `hover-control-border`, `control-bg`, `control-shadow`, `placeholder-fg`, `trigger-gap`, `trigger-padding-inline`, `trigger-radius`, `trigger-border-width`, `trigger-border`, `hover-trigger-border`, `trigger-bg`, `trigger-fg`, `hover-trigger-fg`, `submit-fg`, `transition-duration`, `transition-ease`, `disabled-opacity`, `disabled-fg`, `primary-solid`, `secondary-solid`, `neutral-solid`, `affirm-solid`, `risk-solid`, `threat-solid`, `primary-border`, `secondary-border`, `neutral-border`, `affirm-border`, `risk-border`, `threat-border` · 3 privada(s) forward — `_palette-solid`, `_palette-border`, `_palette-solid-hover` - **Eje `size`**: sí · **ficheros**: `editable.css` ## 1. Knobs fuera de alcance -### 1.1 Directo a primitivo global (1) +### 1.1 Directo a primitivo global (0) -| # | fichero:línea | selector | propiedad | valor | -| ---: | --- | --- | --- | --- | -| 1 | `editable.css:244` | `[data-editable][data-disabled] [data-editable-preview], [data-editable][data-disabled] [data-editable-input], [data-editable][data-disabled] [data-editable-edit-trigger], [data-editable][data-disabled] [data-editable-submit-trigger], [data-editable][data-disabled] [data-editable-cancel-trigger], [data-editable-preview][data-disabled], [data-editable-input][data-disabled], [data-editable-edit-trigger][data-disabled], [data-editable-submit-trigger][data-disabled], [data-editable-cancel-trigger][data-disabled]` | `color` | `var(--color-content-disabled, var(--color-content-muted))` | +_Ninguno._ ### 1.2 A través de un privado (7) @@ -30,21 +28,21 @@ | 6 | `editable.css:198` | `[data-editable-submit-trigger]:hover:not(:disabled)` | `background` | `var(--_editable-palette-solid-hover)` | | 7 | `editable.css:199` | `[data-editable-submit-trigger]:hover:not(:disabled)` | `border-color` | `var(--_editable-palette-solid-hover)` | -### 1.3 Literales (2) +### 1.3 Literales (0) -| # | fichero:línea | selector | propiedad | valor | -| ---: | --- | --- | --- | --- | -| 1 | `editable.css:89` | `[data-editable-preview]` | `inline-size` | `fit-content` | -| 2 | `editable.css:95` | `[data-editable-input]` | `inline-size` | `100%` | +_Ninguno._ -### 1.4 Excepciones firmadas (0) — fuera del ratio +### 1.4 Excepciones firmadas (2) — 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 | `editable.css:89` | `[data-editable-preview]` | `inline-size` | `fit-content` | +| 2 | `editable.css:95` | `[data-editable-input]` | `inline-size` | `100%` | ## 2. Sistema transversal (4) — informativo, fuera del ratio Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2). @@ -71,7 +69,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` (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 @@ -82,7 +80,6 @@ no distingue lo que debería — se marca `⚠`. | token (`--editable-…`) | scope TSC | valor propuesto | usos | | --- | --- | --- | ---: | | `preview-width` | `root` | `fit-content` | 1 | -| `disabled-preview-fg` | `root` | `var(--color-content-disabled, var(--color-content-muted))` | 1 | ### 4.2 Sin nombre mecánico (8) @@ -101,6 +98,80 @@ no distingue lo que debería — se marca `⚠`. -_(pendiente — lo escribe el autor; se conserva al regenerar)_ +**Ejecutado 2026-08-23 — 75 % → 82 %, contrato 56 claves, centinela 42/56 (14 +adjudicadas).** Diff de computed **0** en las dos caras del componente —modo +PREVIEW (1.696 valores) y modo EDICIÓN (2.528)— y la captura 2× del reposo +idéntica al byte. + +**El componente tiene DOS superficies y sólo una está en pantalla.** Preview e +input comparten celda de rejilla y se excluyen (`display: none`), igual que los +triggers: Edit en reposo, Save/Cancel editando. Cualquier medida de una sola +pasada deja fuera la mitad de la receta, así que todo aquí se midió dos veces. + +**Y la apertura es FRÁGIL en la dirección contraria a la habitual**: salir del +modo edición es lo que hace un **blur** —da igual lo que diga `submitMode`, esa +opción sólo decide si el valor se compromete—, y el guard hace `blur()` justo +después de abrir. Abriendo con clic en el trigger, la superficie ya estaba +cerrada cuando se medía: `submit-fg` leía muerto y mueve perfectamente cuando +está abierta de verdad (`rgb(255,255,255)` → `rgb(1,2,3)`). Se resuelve +poniendo la demo en `activationMode=focus` y abriendo por **foco**, que es la +apertura que el guard no deshace. **Regla: antes de creer un «no effect», +comprueba que la superficie seguía abierta** — la misma de `navigation-menu`, +con el gesto invertido. + +**Lo que se acuñó (1):** + +- **`disabled-fg`** — la tinta apagada de TODO el componente (preview, input y + los tres triggers comparten UNA regla), así que el nombre desnudo del catálogo + es el que toca: `label` y `select` ya lo llevan con este mismo valor. Era el + único knob que iba a un primitivo a pelo, y encima con un respaldo escrito a + mano —`var(--color-content-disabled, var(--color-content-muted))`— que estaba + **muerto**: el token sí está declarado en `:root`, así que el respaldo no podía + pintar nunca. Retirado con él. + +**Lo que se retiró (1 clave huérfana):** + +- **`control-border`** — ninguna regla lo consumía. El borde en reposo del + control es `transparent` por diseño y el del input es el acento + (`--_editable-palette-border`), así que esa clave pública prometía un knob que + no existe. Es la deuda INVERSA del eje. Diff 0, y el centinela lo confirma: + antes leía muerto. + +**Dos literales de identidad firmados**: el `fit-content` del preview (es tan +ancho como el texto que enseña — la ficha proponía acuñarlo como +`preview-width`, y sería acuñar la definición de la parte) y el `100 %` del +input (llena la celda de rejilla que comparte con el preview). + +**Lo que se queda fuera, y por qué (el techo honesto es 82 %):** los **siete** +knobs que van por privado son el puente de paleta THM-2 +(`--_editable-palette-{solid,border,solid-hover}`) que la capa compartida +alimenta por instancia desde `[data-color]`; un público encima dejaría que un +tema lo fijara y matara el `color=` de cada instancia (§3.pre del handoff). +Los otros cuatro son sistema transversal —anillo de foco y velo de hover— y ya +están fuera del ratio. + +**Las catorce adjudicaciones**, en dos razones: el par `transition-*` **ES** la +transición que el guard congela (medido sin congelar: `0.12s → 4.321s` y +`cubic-bezier(0.4, 0, 0.2, 1) → steps(3)`), y las **doce claves de tono** son la +cascada de paleta, adjudicadas por PATRÓN con el mecanismo a la vista: el nodo +lleva `--palette-solid` de la capa por instancia, el bloque genérico +`[data-editable][data-color]` —emitido EL ÚLTIMO, misma especificidad— lo lee, y +el respaldo que nombra la clave del componente no llega nunca; quítale +`data-color` a la misma instancia y `--editable-primary-solid` vuelve a pintar. +soma estampa `data-color` siempre, así que no se escapa ninguna. + +⚠ **`risk-border` no cae en ese patrón, y comprobarlo importó**: además del +camino de paleta, esa clave la leen DIRECTAMENTE las tres reglas de inválido, así +que su silencio tiene una segunda causa —la demo es válida— y su adjudicación es +propia: forzando `data-invalid` sobre el root real alcanza en el input Y en el +control (`oklch(0.8059 0.1123 59.96)` → `rgb(1, 2, 3)`). Una excepción por +patrón que engulle una clave con vida propia es una adjudicación falsa. + +**Defecto real anotado, NO arreglado**: ese mismo hecho es el defecto. El estado +inválido pinta con la clave de TONO, así que un tema que quiera otro tinte de +error tiene que mover el tono `risk`, que también viste las instancias con +`color="risk"` — dos knobs distintos con una sola llave. El arreglo es una clave +de estado por parte (`invalid-input-border` / `invalid-control-border`, la forma +que `switch` acuñó el mismo día), y es contrato: se lista. diff --git a/docs/process/PLAN-theming.md b/docs/process/PLAN-theming.md index 4953821fc..61529140b 100644 --- a/docs/process/PLAN-theming.md +++ b/docs/process/PLAN-theming.md @@ -486,6 +486,28 @@ commits de corrección. ## 8. Registro +- 2026-08-23 — **`editable` 75 % → 82 %** (COMMIT), contrato 56 claves, + centinela **42/56** con 14 adjudicadas. **Su apertura es frágil AL REVÉS**: + salir del modo edición es lo que hace un `blur` —da igual lo que diga + `submitMode`, esa opción sólo decide si el valor se compromete— y el guard + blurea justo después de abrir, así que abrir con clic medía una superficie ya + cerrada (`submit-fg` leía muerto y mueve perfectamente abierta). Se abre por + FOCO, con la demo en `activationMode=focus`. Además tiene **DOS superficies + excluyentes** (preview/input comparten celda; Edit contra Save+Cancel), así + que todo se midió dos veces: diff 0 en modo preview (1.696 valores) y en modo + edición (2.528). Entra **`disabled-fg`** —la tinta apagada de TODO el + componente, una sola regla; era el único knob a primitivo crudo y traía un + respaldo `, var(--color-content-muted)` MUERTO— y sale **`control-border`**, + clave pública que no consumía ninguna regla (deuda inversa). Dos literales de + identidad firmados. Los 7 privados son el puente de paleta THM-2: **82 % es su + techo**. ⚠ **`risk-border` NO cae en el patrón de paleta**: las tres reglas de + inválido la leen directamente, así que su silencio tiene otra causa y su + propia adjudicación —forzado `data-invalid` alcanza en input y control—; una + excepción por patrón que engulle una clave con vida propia es una adjudicación + falsa. **Defecto anotado**: el tinte inválido ES la clave de tono, o sea dos + knobs con una sola llave; el arreglo es `invalid-{parte}-border` (la forma que + `switch` acuñó el mismo día) y es contrato. + - 2026-08-23 — **`avatar` 75 % → 90 %** (COMMIT), contrato 84 → 88 claves, centinela **83/88** con 5 adjudicadas. **Su contrato existía y NINGÚN instrumento lo veía**: es la única entrada de `base.ts` construida por una diff --git a/scripts/theming-sentinel-exceptions.ts b/scripts/theming-sentinel-exceptions.ts index 272dca34e..9bbdbf6d1 100644 --- a/scripts/theming-sentinel-exceptions.ts +++ b/scripts/theming-sentinel-exceptions.ts @@ -36,6 +36,19 @@ export const SENTINEL_EXCEPTIONS: Record> = { 'group-overlap-xxl': 'same xxl step, on AvatarGroup; forced data-size=xxl on the real group -> reaches (margin-inline-start -33.6px -> 37px)' }, + editable: { + // 2026-08-23. Only two reasons, both measured: the transition pair IS the + // transition the guard freezes, and the twelve tone keys are the palette + // cascade (adjudicated by PATTERN below). + 'transition-duration': + 'the guard freezes transitions and this token IS the transition; measured unfrozen -> reaches (0.12s -> 4.321s on the three transitioned properties of the preview)', + 'transition-ease': + 'same freeze; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(3))', + // NOT the palette pattern: this one key is ALSO read straight by the invalid + // rules, so its silence has a second, different cause — the demo is valid. + 'risk-border': + 'the palette path is superseded like its eleven siblings, but this key is ALSO the invalid tint, read directly by three rules; forced data-invalid on the real root -> reaches on the input AND on the control (oklch(0.8059 0.1123 59.96) -> rgb(1, 2, 3))' + }, background: { // 2026-08-23. Its parts are independent OPT-IN layers and only ONE pattern // renders at a time, so the default stage moved 3 of 37 tokens. The guard @@ -1106,6 +1119,13 @@ export const SENTINEL_PATTERN_EXCEPTIONS: Array<{ reason: 'SUPERSEDED by the composed Button: [data-form-submit] & co. also carry `data-button`, and `[data-button]` matches at the SAME specificity (0,1,0) but loads LATER, so button.css paints the whole action chrome. Measured 2026-08-23, eight properties one by one — height, padding-inline, font-size, font-weight, line-height, gap, radius, border-width — plus the transition pair and the disabled opacity: NOT ONE of them moves. Same defect gradient-picker documented (36 of its 45 keys) and emoji-picker found in its trigger; the answer there was to retire the dead declarations, and here that is 24 public keys — a decision for the author, so they are adjudicated meanwhile (next-features 13)' }, + { + component: 'editable', + pattern: /^(primary|secondary|neutral|affirm|risk|threat)-(solid|border)$/, + reason: + PALETTE_SUPERSEDED + + ' — measured here 2026-08-23 with the mechanism in the open: the node carries --palette-solid (oklch(0.5556 …)) from the shared per-instance layer, the generic block reads it, and the fallback that names the component key never applies; strip data-color from the same instance and --editable-primary-solid paints again (rgb(1, 2, 3)). soma always stamps data-color, so no instance escapes' + }, { component: 'surface', pattern: /^(primary|secondary|neutral|affirm|fulfill|risk|threat|loss)-(track|solid|contrast)$/, diff --git a/scripts/theming-sentinel.ts b/scripts/theming-sentinel.ts index 17cb8d9d9..2c8c77ee6 100644 --- a/scripts/theming-sentinel.ts +++ b/scripts/theming-sentinel.ts @@ -187,6 +187,22 @@ const COMPONENT_OVERRIDES: Record< sweepAttr: { attr: 'data-variant', values: ['solid', 'soft', 'outline'] }, urls: ['/uix/components/avatar', '/uix/components/avatar-group'] }, + // Its whole edit surface — input, submit, cancel — is `display: none` until the + // component enters edit mode, and the demo boots in PREVIEW. But the opening is + // FRAGILE: leaving edit mode is what a blur does (whatever `submitMode` says — + // that flag only decides whether the value commits), and the guard blurs right + // after opening, so clicking the edit trigger measured a surface that was + // already closed again (`submit-fg` read dead and moves 1234 when it is really + // open). Switching the demo to `activationMode=focus` makes FOCUS the opening, + // which is the one the guard does not undo. + editable: { + prepareWith: [ + '[data-uix-control]:has([data-uix-control-label]:text-is("activationMode")) [data-uix-chip]:text-is("focus")' + ], + openBy: 'focus', + openWith: ['[data-editable-preview]'], + openMarker: '[data-editable][data-editing]' + }, 'picker-shell': { attrPrefix: 'data-picker', openWith: ['[data-uix-stage-area] [data-popover-trigger]'], diff --git a/src/uix/eidos/components/editable/README.md b/src/uix/eidos/components/editable/README.md index 7429eabe0..17677cb21 100644 --- a/src/uix/eidos/components/editable/README.md +++ b/src/uix/eidos/components/editable/README.md @@ -52,6 +52,44 @@ Referencias externas consultadas: - `color` usa paletas canonicas de Eidos para foco y submit. - El soporte textarea se mantiene en Soma mediante `child`. +## Talla y tema + +56 claves públicas. Las escalas van por talla con su nombre resuelto (`gap-{k}`, +`control-height-{k}` sobre el bundle `--size-{k}-control-height`, +`control-padding-inline-{k}`, `font-size-{k}`) y el cromo por PARTE: el control +(`control-radius`, `control-border-width`, `control-bg`, `control-shadow`, +`hover-control-border`, `placeholder-fg`) y los triggers (`trigger-*`, +`hover-trigger-*`, `submit-fg`). + +Lo que entró el **2026-08-23** (75 % → 82 %): + +- **`disabled-fg`** — la tinta apagada de TODO el componente: preview, input y + los tres triggers comparten la regla, así que el nombre desnudo del catálogo + es el que toca (`label` y `select` ya lo llevan con este mismo valor). El CSS + leía el primitivo a pelo, y además con un respaldo escrito a mano + (`, var(--color-content-muted)`) que estaba MUERTO: `--color-content-disabled` + sí está declarado, así que el respaldo no podía pintar nunca. +- **`control-border` RETIRADO**: no lo consumía ninguna regla. El borde en + reposo del control es `transparent` por diseño y el del input es el acento + (`--_editable-palette-border`), así que esa clave pública prometía un knob que + no existe. Retirarla no mueve un píxel. +- **Dos literales de identidad firmados**: el `fit-content` del preview (es tan + ancho como el texto que enseña) y el `100 %` del input (llena la celda de + rejilla que comparte con el preview). + +**Lo que queda fuera, y por qué**: los siete knobs que van por privado son el +puente de paleta THM-2 (`--_editable-palette-{solid,border,solid-hover}`), que +la capa compartida alimenta por instancia desde `[data-color]` — un público +encima dejaría que un tema lo fijara y matara el `color=` de cada instancia. Y +cuatro knobs son sistema transversal (el anillo de foco y el velo de hover). + +⚠ **Las doce claves de tono (`{tono}-{solid,border}`) no mueven nada hoy**: el +bloque genérico `[data-editable][data-color]` del forward de paleta se emite EL +ÚLTIMO y gana por orden a igual especificidad, así que la pintura sale del +`--palette-*` global. Medido con el mecanismo a la vista — quitando `data-color` +de la instancia, `--editable-primary-solid` vuelve a pintar. Es el hallazgo de +cascada de paleta de next-features §13 y espera firma. + ## Eventos Morfo declara: diff --git a/src/uix/eidos/components/editable/editable.css b/src/uix/eidos/components/editable/editable.css index 4c76dda3c..0348de924 100644 --- a/src/uix/eidos/components/editable/editable.css +++ b/src/uix/eidos/components/editable/editable.css @@ -86,13 +86,13 @@ [data-editable-preview] { display: inline-flex; align-items: center; - inline-size: fit-content; + inline-size: fit-content; /* literal: identity — the preview is as wide as the text it shows */ max-inline-size: 100%; cursor: text; } [data-editable-input] { - inline-size: 100%; + inline-size: 100%; /* literal: identity — the input fills the grid area it shares with the preview */ border-color: var(--_editable-palette-border); background: var(--editable-control-bg); box-shadow: var(--editable-control-shadow); @@ -241,7 +241,7 @@ [data-editable-edit-trigger][data-disabled], [data-editable-submit-trigger][data-disabled], [data-editable-cancel-trigger][data-disabled] { - color: var(--color-content-disabled, var(--color-content-muted)); + color: var(--editable-disabled-fg); cursor: not-allowed; } diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index d5bc05046..e90e28159 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -3120,7 +3120,6 @@ --editable-area-min-inline-size: 12ch; --editable-control-radius: var(--radius-md); --editable-control-border-width: var(--border-width); - --editable-control-border: var(--color-border-subtle); --editable-hover-control-border: var(--color-border-default); --editable-control-bg: var(--color-surface-default); --editable-control-shadow: var(--shadow-subtle); @@ -3138,6 +3137,7 @@ --editable-transition-duration: var(--duration-fast); --editable-transition-ease: var(--ease-default); --editable-disabled-opacity: var(--opacity-disabled); + --editable-disabled-fg: var(--color-content-disabled); --editable-primary-solid: var(--color-primary-solid); --editable-secondary-solid: var(--color-secondary-solid); --editable-neutral-solid: var(--color-neutral-solid); diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index a38d7ca68..9fee736cd 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -4802,7 +4802,6 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({ 'area-min-inline-size': '12ch', 'control-radius': 'var(--radius-md)', 'control-border-width': 'var(--border-width)', - 'control-border': 'var(--color-border-subtle)', 'hover-control-border': 'var(--color-border-default)', 'control-bg': 'var(--color-surface-default)', 'control-shadow': 'var(--shadow-subtle)', @@ -4820,6 +4819,12 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({ 'transition-duration': 'var(--duration-fast)', 'transition-ease': 'var(--ease-default)', 'disabled-opacity': 'var(--opacity-disabled)', + // The disabled ink of the WHOLE component — preview, input and the three + // triggers share one rule — so the bare `disabled-fg` of the catalogue is the + // name, and `--color-content-disabled` its value in the two recipes that + // already carry it. The CSS read the primitive with a hand-written fallback + // (`, var(--color-content-muted)`) that was dead: the token IS declared. + 'disabled-fg': 'var(--color-content-disabled)', 'primary-solid': 'var(--color-primary-solid)', 'secondary-solid': 'var(--color-secondary-solid)', 'neutral-solid': 'var(--color-neutral-solid)', diff --git a/web/routes/uix/components/editable/+page.svelte b/web/routes/uix/components/editable/+page.svelte index c4bbd955e..814f544af 100644 --- a/web/routes/uix/components/editable/+page.svelte +++ b/web/routes/uix/components/editable/+page.svelte @@ -11,8 +11,9 @@ import { getActiveUix } from '$active-uix'; import { page } from '$app/state'; import { colorValue, PALETTE_SCALES } from '../../lib/harness.svelte'; + import TokensPanel from '../../lib/TokensPanel.svelte'; - type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; + type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens' | 'a11y'; type ActivationMode = 'focus' | 'click' | 'dblclick' | 'none'; type SubmitMode = 'enter' | 'blur' | 'both' | 'none'; @@ -276,6 +277,9 @@ {events.length} + @@ -629,6 +633,10 @@ {/if} + {#if tab === 'tokens'} + + {/if} + {#if tab === 'recipe'}

Eidos recipe