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/image-picker.md

137 lines
7.6 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.

# image-picker — 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-24 · **Alcance**: **100%** — 13 de 13 knobs por token público
- **Knobs de apariencia**: 16 — público 13 · privado 0 · global 0 · literal 0 · sistema 3 · excepción 4 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 14 pública(s) — `gap`, `preview-radius`, `preview-bg`, `preview-border`, `preview-aspect`, `toolbar-gap`, `toolbar-inset`, `button-size`, `button-bg`, `button-fg`, `button-border`, `button-radius`, `button-font-size`, `hover-button-bg`
- **Eje `size`**: no · **ficheros**: `image-picker.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (0)
_Ninguno._
### 1.2 A través de un privado (0)
_Ninguno._
### 1.3 Literales (0)
_Ninguno._
### 1.4 Excepciones firmadas (4) — 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 | `image-picker.css:45` | `[data-image-picker-canvas] > *` | `inline-size` | `100%` |
| 2 | `image-picker.css:46` | `[data-image-picker-canvas] > *` | `block-size` | `100%` |
| 3 | `image-picker.css:79` | `[data-image-picker-rotate], [data-image-picker-remove]` | `line-height` | `1` |
| 4 | `image-picker.css:109` | `[data-image-picker-rotate]:focus-visible, [data-image-picker-remove]:focus-visible` | `outline-color` | `Highlight` |
## 2. Sistema transversal (3) — 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 | `image-picker.css:19` | `[data-image-picker][data-disabled]` | `opacity` | `var(--opacity-disabled)` |
| 2 | `image-picker.css:90` | `[data-image-picker-rotate]:focus-visible, [data-image-picker-remove]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 3 | `image-picker.css:96` | `[data-image-picker-rotate][disabled], [data-image-picker-remove][disabled]` | `opacity` | `var(--opacity-disabled)` |
## 3. Privados de la receta — ¿de dónde sale su valor?
_La receta no declara privados propios en su CSS._
## 4. Propuesta de corrección
### 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
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 (`--image-picker-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
### 4.2 Sin nombre mecánico (4)
- **⚠ 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** — 2: `inline-size`, `block-size`.
- **⚠ decisión: `1` 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: `line-height`.
- **foco: lo posee el sistema (`--focus-ring-*`, theming §32) — no acuñar token propio** — 1: `outline-color`.
### 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 — 71 % → 100 %, una clave nueva y cuatro literales
firmados.** Diff de computed 0 sobre 1.344 valores en 7 estados (6 nodos);
R-5.4 de 13/13 a **14/14 sin una sola adjudicación**; capturas 2× antes/después
idénticas.
**El instrumento medía UN nodo.** El picker arranca VACÍO —un dropzone de
FileUpload— y todo lo que la receta pinta (preview, canvas, toolbar y los dos
botones de icono) sólo existe en el estado `ready`, al que no llega ningún
trigger: hay que cargar un fichero. Con el chip «Load sample image» de la demo
la sonda pasa de 1 a **6 nodos**. Sonda (`DEMO_VARIANTS`) y centinela
(`prepareWith`) llevan ya el interruptor; el centinela además necesita
`openMarker` para que su re-apertura por token no vuelva a pulsar el chip y
remonte la vista previa a mitad de corrida.
**La única clave: `hover-button-bg`.** Era el único knob que iba a un primitivo
global a pelo (`--color-surface-default`). La §4 lo nombraba `hover-rotate-bg`
por el PRIMER selector de la regla, y miente dos veces: pinta rotate **y**
remove, y el contrato de este componente ya llama `button-*` a ese par desde que
nació. El catálogo decide — `hover-{parte}-{slot}` es la forma de las 60 claves
`hover-*` de `base.ts`.
**No es el velo del sistema, y por eso se acuña.** Los dos botones flotan SOBRE
la foto: su relleno de reposo es translúcido (`color-mix(… 88%, transparent)`) y
el hover lo vuelve OPACO — un cambio de RELLENO, no una capa encima. Medido
desde el píxel hacia arriba con las transiciones congeladas: el cambio cae en el
`<button>` (el nodo con forma, radio 6 px, `srgb .988/.88` → `oklch(.9911 0 0)`)
y **ningún ancestro recibe `background-image`**, porque el arquetipo `action` no
trae velo — `archetypes.css` sólo vela `trigger`, `item` y `option`. No se está
bendiciendo un duplicado, que es lo que paró el `hover-*` de `listbox`.
**Los cuatro literales se firman, no se acuñan** (válvula de recipe-contract §3):
el `100%` doble del hijo del canvas es identidad (la `<Image>` ES la caja del
canvas, que ya está en `inset: 0`), el `line-height: 1` es un botón de un solo
glifo, y el `Highlight` de `forced-colors` es la paleta del sistema operativo —
un valor de tema ahí lo sustituiría el UA, que es justo lo que ese modo hace.
**Fuera del ratio por doctrina**: `--opacity-disabled` (×2) y el par
`--focus-ring-*` son de la capa de sistema (theming §32).
**Sin eje `size` y es correcto**: la prop `size` del wrapper viaja al
`ImageAdjustments` embebido; este cromo de orquestación no escala.
⚠ **Un defecto real, ANOTADO, no arreglado**: ese hover neutro es una invención
por componente, y §38 / R-4.3 mandan que el hover neutro sea la capa
`--state-*`. Migrarlo cambia el default (velo translúcido en vez de relleno
opaco) y D-TH.5 lo prohíbe dentro de este eje. Queda escrito aquí y en el README
del componente. Nota para quien lo firme: el arquetipo del par es `action`, que
HOY no recibe velo del sistema, así que la migración no es sólo cambiar la
receta — o `action` gana velo en `archetypes.css` (mueve píxel en todos los
`action` del catálogo) o el par cambia de arquetipo, que es morfo.
<!-- veredicto:end -->

Powered by TurnKey Linux.