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/file-upload.md

161 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.

# file-upload — 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**: **83%** — 45 de 54 knobs por token público
- **Knobs de apariencia**: 56 — público 45 · privado 9 · global 0 · literal 0 · sistema 2 · excepción 4 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 98 pública(s) — `gap-sm`, `gap-md`, `gap-lg`, `padding-sm`, `padding-md`, `padding-lg`, `control-height-sm`, `control-height-md`, `control-height-lg`, `font-size-sm`, `font-size-md`, `font-size-lg`, `preview-size-sm`, `preview-size-md`, `preview-size-lg`, `font-family`, `line-height`, `fg`, `label-fg`, `label-font-weight`, `dropzone-gap`, `dropzone-min-height`, `dropzone-border-width`, `dropzone-border`, `dropzone-radius`, `dropzone-bg`, `dropzone-fg`, `button-gap`, `button-padding-inline`, `button-border-width`, `button-border`, `hover-button-border`, `button-radius`, `button-fg`, `hover-button-fg`, `button-solid-fg`, `list-gap`, `item-gap`, `item-padding`, `item-border-width`, `item-border`, `item-radius`, `item-bg`, `item-shadow`, `preview-radius`, `preview-bg`, `preview-fg`, `item-name-fg`, `item-name-font-weight`, `item-size-fg`, `item-size-font-size`, `progress-height`, `progress-radius`, `progress-track-bg`, `transition-duration`, `transition-ease`, `disabled-opacity`, `invalid-dropzone-border`, `primary-solid`, `primary-solid-hover`, `primary-text`, `secondary-solid`, `secondary-solid-hover`, `secondary-text`, `neutral-solid`, `neutral-solid-hover`, `neutral-text`, `affirm-solid`, `affirm-solid-hover`, `affirm-text`, `fulfill-solid`, `fulfill-solid-hover`, `fulfill-track`, `fulfill-border`, `fulfill-text`, `risk-solid`, `risk-solid-hover`, `risk-text`, `threat-solid`, `threat-solid-hover`, `threat-text`, `loss-solid`, `loss-solid-hover`, `loss-track`, `loss-border`, `loss-text`, `primary-track`, `primary-border`, `secondary-track`, `secondary-border`, `neutral-track`, `neutral-border`, `affirm-track`, `affirm-border`, `risk-track`, `risk-border`, `threat-track`, `threat-border` · 5 privada(s) forward — `_palette-solid`, `_palette-solid-hover`, `_palette-track`, `_palette-border`, `_palette-text`
- **Eje `size`**: sí · **ficheros**: `file-upload.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (0)
_Ninguno._
### 1.2 A través de un privado (9)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `file-upload.css:69` | `[data-file-upload-dropzone]:hover:not([data-disabled]), [data-file-upload-dropzone][data-dragging]` | `border-color` | `var(--_file-upload-palette-border)` |
| 2 | `file-upload.css:70` | `[data-file-upload-dropzone]:hover:not([data-disabled]), [data-file-upload-dropzone][data-dragging]` | `background` | `var(--_file-upload-palette-track)` |
| 3 | `file-upload.css:71` | `[data-file-upload-dropzone]:hover:not([data-disabled]), [data-file-upload-dropzone][data-dragging]` | `color` | `var(--_file-upload-palette-text)` |
| 4 | `file-upload.css:107` | `[data-file-upload-trigger]` | `border-color` | `var(--_file-upload-palette-solid)` |
| 5 | `file-upload.css:108` | `[data-file-upload-trigger]` | `background` | `var(--_file-upload-palette-solid)` |
| 6 | `file-upload.css:113` | `[data-file-upload-trigger]:hover:not([data-disabled])` | `border-color` | `var(--_file-upload-palette-solid-hover)` |
| 7 | `file-upload.css:114` | `[data-file-upload-trigger]:hover:not([data-disabled])` | `background` | `var(--_file-upload-palette-solid-hover)` |
| 8 | `file-upload.css:194` | `[data-file-upload-item-progress]::before` | `inline-size` | `calc(var(--_file-upload-progress-value, 0) * 1%)` |
| 9 | `file-upload.css:196` | `[data-file-upload-item-progress]::before` | `background` | `var(--_file-upload-palette-solid)` |
### 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 | `file-upload.css:10` | `[data-file-upload]` | `inline-size` | `100%` |
| 2 | `file-upload.css:161` | `[data-file-upload-item-preview] :where(img, video)` | `inline-size` | `100%` |
| 3 | `file-upload.css:162` | `[data-file-upload-item-preview] :where(img, video)` | `block-size` | `100%` |
| 4 | `file-upload.css:183` | `[data-file-upload-item-progress]` | `inline-size` | `100%` |
## 2. Sistema transversal (2) — 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 | `file-upload.css:78` | `[data-file-upload-dropzone]:focus-visible, [data-file-upload-trigger]:focus-visible, [data-file-upload-item-remove]:focus-visible, [data-file-upload-clear-trigger]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 2 | `file-upload.css:125` | `[data-file-upload-item-remove]:hover:not([data-disabled]), [data-file-upload-clear-trigger]:hover:not([data-disabled])` | `background-image` | `linear-gradient(var(--state-hover), var(--state-hover))` |
## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_file-upload-gap` | 3 | `var(--file-upload-gap-md)`, `var(--file-upload-gap-sm)`, `var(--file-upload-gap-lg)` | public | **sí** |
| `--_file-upload-padding` | 3 | `var(--file-upload-padding-md)`, `var(--file-upload-padding-sm)`, `var(--file-upload-padding-lg)` | public | **sí** |
| `--_file-upload-control-height` | 3 | `var(--file-upload-control-height-md)`, `var(--file-upload-control-height-sm)`, `var(--file-upload-control-height-lg)` | public | **sí** |
| `--_file-upload-font-size` | 3 | `var(--file-upload-font-size-md)`, `var(--file-upload-font-size-sm)`, `var(--file-upload-font-size-lg)` | public | **sí** |
| `--_file-upload-preview-size` | 3 | `var(--file-upload-preview-size-md)`, `var(--file-upload-preview-size-sm)`, `var(--file-upload-preview-size-lg)` | public | **sí** |
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_file-upload-palette-border`, `--_file-upload-palette-solid`, `--_file-upload-palette-solid-hover`, `--_file-upload-palette-text`, `--_file-upload-palette-track`, `--_file-upload-progress-value`.
## 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` (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 (`--file-upload-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
### 4.2 Sin nombre mecánico (13)
- **⚠ 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** — 4: `inline-size`, `block-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** — 9: `border-color`, `background`, `color`, `inline-size`.
### 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 — 76 % → 83 %.** Una clave acuñada, **una retirada con su
declaración muerta**, cuatro literales firmados y cuarenta y cuatro
adjudicaciones medidas (4 propias + 40 por patrón). Diff de computed **0 sobre
11.448 valores en 17 estados** y capturas 2× **idénticas byte a byte**.
**`invalid-dropzone-border`** era el único knob que iba a un primitivo global a
pelo (`--color-risk-border`). Lleva la PARTE en el nombre —y no el
`invalid-border` que el catálogo repite seis veces— porque aquí hay TRES partes
con borde (dropzone, botón, ítem) y el nombre desnudo no diría cuál tiñe. Su
estado no lo alcanza esta ruta: `isInvalid` sale de un `Field` envolvente y la
demo no monta ninguno, así que se mide forzando el atributo
(`oklch(0.8059 0.1123 59.96)` → `rgb(1,2,3)`).
⚠ **`button-bg` NO PINTABA, y se retira con su declaración.** La regla
compartida de los tres botones declaraba `background: var(--file-upload-button-bg)`
y los tres la pisan después: el trigger con el sólido de la paleta (línea 108),
`ItemRemove` y `ClearTrigger` con `transparent` (línea 119). Medido sobre los
tres nodos, uno a uno: el sentinel no mueve NADA en ninguno
(`oklch(0.5556 …)` y `rgba(0,0,0,0)` intactos). Es la clase «un token que no
mueve nada es un token que miente» de F2-A, y con UNA clave la respuesta es
retirar, no adjudicar: retirarla da **diff 0**, que es la prueba de que estaba
muerta. La clave sale del contrato con ella (un público sin consumidor es un
huérfano).
**Cuatro literales firmados** en vez de acuñados, con su anotación en la propia
declaración: el `100%` del campo (ocupa el ancho que le dan), los dos de la
miniatura (`img`/`video` LLENAN la caja en la que se recortan) y el de la barra
de progreso (ocupa la fila que le da el grid). Identidad, no knob.
⚠⚠ **CUARENTA claves de tono no pintan** — 8 tonos × 5 ranuras
(`solid`, `solid-hover`, `track`, `border`, `text`), 40 de sus 98. Es la cascada
de paleta: el forward emite AL FINAL un bloque genérico
`[data-file-upload][data-color]` que resuelve desde el `--palette-*` global,
misma especificidad (0,2,0), gana el último. Medido para los ocho tonos sobre
las dos superficies que las consumen —el trigger (`solid`) y el dropzone en
hover (`track`)—: ninguna se mueve, mientras `--palette-solid` sobre el nodo
repinta. Su `host` es `primary` y la demo pasa `color` explícito, así que el
fallback tampoco actúa. Adjudicadas por PATRÓN; incidencia de fondo en
`next-features.md` §13.
**Los nueve privados se quedan, y ninguno es deuda de valor**: ocho son el
puente de paleta THM-2 (`--_file-upload-palette-*` en el hover/arrastre del
dropzone, en el trigger y en el relleno de la barra) —un público encima dejaría
que un tema fijara el tono y matara el `color=` de cada instancia— y el noveno
es **el canal de valor**, `--_file-upload-progress-value`, que
`file-upload-item-progress.svelte` escribe INLINE por fila. Lo que el componente
escribe inline no es tema. **El techo honesto de este componente no es el
100 %.**
**Medición**: sonda estándar 0 diffs (4.608 valores · 8 estados · 19 nodos, la
demo arranca con dos ficheros sembrados, así que monta la superficie entera) más
un barrido de estados que la ruta no arranca —3 variantes × 3 tallas, `invalid`,
`disabled` y `dragging`— con el CSS de HEAD **inyectado en la página** como
control (gana por orden a igual especificidad): 0 diffs sobre 6.840 valores. El
instrumento pasó su muta-prueba: alterando el CSS de control acusa 19 diffs, y 1
si sólo se altera el borde inválido.
<!-- veredicto:end -->

Powered by TurnKey Linux.