uix(tree-view): temable — 0 % → 92 %, y el velo que tiñe la carpeta entera

46 claves en el bloque `tree-view` de `recipes/base.ts` (fusionadas con el
forward de paleta). Censo 0 % → 92 %, cero globales. Global 46 %, sin contrato
47 → 46. Molde del hermano `tree-grid` allí donde el problema es el mismo.

LA PROPUESTA GENERADA FALLABA EN EL NOMBRE DE CASI TODO LO QUE IMPORTA: el
clasificador toma el PRIMER selector de cada regla como si fuera la parte
pintada, y aquí ese primero es el root o el branch-control cuando el nodo real
es otro.

1. `root-width: 1px` / `root-bg` NO son del root: son la GUÍA de indentación
   (el `::before` del branch, una línea vertical). → `guide-width` + `guide-fg`,
   literalmente los nombres de tree-grid. Un `root-bg` que tiñe una línea es la
   clase de nombre que miente.
2. `branch-control-padding-inline-{k}` ⚠ fundía DOS ejes que se SUMAN en el
   mismo calc: el ritmo de la fila y el PASO DE PROFUNDIDAD
   (`--tree-depth × indent + padding`). Fundidos, tematizar la sangría habría
   movido el padding. → `row-padding-inline-{k}` + `indent-{k}`.
3. El prefijo `branch-control-` es erróneo: la regla reza
   `[data-tree-view-branch-control], [data-tree-view-item]` — las DOS filas. El
   CSS ya llamaba `row` a sus privados. → `row-*`, como tree-grid y como la
   lección de table. Y fuera el prefijo `root-` del chasis: el wrapper ES el
   componente.
4. `row-padding-block` ⚠ no eran dos valores en pugna sino cinco por talla: xs
   colapsa a `0`, las otras cuatro `--space-1`.
5. `branch-indicator-width` + `-height` son UN knob (`1em`) →
   `branch-indicator-size`.

Bundle: la altura de fila casa 1:1; padding e indent llevan ritmo propio; la
tipografía un paso por debajo desde lg — verbatim, como table y tree-grid. Los
resueltos con `parts: ['root']` (el `data-size` va en `[data-tree-view-root]`,
wrapper de eidos, no parte del morfo). Los `transparent` de outline/ghost y el
`padding: 0` de ghost son identidad de variante y siguen literales.

`hover-row-bg` SÍ se acuña, al revés que en grid-list, y por medición: la misma
regla pinta dos nodos con arquetipo distinto, y `branch-control` NO lleva
archetype — ahí el plano de la receta es la única pintura (velo medido ausente)
y el token es toda la superficie de tema del hover. Tener un consumidor
legítimo es lo que lo distingue del caso grid-list.

EL DEFECTO QUE LA MEDICIÓN DESTAPÓ (preexistente, NO se toca aquí →
next-features §12.8): al pasar el ratón por la fila de una carpeta abierta se
tiñe LA CARPETA ENTERA, hijos incluidos. `archetype: 'item'` está declarado en
el `<li>` branch (`morfo/components/tree-view.ts:103`), que contiene el control
Y todo el `branch-content`, mientras la fila que el usuario señala es el
`branch-control` de dentro: el nodo velado mide 336 px frente a los 36 px de la
fila — medido y capturado. Un branch anidado acumula además el velo de sus
ancestros, así que sus filas se ven más oscuras que las hermanas. Mover el
arquetipo es morfo y mueve píxel en toda la demo. Es la TERCERA variante de la
misma familia: table lo tiene en fila Y celda, grid-list en fila Y celda sobre
el mismo nodo, y tree-view en un ANCESTRO del nodo señalado.

Artefactos y gates:
- Sonda antes/después = 0 diffs en 8.062 valores × 7 estados.
- R-5.4: 45/46; `disabled-row-fg` adjudicado en el ledger (la demo monta CERO
  filas deshabilitadas, contadas; forzada alcanza oklch(0.61 0 0) → rgb(1,2,3)).
- censo --only 92 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0
  · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check 0
  · docs:check 0 · check por fichero limpio (72 globales, ninguno mío).
- README con su «Talla y tema» y tab `Tokens` en la demo (46 claves, en vivo).

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

@ -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**: **46%** — 2150 de 4680 knobs pasan por un token público del componente
- **Reparto**: público 2150 · privado 661 · global 1286 · literal 583 · sistema transversal 509 _(fuera del ratio)_
- **Sin token público propio**: 47 · **alcance < 20 %**: 41 · **alcance 100 %**: 7 · **con eje `size`**: 66
- **Alcance global**: **46%** — 2174 de 4680 knobs pasan por un token público del componente
- **Reparto**: público 2174 · privado 654 · global 1272 · literal 580 · sistema transversal 509 _(fuera del ratio)_
- **Sin token público propio**: 46 · **alcance < 20 %**: 40 · **alcance 100 %**: 7 · **con eje `size`**: 65
## Cómo se lee
@ -86,17 +86,17 @@ theming §6.7 lo pone delante (`hover-bg`). Las fichas proponen la forma
| 14 | [radio-cards](./radio-cards.md) | 28 | 60% | 72 | 39 |
| 15 | [drawer](./drawer.md) | 27 | 55% | 62 | 44 |
| 16 | [code-block](./code-block.md) | 26 | 0% | 26 | 0 |
| 17 | [tree-view](./tree-view.md) | 26 | 0% | 28 | 0 |
| 18 | [mockup](./mockup.md) | 25 | 14% | 29 | 0 |
| 19 | [aura](./aura.md) | 24 | 31% | 35 | 24 |
| 20 | [password-field](./password-field.md) | 24 | 31% | 36 | 23 |
| 17 | [mockup](./mockup.md) | 25 | 14% | 29 | 0 |
| 18 | [aura](./aura.md) | 24 | 31% | 35 | 24 |
| 19 | [password-field](./password-field.md) | 24 | 31% | 36 | 23 |
| 20 | [timeline](./timeline.md) | 24 | 51% | 49 | 45 |
## Sin ningún token público propio (47)
## Sin ningún token público propio (46)
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) · [`card-group`](./card-group.md) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`code`](./code.md) · [`code-block`](./code-block.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) · [`kbd`](./kbd.md) · [`knob`](./knob.md) · [`label`](./label.md) · [`link`](./link.md) · [`link-preview`](./link-preview.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) · [`picker-shell`](./picker-shell.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skeleton`](./skeleton.md) · [`skin-media-player`](./skin-media-player.md) · [`spinner`](./spinner.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) · [`tree-view`](./tree-view.md) · [`virtual-grid`](./virtual-grid.md) · [`virtual-list`](./virtual-list.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) · [`card-group`](./card-group.md) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`code`](./code.md) · [`code-block`](./code-block.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) · [`kbd`](./kbd.md) · [`knob`](./knob.md) · [`label`](./label.md) · [`link`](./link.md) · [`link-preview`](./link-preview.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) · [`picker-shell`](./picker-shell.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skeleton`](./skeleton.md) · [`skin-media-player`](./skin-media-player.md) · [`spinner`](./spinner.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) · [`virtual-grid`](./virtual-grid.md) · [`virtual-list`](./virtual-list.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md)
## Tabla completa (162 recetas, por alcance ascendente)
@ -108,7 +108,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [month-grid](./month-grid.md) | 0% | 61 | 0 | 7 | 0 | 5 | 49 | 0 | – |
| [year-grid](./year-grid.md) | 0% | 61 | 0 | 7 | 0 | 5 | 49 | 0 | – |
| [field-langs](./field-langs.md) | 0% | 41 | 0 | 0 | 31 | 8 | 2 | 0 | – |
| [tree-view](./tree-view.md) | 0% | 28 | 0 | 8 | 14 | 4 | 2 | 0 | y |
| [code-block](./code-block.md) | 0% | 26 | 0 | 2 | 24 | 0 | 0 | 0 | – |
| [card-group](./card-group.md) | 0% | 24 | 0 | 0 | 18 | 4 | 2 | 0 | y |
| [spinner](./spinner.md) | 0% | 23 | 0 | 15 | 4 | 4 | 0 | 0 | y |
@ -240,6 +239,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [chat-typing](./chat-typing.md) | 89% | 9 | 8 | 0 | 1 | 0 | 0 | 7 | – |
| [breadcrumb](./breadcrumb.md) | 90% | 23 | 18 | 2 | 0 | 0 | 3 | 28 | y |
| [collapsible](./collapsible.md) | 90% | 12 | 9 | 0 | 1 | 0 | 2 | 11 | – |
| [tree-view](./tree-view.md) | 92% | 28 | 24 | 1 | 0 | 1 | 2 | 46 | – |
| [feed](./feed.md) | 94% | 33 | 30 | 0 | 1 | 1 | 1 | 50 | – |
| [scroll-area](./scroll-area.md) | 94% | 17 | 15 | 0 | 0 | 1 | 1 | 13 | – |
| [tree-grid](./tree-grid.md) | 95% | 42 | 37 | 1 | 0 | 1 | 3 | 47 | – |

@ -5,53 +5,28 @@
> 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 26 knobs por token público
- **Knobs de apariencia**: 28 — público 0 · privado 8 · global 14 · literal 4 · sistema 2 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 0 pública(s) · 1 privada(s) forward — `_palette-element`
- **Eje `size`**: sí · **ficheros**: `tree-view.css`
- **Medido**: 2026-08-21 · **Alcance**: **92%** — 24 de 26 knobs por token público
- **Knobs de apariencia**: 28 — público 24 · privado 1 · global 0 · literal 1 · sistema 2 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 46 pública(s) — `row-height-xs`, `row-height-sm`, `row-height-md`, `row-height-lg`, `row-height-xl`, `row-padding-block-xs`, `row-padding-block-sm`, `row-padding-block-md`, `row-padding-block-lg`, `row-padding-block-xl`, `row-padding-inline-xs`, `row-padding-inline-sm`, `row-padding-inline-md`, `row-padding-inline-lg`, `row-padding-inline-xl`, `indent-xs`, `indent-sm`, `indent-md`, `indent-lg`, `indent-xl`, `font-size-xs`, `font-size-sm`, `font-size-md`, `font-size-lg`, `font-size-xl`, `row-height`, `row-padding-block`, `row-padding-inline`, `indent`, `font-size`, `radius`, `font-family`, `line-height`, `fg`, `border-width`, `border`, `surface-bg`, `padding`, `row-gap`, `row-radius`, `hover-row-bg`, `disabled-row-fg`, `branch-indicator-size`, `branch-indicator-fg`, `guide-width`, `guide-fg` · 1 privada(s) forward — `_palette-element`
- **Eje `size`**: no · **ficheros**: `tree-view.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (14)
### 1.1 Directo a primitivo global (0)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `tree-view.css:34` | `[data-tree-view-root]` | `font-family` | `var(--style-label-font-family)` |
| 2 | `tree-view.css:36` | `[data-tree-view-root]` | `line-height` | `var(--leading-ui)` |
| 3 | `tree-view.css:37` | `[data-tree-view-root]` | `color` | `var(--color-content-primary)` |
| 4 | `tree-view.css:47` | `[data-tree-view-root][data-variant='surface']` | `border` | `var(--border-width) solid var(--color-border-default)` |
| 5 | `tree-view.css:48` | `[data-tree-view-root][data-variant='surface']` | `background` | `var(--color-surface-default)` |
| 6 | `tree-view.css:49` | `[data-tree-view-root][data-variant='surface']` | `padding` | `var(--space-2)` |
| 7 | `tree-view.css:53` | `[data-tree-view-root][data-variant='outline']` | `border` | `var(--border-width) solid var(--color-border-default)` |
| 8 | `tree-view.css:55` | `[data-tree-view-root][data-variant='outline']` | `padding` | `var(--space-2)` |
| 9 | `tree-view.css:59` | `[data-tree-view-root][data-variant='ghost']` | `border` | `var(--border-width) solid transparent` |
| 10 | `tree-view.css:121` | `[data-tree-view-branch-control], [data-tree-view-item]` | `gap` | `var(--space-2)` |
| 11 | `tree-view.css:128` | `[data-tree-view-branch-control], [data-tree-view-item]` | `border-radius` | `var(--radius-sm)` |
| 12 | `tree-view.css:137` | `[data-tree-view-branch-control]:hover:not([data-disabled]), [data-tree-view-item]:hover:not([data-disabled])` | `background` | `var(--color-surface-overlay)` |
| 13 | `tree-view.css:147` | `[data-tree-view-branch-control][data-disabled], [data-tree-view-item][data-disabled]` | `color` | `var(--color-content-muted)` |
| 14 | `tree-view.css:166` | `[data-tree-view-branch-indicator]` | `color` | `var(--color-content-secondary)` |
### 1.2 A través de un privado (8)
_Ninguno._
### 1.2 A través de un privado (1)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `tree-view.css:33` | `[data-tree-view-root]` | `border-radius` | `var(--_tree-view-radius)` |
| 2 | `tree-view.css:35` | `[data-tree-view-root]` | `font-size` | `var(--_tree-view-font-size)` |
| 3 | `tree-view.css:122` | `[data-tree-view-branch-control], [data-tree-view-item]` | `min-block-size` | `var(--_tree-view-row-block-size)` |
| 4 | `tree-view.css:123` | `[data-tree-view-branch-control], [data-tree-view-item]` | `padding-block` | `var(--_tree-view-row-padding-block)` |
| 5 | `tree-view.css:124` | `[data-tree-view-branch-control], [data-tree-view-item]` | `padding-inline-end` | `var(--_tree-view-row-padding-inline)` |
| 6 | `tree-view.css:125` | `[data-tree-view-branch-control], [data-tree-view-item]` | `padding-inline-start` | `calc( var(--tree-depth, 0) * var(--_tree-view-indent) + var(--_tree-view-row-padding-inline) )` |
| 7 | `tree-view.css:142` | `[data-tree-view-branch-control][data-selected]:not([data-disabled]), [data-tree-view-item][data-selected]:not([data-disabled])` | `background` | `var(--_tree-view-palette-element)` |
| 8 | `tree-view.css:200` | `[data-tree-view-root][data-indent-guides] [data-tree-view-branch]::before` | `background` | `var(--_tree-view-guide)` |
| 1 | `tree-view.css:115` | `[data-tree-view-branch-control][data-selected]:not([data-disabled]), [data-tree-view-item][data-selected]:not([data-disabled])` | `background` | `var(--_tree-view-palette-element)` |
### 1.3 Literales (4)
### 1.3 Literales (1)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `tree-view.css:42` | `[data-tree-view-root][data-block]` | `inline-size` | `100%` |
| 2 | `tree-view.css:164` | `[data-tree-view-branch-indicator]` | `inline-size` | `1em` |
| 3 | `tree-view.css:165` | `[data-tree-view-branch-indicator]` | `block-size` | `1em` |
| 4 | `tree-view.css:199` | `[data-tree-view-root][data-indent-guides] [data-tree-view-branch]::before` | `inline-size` | `1px` |
| 1 | `tree-view.css:38` | `[data-tree-view-root][data-block]` | `inline-size` | `100%` |
## 2. Sistema transversal (2) — informativo, fuera del ratio
@ -59,28 +34,18 @@ Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `tree-view.css:149` | `[data-tree-view-branch-control][data-disabled], [data-tree-view-item][data-disabled]` | `opacity` | `var(--opacity-disabled)` |
| 2 | `tree-view.css:154` | `[data-tree-view-branch-control]:focus-visible, [data-tree-view-item]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 1 | `tree-view.css:122` | `[data-tree-view-branch-control][data-disabled], [data-tree-view-item][data-disabled]` | `opacity` | `var(--opacity-disabled)` |
| 2 | `tree-view.css:127` | `[data-tree-view-branch-control]:focus-visible, [data-tree-view-item]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_tree-view-radius` | 1 | `var(--radius-md)` | global | no |
| `--_tree-view-row-block-size` | 5 | `var(--control-height-md)`, `var(--control-height-xs)`, `var(--control-height-sm)`, `var(--control-height-lg)`, `var(--control-height-xl)` | global | no |
| `--_tree-view-row-padding-block` | 2 | `var(--space-1)`, `0` | global, literal | no |
| `--_tree-view-row-padding-inline` | 5 | `var(--space-2)`, `var(--space-1)`, `var(--space-3)`, `var(--space-4)` | global | no |
| `--_tree-view-indent` | 5 | `var(--space-4)`, `var(--space-3)`, `var(--space-5)`, `var(--space-6)` | global | no |
| `--_tree-view-font-size` | 5 | `var(--font-size-sm)`, `var(--font-size-xs)`, `var(--font-size-md)`, `var(--font-size-lg)` | global | no |
| `--_tree-view-guide` | 1 | `var(--color-border-default)` | global | no |
_La receta no declara privados propios en su CSS._
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_tree-view-palette-element`.
## 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` (30)
### 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
@ -90,54 +55,12 @@ no distingue lo que debería — se marca `⚠`.
| token (`--tree-view-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `branch-control-padding-block` | `root` | ⚠ `var(--space-1)` / `0` | 2 |
| `branch-control-padding-inline-md` | `size:md` | ⚠ `var(--space-2)` / `var(--space-4)` | 2 |
| `branch-control-padding-inline-xs` | `size:xs` | ⚠ `var(--space-1)` / `var(--space-3)` | 2 |
| `branch-control-padding-inline-sm` | `size:sm` | ⚠ `var(--space-2)` / `var(--space-3)` | 2 |
| `branch-control-padding-inline-lg` | `size:lg` | ⚠ `var(--space-3)` / `var(--space-5)` | 2 |
| `branch-control-padding-inline-xl` | `size:xl` | ⚠ `var(--space-4)` / `var(--space-6)` | 2 |
| `root-radius` | `root` | `var(--radius-md)` | 1 |
| `root-font-family` | `root` | `var(--style-label-font-family)` | 1 |
| `root-font-size-md` | `size:md` | `var(--font-size-sm)` | 1 |
| `root-font-size-xs` | `size:xs` | `var(--font-size-xs)` | 1 |
| `root-font-size-sm` | `size:sm` | `var(--font-size-sm)` | 1 |
| `root-font-size-lg` | `size:lg` | `var(--font-size-md)` | 1 |
| `root-font-size-xl` | `size:xl` | `var(--font-size-lg)` | 1 |
| `root-line-height` | `root` | `var(--leading-ui)` | 1 |
| `root-fg` | `root` | `var(--color-content-primary)` | 1 |
| `surface-root-bg` | `root` | `var(--color-surface-default)` | 1 |
| `branch-control-gap` | `root` | `var(--space-2)` | 1 |
| `branch-control-height-md` | `size:md` | `var(--control-height-md)` | 1 |
| `branch-control-height-xs` | `size:xs` | `var(--control-height-xs)` | 1 |
| `branch-control-height-sm` | `size:sm` | `var(--control-height-sm)` | 1 |
| `branch-control-height-lg` | `size:lg` | `var(--control-height-lg)` | 1 |
| `branch-control-height-xl` | `size:xl` | `var(--control-height-xl)` | 1 |
| `branch-control-radius` | `root` | `var(--radius-sm)` | 1 |
| `hover-branch-control-bg` | `root` | `var(--color-surface-overlay)` | 1 |
| `disabled-branch-control-fg` | `root` | `var(--color-content-muted)` | 1 |
| `branch-indicator-width` | `root` | `1em` | 1 |
| `branch-indicator-height` | `root` | `1em` | 1 |
| `branch-indicator-fg` | `root` | `var(--color-content-secondary)` | 1 |
| `root-width` | `root` | `1px` | 1 |
| `root-bg` | `root` | `var(--color-border-default)` | 1 |
### 4.2 Sin nombre mecánico (7)
### 4.2 Sin nombre mecánico (2)
- **⚠ 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** — 1: `inline-size`.
- **⚠ 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)** — 3: `border`.
- **⚠ 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)** — 2: `padding`.
- **⚠ 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** — 1: `background`.
### 4.3 Avisos sobre los tokens propuestos (7)
- **el privado `--_tree-view-radius` debe pasar a leer este público (o desaparecer)** — `--tree-view-root-radius`
- **el privado `--_tree-view-font-size` debe pasar a leer este público (o desaparecer)** — `--tree-view-root-font-size-md`, `--tree-view-root-font-size-xs`, `--tree-view-root-font-size-sm`, `--tree-view-root-font-size-lg`, `--tree-view-root-font-size-xl`
- **el privado `--_tree-view-row-block-size` debe pasar a leer este público (o desaparecer)** — `--tree-view-branch-control-height-md`, `--tree-view-branch-control-height-xs`, `--tree-view-branch-control-height-sm`, `--tree-view-branch-control-height-lg`, `--tree-view-branch-control-height-xl`
- **el privado `--_tree-view-row-padding-block` debe pasar a leer este público (o desaparecer)** — `--tree-view-branch-control-padding-block`
- **el privado `--_tree-view-row-padding-inline` debe pasar a leer este público (o desaparecer)** — `--tree-view-branch-control-padding-inline-md`, `--tree-view-branch-control-padding-inline-xs`, `--tree-view-branch-control-padding-inline-sm`, `--tree-view-branch-control-padding-inline-lg`, `--tree-view-branch-control-padding-inline-xl`
- **el privado `--_tree-view-indent` debe pasar a leer este público (o desaparecer)** — `--tree-view-branch-control-padding-inline-md`, `--tree-view-branch-control-padding-inline-xs`, `--tree-view-branch-control-padding-inline-sm`, `--tree-view-branch-control-padding-inline-lg`, `--tree-view-branch-control-padding-inline-xl`
- **el privado `--_tree-view-guide` debe pasar a leer este público (o desaparecer)** — `--tree-view-root-bg`
### 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.
@ -150,6 +73,69 @@ no distingue lo que debería — se marca `⚠`.
<!-- veredicto:start -->
_(pendiente — lo escribe el autor; se conserva al regenerar)_
**Medido 2026-08-21.** La §4 propone 30 tokens y **falla en el nombre de casi
todos los que importan**: el clasificador tomó el PRIMER selector de cada regla
como si fuera la parte pintada, y aquí ese primer selector es el root o el
branch-control cuando el nodo real es otro. Corregido queda en 46 claves, con
el molde del hermano `tree-grid` allí donde el problema es el mismo.
1. **`root-width: 1px` y `root-bg: var(--color-border-default)` NO son del
root: son la GUÍA de indentación.** El selector es
`[data-tree-view-root][data-indent-guides] [data-tree-view-branch]::before`
y lo pintado es el pseudo del branch. Nombres correctos **`guide-width` +
`guide-fg`**, literalmente los de `tree-grid`. Un `root-bg` que en realidad
tiñe una línea vertical es la clase de nombre que miente.
2. **`branch-control-padding-inline-{k}` ⚠ funde DOS ejes distintos**: el
ritmo de la fila (`--_tree-view-row-padding-inline`, space-1/2/2/3/4) y el
PASO DE PROFUNDIDAD (`--_tree-view-indent`, space-3/3/4/5/6), que se
multiplican en el mismo `calc`. → `row-padding-inline-{k}` + `indent-{k}`,
como `tree-grid`. Fundirlos habría hecho imposible tematizar la sangría sin
mover el padding.
3. **El prefijo `branch-control-` es erróneo: el knob es de la FILA.** Las
reglas rezan `[data-tree-view-branch-control], [data-tree-view-item]` — las
dos filas del árbol. El propio CSS ya llama `row` a sus privados. →
`row-*`, coherente con `tree-grid` y con la lección de `table` («el valor es
la altura de la FILA, no del root»). Y fuera el prefijo `root-` del chasis:
el wrapper ES el componente (mismo criterio que `tree-grid`).
4. **`row-padding-block` ⚠ es POR TALLA**, no dos valores en pugna: xs vale
`0` y las otras cuatro `--space-1`. Cinco coordenadas.
5. **`branch-indicator-width` + `-height` son UN knob** (`1em` los dos) →
`branch-indicator-size`. Mismo caso que el checkbox de `grid-list`.
6. **Los shorthand se parten**: `border` de surface/outline/ghost → un
`border-width` + un `border` (color); `padding` de surface/outline →
`padding`. Los `transparent` de ghost/outline y el `padding: 0` de ghost son
IDENTIDAD de variante y se quedan literales, igual que el `100%` de
`[data-block]`.
7. **La tipografía va un paso por debajo desde lg** (xs→xs, sm→sm, md→sm,
lg→md, xl→lg) — el patrón exacto de `table` y `tree-grid`, verbatim, no se
«corrige» al 1:1. La altura de fila SÍ es 1:1 con el bundle. Los resueltos
llevan `parts: ['root']`: `data-size` se estampa en `[data-tree-view-root]`,
que es wrapper de eidos y no parte del morfo (precedente `tree-grid`).
8. **`hover-row-bg` SÍ se acuña — al revés que en `grid-list`, y por
medición.** La regla cubre dos nodos con arquetipo distinto:
`branch-control` **no lleva archetype**, así que ahí el plano de la receta
es la ÚNICA pintura (velo ausente: `background-image: none` medido), y el
token es la única superficie de tema del hover. En `[data-tree-view-item]`
(que sí es `item`) se le suma el velo encima, pero el token sigue moviendo
el plano de debajo. Tener un consumidor legítimo es lo que lo distingue del
caso `grid-list`, donde no tenía ninguno.
### ⚠ El defecto que la medición destapó — el velo DERRAMA sobre el subárbol
Al pasar el ratón por la fila de una carpeta abierta **se tiñe la carpeta
entera, hijos incluidos**: capturado sobre `src`, el nodo velado mide **336 px**
(la rama completa) en vez de los 36 px de la fila. La causa es que
`archetype: 'item'` está declarado en el **`<li>` branch**
(`morfo/components/tree-view.ts:103`), que contiene el control Y el
`branch-content` con todo el subárbol, mientras la fila que el usuario señala
es el `branch-control` de dentro. Un branch anidado acumula además el velo de
sus ancestros: en la captura, `lib` se ve más oscuro que sus hermanos por
llevar dos.
Es PREEXISTENTE (nada que ver con la tokenización), mueve píxel y **mover el
arquetipo es morfo**: se mide, se anota en `next-features.md` §12 y se sigue.
Es la tercera variante de la misma familia — `table` lo tiene en fila Y celda,
`grid-list` en fila Y celda sobre el mismo nodo, y `tree-view` en un ANCESTRO
del nodo señalado.
<!-- veredicto:end -->

@ -436,6 +436,24 @@ el mismo nodo entra en una relación que nunca se escribió.
que bajo el puntero se apilan velo de celda + velo de fila + plano — tres
capas. El par de portadores del punto 2 aplica igual aquí.
8. **En `tree-view` el velo DERRAMA sobre el subárbol entero** (medido y
capturado 2026-08-21 al tokenizarlo). `archetype: 'item'` está declarado en
el **`<li>` branch** (`morfo/components/tree-view.ts:103`), que contiene el
`branch-control` Y el `branch-content` con todos los descendientes — así que
al pasar el ratón por la fila de una carpeta abierta se tiñe **la carpeta
entera**: el nodo velado mide **336 px** (la rama completa) frente a los
36 px de la fila que el usuario señala. Un branch anidado acumula además el
velo de sus ancestros, así que sus filas se ven más oscuras que las
hermanas. Es la tercera variante de la misma familia: `table` lo tiene en
fila Y celda (punto 2), `grid-list` en fila Y celda sobre el MISMO nodo
(punto 7), y aquí en un **ANCESTRO** del nodo señalado. Mover el arquetipo
del `<li>` al control es morfo, y mueve píxel en toda la demo.
Ojo al tokenizar la familia: en `tree-view` el `branch-control` **no lleva
archetype**, así que ahí el plano de la receta es la única pintura y su
`hover-row-bg` SÍ es un token legítimo — al revés que en `grid-list`. La
misma regla CSS cubre los dos nodos.
### Qué habría que decidir
En una frase: **quién manda cuando el arquetipo y la receta pintan el mismo

@ -114,6 +114,10 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'sentinel-spinner-reduced-duration':
'behind prefers-reduced-motion; emulated reduce context → reaches (4s → 11.5s)'
},
'tree-view': {
'disabled-row-fg':
'no disabled row in the demo (0 mounted, counted); forced data-disabled → reaches (oklch(0.61 0 0) → rgb(1,2,3))'
},
textarea: {
'focus-input-border':
'consumed only under [data-focused], which the guard blurs away ON PURPOSE (the click-focus poisoning fix); focused by hand with the pointer parked → reaches (rgb(4,5,6))',

@ -78,6 +78,64 @@ Referencias: [APG TreeView](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/)
`aria-hidden`): la línea vertical de nivel es anatomía themeable, no un
border mágico de la receta.
## Talla y tema
**Guard R-5.4** (`npm run theming:sentinel -- tree-view <url>`): 45/46 tokens
mueven un computed en vivo; el que calla está adjudicado con su porqué medido
en `scripts/theming-sentinel-exceptions.ts` (la demo no monta ninguna fila
deshabilitada). Corrido 2026-08-21.
Contrato en `lib/recipes/base.ts > tree-view` (46 claves). Alcance del censo:
**92 %** (era 0 % antes del 2026-08-21), con **cero globales**.
**El eje `size`** (xs..xl) se estampa en `[data-tree-view-root]` —un wrapper de
eidos, no una parte del morfo—, así que los cinco nombres resueltos declaran
`parts: ['root']`; el CSS ya no lleva bloques `[data-size]`. La altura de fila
casa **1:1** con el bundle; el padding y el `indent` llevan ritmo propio (el
spacing queda fuera del guard del bundle); y la tipografía casa **desplazada**,
un paso por debajo desde lg (`font-size-lg` = `var(--size-md-font-size)`),
igual que `table` y `tree-grid`. En xs el `row-padding-block` colapsa a `0`: a
esa talla la altura de fila lleva sola el ritmo.
**El `indent` es un eje aparte del `row-padding-inline`, y tenía que serlo.**
Los dos se suman en el mismo `calc` (`--tree-depth × indent + padding`), y la
propuesta generada los fundió en un solo nombre por talla — con eso, tematizar
la sangría habría movido también el padding. Son dos knobs.
**Las guías de indentación son `guide-width` + `guide-fg`**, los mismos nombres
que en `tree-grid`. La propuesta las llamaba `root-width` / `root-bg` porque el
selector empieza por `[data-tree-view-root]`, pero lo pintado es el `::before`
del branch: una línea vertical, no el fondo del root.
### El hover: por qué aquí SÍ hay token y en `grid-list` no
Una sola regla pinta las dos filas del árbol, y llevan arquetipo distinto:
`branch-control` **no tiene archetype**, así que ahí el plano de la receta es
la ÚNICA pintura (velo medido ausente, `background-image: none`) y
`hover-row-bg` es toda la superficie de tema del hover. En
`[data-tree-view-item]`, que sí es `item`, el velo del sistema se apila encima,
pero el token sigue moviendo el plano de debajo. Tener un consumidor legítimo
es lo que lo distingue de `grid-list`, donde el token no tenía ninguno.
### ⚠ El velo derrama sobre el subárbol — preexistente, no es de este eje
Medido y capturado el 2026-08-21, **anterior a la tokenización**: al pasar el
ratón por la fila de una carpeta abierta se tiñe **la carpeta entera, hijos
incluidos**. `archetype: 'item'` está declarado en el `<li>` **branch**
(`morfo/components/tree-view.ts:103`), que contiene el control y todo el
`branch-content`, mientras la fila que el usuario señala es el
`branch-control` de dentro: el nodo velado mide **336 px** frente a los 36 px
de la fila. Un branch anidado acumula además el velo de sus ancestros, así que
sus filas se ven más oscuras que las hermanas.
Mover el arquetipo del `<li>` al control es **morfo** y mueve píxel en toda la
demo: se mide, se anota y se sigue (`next-features.md` §12, punto 8 — es la
tercera variante de la misma familia, con `table` y `grid-list`).
`R-5.1 exception: inline-size: 100%` — `[data-block]` llena su contenedor. Los
`transparent` de outline/ghost y el `padding: 0` de ghost son identidad de
variante, no knobs.
## Gaps
| Gap | Disposición | Detalle |

@ -11,8 +11,8 @@
* [data-tree-view-label] → text label slot
*
* Indentation uses the `--tree-depth` custom property that soma writes
* on every Branch / Item — multiplied by `--_tree-view-indent` (scaled
* by `data-size`). Wrapper `<div data-tree-view-root>` carries the
* on every Branch / Item — multiplied by `--tree-view-indent` (resolved
* per `data-size` by the TSC). Wrapper `<div data-tree-view-root>` carries the
* eidos visual data-attrs (variant / size / color / block / scroll /
* indent-guides) so the semantic `<ul>` stays clean.
*
@ -21,20 +21,16 @@
*/
[data-tree-view-root] {
--_tree-view-radius: var(--radius-md);
--_tree-view-row-block-size: var(--control-height-md);
--_tree-view-row-padding-block: var(--space-1);
--_tree-view-row-padding-inline: var(--space-2);
--_tree-view-indent: var(--space-4);
--_tree-view-font-size: var(--font-size-sm);
--_tree-view-guide: var(--color-border-default);
/* The per-size coordinates (row height, row padding, indent, font-size) are
EMITTED by the TSC from the recipe contract onto this part; the old
`[data-size]` blocks are gone — keeping them would shadow the new tokens
with the old values. */
display: block;
border-radius: var(--_tree-view-radius);
font-family: var(--style-label-font-family);
font-size: var(--_tree-view-font-size);
line-height: var(--leading-ui);
color: var(--color-content-primary);
border-radius: var(--tree-view-radius);
font-family: var(--tree-view-font-family);
font-size: var(--tree-view-font-size);
line-height: var(--tree-view-line-height);
color: var(--tree-view-fg);
overflow: hidden;
}
@ -44,49 +40,26 @@
/* ── Variant ─ */
[data-tree-view-root][data-variant='surface'] {
border: var(--border-width) solid var(--color-border-default);
background: var(--color-surface-default);
padding: var(--space-2);
border: var(--tree-view-border-width) solid var(--tree-view-border);
background: var(--tree-view-surface-bg);
padding: var(--tree-view-padding);
}
[data-tree-view-root][data-variant='outline'] {
border: var(--border-width) solid var(--color-border-default);
border: var(--tree-view-border-width) solid var(--tree-view-border);
background: transparent;
padding: var(--space-2);
padding: var(--tree-view-padding);
}
/* Ghost: `transparent` and `0` are variant IDENTITY (chrome-less), not knobs. */
[data-tree-view-root][data-variant='ghost'] {
border: var(--border-width) solid transparent;
border: var(--tree-view-border-width) solid transparent;
background: transparent;
padding: 0;
}
/* ── Sizes ─ */
[data-tree-view-root][data-size='xs'] {
--_tree-view-row-block-size: var(--control-height-xs);
--_tree-view-row-padding-block: 0;
--_tree-view-row-padding-inline: var(--space-1);
--_tree-view-indent: var(--space-3);
--_tree-view-font-size: var(--font-size-xs);
}
[data-tree-view-root][data-size='sm'] {
--_tree-view-row-block-size: var(--control-height-sm);
--_tree-view-row-padding-inline: var(--space-2);
--_tree-view-indent: var(--space-3);
--_tree-view-font-size: var(--font-size-sm);
}
[data-tree-view-root][data-size='lg'] {
--_tree-view-row-block-size: var(--control-height-lg);
--_tree-view-row-padding-inline: var(--space-3);
--_tree-view-indent: var(--space-5);
--_tree-view-font-size: var(--font-size-md);
}
[data-tree-view-root][data-size='xl'] {
--_tree-view-row-block-size: var(--control-height-xl);
--_tree-view-row-padding-inline: var(--space-4);
--_tree-view-indent: var(--space-6);
--_tree-view-font-size: var(--font-size-lg);
}
/* ── Sizes ─
* Emitted by the TSC on `[data-tree-view-root]` from the recipe contract. */
/* Scrollable cap when consumer sets `maxHeight`. */
[data-tree-view-root][data-scrollable] {
@ -118,14 +91,14 @@
position: relative;
display: flex;
align-items: center;
gap: var(--space-2);
min-block-size: var(--_tree-view-row-block-size);
padding-block: var(--_tree-view-row-padding-block);
padding-inline-end: var(--_tree-view-row-padding-inline);
gap: var(--tree-view-row-gap);
min-block-size: var(--tree-view-row-height);
padding-block: var(--tree-view-row-padding-block);
padding-inline-end: var(--tree-view-row-padding-inline);
padding-inline-start: calc(
var(--tree-depth, 0) * var(--_tree-view-indent) + var(--_tree-view-row-padding-inline)
var(--tree-depth, 0) * var(--tree-view-indent) + var(--tree-view-row-padding-inline)
);
border-radius: var(--radius-sm);
border-radius: var(--tree-view-row-radius);
color: inherit;
cursor: pointer;
user-select: none;
@ -134,7 +107,7 @@
[data-tree-view-branch-control]:hover:not([data-disabled]),
[data-tree-view-item]:hover:not([data-disabled]) {
background: var(--color-surface-overlay);
background: var(--tree-view-hover-row-bg);
}
[data-tree-view-branch-control][data-selected]:not([data-disabled]),
@ -144,7 +117,7 @@
[data-tree-view-branch-control][data-disabled],
[data-tree-view-item][data-disabled] {
color: var(--color-content-muted);
color: var(--tree-view-disabled-row-fg);
cursor: not-allowed;
opacity: var(--opacity-disabled);
}
@ -161,9 +134,9 @@
align-items: center;
justify-content: center;
flex: 0 0 auto;
inline-size: 1em;
block-size: 1em;
color: var(--color-content-secondary);
inline-size: var(--tree-view-branch-indicator-size);
block-size: var(--tree-view-branch-indicator-size);
color: var(--tree-view-branch-indicator-fg);
transition: transform var(--duration-fast) var(--ease-default);
}
@ -196,16 +169,16 @@
content: '';
position: absolute;
inset-block: 0;
inline-size: 1px;
background: var(--_tree-view-guide);
inline-size: var(--tree-view-guide-width);
background: var(--tree-view-guide-fg);
/* Logical inset, so the guide mirrors with the rows it marks. A `:dir(rtl)`
rule that moved it to `inset-inline-end` used to live here: that is a
DOUBLE flip — the logical inset had already mirrored — and it parked the
guide on the far side of its own subtree. RTL-1 cannot see this shape. */
inset-inline-start: calc(
var(--tree-depth, 0) * var(--_tree-view-indent) +
var(--_tree-view-indent) / 2 +
var(--_tree-view-row-padding-inline)
var(--tree-depth, 0) * var(--tree-view-indent) +
var(--tree-view-indent) / 2 +
var(--tree-view-row-padding-inline)
);
pointer-events: none;
}

@ -1739,6 +1739,47 @@
--tree-grid-guide-fg: var(--color-border-default);
--tree-grid-guide-width: 1px;
--tree-grid-branch-placeholder-size: 1em;
--tree-view-row-height-xs: var(--size-xs-control-height);
--tree-view-row-height-sm: var(--size-sm-control-height);
--tree-view-row-height-md: var(--size-md-control-height);
--tree-view-row-height-lg: var(--size-lg-control-height);
--tree-view-row-height-xl: var(--size-xl-control-height);
--tree-view-row-padding-block-xs: 0;
--tree-view-row-padding-block-sm: var(--space-1);
--tree-view-row-padding-block-md: var(--space-1);
--tree-view-row-padding-block-lg: var(--space-1);
--tree-view-row-padding-block-xl: var(--space-1);
--tree-view-row-padding-inline-xs: var(--space-1);
--tree-view-row-padding-inline-sm: var(--space-2);
--tree-view-row-padding-inline-md: var(--space-2);
--tree-view-row-padding-inline-lg: var(--space-3);
--tree-view-row-padding-inline-xl: var(--space-4);
--tree-view-indent-xs: var(--space-3);
--tree-view-indent-sm: var(--space-3);
--tree-view-indent-md: var(--space-4);
--tree-view-indent-lg: var(--space-5);
--tree-view-indent-xl: var(--space-6);
--tree-view-font-size-xs: var(--size-xs-font-size);
--tree-view-font-size-sm: var(--size-sm-font-size);
--tree-view-font-size-md: var(--size-sm-font-size);
--tree-view-font-size-lg: var(--size-md-font-size);
--tree-view-font-size-xl: var(--size-lg-font-size);
--tree-view-radius: var(--radius-md);
--tree-view-font-family: var(--style-label-font-family);
--tree-view-line-height: var(--leading-ui);
--tree-view-fg: var(--color-content-primary);
--tree-view-border-width: var(--border-width);
--tree-view-border: var(--color-border-default);
--tree-view-surface-bg: var(--color-surface-default);
--tree-view-padding: var(--space-2);
--tree-view-row-gap: var(--space-2);
--tree-view-row-radius: var(--radius-sm);
--tree-view-hover-row-bg: var(--color-surface-overlay);
--tree-view-disabled-row-fg: var(--color-content-muted);
--tree-view-branch-indicator-size: 1em;
--tree-view-branch-indicator-fg: var(--color-content-secondary);
--tree-view-guide-width: 1px;
--tree-view-guide-fg: var(--color-border-default);
--feed-font-size-xs: var(--size-xs-font-size);
--feed-font-size-sm: var(--size-sm-font-size);
--feed-font-size-md: var(--size-sm-font-size);
@ -5581,9 +5622,46 @@
}
[data-tree-view-root] {
--tree-view-row-height: var(--tree-view-row-height-md);
--tree-view-row-padding-block: var(--tree-view-row-padding-block-md);
--tree-view-row-padding-inline: var(--tree-view-row-padding-inline-md);
--tree-view-indent: var(--tree-view-indent-md);
--tree-view-font-size: var(--tree-view-font-size-md);
--_tree-view-palette-element: var(--color-primary-element);
}
[data-tree-view-root][data-size='xs'] {
--tree-view-row-height: var(--tree-view-row-height-xs);
--tree-view-row-padding-block: var(--tree-view-row-padding-block-xs);
--tree-view-row-padding-inline: var(--tree-view-row-padding-inline-xs);
--tree-view-indent: var(--tree-view-indent-xs);
--tree-view-font-size: var(--tree-view-font-size-xs);
}
[data-tree-view-root][data-size='sm'] {
--tree-view-row-height: var(--tree-view-row-height-sm);
--tree-view-row-padding-block: var(--tree-view-row-padding-block-sm);
--tree-view-row-padding-inline: var(--tree-view-row-padding-inline-sm);
--tree-view-indent: var(--tree-view-indent-sm);
--tree-view-font-size: var(--tree-view-font-size-sm);
}
[data-tree-view-root][data-size='lg'] {
--tree-view-row-height: var(--tree-view-row-height-lg);
--tree-view-row-padding-block: var(--tree-view-row-padding-block-lg);
--tree-view-row-padding-inline: var(--tree-view-row-padding-inline-lg);
--tree-view-indent: var(--tree-view-indent-lg);
--tree-view-font-size: var(--tree-view-font-size-lg);
}
[data-tree-view-root][data-size='xl'] {
--tree-view-row-height: var(--tree-view-row-height-xl);
--tree-view-row-padding-block: var(--tree-view-row-padding-block-xl);
--tree-view-row-padding-inline: var(--tree-view-row-padding-inline-xl);
--tree-view-indent: var(--tree-view-indent-xl);
--tree-view-font-size: var(--tree-view-font-size-xl);
}
[data-tree-view-root][data-color], [data-tree-view-root][data-color-custom] {
--_tree-view-palette-element: var(--palette-element, var(--color-primary-element));
}

@ -2198,6 +2198,133 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
}
},
'tree-view': {
// ── Per-size coordinates (xs..xl) ──
// Row height is 1:1 with the bundle. Padding and indent are the tree's OWN
// rhythm (spacing sits outside the bundle guard, which governs
// control-height / font-size / icon-size only) — same split as `tree-grid`.
'row-height-xs': 'var(--size-xs-control-height)',
'row-height-sm': 'var(--size-sm-control-height)',
'row-height-md': 'var(--size-md-control-height)',
'row-height-lg': 'var(--size-lg-control-height)',
'row-height-xl': 'var(--size-xl-control-height)',
// xs collapses the block padding to zero: at that size the row height
// alone carries the rhythm.
'row-padding-block-xs': '0',
'row-padding-block-sm': 'var(--space-1)',
'row-padding-block-md': 'var(--space-1)',
'row-padding-block-lg': 'var(--space-1)',
'row-padding-block-xl': 'var(--space-1)',
'row-padding-inline-xs': 'var(--space-1)',
'row-padding-inline-sm': 'var(--space-2)',
'row-padding-inline-md': 'var(--space-2)',
'row-padding-inline-lg': 'var(--space-3)',
'row-padding-inline-xl': 'var(--space-4)',
// The depth step — the tree's own axis, multiplied by soma's
// `--tree-depth`. It is NOT the row's inline padding: the two add up in
// one calc, which is why the generated proposal fused them into a single
// name and why they must stay apart.
'indent-xs': 'var(--space-3)',
'indent-sm': 'var(--space-3)',
'indent-md': 'var(--space-4)',
'indent-lg': 'var(--space-5)',
'indent-xl': 'var(--space-6)',
// Type: like `table` and `tree-grid`, one step below the 1:1 from lg up.
// The coordinate names that step instead of hiding it in a primitive.
'font-size-xs': 'var(--size-xs-font-size)',
'font-size-sm': 'var(--size-sm-font-size)',
'font-size-md': 'var(--size-sm-font-size)',
'font-size-lg': 'var(--size-md-font-size)',
'font-size-xl': 'var(--size-lg-font-size)',
// The RESOLVED names. `data-size` is stamped on `[data-tree-view-root]` —
// an eidos wrapper, not a morfo part — so the per-size cascade targets
// that part (precedent: tree-grid, proof-of-human).
'row-height': {
parts: ['root'],
declarations: [
{ value: 'var(--tree-view-row-height-md)', scope: 'host' },
{ value: 'var(--tree-view-row-height-xs)', scope: 'size:xs' },
{ value: 'var(--tree-view-row-height-sm)', scope: 'size:sm' },
{ value: 'var(--tree-view-row-height-lg)', scope: 'size:lg' },
{ value: 'var(--tree-view-row-height-xl)', scope: 'size:xl' }
]
},
'row-padding-block': {
parts: ['root'],
declarations: [
{ value: 'var(--tree-view-row-padding-block-md)', scope: 'host' },
{ value: 'var(--tree-view-row-padding-block-xs)', scope: 'size:xs' },
{ value: 'var(--tree-view-row-padding-block-sm)', scope: 'size:sm' },
{ value: 'var(--tree-view-row-padding-block-lg)', scope: 'size:lg' },
{ value: 'var(--tree-view-row-padding-block-xl)', scope: 'size:xl' }
]
},
'row-padding-inline': {
parts: ['root'],
declarations: [
{ value: 'var(--tree-view-row-padding-inline-md)', scope: 'host' },
{ value: 'var(--tree-view-row-padding-inline-xs)', scope: 'size:xs' },
{ value: 'var(--tree-view-row-padding-inline-sm)', scope: 'size:sm' },
{ value: 'var(--tree-view-row-padding-inline-lg)', scope: 'size:lg' },
{ value: 'var(--tree-view-row-padding-inline-xl)', scope: 'size:xl' }
]
},
indent: {
parts: ['root'],
declarations: [
{ value: 'var(--tree-view-indent-md)', scope: 'host' },
{ value: 'var(--tree-view-indent-xs)', scope: 'size:xs' },
{ value: 'var(--tree-view-indent-sm)', scope: 'size:sm' },
{ value: 'var(--tree-view-indent-lg)', scope: 'size:lg' },
{ value: 'var(--tree-view-indent-xl)', scope: 'size:xl' }
]
},
'font-size': {
parts: ['root'],
declarations: [
{ value: 'var(--tree-view-font-size-md)', scope: 'host' },
{ value: 'var(--tree-view-font-size-xs)', scope: 'size:xs' },
{ value: 'var(--tree-view-font-size-sm)', scope: 'size:sm' },
{ value: 'var(--tree-view-font-size-lg)', scope: 'size:lg' },
{ value: 'var(--tree-view-font-size-xl)', scope: 'size:xl' }
]
},
// ── Chassis ── no `root-` prefix: the wrapper IS the component.
radius: 'var(--radius-md)',
'font-family': 'var(--style-label-font-family)',
'line-height': 'var(--leading-ui)',
fg: 'var(--color-content-primary)',
'border-width': 'var(--border-width)',
// Shared by the surface and outline variants. Ghost's `transparent` is
// variant IDENTITY, not a knob, and stays literal.
border: 'var(--color-border-default)',
'surface-bg': 'var(--color-surface-default)',
// Surface and outline pad alike; ghost's `0` is identity.
padding: 'var(--space-2)',
// ── Rows ── one rule paints BOTH `branch-control` and `item`.
'row-gap': 'var(--space-2)',
'row-radius': 'var(--radius-sm)',
// Coined on purpose, unlike grid-list's: `branch-control` carries NO
// archetype, so on that node the recipe's plane is the ONLY paint (veil
// measured absent) and this token is the hover's whole theming surface.
// On `[data-tree-view-item]` the system veil stacks on top; see the
// README and next-features §12 for the branch-veil spill.
'hover-row-bg': 'var(--color-surface-overlay)',
'disabled-row-fg': 'var(--color-content-muted)',
// ── BranchIndicator (chevron) ──
'branch-indicator-size': '1em',
'branch-indicator-fg': 'var(--color-content-secondary)',
// ── Indent guides ──
// The generated proposal called these `root-width` / `root-bg`: they are
// neither. The painted node is the branch's `::before`, a vertical line.
// Same names as tree-grid's, for the same thing.
'guide-width': '1px',
'guide-fg': 'var(--color-border-default)',
'_palette-element': {
parts: ['root'],
declarations: [{ value: 'var(--color-primary-element)', scope: 'host' }]

@ -9,10 +9,11 @@
import { compileMorfo } from '$uix/morfo';
import { treeViewMorfo } from '@/uix/morfo/components/tree-view';
import { getActiveUix } from '$active-uix';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix();
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens' | 'a11y';
let tab = $state<Tab>('live');
// ── Sample data ──────────────────────────────────────────────────────
@ -291,6 +292,9 @@
<span data-uix-tab-count>{events.length}</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>46</span>
</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div>
@ -573,6 +577,10 @@
</section>
{/if}
{#if tab === 'tokens'}
<TokensPanel component="tree-view" 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.