uix(label): temable — 0 % → 100 % con dos claves

2 claves fusionadas en el bloque `label` de `recipes/base.ts` (donde ya vivía
el forward de paleta). Censo 0 % → **100 %**.

**Seis de sus ocho knobs son SISTEMA y no se acuñan.** `label` es uno de los
seis primitivos tipográficos: sus seis ejes consumen `--style-label-*` a través
de la escotilla por instancia (`var(--_label-{eje}, var(--style-label-{eje}))`),
la forma que D-TH.2-b fijó y la que hace que un `<Label>` y un
`<Text style="label">` se lean idénticos. La capa los posee; re-declararlos
sería el vocabulario paralelo que la regla 2 prohíbe.

Quedan DOS knobs propios, los que el primitivo añade por su cuenta: el `gap` al
icono o indicador que puede acompañar al texto, y `disabled-fg`, la tinta
cuando el consumidor marca la etiqueta como deshabilitada junto a su control.
`--_label-color` sigue privado: conmutador de dos fuentes (eje de tinta en
línea / forward de paleta cuando llega `data-color`), el mismo patrón ya
resuelto igual en `code`, `textarea`, `spinner` y `skeleton`.

Artefactos y gates:
- Sonda antes/después = 0 diffs en 174 valores × 7 estados.
- R-5.4: 1/2. `disabled-fg` adjudicado: la demo no monta ninguna etiqueta
  deshabilitada (0 nodos, contados); forzada, alcanza (rgb(1,2,3)).
- censo --only 100 % · component:audit PASS · suite eidos sin rojos nuevos ·
  rtl:check 0 · docs:check 0 · check por fichero limpio.
- README con «Talla y tema» y tab `Tokens` en la demo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent a6d4d4f5a3
commit 7565eff47b

@ -10,9 +10,9 @@
- **Medido**: 2026-08-21 · **162 recetas** con CSS + **8 componentes sin receta** = 170 fichas, el árbol entero de `eidos/components/`
- **La pregunta**: ¿cuánto de la apariencia de cada componente puede cambiar un tema **sin tocar el sistema ni la receta**?
- **Alcance global**: **49%** — 2309 de 4676 knobs pasan por un token público del componente
- **Reparto**: público 2309 · privado 627 · global 1173 · literal 567 · sistema transversal 509 _(fuera del ratio)_
- **Sin token público propio**: 35 · **alcance < 20 %**: 28 · **alcance 100 %**: 9 · **con eje `size`**: 59
- **Alcance global**: **49%** — 2311 de 4676 knobs pasan por un token público del componente
- **Reparto**: público 2311 · privado 627 · global 1171 · literal 567 · sistema transversal 509 _(fuera del ratio)_
- **Sin token público propio**: 34 · **alcance < 20 %**: 27 · **alcance 100 %**: 10 · **con eje `size`**: 59
## Cómo se lee
@ -91,12 +91,12 @@ theming §6.7 lo pone delante (`hover-bg`). Las fichas proponen la forma
| 19 | [timeline](./timeline.md) | 24 | 51% | 49 | 45 |
| 20 | [avatar](./avatar.md) | 23 | 36% | 36 | 0 |
## Sin ningún token público propio (35)
## Sin ningún token público propio (34)
Ni una clave pública en `lib/recipes/base.ts` — alguno tiene privados forward
(`_palette-*`), que no son contrato: un tema no puede nombrarlos.
[`alert-dialog`](./alert-dialog.md) · [`announce`](./announce.md) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`field-langs`](./field-langs.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`knob`](./knob.md) · [`label`](./label.md) · [`mark`](./mark.md) · [`mask-field`](./mask-field.md) · [`menu-dial`](./menu-dial.md) · [`mockup`](./mockup.md) · [`month-grid`](./month-grid.md) · [`motion`](./motion.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skin-media-player`](./skin-media-player.md) · [`stack`](./stack.md) · [`text`](./text.md) · [`text-blur`](./text-blur.md) · [`text-circular`](./text-circular.md) · [`text-focus`](./text-focus.md) · [`text-gradient`](./text-gradient.md) · [`text-scramble`](./text-scramble.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md)
[`alert-dialog`](./alert-dialog.md) · [`announce`](./announce.md) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`field-langs`](./field-langs.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`knob`](./knob.md) · [`mark`](./mark.md) · [`mask-field`](./mask-field.md) · [`menu-dial`](./menu-dial.md) · [`mockup`](./mockup.md) · [`month-grid`](./month-grid.md) · [`motion`](./motion.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skin-media-player`](./skin-media-player.md) · [`stack`](./stack.md) · [`text`](./text.md) · [`text-blur`](./text-blur.md) · [`text-circular`](./text-circular.md) · [`text-focus`](./text-focus.md) · [`text-gradient`](./text-gradient.md) · [`text-scramble`](./text-scramble.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md)
## Tabla completa (162 recetas, por alcance ascendente)
@ -112,7 +112,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [scroll-frames](./scroll-frames.md) | 0% | 9 | 0 | 1 | 4 | 4 | 0 | 0 | – |
| [announce](./announce.md) | 0% | 8 | 0 | 0 | 8 | 0 | 0 | 0 | – |
| [clipboard](./clipboard.md) | 0% | 8 | 0 | 2 | 5 | 1 | 0 | 0 | – |
| [label](./label.md) | 0% | 8 | 0 | 0 | 2 | 0 | 6 | 0 | – |
| [menu-dial](./menu-dial.md) | 0% | 8 | 0 | 3 | 1 | 4 | 0 | 0 | y |
| [mark](./mark.md) | 0% | 5 | 0 | 2 | 1 | 2 | 0 | 0 | – |
| [aspect-ratio](./aspect-ratio.md) | 0% | 3 | 0 | 0 | 1 | 2 | 0 | 0 | – |
@ -250,6 +249,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [link-preview](./link-preview.md) | 100% | 15 | 12 | 0 | 0 | 0 | 3 | 33 | – |
| [code](./code.md) | 100% | 13 | 7 | 0 | 0 | 0 | 6 | 6 | – |
| [box](./box.md) | 100% | 9 | 9 | 0 | 0 | 0 | 0 | 43 | – |
| [label](./label.md) | 100% | 8 | 2 | 0 | 0 | 0 | 6 | 2 | – |
| [display](./display.md) | — | 6 | 0 | 0 | 0 | 0 | 6 | 0 | – |
| [barcode](./barcode.md) | 100% | 5 | 5 | 0 | 0 | 0 | 0 | 7 | – |
| [flex](./flex.md) | 100% | 2 | 2 | 0 | 0 | 0 | 0 | 7 | – |

@ -5,19 +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-20 · **Alcance**: **0%** — 0 de 2 knobs por token público
- **Knobs de apariencia**: 8 — público 0 · privado 0 · global 2 · literal 0 · sistema 6 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 0 pública(s) · 1 privada(s) forward — `_palette-text`
- **Medido**: 2026-08-21 · **Alcance**: **100%** — 2 de 2 knobs por token público
- **Knobs de apariencia**: 8 — público 2 · privado 0 · global 0 · literal 0 · sistema 6 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 2 pública(s) — `gap`, `disabled-fg` · 1 privada(s) forward — `_palette-text`
- **Eje `size`**: no · **ficheros**: `label.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (2)
### 1.1 Directo a primitivo global (0)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `label.css:14` | `[data-label]` | `gap` | `var(--space-1)` |
| 2 | `label.css:34` | `[data-label][data-disabled]` | `color` | `var(--color-content-disabled)` |
_Ninguno._
### 1.2 A través de un privado (0)
@ -50,18 +47,7 @@ Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inl
## 4. Propuesta de corrección
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (2)
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 (`--label-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `gap` | `root` | `var(--space-1)` | 1 |
| `disabled-fg` | `root` | `var(--color-content-disabled)` | 1 |
_Nada que proponer: no hay knobs fuera de alcance._
### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)
@ -75,6 +61,23 @@ no distingue lo que debería — se marca `⚠`.
<!-- veredicto:start -->
_(pendiente — lo escribe el autor; se conserva al regenerar)_
**Medido 2026-08-22.** `label` es uno de los seis PRIMITIVOS TIPOGRÁFICOS, y el
censo ya lo trata como tal: **seis de sus ocho knobs son sistema** porque sus
seis ejes consumen `--style-label-*` con la escotilla por instancia
(`var(--_label-{eje}, var(--style-label-{eje}))`) — la forma que D-TH.2-b fijó,
y la misma que hace que un `<Label>` y un `<Text style="label">` se lean
idénticos. **Ninguno de esos seis se acuña**: la capa los posee.
Quedan exactamente **dos** knobs propios, y son los dos que el primitivo añade
por su cuenta:
1. `gap` — el hueco al icono o indicador que puede acompañar al texto
(`inline-flex` + gap, igual que `Field.Label`).
2. `disabled-fg` — la tinta cuando el consumidor marca la etiqueta como
deshabilitada junto a su control.
`--_label-color` se queda privado: es el conmutador de dos fuentes (el eje de
tinta en línea y el forward de paleta cuando llega `data-color`), el mismo
patrón que en `code`, `textarea`, `spinner` y `skeleton`.
<!-- veredicto:end -->

@ -144,6 +144,10 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'underline-offset':
"same: no underline at rest under data-underline='hover'; forced → reaches (2 → 11px). textUnderlineOffset added to the guard prop list too"
},
label: {
'disabled-fg':
'the demo mounts no disabled label (0 [data-disabled] nodes, counted); forced → reaches (rgb(1,2,3))'
},
'link-preview': {
'trigger-fg':
'the panel opens on HOVER, so measuring it REQUIRES the pointer on the trigger — and hover is exactly the state whose rule overrides the resting ink. Structural, not a demo gap: the hover token (`hover-trigger-fg`) reads live in the same run',

@ -69,6 +69,28 @@ control outside a `Field`, `<Field.Label>` inside one (it auto-wires
standalone `Label` uses the foundation label weight (`medium`); `Field.Label`
overrides to `regular` for form-field density. Pass `weight="regular"` to match.
## Talla y tema
**Guard R-5.4** (`npm run theming:sentinel -- label <url>`): 1/2 en vivo; el
otro está adjudicado con su medición en
`scripts/theming-sentinel-exceptions.ts`. Corrido 2026-08-22.
Contrato en `lib/recipes/base.ts > label` (2 claves, fusionadas con el forward
de paleta). Alcance del censo: **100 %** (era 0 %).
**Seis de sus ocho knobs son SISTEMA y no se acuñan.** `label` es uno de los
seis primitivos tipográficos: sus seis ejes consumen `--style-label-*` a través
de la escotilla por instancia (`var(--_label-{eje}, var(--style-label-{eje}))`),
que es la forma que D-TH.2-b fijó y la que hace que un `<Label>` y un
`<Text style="label">` se lean idénticos. La capa los posee.
Quedan **dos** knobs propios, los que el primitivo añade por su cuenta: el
`gap` al icono o indicador que puede acompañar al texto, y `disabled-fg`, la
tinta cuando el consumidor marca la etiqueta como deshabilitada junto a su
control. `--_label-color` sigue privado: es el conmutador de dos fuentes (eje
de tinta en línea / forward de paleta cuando llega `data-color`), el mismo
patrón que en `code`, `textarea`, `spinner` y `skeleton`.
## Gaps
| Capability | Disposition | Notes |

@ -11,7 +11,7 @@
[data-label] {
display: inline-flex;
align-items: center;
gap: var(--space-1);
gap: var(--label-gap);
font-family: var(--_label-font-family, var(--style-label-font-family));
font-size: var(--_label-font-size, var(--style-label-font-size));
font-weight: var(--_label-font-weight, var(--style-label-font-weight));
@ -31,6 +31,6 @@
/* Consumer marks the label disabled (data-disabled) when its control is. */
[data-label][data-disabled] {
color: var(--color-content-disabled);
color: var(--label-disabled-fg);
cursor: not-allowed;
}

@ -2127,6 +2127,8 @@
--code-border: var(--color-neutral-border);
--code-padding-inline: 0.32em;
--code-padding-block: 0.1em;
--label-gap: var(--space-1);
--label-disabled-fg: var(--color-content-disabled);
--proof-of-human-min-height-sm: calc(var(--size-sm-control-height) * 3);
--proof-of-human-min-height-md: calc(var(--size-md-control-height) * 3);
--proof-of-human-min-height-lg: calc(var(--size-lg-control-height) * 3);

@ -3099,6 +3099,11 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'_palette-text': { value: 'var(--color-primary-text)', scope: 'host' }
},
label: {
// The six TYPE axes are absent on purpose: `label` is a typographic
// primitive and they consume `--style-label-*` through the per-instance
// hatch (D-TH.2-b). These two are what the primitive adds on its own.
gap: 'var(--space-1)',
'disabled-fg': 'var(--color-content-disabled)',
'_palette-text': { value: 'var(--color-primary-text)', scope: 'host' }
},
// ProofOfHuman — `color` tints the challenge accent (path / needle / hand +

@ -7,8 +7,9 @@
} from '$uix/eidos/components/label';
import { compileMorfo } from '$uix/morfo';
import { labelMorfo } from '@/uix/morfo/components/label';
import TokensPanel from '../../lib/TokensPanel.svelte';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens' | 'a11y';
let tab = $state<Tab>('live');
let stageRef = $state<HTMLElement | null>(null);
@ -107,6 +108,9 @@
<span data-uix-tab-count>0</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>2</span>
</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div>
@ -307,6 +311,10 @@
</section>
{/if}
{#if tab === 'tokens'}
<TokensPanel component="label" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'a11y'}
<section data-uix-section>
<h2 data-uix-section-title>Accessibility</h2>

Loading…
Cancel
Save

Powered by TurnKey Linux.