You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/audit/theming/editable.md

184 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# editable — alcance de tema: análisis y propuesta
> Generado por `node --import tsx/esm scripts/theming-census.ts --report`.
> Lo **medido** y la **propuesta** se regeneran; el **Veredicto** (§5) se conserva.
> 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**: **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 (0)
_Ninguno._
### 1.2 A través de un privado (7)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `editable.css:96` | `[data-editable-input]` | `border-color` | `var(--_editable-palette-border)` |
| 2 | `editable.css:129` | `[data-editable-input]:hover:not([data-disabled]):not([data-readonly])` | `border-color` | `var(--_editable-palette-border)` |
| 3 | `editable.css:134` | `[data-editable-preview]:focus-visible, [data-editable-input]:focus-visible` | `border-color` | `var(--_editable-palette-border)` |
| 4 | `editable.css:185` | `[data-editable-submit-trigger]` | `border-color` | `var(--_editable-palette-border)` |
| 5 | `editable.css:186` | `[data-editable-submit-trigger]` | `background` | `var(--_editable-palette-solid)` |
| 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 (0)
_Ninguno._
### 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.
| # | 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).
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `editable.css:99` | `[data-editable-input]` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 2 | `editable.css:136` | `[data-editable-preview]:focus-visible, [data-editable-input]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 3 | `editable.css:193` | `[data-editable-edit-trigger]:hover:not(:disabled), [data-editable-cancel-trigger]:hover:not(:disabled)` | `background-image` | `linear-gradient(var(--state-hover), var(--state-hover))` |
| 4 | `editable.css:205` | `[data-editable-edit-trigger]:focus-visible, [data-editable-submit-trigger]:focus-visible, [data-editable-cancel-trigger]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_editable-gap` | 5 | `var(--editable-gap-md)`, `var(--editable-gap-xs)`, `var(--editable-gap-sm)`, `var(--editable-gap-lg)`, `var(--editable-gap-xl)` | public | **sí** |
| `--_editable-control-height` | 5 | `var(--editable-control-height-md)`, `var(--editable-control-height-xs)`, `var(--editable-control-height-sm)`, `var(--editable-control-height-lg)`, `var(--editable-control-height-xl)` | public | **sí** |
| `--_editable-control-padding-inline` | 5 | `var(--editable-control-padding-inline-md)`, `var(--editable-control-padding-inline-xs)`, `var(--editable-control-padding-inline-sm)`, `var(--editable-control-padding-inline-lg)`, `var(--editable-control-padding-inline-xl)` | public | **sí** |
| `--_editable-font-size` | 5 | `var(--editable-font-size-md)`, `var(--editable-font-size-xs)`, `var(--editable-font-size-sm)`, `var(--editable-font-size-lg)`, `var(--editable-font-size-xl)` | public | **sí** |
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_editable-palette-border`, `--_editable-palette-solid`, `--_editable-palette-solid-hover`.
## 4. Propuesta de corrección
- **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` (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
al final) y theming §6.7 (slots de color, modificador delante). Un token con
DOS valores distintos es una colisión de nombre: son dos knobs, o el nombre
no distingue lo que debería — se marca `⚠`.
| token (`--editable-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `preview-width` | `root` | `fit-content` | 1 |
### 4.2 Sin nombre mecánico (8)
- **⚠ decisión: `100%` es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar** — 1: `inline-size`.
- **⚠ decisión: el privado que alimenta este knob no se declara en el CSS (viene de `base.ts` o de un estilo inline) — hay que resolverlo antes de nombrarlo** — 7: `border-color`, `background`.
### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)
- [ ] **Privado que no deriva de un público** — §3 lo marca; el privado debe leer el público o desaparecer.
- [ ] **Velo o acento en el nodo equivocado** (`archetype: 'item'` en un envoltorio, un `background` en shorthand que mata la capa de estado) — se mide desde el píxel hacia arriba.
- [ ] **Doble animación** al mover un sello a una superficie con animación propia — registro de `animationstart`/`animationend`.
- [ ] **Diff de computed = 0** en reposo · hover · abierto · disabled · foco, por talla, antes y después.
- [ ] **Centinela por token nuevo**: valor imposible en el root → el nodo lo sigue. Si no, el token miente.
## 5. Veredicto
<!-- veredicto:start -->
**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.
⚠ **El guard tiene una CARRERA en este componente** (supervisión 2026-08-23):
sobre el MISMO HEAD, `submit-fg` leyó muerto en una corrida de cuatro
(41/56, exit 1) y vivo en las otras tres (42/56, exit 0). La apertura por foco
+ `prepareWith` no es determinista aquí. Un rojo de `editable` en un barrido
del ledger NO es un resultado hasta re-correrlo.
<!-- veredicto:end -->

Powered by TurnKey Linux.