uix(editable): temable — 75 % → 82 %, y el guard CERRABA la superficie que medía

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 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent b7f5a7d75a
commit 2117d7422f

@ -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: <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 | `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 `⚠`.
<!-- veredicto:start -->
_(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.
<!-- veredicto:end -->

@ -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

@ -36,6 +36,19 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'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)$/,

@ -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]'],

@ -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:

@ -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;
}

@ -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);

@ -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)',

@ -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 @@
<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 <span data-uix-tab-count>56</span>
</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div>
@ -629,6 +633,10 @@
</section>
{/if}
{#if tab === 'tokens'}
<TokensPanel component="editable" 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.