uix(chart): la COSTURA del texto — 49 % → 89 %, y `global` a cero

Seis claves. El valor sigue siendo el del canon; lo que faltaba era el knob.

CORRIGE MI LECTURA DE HACE DOS HORAS. Al tokenizar chart di por doctrina que
«el texto no recibe tokens del componente» porque su valor viene de la capa de
estilos con nombre, y dejé el CSS leyendo `var(--style-caption-font-size)` y
`var(--color-content-*)` A PELO. Eso es media doctrina: PLAN §2-A pide que TODO
knob de apariencia lea `--{c}-{slot}`. Sin la costura, un tema no puede aclarar
las anotaciones de un gráfico denso sin mover `caption` en toda la app, y
`getRecipeTokens()` / `/temas` no ven esa superficie.

El molde ya estaba escrito en la vertebración tipográfica
(`--accordion-trigger-font-family: var(--style-label-font-family)`), y D-TH.2
aclara que sólo los SEIS primitivos quedan fuera: «un componente que sólo LEE un
named style para un eje no es un primitivo y no está en el conjunto».

POR ROL, NO POR PARTE — y se decidió midiendo:

    --chart-caption-font-size      var(--style-caption-font-size)
    --chart-caption-fg             var(--style-caption-color)
    --chart-label-font-size        var(--style-label-font-size)
    --chart-label-fg               var(--style-label-color)
    --chart-label-font-weight      var(--style-label-font-weight)
    --chart-gauge-label-font-size  var(--size-xl-font-size)

Dos roles × tres ejes = 6 claves para 19 nodos de texto; nombrarlo por parte
habrían sido ~30 claves con dos valores. **Tamaño y tinta son ortogonales a
propósito**: `empty` y `bar-list-value` son tamaño `label` con tinta `caption`, y
el CSS lo dice. El readout del gauge no tiene rol en el canon —un `h2` es un
encabezado, no un dato— así que su costura toma el bundle de talla; con eso se
cierra la escotilla que dejé abierta en el commit anterior.

Tres nombres de mi primera propuesta no aguantaron la revisión y no entran:
`label-font-size` a secas chocaba con las seis partes `*-label` del componente
(se acepta el choque, pero nombrando por el ROL del canon, que dice la verdad:
las etiquetas de eje SON captions), `emphasis-*` inventaba vocabulario fuera de
§1/§6.7, y `fg-muted` se apoyaba en un único precedente. La tinta lee el color
DEL ROL (`var(--style-caption-color)`), no la ranura a pelo.

Y RETIRA UNA NOTA EQUIVOCADA DE §13: escribí que el censo penalizaba consumir la
capa tipográfica cuando no eres un primitivo. El censo medía bien; faltaba la
costura. Con ella, `global` cae a CERO y el alcance a 89 % — el techo, porque lo
que queda son los diez literales de identidad (nueve `100%` + el `max-content`
del tooltip). La lección queda escrita: cuando la métrica y la doctrina
discrepan, sospecha de la lectura antes que del instrumento. `listbox` (68 %,
ritmo de fila prestado de `list-surface`) es la misma pregunta un piso más
arriba y queda pendiente de revisar con este molde.

Gates: sonda antes/después **0 diffs en 161.850 valores computados** (16 rutas ×
7 estados) · centinela **43/45**, las dos de transición adjudicadas · censo
--only **89 %** con `global` 36 → 0 · --names 45/45 conformes · component-audit
PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check
0 · docs:check 0 · `check` con 72 errores globales y 0 en los ficheros tocados.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent 0b3382fe67
commit 643179de0f

@ -10,8 +10,8 @@
- **Medido**: 2026-08-22 · **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**: **57%** — 2677 de 4682 knobs pasan por un token público del componente
- **Reparto**: público 2677 · privado 338 · global 1113 · literal 554 · sistema transversal 509 _(fuera del ratio)_
- **Alcance global**: **58%** — 2727 de 4682 knobs pasan por un token público del componente
- **Reparto**: público 2727 · privado 338 · global 1064 · literal 553 · sistema transversal 509 _(fuera del ratio)_
- **Sin token público propio**: 24 · **alcance < 20 %**: 17 · **alcance 100 %**: 17 · **con eje `size`**: 58
## Cómo se lee
@ -76,20 +76,20 @@ theming §6.7 lo pone delante (`hover-bg`). Las fichas proponen la forma
| 4 | [prose](./prose.md) | 73 | 1% | 74 | 2 |
| 5 | [time-range-picker](./time-range-picker.md) | 67 | 20% | 88 | 22 |
| 6 | [time-picker](./time-picker.md) | 47 | 18% | 59 | 16 |
| 7 | [chart](./chart.md) | 46 | 49% | 95 | 39 |
| 8 | [color-picker](./color-picker.md) | 39 | 51% | 82 | 54 |
| 9 | [field-langs](./field-langs.md) | 39 | 0% | 41 | 0 |
| 10 | [date-range-picker](./date-range-picker.md) | 30 | 40% | 53 | 22 |
| 11 | [radio-cards](./radio-cards.md) | 28 | 60% | 72 | 39 |
| 12 | [drawer](./drawer.md) | 26 | 57% | 62 | 44 |
| 13 | [popover](./popover.md) | 26 | 46% | 51 | 36 |
| 14 | [natural-time-picker](./natural-time-picker.md) | 25 | 67% | 77 | 62 |
| 15 | [nav-tree](./nav-tree.md) | 23 | 4% | 25 | 1 |
| 16 | [menubar](./menubar.md) | 22 | 15% | 27 | 5 |
| 17 | [password-field](./password-field.md) | 21 | 40% | 36 | 23 |
| 18 | [aura](./aura.md) | 20 | 43% | 35 | 24 |
| 19 | [float-panel](./float-panel.md) | 20 | 59% | 53 | 28 |
| 20 | [field](./field.md) | 19 | 72% | 73 | 60 |
| 7 | [color-picker](./color-picker.md) | 39 | 51% | 82 | 54 |
| 8 | [field-langs](./field-langs.md) | 39 | 0% | 41 | 0 |
| 9 | [date-range-picker](./date-range-picker.md) | 30 | 40% | 53 | 22 |
| 10 | [radio-cards](./radio-cards.md) | 28 | 60% | 72 | 39 |
| 11 | [drawer](./drawer.md) | 26 | 57% | 62 | 44 |
| 12 | [popover](./popover.md) | 26 | 46% | 51 | 36 |
| 13 | [natural-time-picker](./natural-time-picker.md) | 25 | 67% | 77 | 62 |
| 14 | [nav-tree](./nav-tree.md) | 23 | 4% | 25 | 1 |
| 15 | [menubar](./menubar.md) | 22 | 15% | 27 | 5 |
| 16 | [password-field](./password-field.md) | 21 | 40% | 36 | 23 |
| 17 | [aura](./aura.md) | 20 | 43% | 35 | 24 |
| 18 | [float-panel](./float-panel.md) | 20 | 59% | 53 | 28 |
| 19 | [field](./field.md) | 19 | 72% | 73 | 60 |
| 20 | [navigation-menu](./navigation-menu.md) | 19 | 44% | 38 | 26 |
## Sin ningún token público propio (24)
@ -125,7 +125,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [time-range-picker](./time-range-picker.md) | 20% | 88 | 17 | 19 | 33 | 15 | 4 | 22 | y |
| [palabras](./palabras.md) | 22% | 471 | 103 | 1 | 272 | 90 | 5 | 0 | – |
| [skin-media-player](./skin-media-player.md) | 22% | 11 | 2 | 0 | 1 | 6 | 2 | 10 | y |
| [result](./result.md) | 23% | 22 | 5 | 0 | 13 | 4 | 0 | 12 | – |
| [date-range-field](./date-range-field.md) | 25% | 9 | 2 | 0 | 4 | 2 | 1 | 3 | – |
| [time-range-field](./time-range-field.md) | 25% | 9 | 2 | 0 | 4 | 2 | 1 | 3 | – |
| [surface](./surface.md) | 25% | 4 | 1 | 3 | 0 | 0 | 0 | 25 | – |
@ -147,7 +146,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [navigation-menu](./navigation-menu.md) | 44% | 38 | 15 | 1 | 13 | 5 | 4 | 26 | y |
| [skeleton](./skeleton.md) | 45% | 11 | 5 | 5 | 0 | 1 | 0 | 9 | – |
| [popover](./popover.md) | 46% | 51 | 22 | 4 | 22 | 0 | 3 | 36 | y |
| [chart](./chart.md) | 49% | 95 | 45 | 0 | 36 | 10 | 4 | 39 | – |
| [text-scramble](./text-scramble.md) | 50% | 4 | 2 | 0 | 0 | 2 | 0 | 0 | – |
| [split-button](./split-button.md) | 50% | 2 | 1 | 0 | 1 | 0 | 0 | 1 | y |
| [color-picker](./color-picker.md) | 51% | 82 | 40 | 6 | 22 | 11 | 3 | 54 | y |
@ -214,12 +212,14 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [combobox](./combobox.md) | 85% | 97 | 76 | 4 | 3 | 6 | 8 | 106 | – |
| [kbd](./kbd.md) | 86% | 14 | 12 | 0 | 1 | 1 | 0 | 13 | – |
| [table](./table.md) | 86% | 46 | 37 | 3 | 0 | 3 | 3 | 45 | – |
| [result](./result.md) | 86% | 22 | 19 | 0 | 0 | 3 | 0 | 27 | – |
| [announce](./announce.md) | 88% | 8 | 7 | 0 | 1 | 0 | 0 | 9 | – |
| [media-player](./media-player.md) | 88% | 49 | 43 | 0 | 0 | 6 | 0 | 61 | – |
| [tags-input](./tags-input.md) | 88% | 46 | 37 | 4 | 1 | 0 | 4 | 90 | y |
| [gradient-picker](./gradient-picker.md) | 88% | 27 | 23 | 0 | 2 | 1 | 1 | 25 | – |
| [drag-drop](./drag-drop.md) | 89% | 16 | 8 | 1 | 0 | 0 | 7 | 9 | – |
| [chat-typing](./chat-typing.md) | 89% | 9 | 8 | 0 | 1 | 0 | 0 | 7 | – |
| [chart](./chart.md) | 89% | 95 | 81 | 0 | 0 | 10 | 4 | 45 | – |
| [card-group](./card-group.md) | 90% | 22 | 18 | 0 | 0 | 2 | 2 | 20 | – |
| [collapsible](./collapsible.md) | 90% | 12 | 9 | 0 | 1 | 0 | 2 | 11 | – |
| [accordion](./accordion.md) | 91% | 65 | 58 | 1 | 0 | 5 | 1 | 76 | y |

@ -5,53 +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-22 · **Alcance**: **49%** — 45 de 91 knobs por token público
- **Knobs de apariencia**: 95 — público 45 · privado 0 · global 36 · literal 10 · sistema 4 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 39 pública(s) — `line-width`, `axis-width`, `grid-width`, `bar-radius`, `point-radius`, `area-opacity`, `slice-stroke-width`, `transition-duration`, `transition-ease`, `tooltip-bg`, `tooltip-border`, `tooltip-radius`, `tooltip-shadow`, `tooltip-padding`, `tooltip-gap`, `crosshair-fg`, `crosshair-width`, `axis-fg`, `grid-fg`, `separator-fg`, `smith-boundary-fg`, `track-bg`, `track-height`, `track-radius`, `gap`, `legend-row-gap`, `legend-column-gap`, `legend-item-gap`, `swatch-size`, `swatch-radius`, `tooltip-swatch-size`, `bar-list-row-gap`, `bar-list-column-gap`, `bar-segment-gap`, `bar-segment-track-gap`, `bar-segment-track-radius`, `bar-segment-fill-min-inline-size`, `bar-segment-legend-row-gap`, `bar-segment-legend-column-gap`
- **Medido**: 2026-08-22 · **Alcance**: **89%** — 81 de 91 knobs por token público
- **Knobs de apariencia**: 95 — público 81 · privado 0 · global 0 · literal 10 · sistema 4 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 45 pública(s) — `line-width`, `axis-width`, `grid-width`, `bar-radius`, `point-radius`, `area-opacity`, `slice-stroke-width`, `transition-duration`, `transition-ease`, `tooltip-bg`, `tooltip-border`, `tooltip-radius`, `tooltip-shadow`, `tooltip-padding`, `tooltip-gap`, `crosshair-fg`, `crosshair-width`, `caption-font-size`, `caption-fg`, `label-font-size`, `label-fg`, `label-font-weight`, `gauge-label-font-size`, `axis-fg`, `grid-fg`, `separator-fg`, `smith-boundary-fg`, `track-bg`, `track-height`, `track-radius`, `gap`, `legend-row-gap`, `legend-column-gap`, `legend-item-gap`, `swatch-size`, `swatch-radius`, `tooltip-swatch-size`, `bar-list-row-gap`, `bar-list-column-gap`, `bar-segment-gap`, `bar-segment-track-gap`, `bar-segment-track-radius`, `bar-segment-fill-min-inline-size`, `bar-segment-legend-row-gap`, `bar-segment-legend-column-gap`
- **Eje `size`**: no · **ficheros**: `chart.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (36)
### 1.1 Directo a primitivo global (0)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `chart.css:46` | `[data-chart-empty]` | `color` | `var(--color-content-secondary)` |
| 2 | `chart.css:47` | `[data-chart-empty]` | `font-size` | `var(--style-label-font-size)` |
| 3 | `chart.css:58` | `[data-chart-axis-label]` | `fill` | `var(--color-content-secondary)` |
| 4 | `chart.css:59` | `[data-chart-axis-label]` | `font-size` | `var(--style-caption-font-size)` |
| 5 | `chart.css:133` | `[data-chart-tooltip]` | `font-size` | `var(--style-caption-font-size)` |
| 6 | `chart.css:134` | `[data-chart-tooltip]` | `color` | `var(--color-content-primary)` |
| 7 | `chart.css:141` | `[data-chart-tooltip-x]` | `font-weight` | `var(--font-weight-medium)` |
| 8 | `chart.css:142` | `[data-chart-tooltip-x]` | `color` | `var(--color-content-primary)` |
| 9 | `chart.css:148` | `[data-chart-tooltip-row]` | `color` | `var(--color-content-secondary)` |
| 10 | `chart.css:159` | `[data-chart-tooltip-value]` | `color` | `var(--color-content-primary)` |
| 11 | `chart.css:176` | `[data-chart-title]` | `font-size` | `var(--style-label-font-size)` |
| 12 | `chart.css:177` | `[data-chart-title]` | `font-weight` | `var(--font-weight-medium)` |
| 13 | `chart.css:178` | `[data-chart-title]` | `color` | `var(--color-content-primary)` |
| 14 | `chart.css:193` | `[data-chart-legend-item]` | `font-size` | `var(--style-caption-font-size)` |
| 15 | `chart.css:194` | `[data-chart-legend-item]` | `color` | `var(--color-content-secondary)` |
| 16 | `chart.css:224` | `[data-chart-bar-list-label]` | `font-size` | `var(--style-label-font-size)` |
| 17 | `chart.css:225` | `[data-chart-bar-list-label]` | `color` | `var(--color-content-primary)` |
| 18 | `chart.css:242` | `[data-chart-bar-list-value]` | `font-size` | `var(--style-label-font-size)` |
| 19 | `chart.css:243` | `[data-chart-bar-list-value]` | `color` | `var(--color-content-secondary)` |
| 20 | `chart.css:283` | `[data-chart-bar-segment-legend-item]` | `font-size` | `var(--style-caption-font-size)` |
| 21 | `chart.css:284` | `[data-chart-bar-segment-legend-item]` | `color` | `var(--color-content-secondary)` |
| 22 | `chart.css:287` | `[data-chart-bar-segment-legend-value]` | `color` | `var(--color-content-primary)` |
| 23 | `chart.css:335` | `[data-chart-smith-label]` | `fill` | `var(--color-content-secondary)` |
| 24 | `chart.css:336` | `[data-chart-smith-label]` | `font-size` | `var(--style-caption-font-size)` |
| 25 | `chart.css:345` | `[data-chart-smith-point-label]` | `fill` | `var(--color-content-primary)` |
| 26 | `chart.css:346` | `[data-chart-smith-point-label]` | `font-size` | `var(--style-caption-font-size)` |
| 27 | `chart.css:347` | `[data-chart-smith-point-label]` | `font-weight` | `var(--font-weight-medium)` |
| 28 | `chart.css:370` | `[data-chart-funnel-label]` | `font-size` | `var(--style-label-font-size)` |
| 29 | `chart.css:371` | `[data-chart-funnel-label]` | `font-weight` | `var(--font-weight-medium)` |
| 30 | `chart.css:380` | `[data-chart-funnel-side]` | `font-size` | `var(--style-label-font-size)` |
| 31 | `chart.css:383` | `[data-chart-funnel-name]` | `fill` | `var(--color-content-primary)` |
| 32 | `chart.css:384` | `[data-chart-funnel-name]` | `font-weight` | `var(--font-weight-medium)` |
| 33 | `chart.css:387` | `[data-chart-funnel-meta]` | `fill` | `var(--color-content-secondary)` |
| 34 | `chart.css:399` | `[data-chart-gauge-label]` | `fill` | `var(--color-content-primary)` |
| 35 | `chart.css:400` | `[data-chart-gauge-label]` | `font-size` | `var(--font-size-xl)` |
| 36 | `chart.css:401` | `[data-chart-gauge-label]` | `font-weight` | `var(--font-weight-medium)` |
_Ninguno._
### 1.2 A través de un privado (0)
@ -61,16 +24,16 @@ _Ninguno._
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `chart.css:25` | `[data-chart-frame]` | `inline-size` | `100%` |
| 2 | `chart.css:29` | `[data-chart-plot]` | `inline-size` | `100%` |
| 3 | `chart.css:35` | `[data-chart]` | `inline-size` | `100%` |
| 4 | `chart.css:45` | `[data-chart-empty]` | `block-size` | `100%` |
| 5 | `chart.css:135` | `[data-chart-tooltip]` | `min-inline-size` | `max-content` |
| 6 | `chart.css:171` | `[data-chart-figure]` | `inline-size` | `100%` |
| 7 | `chart.css:215` | `[data-chart-bar-list]` | `inline-size` | `100%` |
| 8 | `chart.css:237` | `[data-chart-bar-list-bar]` | `block-size` | `100%` |
| 9 | `chart.css:254` | `[data-chart-bar-segment]` | `inline-size` | `100%` |
| 10 | `chart.css:264` | `[data-chart-bar-segment-fill]` | `block-size` | `100%` |
| 1 | `chart.css:27` | `[data-chart-frame]` | `inline-size` | `100%` |
| 2 | `chart.css:31` | `[data-chart-plot]` | `inline-size` | `100%` |
| 3 | `chart.css:37` | `[data-chart]` | `inline-size` | `100%` |
| 4 | `chart.css:47` | `[data-chart-empty]` | `block-size` | `100%` |
| 5 | `chart.css:137` | `[data-chart-tooltip]` | `min-inline-size` | `max-content` |
| 6 | `chart.css:173` | `[data-chart-figure]` | `inline-size` | `100%` |
| 7 | `chart.css:217` | `[data-chart-bar-list]` | `inline-size` | `100%` |
| 8 | `chart.css:239` | `[data-chart-bar-list-bar]` | `block-size` | `100%` |
| 9 | `chart.css:256` | `[data-chart-bar-segment]` | `inline-size` | `100%` |
| 10 | `chart.css:266` | `[data-chart-bar-segment-fill]` | `block-size` | `100%` |
## 2. Sistema transversal (4) — informativo, fuera del ratio
@ -78,10 +41,10 @@ Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `chart.css:271` | `[data-chart-bar-segment-fill][data-dim='true']` | `opacity` | `var(--opacity-scrim)` |
| 2 | `chart.css:313` | `[data-chart-heat-cell][data-dim='true']` | `opacity` | `var(--opacity-scrim)` |
| 3 | `chart.css:321` | `[data-chart-cal-cell][data-dim='true']` | `opacity` | `var(--opacity-scrim)` |
| 4 | `chart.css:361` | `[data-chart-polar-petal][data-dim='true']` | `opacity` | `var(--opacity-scrim)` |
| 1 | `chart.css:273` | `[data-chart-bar-segment-fill][data-dim='true']` | `opacity` | `var(--opacity-scrim)` |
| 2 | `chart.css:315` | `[data-chart-heat-cell][data-dim='true']` | `opacity` | `var(--opacity-scrim)` |
| 3 | `chart.css:323` | `[data-chart-cal-cell][data-dim='true']` | `opacity` | `var(--opacity-scrim)` |
| 4 | `chart.css:363` | `[data-chart-polar-petal][data-dim='true']` | `opacity` | `var(--opacity-scrim)` |
## 3. Privados de la receta — ¿de dónde sale su valor?
@ -89,7 +52,7 @@ _La receta no declara privados propios en su CSS._
## 4. Propuesta de corrección
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (37)
### 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
@ -99,43 +62,7 @@ no distingue lo que debería — se marca `⚠`.
| token (`--chart-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `empty-fg` | `root` | `var(--color-content-secondary)` | 1 |
| `empty-font-size` | `root` | `var(--style-label-font-size)` | 1 |
| `axis-label-fill` | `root` | `var(--color-content-secondary)` | 1 |
| `axis-label-font-size` | `root` | `var(--style-caption-font-size)` | 1 |
| `tooltip-font-size` | `root` | `var(--style-caption-font-size)` | 1 |
| `tooltip-fg` | `root` | `var(--color-content-primary)` | 1 |
| `tooltip-width` | `root` | `max-content` | 1 |
| `tooltip-x-font-weight` | `root` | `var(--font-weight-medium)` | 1 |
| `tooltip-x-fg` | `root` | `var(--color-content-primary)` | 1 |
| `tooltip-row-fg` | `root` | `var(--color-content-secondary)` | 1 |
| `tooltip-value-fg` | `root` | `var(--color-content-primary)` | 1 |
| `title-font-size` | `root` | `var(--style-label-font-size)` | 1 |
| `title-font-weight` | `root` | `var(--font-weight-medium)` | 1 |
| `title-fg` | `root` | `var(--color-content-primary)` | 1 |
| `legend-item-font-size` | `root` | `var(--style-caption-font-size)` | 1 |
| `legend-item-fg` | `root` | `var(--color-content-secondary)` | 1 |
| `bar-list-label-font-size` | `root` | `var(--style-label-font-size)` | 1 |
| `bar-list-label-fg` | `root` | `var(--color-content-primary)` | 1 |
| `bar-list-value-font-size` | `root` | `var(--style-label-font-size)` | 1 |
| `bar-list-value-fg` | `root` | `var(--color-content-secondary)` | 1 |
| `bar-segment-legend-item-font-size` | `root` | `var(--style-caption-font-size)` | 1 |
| `bar-segment-legend-item-fg` | `root` | `var(--color-content-secondary)` | 1 |
| `bar-segment-legend-value-fg` | `root` | `var(--color-content-primary)` | 1 |
| `smith-label-fill` | `root` | `var(--color-content-secondary)` | 1 |
| `smith-label-font-size` | `root` | `var(--style-caption-font-size)` | 1 |
| `smith-point-label-fill` | `root` | `var(--color-content-primary)` | 1 |
| `smith-point-label-font-size` | `root` | `var(--style-caption-font-size)` | 1 |
| `smith-point-label-font-weight` | `root` | `var(--font-weight-medium)` | 1 |
| `funnel-label-font-size` | `root` | `var(--style-label-font-size)` | 1 |
| `funnel-label-font-weight` | `root` | `var(--font-weight-medium)` | 1 |
| `funnel-side-font-size` | `root` | `var(--style-label-font-size)` | 1 |
| `funnel-name-fill` | `root` | `var(--color-content-primary)` | 1 |
| `funnel-name-font-weight` | `root` | `var(--font-weight-medium)` | 1 |
| `funnel-meta-fill` | `root` | `var(--color-content-secondary)` | 1 |
| `gauge-label-fill` | `root` | `var(--color-content-primary)` | 1 |
| `gauge-label-font-size` | `root` | `var(--font-size-xl)` | 1 |
| `gauge-label-font-weight` | `root` | `var(--font-weight-medium)` | 1 |
### 4.2 Sin nombre mecánico (9)
@ -153,9 +80,9 @@ no distingue lo que debería — se marca `⚠`.
<!-- veredicto:start -->
**EJECUTADO 2026-08-22 — 9 % → 49 %**, 22 claves nuevas fusionadas en el bloque
existente (17 → 39). El 49 % es el techo de este diseño, no un trabajo a medias:
lo que queda fuera está medido y cada clase tiene su razón escrita abajo.
**EJECUTADO 2026-08-22 — 9 % → 49 % → 89 %**, en dos pasadas: 22 claves de
tinta-no-texto y geometría, y luego **6 de COSTURA para el texto**. El 89 % es
el techo: lo que queda fuera son los diez literales de identidad.
**La propuesta §4 pedía 71 claves y habría escrito ~20 con el MISMO valor.** La
medición que lo decidió: las 84 filas fuera de alcance colapsaban a **26 valores
@ -165,19 +92,36 @@ distintos** — 9× `content-secondary`, 9× `content-primary`, 9× `100%`, 6×
habría dado cinco claves para el pelo que separa marcas y nueve para la tinta
de anotación. Es la inflación que F2-B ya cazó en `spinner`, con otra forma.
**El texto NO recibe tokens de chart, por doctrina.** Toma su ROL DE TAMAÑO de
los estilos con nombre —`caption` (xs) para las anotaciones y `label` (sm) para
el texto de cromo— y su TINTA y PESO de las ranuras del sistema
(`--color-content-*`, `--font-weight-*`). Acuñar `--chart-label-font-size`
sería inventar tipografía por componente, que es exactamente lo que el autor
prohibió el mismo día para la familia text-effects. Precedente de consumir la
capa: `code-block`, `kbd`, `announce`. Doce declaraciones cambian de
`var(--font-size-xs|sm)` a `var(--style-caption|label-font-size)`, mismo valor.
**Consecuencia medida en la métrica**: esas doce y las veinte de tinta/peso
siguen contando como `global`, porque el censo sólo exime `--style-*` a los
PRIMITIVOS tipográficos y chart no lo es. El censo penaliza aquí hacer lo
correcto — la misma nota que dejó `listbox` en F2-A (§13).
**El texto adopta DOS ROLES del canon, con costura** (firmado 2026-08-22). La
primera pasada dejó el CSS leyendo `var(--style-caption-font-size)` y
`var(--color-content-*)` a pelo: el VALOR era correcto pero faltaba la costura
que PLAN §2-A exige —todo knob lee `--{c}-{slot}`—, y sin ella un tema no puede
aclarar las anotaciones de un gráfico denso sin mover `caption` en toda la app,
ni `getRecipeTokens()` ve esa superficie. Seis claves:
```
--chart-caption-font-size var(--style-caption-font-size) eje · leyenda · tooltip · smith
--chart-caption-fg var(--style-caption-color)
--chart-label-font-size var(--style-label-font-size) título · bar-list · funnel · empty
--chart-label-fg var(--style-label-color)
--chart-label-font-weight var(--style-label-font-weight) los seis nodos en medium
--chart-gauge-label-font-size var(--size-xl-font-size) el readout, sin rol en el canon
```
Es el molde que la propia vertebración tipográfica documenta
(`--accordion-trigger-font-family: var(--style-label-font-family)`), y NO el
alias que §39 mató: aquello espejaba 36 claves de una capa; esto acuña **una
costura por eje**, tres ejes sobre 19 nodos de texto. **Tamaño y tinta son
ortogonales a propósito**: `empty` y `bar-list-value` son tamaño `label` con
tinta `caption`, y el CSS lo dice.
D-TH.2 no aplica aquí y su propio texto lo aclara: los seis primitivos
tipográficos quedan fuera porque la capa ES su API, y «un componente que sólo
LEE un named style para un eje no es un primitivo y no está en el conjunto».
**Con la costura, `global` cae a CERO** y el alcance a 89 %. La nota que dejé
en §13 diciendo que el censo penalizaba hacer lo correcto era mía y estaba
equivocada: el censo medía bien, faltaba la costura.
**Lo que la receta sí posee: la tinta de lo que no es texto** — cinco claves,
cada una un concepto que el CSS repite: `axis-fg` (eje + rejilla smith +
@ -197,9 +141,9 @@ significan «ocupa a tu padre» y el `min-inline-size: max-content` del tooltip,
que es la corrección de composición de la misma clase ya adjudicada en
`date-picker`. Moverlos no es tematizar, es romper el layout.
**Una escotilla sin cerrar**: el `--font-size-xl` del número del gauge. Ningún
estilo con nombre juega ese papel (un readout de dato no es un `h2`) y acuñar
`gauge-label-font-size` sería tipografía por componente. Anotado en §13.
**La escotilla del gauge, cerrada**: su número es un READOUT de dato y ningún
estilo con nombre juega ese papel (un `h2` es un encabezado), así que su
costura toma el bundle de talla en vez de un rol.
**El instrumento estaba ciego de tres maneras, y las tres se arreglaron ANTES
de medir** (ninguna era culpa de los tokens):

@ -960,25 +960,28 @@ para resolver después** (autor, 2026-08-22):
Lo que `chart` destapó (2026-08-22):
- **⚠ El censo penaliza consumir la capa tipográfica cuando no eres un
primitivo.** `chart` pasa doce declaraciones de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)` —lo que la doctrina manda: el rol de
tamaño lo poseen los estilos con nombre— y las doce siguen contando como
`global`, porque `TYPOGRAPHIC_PRIMITIVES` sólo exime a los seis primitivos
y `LAYER_VOCABULARY` no cubre `--style-*` para el resto. Es la misma nota que
dejó `listbox` en F2-A, ahora con una segunda ocurrencia y una forma clara:
**leer la capa que posee el eje debería contar como alcance en CUALQUIER
componente, no sólo en los primitivos.** Cambiarlo mueve la cifra de varios
componentes de golpe, así que es decisión, no arreglo.
- **⚠ El número del gauge no tiene estilo con nombre que lo represente.**
`[data-chart-gauge-label]` es un READOUT de dato a `--font-size-xl`; el canon
tiene `h2` en ese tamaño, pero un dato no es un encabezado, y acuñar
`--chart-gauge-label-font-size` sería tipografía por componente (prohibido,
2026-08-22). Queda leyendo el primitivo. Si aparecen más readouts —el valor
del `Gauge`, el total de un `Funnel`, el número de un `Stat`— la respuesta
es un estilo con nombre nuevo en el módulo de tipografía, que es firma del
autor.
- **~~El censo penaliza consumir la capa tipográfica~~ — RESUELTO el mismo día,
y la nota estaba equivocada.** Escribí que las doce declaraciones de `chart`
que pasaban a `var(--style-caption|label-font-size)` seguían contando
`global` porque el censo sólo exime `--style-*` a los seis primitivos. El
censo medía BIEN: lo que faltaba era la **costura**. PLAN §2-A pide que todo
knob lea `--{c}-{slot}`, y leer el canon a pelo desde el CSS deja a un tema
sin poder retocar ESE componente sin mover el rol en toda la app. Con seis
claves `--chart-{caption,label}-*` cuyo VALOR es `var(--style-{rol}-*)`,
`global` cayó a cero y el alcance a 89 %. **La lección: cuando la métrica y
la doctrina discrepan, sospecha de la lectura antes que del instrumento.**
El molde canónico ya estaba escrito en la vertebración tipográfica
(`--accordion-trigger-font-family: var(--style-label-font-family)`), y D-TH.2
aclara que sólo los PRIMITIVOS quedan fuera. `listbox` (68 %, ritmo de fila
de `list-surface`) es la misma pregunta un piso más arriba: **una capa
compartida no se lee a pelo, se cose** — pendiente de revisar con este molde.
- **⚠ Un READOUT de dato no tiene estilo con nombre en el canon.** El número del
gauge (`--font-size-xl`), el total de un funnel, el valor de un `Stat`: no
son encabezados (`h2` está en ese tamaño pero significa otra cosa) ni texto de
cuerpo. Su costura toma el bundle de talla, que es correcto pero no dice nada
del ROL. Si aparecen más, la respuesta es un estilo con nombre nuevo en el
módulo de tipografía — firma del autor, y el módulo no se toca sin ella.
**Deps**: ninguna. Son mejoras del instrumental del eje, ejecutables cuando
estorben.

@ -4,15 +4,15 @@
con su revisión adversarial pasada (2026-08-22); la cola sigue abierta y
`text-gradient` ya está dentro (2026-08-22).** Alcance global **56 %** (33 % al abrir el eje, 45 % tras F2-A, 50 % tras F2-B,
y **+6 puntos al arreglar la MEDICIÓN**, no el código: ver abajo). Sin contrato
**24**; al 100 % **17**; alcance <20 % **17**. Últimos: `chart` 9 % → 49 % y
`result` 14 % → 23 %.
**24**; al 100 % **17**; alcance <20 % **17**. Últimos: `chart` 9 % → **89 %**
y `result` 14 % → **86 %**, los dos con `global` a CERO tras la COSTURA.
## LO PRIMERO AL ENTRAR (cinco minutos, en este orden)
1. **Comprueba que el suelo no se ha movido**:
```bash
node --import tsx/esm scripts/theming-census.ts # 162 · 5.191 · 2.677 (57 %) · 24 sin contrato
node --import tsx/esm scripts/theming-census.ts # 162 · 5.191 · 2.727 (58 %) · 24 sin contrato
node --import tsx/esm scripts/theming-census.ts --names # DESVIADAS 0 — la gramática se mantiene
```
@ -135,7 +135,33 @@ declarados). Tabla en next-features §13. La más grave no es de esta familia:
`--style-*-font-variation-settings` de `text.css` significan que **el eje de
fuente variable no llega nunca al primitivo `Text`**. Ninguna se arregla aquí.
### 3.quater `chart` 9 % → 49 % (2026-08-22) — y el instrumento, tres veces ciego
### 3.quater LA COSTURA (2026-08-22) — lo más importante de la sesión
**El valor es del sistema; el knob es del componente.** Tokenizando `chart` y
`result` di por doctrina que «el texto no recibe tokens del componente» porque
su valor viene del canon, y dejé el CSS leyendo `var(--style-caption-font-size)`
y `var(--color-content-*)` A PELO. Es media doctrina: PLAN §2-A pide que TODO
knob de apariencia lea `--{c}-{slot}`. Sin la costura, un tema no puede aclarar
las anotaciones de un gráfico denso sin mover `caption` en toda la app, y
`getRecipeTokens()` / `/temas` no ven esa superficie. El molde ya estaba
escrito: `--accordion-trigger-font-family: var(--style-label-font-family)`
(§Vertebración tipográfica), y D-TH.2 aclara que **sólo los seis PRIMITIVOS**
quedan fuera. Resultado: chart 49 % → **89 %**, result 23 % → **86 %**, y
`global` a CERO en los dos.
**Dos formas, y se eligen midiendo**: por ROL cuando un rol se repite sobre
muchos nodos (chart: 2 roles × 3 ejes = 6 claves para 19 nodos de texto; por
parte habrían sido ~30 con dos valores) y por PARTE cuando cada parte tiene
tipografía propia y ningún rol la cubre (result: 15). Agrupa las filas por valor
ANTES de decidir.
**Y una nota de §13 retirada por equivocada**: escribí que el censo penalizaba
consumir la capa tipográfica. El censo medía bien; faltaba la costura. **Cuando
la métrica y la doctrina discrepan, sospecha de la lectura antes que del
instrumento.** `listbox` (68 %, ritmo de fila prestado de `list-surface`) es la
misma pregunta un piso más arriba: pendiente de revisar con este molde.
### 3.quinquies `chart` — el instrumento, tres veces ciego
Lo que hay que llevarse de aquí:

@ -455,6 +455,60 @@ de commit. Sin artefacto, el paso no se ha hecho.
## 8. Registro
- 2026-08-22 — **LA COSTURA: `chart` 49 % → 89 % y `result` 23 % → 86 %**
(6 + 15 claves). Global **57 % → 58 %**; `global` del catálogo 1.113 → 1.064.
**Corrige una lectura mía de las dos horas anteriores.** Al tokenizar `chart`
y `result` di por doctrina que «el texto no recibe tokens del componente»
porque su valor viene del canon —la capa de estilos con nombre, las ranuras de
contenido, el bundle de talla— y lo dejé leyendo el canon A PELO desde el CSS.
Eso es media doctrina: **el valor es del sistema, pero el knob tiene que ser
del componente** (PLAN §2-A: todo knob de apariencia lee `--{c}-{slot}`). Sin
la costura, un tema no puede aclarar las anotaciones de un gráfico denso sin
mover `caption` en toda la app, y `getRecipeTokens()` / `/temas` no ven esa
superficie. El molde ya estaba escrito en la vertebración tipográfica
(`--accordion-trigger-font-family: var(--style-label-font-family)`) y D-TH.2
aclara que **sólo los seis PRIMITIVOS** quedan fuera («un componente que sólo
LEE un named style para un eje no es un primitivo»).
**Dos formas, porque los componentes son distintos** — y ésa es la parte que
se firmó:
- `chart`, **por ROL**: dos roles del canon (`caption` para las anotaciones,
`label` para el cromo) × tres ejes = 6 claves para 19 nodos de texto.
Nombrar por parte habría dado ~30 claves con dos valores. Tamaño y tinta
son ORTOGONALES a propósito (`empty` y `bar-list-value` son tamaño `label`
con tinta `caption`). El readout del gauge no tiene rol en el canon y su
costura toma el bundle.
- `result`, **por PARTE**: cinco partes con tipografía distinta y ningún rol
que las cubra → 15 claves `title-*`, `description-*`, `extra-*`, `code-*`,
`media-*`. Es la forma dominante del catálogo.
**Nombres**: los tres que propuse primero no aguantaron la revisión
(`label-font-size` chocando con las seis partes `*-label`, `emphasis-*`
inventando vocabulario, `fg-muted` apoyado en un solo precedente). Se nombra
por el ROL DEL CANON que el texto adopta, y la tinta lee el color DEL ROL
(`var(--style-caption-color)`), no la ranura a pelo.
Con esto **`global` cae a CERO en los dos**: lo que queda fuera son los diez
literales de identidad de chart (nueve `100%` + `max-content`) y tres de
result.
Gates: sonda **0 diffs en 161.850 valores** (chart, 16 rutas × 7 estados) y
**990** (result) · centinela chart **43/45** y result **15/27**, y las doce
que callan están medidas SOBRE NODOS REALES con los controles de la demo
(chip `not-found` para el código HTTP, casilla del `extra`, quitar
`data-status` para la tinta por defecto del glifo) · censo 89 % / 86 % ·
`--names` 45/45 y 27/27 · audit PASS · lint 0 · suite con el rojo conocido ·
rtl 0 · docs 0 · `check` sin errores propios.
**Y retira una nota equivocada de §13**: escribí que el censo penalizaba
consumir la capa tipográfica. El censo medía bien; faltaba la costura. La
lección queda escrita ahí: cuando la métrica y la doctrina discrepan,
sospecha de la lectura antes que del instrumento. `listbox` (68 %, ritmo de
fila prestado de `list-surface`) es la misma pregunta un piso más arriba y
queda pendiente de revisar con este molde.
- 2026-08-22 — **`result` 14 % → 23 %** (4 claves, 8 → 12). Global 57 %,
<20 % 18 → 17. Aplicación directa de la doctrina que cerró `chart`: **la
receta posee su CAJA** —inset de página, ritmo entre bloques— y nada más. Su

@ -159,21 +159,32 @@ tick measures as "no overlap" while reading as contact.
## Theming
Contrato en `lib/recipes/base.ts > chart` — **39 claves** (17 + 22 el
2026-08-22). Alcance del censo: **49 %** (era 9 %). **Guard R-5.4**
(`npm run theming:sentinel -- chart <url>`): **37/39** en vivo; las dos que
callan son `transition-duration` / `-ease`, adjudicadas con su medición (el
guard congela transiciones a propósito).
Contrato en `lib/recipes/base.ts > chart` — **45 claves** (17 + 22 + 6 el
2026-08-22). Alcance del censo: **89 %** (era 9 %), con `global` a CERO.
**Guard R-5.4** (`npm run theming:sentinel -- chart <url>`): **43/45** en vivo;
las dos que callan son `transition-duration` / `-ease`, adjudicadas con su
medición (el guard congela transiciones a propósito).
**Series**: `var(--color-{role}-solid)`, inline por serie.
**El texto no tiene tokens de chart, y es doctrina**: toma su ROL DE TAMAÑO de
los estilos con nombre del canon —`caption` para las anotaciones (etiquetas de
eje, leyenda, tooltip, etiquetas smith) y `label` para el texto de cromo
(título, filas del bar-list, funnel)— y su TINTA y su PESO de las ranuras del
sistema (`--color-content-*`, `--font-weight-*`), que es para lo que existen.
La receta no acuña tipografía: mismo precedente que `code-block`, `kbd` y
`announce`. Un `Chart` dentro de un tema que retoca `caption` le sigue.
**El texto adopta DOS ROLES del canon, a través de una costura.** El valor
viene de los estilos con nombre; el knob es de chart, que es lo que permite
retocar ESTE componente sin mover el rol en toda la app:
```
--chart-caption-font-size var(--style-caption-font-size) anotaciones: eje,
--chart-caption-fg var(--style-caption-color) leyenda, tooltip, smith
--chart-label-font-size var(--style-label-font-size) cromo: título, bar-list,
--chart-label-fg var(--style-label-color) funnel, empty
--chart-label-font-weight var(--style-label-font-weight) el énfasis
--chart-gauge-label-font-size var(--size-xl-font-size) el readout del gauge
```
Mismo molde que `--accordion-trigger-font-family: var(--style-label-font-family)`
(vertebración tipográfica). **Tamaño y tinta son ortogonales**: `empty` y
`bar-list-value` llevan tamaño `label` con tinta `caption`. El número del gauge
no tiene rol en el canon —un readout de dato no es un `h2`— y por eso su costura
toma el bundle de talla.
**Lo que la receta SÍ posee es la tinta de lo que no es texto**, porque nada
más puede nombrarlo, y cada clave es UN concepto que el CSS repite:
@ -197,8 +208,7 @@ primitivo crudo `--control-height-sm` que el CSS traía: el crudo es lo que
`recipe-css-contract` prohíbe, y su `var(--space-7)` de respaldo estaba muerto.
**Fuera de contrato por identidad**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip. Y una escotilla sin cerrar: el
`--font-size-xl` del número del gauge, que no tiene estilo con nombre que juegue
ese papel (un readout de dato no es un `h2`) — anotado en next-features §13.
`min-inline-size: max-content` del tooltip. Son los diez knobs que faltan para
el 100 %, y ninguno es tema.
`chart.css` es code-split (se importa desde `chart.svelte`).

@ -6,12 +6,14 @@
* primitives — see `lib/recipes/base.ts`. Series fills/strokes are themed per
* series inline (`var(--color-{role}-solid)`).
*
* TEXT takes its SIZE ROLE from the canon's named styles — `caption` for the
* annotations (axis labels, legend, tooltip, smith labels) and `label` for the
* chrome text (title, bar-list rows, funnel) — and its INK and WEIGHT from the
* system's content / weight slots, which is what those slots are for. The
* recipe mints NO typography of its own (theming §6, D-TH.2 precedent:
* code-block / kbd / announce read the same layer).
* TEXT adopts two ROLES of the canon — `caption` for the annotations (axis
* labels, legend, tooltip, smith labels) and `label` for the chrome text
* (title, bar-list rows, funnel) — through a SEAM per axis:
* `--chart-caption-*` / `--chart-label-*` carry `var(--style-{role}-*)`. The
* recipe invents no typography, and a theme can still retune THIS component
* without moving the role across the app (PLAN §2-A: every knob reads
* `--{c}-{slot}`; the vertebration's own example is
* `--accordion-trigger-font-family: var(--style-label-font-family)`).
*
* What the recipe DOES own, because nothing else can name it: the ink of the
* lines and fills that are not text — axis, grid, the hairline that separates
@ -43,8 +45,8 @@
display: grid;
place-items: center;
block-size: 100%;
color: var(--color-content-secondary);
font-size: var(--style-label-font-size);
color: var(--chart-caption-fg);
font-size: var(--chart-label-font-size);
}
/* ── Axes ─────────────────────────────────────────────────────────────── */
@ -55,8 +57,8 @@
}
[data-chart-axis-label] {
fill: var(--color-content-secondary);
font-size: var(--style-caption-font-size);
fill: var(--chart-caption-fg);
font-size: var(--chart-caption-font-size);
}
/* ── Grid ─────────────────────────────────────────────────────────────── */
@ -130,22 +132,22 @@
border: var(--chart-axis-width) solid var(--chart-tooltip-border);
border-radius: var(--chart-tooltip-radius);
box-shadow: var(--chart-tooltip-shadow);
font-size: var(--style-caption-font-size);
color: var(--color-content-primary);
font-size: var(--chart-caption-font-size);
color: var(--chart-label-fg);
min-inline-size: max-content;
}
[data-chart-tooltip][data-flip='true'] {
transform: translate(calc(-100% - var(--space-3)), -50%);
}
[data-chart-tooltip-x] {
font-weight: var(--font-weight-medium);
color: var(--color-content-primary);
font-weight: var(--chart-label-font-weight);
color: var(--chart-label-fg);
}
[data-chart-tooltip-row] {
display: flex;
align-items: center;
gap: var(--chart-tooltip-gap);
color: var(--color-content-secondary);
color: var(--chart-caption-fg);
}
[data-chart-tooltip-swatch] {
inline-size: var(--chart-tooltip-swatch-size);
@ -156,7 +158,7 @@
[data-chart-tooltip-value] {
margin-inline-start: var(--space-4);
font-variant-numeric: tabular-nums;
color: var(--color-content-primary);
color: var(--chart-label-fg);
}
/* ── Figure chrome (presets) ──────────────────────────────────────────── */
@ -173,9 +175,9 @@
[data-chart-title] {
margin: 0;
font-size: var(--style-label-font-size);
font-weight: var(--font-weight-medium);
color: var(--color-content-primary);
font-size: var(--chart-label-font-size);
font-weight: var(--chart-label-font-weight);
color: var(--chart-label-fg);
}
[data-chart-legend] {
@ -190,8 +192,8 @@
display: inline-flex;
align-items: center;
gap: var(--chart-legend-item-gap);
font-size: var(--style-caption-font-size);
color: var(--color-content-secondary);
font-size: var(--chart-caption-font-size);
color: var(--chart-caption-fg);
}
[data-chart-swatch] {
@ -221,8 +223,8 @@
align-items: center;
}
[data-chart-bar-list-label] {
font-size: var(--style-label-font-size);
color: var(--color-content-primary);
font-size: var(--chart-label-font-size);
color: var(--chart-label-fg);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@ -239,8 +241,8 @@
transition: inline-size var(--chart-transition-duration) var(--chart-transition-ease);
}
[data-chart-bar-list-value] {
font-size: var(--style-label-font-size);
color: var(--color-content-secondary);
font-size: var(--chart-label-font-size);
color: var(--chart-caption-fg);
font-variant-numeric: tabular-nums;
text-align: end;
}
@ -280,11 +282,11 @@
display: inline-flex;
align-items: center;
gap: var(--chart-legend-item-gap);
font-size: var(--style-caption-font-size);
color: var(--color-content-secondary);
font-size: var(--chart-caption-font-size);
color: var(--chart-caption-fg);
}
[data-chart-bar-segment-legend-value] {
color: var(--color-content-primary);
color: var(--chart-label-fg);
font-variant-numeric: tabular-nums;
}
@ -332,8 +334,8 @@
stroke-width: var(--border-width-thin);
}
[data-chart-smith-label] {
fill: var(--color-content-secondary);
font-size: var(--style-caption-font-size);
fill: var(--chart-caption-fg);
font-size: var(--chart-caption-font-size);
font-variant-numeric: tabular-nums;
pointer-events: none;
}
@ -342,9 +344,9 @@
stroke-width: var(--border-width-medium);
}
[data-chart-smith-point-label] {
fill: var(--color-content-primary);
font-size: var(--style-caption-font-size);
font-weight: var(--font-weight-medium);
fill: var(--chart-label-fg);
font-size: var(--chart-caption-font-size);
font-weight: var(--chart-label-font-weight);
pointer-events: none;
}
@ -367,8 +369,8 @@
stroke-width: var(--chart-slice-stroke-width);
}
[data-chart-funnel-label] {
font-size: var(--style-label-font-size);
font-weight: var(--font-weight-medium);
font-size: var(--chart-label-font-size);
font-weight: var(--chart-label-font-weight);
pointer-events: none;
}
/* Right-side labels (always legible — theme text colour, never on a coloured fill). */
@ -377,14 +379,14 @@
stroke-width: var(--chart-grid-width);
}
[data-chart-funnel-side] {
font-size: var(--style-label-font-size);
font-size: var(--chart-label-font-size);
}
[data-chart-funnel-name] {
fill: var(--color-content-primary);
font-weight: var(--font-weight-medium);
fill: var(--chart-label-fg);
font-weight: var(--chart-label-font-weight);
}
[data-chart-funnel-meta] {
fill: var(--color-content-secondary);
fill: var(--chart-caption-fg);
font-variant-numeric: tabular-nums;
}
@ -396,8 +398,8 @@
transition: opacity var(--chart-transition-duration) var(--chart-transition-ease);
}
[data-chart-gauge-label] {
fill: var(--color-content-primary);
font-size: var(--font-size-xl);
font-weight: var(--font-weight-medium);
fill: var(--chart-label-fg);
font-size: var(--chart-gauge-label-font-size);
font-weight: var(--chart-label-font-weight);
font-variant-numeric: tabular-nums;
}

@ -2486,6 +2486,12 @@
--chart-tooltip-gap: var(--space-1-5);
--chart-crosshair-fg: var(--color-border-strong);
--chart-crosshair-width: var(--border-width-thin);
--chart-caption-font-size: var(--style-caption-font-size);
--chart-caption-fg: var(--style-caption-color);
--chart-label-font-size: var(--style-label-font-size);
--chart-label-fg: var(--style-label-color);
--chart-label-font-weight: var(--style-label-font-weight);
--chart-gauge-label-font-size: var(--size-xl-font-size);
--chart-axis-fg: var(--color-border-default);
--chart-grid-fg: var(--color-border-subtle);
--chart-separator-fg: var(--color-surface-default);

@ -3747,6 +3747,30 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'tooltip-gap': 'var(--space-1-5)',
'crosshair-fg': 'var(--color-border-strong)',
'crosshair-width': 'var(--border-width-thin)',
// ── The two TEXT ROLES, as a seam (2026-08-22, firmado) ──────────────
// Chart's text plays two roles of the canon and the value comes from
// them, but the KNOB has to be the component's own: PLAN §2-A asks every
// appearance knob to read `--{c}-{slot}`, and reading `var(--style-…)`
// straight from the CSS leaves a theme unable to retune THIS component
// without moving the role across the whole app. Same shape as the
// vertebration's own example, `--accordion-trigger-font-family:
// var(--style-label-font-family)`. Three axes, two roles, 19 text nodes:
// naming per part would be ~30 keys carrying two values.
//
// `caption` = the annotations (axis labels, legend, tooltip rows, smith
// labels); `label` = the chrome text (title, bar-list rows, funnel) and
// the emphasis ink + weight wherever a node stands out. The SIZE and the
// INK are orthogonal on purpose: `empty` and `bar-list-value` are label
// SIZE with caption INK, and the CSS says so.
'caption-font-size': 'var(--style-caption-font-size)',
'caption-fg': 'var(--style-caption-color)',
'label-font-size': 'var(--style-label-font-size)',
'label-fg': 'var(--style-label-color)',
'label-font-weight': 'var(--style-label-font-weight)',
// The gauge's big number is a data READOUT and no named style plays that
// role (an `h2` is a heading, not a datum), so this one takes the size
// BUNDLE. It is the escape hatch the previous commit left open.
'gauge-label-font-size': 'var(--size-xl-font-size)',
// ── The ink of everything that is NOT text (2026-08-22) ──────────────
// Text takes its size role from the named styles (`caption` / `label`)
// and its ink from the content slots — the recipe mints no typography.

Loading…
Cancel
Save

Powered by TurnKey Linux.