Segundo del bloque F2-A. 46 knobs sin una sola entrada de contrato; ahora 45
claves públicas.
censo 0 % → 86 % · global 39 % → 40 %
computed 13.050 valores en 7 estados: **0 diffs**
**Una trampa esquivada de milagro**: `table` YA tenía bloque en `base.ts` — los
forwards de paleta THM-2. Insertar uno nuevo habría creado un segundo bloque
`table:` y el catálogo lo habría descartado EN SILENCIO (gana el último). Las
claves se fusionan en el existente; el guard de duplicadas lo confirma.
**Dos nombres corregidos, como decía el veredicto**: lo que el privado llamaba
`root-height-{k}` es la altura de la FILA (`row-height-{k}`), y el prefijo
`root-` desaparece del resto — el envoltorio ES el componente y
recipe-contract §1 no repite la parte en ese caso.
**Qué casa con el bundle y qué no**, medido eje a eje: la altura de fila sí
(1:1); el padding NO (la rejilla empaqueta más denso en xs y más suelto de md
en adelante — forzar el bundle cambiaría el default); y la tipografía casa
DESPLAZADA: de md en adelante la tabla tipa un paso por debajo
(`font-size-md` = `var(--size-sm-font-size)`), que es deliberado y ahora se lee
en el propio nombre de la coordenada en vez de esconderse en un primitivo.
**Dos hallazgos del arquetipo `item`, y ninguno es mío** — la fila lo lleva, y
ese velo de `archetypes.css` está a especificidad PLENA (0,5,0), a propósito:
1. **La prop `hoverable` no suprime nada.** Una fila se ilumina al pasar el
ratón aunque el atributo no esté — medido con hover real, y ocurría ANTES de
tocar esta receta. El arquetipo y la prop no se ponen de acuerdo sobre quién
decide: es decisión de morfo, se presenta, no se toca de oficio.
2. **`selected-row-fg` era un token que mentía.** La misma regla fija el
`color` de la fila seleccionada y gana a la receta (0,5,0 contra 0,3,0), así
que el token no podía mover nada nunca. Lo cazó el centinela; retirado con su
declaración muerta. La tinta de la fila seleccionada es del arquetipo.
Y una corrección de rumbo a mitad de camino: al pasar todo a `background-color`
el velo apareció en 3 sort-triggers (su velo sí va en `:where()`, 0,0,0). Eso
es el barrido sistemático del velo, que NO está firmado — así que en los nodos
velados se mantiene el atajo a propósito, con el porqué escrito en la línea.
Primero lo diagnostiqué como regresión mía; la medición lo corrigió.
Los 6 knobs que quedan: 3 privados de paleta (patrón THM-2, capa compartida) y
3 literales de layout/identidad (`inline-size: 100%` ×2, `opacity: 1`).
El test `horizontal-escape` afirmaba el nombre del privado; ahora afirma el
público, que es justo lo que este eje persigue.
audit PASS · eidos-lint 0 invalid · rtl 0 · docs 0 · check 0 en tocados ·
suite: sólo el rojo conocido `skin-media-player`
Demo con tab Tokens: 46 filas, 0 sin computar. README con «Talla y tema» y los
dos hallazgos del arquetipo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_command-radius` | 2 | `var(--command-radius)`, `var(--dialog-content-radius)` | global, public | 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): `--_command-loading-progress`.
@ -49,7 +45,7 @@ Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inl
- **Consume la capa compartida `list-surface`.** Un eje que la capa posee se consume como `var(--_x, var(--x))`; el consumidor **no acuña**`--command-{eje}` para él — sería un vocabulario paralelo (README de `eidos/components`, «Capas compartidas» regla 2).
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (1)
### 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
@ -59,16 +55,11 @@ no distingue lo que debería — se marca `⚠`.
- **⚠ 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`.
### 4.3 Avisos sobre los tokens propuestos (1)
- **el privado `--_command-radius` debe pasar a leer este público (o desaparecer)** — `--command-radius`
### 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.
## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_table-radius` | 1 | `var(--radius-md)` | global | no |
| `--_table-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 |
| `--_table-cell-padding-inline` | 5 | `var(--space-3)`, `var(--space-2)`, `var(--space-4)`, `var(--space-5)` | global | no |
| `--_table-cell-padding-block` | 5 | `var(--space-2)`, `var(--space-1)`, `var(--space-3)` | global | no |
| `--_table-font-size` | 5 | `var(--font-size-sm)`, `var(--font-size-xs)`, `var(--font-size-md)`, `var(--font-size-lg)` | global | no |
| `--_table-header-bg` | 1 | `var(--color-surface-muted)` | global | no |
| `--_table-row-hover-bg` | 1 | `var(--color-surface-overlay)` | global | no |
| `--_table-row-striped-bg` | 1 | `var(--color-surface-muted)` | global | no |
| `--_table-divider` | 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): `--_table-palette-element`, `--_table-palette-solid`, `--_table-stable-rows`.
## 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` (51)
### 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
@ -110,79 +60,13 @@ no distingue lo que debería — se marca `⚠`.
- **⚠ decisión: `100%` es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar** — 2: `inline-size`.
- **⚠ decisión: `border` 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: `border-block-end` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 3: `border-block-end`.
- **⚠ 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** — 3: `color`, `background`.
- **⚠ decisión: `border-block-start` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `border-block-start`.
- **⚠ decisión: `padding` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `padding`.
- **⚠ decisión: `1` es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar** — 1: `opacity`.
### 4.3 Avisos sobre los tokens propuestos (8)
- **el privado `--_table-radius` debe pasar a leer este público (o desaparecer)** — `--table-root-radius`
- **el privado `--_table-row-block-size` debe pasar a leer este público (o desaparecer)** — `--table-root-height-md`, `--table-root-height-xs`, `--table-root-height-sm`, `--table-root-height-lg`, `--table-root-height-xl`
- **el privado `--_table-font-size` debe pasar a leer este público (o desaparecer)** — `--table-font-size-md`, `--table-font-size-xs`, `--table-font-size-sm`, `--table-font-size-lg`, `--table-font-size-xl`
- **el privado `--_table-header-bg` debe pasar a leer este público (o desaparecer)** — `--table-header-bg`, `--table-root-bg`, `--table-column-header-bg`, `--table-footer-bg`
- **el privado `--_table-cell-padding-block` debe pasar a leer este público (o desaparecer)** — `--table-column-header-padding-block-md`, `--table-column-header-padding-block-xs`, `--table-column-header-padding-block-sm`, `--table-column-header-padding-block-lg`, `--table-column-header-padding-block-xl`, `--table-cell-padding-block-md`, `--table-cell-padding-block-xs`, `--table-cell-padding-block-sm`, `--table-cell-padding-block-lg`, `--table-cell-padding-block-xl`
- **el privado `--_table-cell-padding-inline` debe pasar a leer este público (o desaparecer)** — `--table-column-header-padding-inline-md`, `--table-column-header-padding-inline-xs`, `--table-column-header-padding-inline-sm`, `--table-column-header-padding-inline-lg`, `--table-column-header-padding-inline-xl`, `--table-cell-padding-inline-md`, `--table-cell-padding-inline-xs`, `--table-cell-padding-inline-sm`, `--table-cell-padding-inline-lg`, `--table-cell-padding-inline-xl`
- **el privado `--_table-row-striped-bg` debe pasar a leer este público (o desaparecer)** — `--table-root-bg`
- **el privado `--_table-row-hover-bg` debe pasar a leer este público (o desaparecer)** — `--table-hover-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.
| `row-height-{k}` | **sí**, 1:1 | es la altura de control, la del bundle |
| `cell-padding-{inline,block}-{k}` | **no**, verbatim | la tabla empaqueta más que una pila de controles: más denso en xs, más suelto de md en adelante. Forzar el bundle cambiaría el default |
| `font-size-{k}` | **sí, pero desplazado** | de md en adelante la tabla tipa UN PASO POR DEBAJO (`font-size-md` = `var(--size-sm-font-size)`): una rejilla se lee en bloque, no de un vistazo. El nombre de la coordenada deja esa desviación a la vista en vez de esconderla en un primitivo crudo |
**El nombre que estaba mal**: lo que el privado llamaba `root-height-{k}` es la
altura de la FILA, no del root — ahora `row-height-{k}`. Y el prefijo `root-`
desaparece del resto (`bg`, `radius`, `fg`): el envoltorio ES el componente, y
recipe-contract §1 no repite la parte cuando la parte es el componente.
**Los tres privados que quedan** son los forwards de paleta THM-2
(`--_table-palette-solid` / `-element`, capa compartida), que siguen privados
por diseño. `R-5.1 exception: inline-size: 100%`×2 y `opacity: 1` — mecánica
de layout e identidad, no apariencia.
### El arquetipo y la prop `hoverable` no se ponen de acuerdo
Medido el 2026-08-20, y es **anterior a la tokenización**: la fila lleva
`archetype: 'item'`, y el velo de hover de `archetypes.css` para ese arquetipo
está a especificidad PLENA (0,5,0) — a propósito, según su propio comentario —
así que gana a la regla base de la fila, se escriba con atajo o con longhand. El
resultado es que **una fila se ilumina al pasar el ratón aunque `hoverable` esté
apagada**: la prop no suprime nada.
O el arquetipo decide (y `hoverable` sobra, o pasa a significar sólo el tinte
propio de la receta), o la fila no es un `item` para el sistema. Es una
decisión de morfo y se presenta; no se toca de oficio (PLAN-theming §7.3-9).
Lo mismo se llevó por delante un token: la regla del arquetipo fija también el
`color` de la fila seleccionada, a (0,5,0) contra los (0,3,0) de la receta, así
que un `selected-row-fg` no podría mover nada nunca. Lo cazó el centinela y se
retiró con su declaración — un token que no mueve nada es un token que miente.
La tinta de la fila seleccionada es del arquetipo.
Nota de cascada relacionada: el velo del `sort-trigger` sí va envuelto en
`:where()` (0,0,0), así que el `background: transparent` de la receta lo cancela
y el botón no tiene realimentación de hover. Dárselo es el barrido sistemático
// ── Sample data ──────────────────────────────────────────────────────
@ -37,27 +38,149 @@
};
const PEOPLE: Person[] = [
{id:'1',name:'Ada Lovelace',role:'Founder',team:'engineering',score: 96,joined:'2018-03-12',status:'active',bio:'Pioneer of analytical engines. Believes in legible code over clever tricks.'},
{id:'2',name:'Ben Eich',role:'Architect',team:'engineering',score: 89,joined:'2019-07-08',status:'active',bio:'Standards veteran. Writes interpreters at night.'},
{id:'3',name:'Cora Ng',role:'Designer',team:'design',score: 91,joined:'2020-02-19',status:'active',bio:'Type-driven systems thinker. Aspiring origami master.'},
{id:'5',name:'Elin Sato',role:'Account exec',team:'sales',score: 84,joined:'2020-11-23',status:'active',bio:'Closes deals with empathy. Hikes on Sundays.'},
{id:'6',name:'Fae Olu',role:'Researcher',team:'design',score: 88,joined:'2019-04-14',status:'invited',bio:'Diary interviewer. Believes in slow research.'},
{id:'7',name:'Gus Lin',role:'Operations',team:'ops',score: 72,joined:'2022-01-30',status:'active',bio:'Logistics minded. Plans every commute.'},
{id:'8',name:'Hari Sen',role:'Engineer',team:'engineering',score: 81,joined:'2021-06-11',status:'active',bio:'CRDTs by day, contra-dance by night.'},
{id:'11',name:'Kai Reyes',role:'Designer',team:'design',score: 79,joined:'2022-03-17',status:'invited',bio:'Motion-system curator. Sketches every meeting.'},
{id:'12',name:'Lin Park',role:'Operations',team:'ops',score: 85,joined:'2019-12-01',status:'active',bio:'Process auditor. Knows where every key is kept.'}
{
id: '1',
name: 'Ada Lovelace',
role: 'Founder',
team: 'engineering',
score: 96,
joined: '2018-03-12',
status: 'active',
bio: 'Pioneer of analytical engines. Believes in legible code over clever tricks.'
},
{
id: '2',
name: 'Ben Eich',
role: 'Architect',
team: 'engineering',
score: 89,
joined: '2019-07-08',
status: 'active',
bio: 'Standards veteran. Writes interpreters at night.'
},
{
id: '3',
name: 'Cora Ng',
role: 'Designer',
team: 'design',
score: 91,
joined: '2020-02-19',
status: 'active',
bio: 'Type-driven systems thinker. Aspiring origami master.'
<tr><tdclass="name">table</td><tdclass="type">TableInstance</td><tdclass="default empty">required</td><td>Returned by <code>createTable</code>.</td></tr>
<tr><tdclass="name">color <spandata-uix-tagdata-kind="eidos">eidos</span></td><tdclass="type">ColorRole</td><tdclass="default">'primary'</td><td>Accent for selected rows + sort indicator + focus.</td></tr>
<tr><tdclass="name">block <spandata-uix-tagdata-kind="eidos">eidos</span></td><tdclass="type">boolean</td><tdclass="default">true</td><td>Stretch root to container inline-size.</td></tr>
<tr><tdclass="name">striped <spandata-uix-tagdata-kind="eidos">eidos</span></td><tdclass="type">boolean</td><tdclass="default">false</td><td>Alternate-row tint (skipped in ghost variant).</td></tr>
<tr><tdclass="name">hoverable <spandata-uix-tagdata-kind="eidos">eidos</span></td><tdclass="type">boolean</td><tdclass="default">true</td><td>Highlight rows on hover.</td></tr>
<tr><tdclass="name">stickyHeader <spandata-uix-tagdata-kind="eidos">eidos</span></td><tdclass="type">boolean</td><tdclass="default">false</td><td>Pins <code><thead></code> to the top of the scroll region.</td></tr>
<tr><tdclass="name">maxHeight <spandata-uix-tagdata-kind="eidos">eidos</span></td><tdclass="type">string</td><tdclass="default empty">—</td><td>Wraps the table in a scrolling region with the given CSS block-size.</td></tr>
<tr><tdclass="name">stableRows <spandata-uix-tagdata-kind="eidos">eidos</span></td><tdclass="type">number</td><tdclass="default empty">—</td><td>Reserve vertical space for N rows + header. Keeps the wrapper height steady so pagination/footer don't jump when filters change row count. Ignored when <code>maxHeight</code> is set.</td></tr>
<pdata-uix-section-desc>Selectors at <code>src/uix/eidos/components/table/table.css</code>. The visual data-attrs (<code>data-size</code>, <code>data-variant</code>, <code>data-color</code>, <code>data-block</code>, <code>data-striped</code>, <code>data-hoverable</code>, <code>data-sticky-header</code>, <code>data-scrollable</code>) live on the <code><div data-table-root></code> wrapper so the semantic <code><table></code> stays clean.</p>
<pdata-uix-section-desc>
Selectors at <code>src/uix/eidos/components/table/table.css</code>. The visual data-attrs (<code
<tr><tdclass="name">[data-table-root][data-variant]</td><td><spandata-uix-tagdata-kind="eidos">eidos</span></td><td>Border + background per variant.</td></tr>
><td>Header sort affordance — bare button + chevron.</td></tr
>
</tbody>
</table>
</div>
</section>
{/if}
{#iftab==='tokens'}
<TokensPanelcomponent="table"stage={stageRef}/>
{/if}
{#iftab==='a11y'}
<sectiondata-uix-section>
<h2data-uix-section-title>Accessibility</h2>
<pdata-uix-section-desc>
Emits a native <code><table></code> with <code>aria-label</code> and per-column <code>aria-sort</code>. Rows expose <code>aria-rowindex</code> + <code>aria-selected</code> (when selection is enabled). Detail panels are wired with <code>aria-expanded</code> + <code>aria-controls</code>. Use <code>TreeGrid</code> for true hierarchical rows (arrow-key navigation) — Table is for flat tabular data only.
Emits a native <code><table></code> with <code>aria-label</code> and per-column