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/field-langs.md

177 lines
13 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.

# field-langs — 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**: **0%** — 0 de 37 knobs por token público
- **Knobs de apariencia**: 39 — público 0 · privado 0 · global 31 · literal 6 · sistema 2 · excepción 2 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): **sin entrada en `base.ts`**
- **Eje `size`**: no · **ficheros**: `field-langs.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (31)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `field-langs.css:54` | `[data-field-langs][data-switcher-side='end'] [data-field-control] [data-field-langs-switcher][data-mode='segmented']` | `border-inline-start` | `var(--border-width-thin) solid var(--color-border-subtle)` |
| 2 | `field-langs.css:63` | `[data-field-langs][data-switcher-side='start'] [data-field-control] [data-field-langs-switcher][data-mode='segmented']` | `border-inline-end` | `var(--border-width-thin) solid var(--color-border-subtle)` |
| 3 | `field-langs.css:72` | `[data-field-langs-switcher][data-mode='segmented'] [data-field-langs-lang-option]` | `gap` | `var(--space-1)` |
| 4 | `field-langs.css:75` | `[data-field-langs-switcher][data-mode='segmented'] [data-field-langs-lang-option]` | `color` | `var(--color-content-secondary)` |
| 5 | `field-langs.css:77` | `[data-field-langs-switcher][data-mode='segmented'] [data-field-langs-lang-option]` | `font-size` | `var(--_fls-font)` |
| 6 | `field-langs.css:79` | `[data-field-langs-switcher][data-mode='segmented'] [data-field-langs-lang-option]` | `padding-inline` | `var(--space-2)` |
| 7 | `field-langs.css:87` | `[data-field-langs-switcher][data-mode='segmented'] [data-field-langs-lang-option] + [data-field-langs-lang-option]` | `border-inline-start` | `var(--border-width-thin) solid var(--color-border-subtle)` |
| 8 | `field-langs.css:92` | `[data-field-langs-switcher][data-mode='segmented'] [data-field-langs-lang-option][data-active]` | `background` | `var(--color-primary-solid)` |
| 9 | `field-langs.css:93` | `[data-field-langs-switcher][data-mode='segmented'] [data-field-langs-lang-option][data-active]` | `color` | `var(--color-primary-contrast)` |
| 10 | `field-langs.css:98` | `[data-field-langs-switcher][data-mode='segmented'] [data-field-langs-lang-option]:disabled` | `color` | `var(--color-content-disabled)` |
| 11 | `field-langs.css:115` | `[data-field-langs-switcher][data-mode='select'] [data-popover-trigger]` | `gap` | `var(--space-1)` |
| 12 | `field-langs.css:118` | `[data-field-langs-switcher][data-mode='select'] [data-popover-trigger]` | `color` | `var(--color-content-secondary)` |
| 13 | `field-langs.css:120` | `[data-field-langs-switcher][data-mode='select'] [data-popover-trigger]` | `font-size` | `var(--_fls-font)` |
| 14 | `field-langs.css:122` | `[data-field-langs-switcher][data-mode='select'] [data-popover-trigger]` | `padding-inline` | `var(--space-2)` |
| 15 | `field-langs.css:131` | `[data-field-langs-menu]` | `gap` | `var(--space-1)` |
| 16 | `field-langs.css:138` | `[data-field-langs-menu] [data-field-langs-lang-option]` | `gap` | `var(--space-2)` |
| 17 | `field-langs.css:141` | `[data-field-langs-menu] [data-field-langs-lang-option]` | `border-radius` | `var(--radius-sm)` |
| 18 | `field-langs.css:143` | `[data-field-langs-menu] [data-field-langs-lang-option]` | `color` | `var(--color-content-primary)` |
| 19 | `field-langs.css:145` | `[data-field-langs-menu] [data-field-langs-lang-option]` | `font-size` | `var(--font-size-sm)` |
| 20 | `field-langs.css:147` | `[data-field-langs-menu] [data-field-langs-lang-option]` | `padding` | `var(--space-1) var(--space-2)` |
| 21 | `field-langs.css:153` | `[data-field-langs-menu] [data-field-langs-lang-option][data-active]` | `background` | `var(--color-primary-surface)` |
| 22 | `field-langs.css:154` | `[data-field-langs-menu] [data-field-langs-lang-option][data-active]` | `color` | `var(--color-primary-text)` |
| 23 | `field-langs.css:171` | `[data-field-langs-lang-option]::after` | `border-radius` | `var(--radius-full)` |
| 24 | `field-langs.css:172` | `[data-field-langs-lang-option]::after` | `background` | `var(--color-content-muted)` |
| 25 | `field-langs.css:181` | `[data-field-langs-lang-option][data-required]:not([data-complete])::after` | `background` | `var(--color-risk-solid)` |
| 26 | `field-langs.css:185` | `[data-field-langs-lang-option][data-complete]::after` | `background` | `var(--color-affirm-solid)` |
| 27 | `field-langs.css:201` | `[data-field-langs-textarea] [data-textarea-input]` | `padding-block-start` | `var(--_fls-bar)` |
| 28 | `field-langs.css:210` | `[data-field-langs-textarea] [data-field-langs-switcher]` | `background` | `var(--color-surface-raised)` |
| 29 | `field-langs.css:211` | `[data-field-langs-textarea] [data-field-langs-switcher]` | `border` | `var(--border-width-thin) solid var(--color-border-subtle)` |
| 30 | `field-langs.css:212` | `[data-field-langs-textarea] [data-field-langs-switcher]` | `border-radius` | `var(--radius-sm)` |
| 31 | `field-langs.css:226` | `[data-field-langs-textarea] [data-field-langs-switcher][data-mode='segmented'] [data-field-langs-lang-option]` | `padding-block` | `var(--space-1)` |
### 1.2 A través de un privado (0)
_Ninguno._
### 1.3 Literales (6)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `field-langs.css:12` | `[data-field-langs]` | `inline-size` | `100%` |
| 2 | `field-langs.css:78` | `[data-field-langs-switcher][data-mode='segmented'] [data-field-langs-lang-option]` | `line-height` | `1` |
| 3 | `field-langs.css:121` | `[data-field-langs-switcher][data-mode='select'] [data-popover-trigger]` | `line-height` | `1` |
| 4 | `field-langs.css:132` | `[data-field-langs-menu]` | `min-inline-size` | `9rem` |
| 5 | `field-langs.css:139` | `[data-field-langs-menu] [data-field-langs-lang-option]` | `inline-size` | `100%` |
| 6 | `field-langs.css:146` | `[data-field-langs-menu] [data-field-langs-lang-option]` | `line-height` | `1.2` |
### 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 | `field-langs.css:169` | `[data-field-langs-lang-option]::after` | `inline-size` | `0.5em` |
| 2 | `field-langs.css:170` | `[data-field-langs-lang-option]::after` | `block-size` | `0.5em` |
## 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 | `field-langs.css:102` | `[data-field-langs-switcher][data-mode='segmented'] [data-field-langs-lang-option]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 2 | `field-langs.css:158` | `[data-field-langs-menu] [data-field-langs-lang-option]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
## 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` (20)
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 (`--field-langs-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `switcher-fg` | `root` | ⚠ `var(--color-content-secondary)` / `var(--color-primary-contrast)` | 3 |
| `lang-option-bg` | `root` | ⚠ `var(--color-content-muted)` / `var(--color-risk-solid)` / `var(--color-affirm-solid)` | 3 |
| `switcher-gap` | `root` | `var(--space-1)` | 2 |
| `switcher-font-size` | `root` | `var(--_fls-font)` | 2 |
| `switcher-padding-inline` | `root` | `var(--space-2)` | 2 |
| `menu-gap` | `root` | ⚠ `var(--space-1)` / `var(--space-2)` | 2 |
| `menu-fg` | `root` | ⚠ `var(--color-content-primary)` / `var(--color-primary-text)` | 2 |
| `textarea-padding-block` | `root` | ⚠ `var(--_fls-bar)` / `var(--space-1)` | 2 |
| `switcher-bg` | `root` | `var(--color-primary-solid)` | 1 |
| `disabled-switcher-fg` | `root` | `var(--color-content-disabled)` | 1 |
| `menu-width` | `root` | `9rem` | 1 |
| `menu-radius` | `root` | `var(--radius-sm)` | 1 |
| `menu-font-size` | `root` | `var(--font-size-sm)` | 1 |
| `menu-line-height` | `root` | `1.2` | 1 |
| `menu-bg` | `root` | `var(--color-primary-surface)` | 1 |
| `lang-option-width` | `root` | `0.5em` | 1 |
| `lang-option-height` | `root` | `0.5em` | 1 |
| `lang-option-radius` | `root` | `var(--radius-full)` | 1 |
| `textarea-bg` | `root` | `var(--color-surface-raised)` | 1 |
| `textarea-radius` | `root` | `var(--radius-sm)` | 1 |
### 4.2 Sin nombre mecánico (9)
- **⚠ 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`.
- **⚠ decisión: `border-inline-start` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 2: `border-inline-start`.
- **⚠ decisión: `border-inline-end` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `border-inline-end`.
- **⚠ 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** — 2: `line-height`.
- **⚠ decisión: `padding` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `padding`.
- **⚠ decisión: `border` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `border`.
### 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 -->
**Revisión 2026-08-20 — verificación previa a implementación (Opus).**
**Análisis: CORRECTO en cifras** (41 · 0 · 0 · 31 · 8 · 2), y el «privado 0»
vuelve a ser falso de nombre: la receta declara `--_fls-*` — prefijo ABREVIADO
(regla 5) — y además su fórmula lee un privado DE FIELD:
`--_fls-font: calc(var(--_field-control-font-size) − (md − sm))`
(`field-langs.css:25-28`) — la fórmula canónica del label un paso por debajo,
que sólo resuelve ANIDADO en un Field. **Pasos previos: renombrar `--_fls-*` →
`--_field-langs-*`**, y el consumo del privado ajeno queda ANOTADO como parte
del mandato field-composition (`theming-audit.md` §5.3-3) — no se «arregla»
aquí ni se copia el valor congelado.
**Propuesta: APTA EN SU MAYOR PARTE, con tres correcciones:**
1. `switcher-font-size = var(--_fls-font)` y `textarea-padding-block =
var(--_fls-bar)` **no se copian con ese valor**: tras el renombrado, o el
privado deriva de un público propio (`switcher-font-size` como token cuya
fórmula vive en el VALOR) o queda en el lote field-composition. El ⚠ de
`textarea-padding-block` son dos knobs (el hueco de la barra vs el padding
normal) — separar.
2. Los ⚠ de `menu-gap` / `menu-fg` son estados/partes sin parsear (item normal
vs seleccionado): separar por estado (`selected-menu-item-fg`…) leyendo el
selector real.
3. `menu-line-height: 1.2` y `menu-width: 9rem` — literales: token con valor
verbatim (y el line-height, mirar si `--leading-ui` computa igual; si no,
verbatim y desviación visible).
Sin eje `size` (nada estampa `data-size`) — scope `root` para todo.
**Bloqueos de firma**: el lote field-composition (§5.3-3) para los dos knobs
que leen el privado de Field.
<!-- veredicto:end -->

Powered by TurnKey Linux.