Octavo y último del bloque.
censo 0 % → 94 % · global 43 %
computed reposo y las CINCO tallas, idénticas al byte
**El título no escala por talla: escala por NIVEL DE ENCABEZADO.** El informe
leyó sus cuatro valores como una colisión de `size`, y son `aria-level` 3..6 —
un encabezado más profundo es más pequeño porque es más profundo. Sus tokens
son `article-title-font-size-{3,4,5,6}`, no coordenadas de talla. Tercera
corrección de este tipo a un veredicto en el bloque.
**El ⚠ del `sentinel` eran DOS medidas**, como sospechaba el veredicto: la
fila-sonda mantiene un alto mínimo para que el observador tenga algo que
intersecar, y el spinner que va dentro tiene su propio diámetro.
El feed tipa por debajo del 1:1 de md en adelante, como `table` y `tree-grid`;
la coordenada lo nombra en vez de esconderlo.
**Sobre la medición, que aquí hubo que trabajarla.** La sonda dio 7 diffs y
ninguno era un cambio:
- el `box-shadow` del nodo `[data-busy]` está ANIMADO, y la sonda congela
`transition` pero nunca `animation` —a propósito, porque congelarla impide
que Presence monte nada—, así que lo muestrea en una fase distinta cada vez:
dos corridas del MISMO código dan cinco diffs;
- y el estado `hover` se mide después de haber forzado las tallas, así que su
altura arrastra la última.
Lo que decide es que las SEIS medidas estables —reposo y las cinco tallas—
salieron idénticas al byte: 544,5 · 564,5 · 628,5 · 792,5 · 966,5 px. Queda
escrito en el README para que el siguiente no persiga el mismo fantasma.
audit PASS · eidos-lint 0 invalid · rtl 0 · docs 0 · check 0 en tocados ·
suite: sólo el rojo conocido
Demo con tab Tokens: 50 filas, 0 sin computar.
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? |
| --- | ---: | --- | --- | :-: |
| `--_feed-radius` | 1 | `var(--radius-md)` | global | no |
| `--_feed-article-padding-block` | 5 | `var(--space-3)`, `var(--space-2)`, `var(--space-4)`, `var(--space-5)` | global | no |
| `--_feed-article-padding-inline` | 5 | `var(--space-4)`, `var(--space-3)`, `var(--space-5)`, `var(--space-6)` | global | no |
| `--_feed-gap` | 1 | `var(--space-2)` | global | no |
| `--_feed-thread-indent` | 5 | `var(--space-5)`, `var(--space-4)`, `var(--space-6)`, `var(--space-7)` | global | no |
| `--_feed-font-size` | 5 | `var(--font-size-sm)`, `var(--font-size-xs)`, `var(--font-size-md)`, `var(--font-size-lg)` | global | no |
| `--_feed-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): `--_feed-palette-element`, `--_feed-palette-solid`.
## 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` (38)
### 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
@ -95,60 +54,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`.
- **⚠ 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)** — 1: `border-block-end`.
- **⚠ decisión: `border-inline-start` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `border-inline-start`.
### 4.3 Avisos sobre los tokens propuestos (6)
- **el privado `--_feed-radius` debe pasar a leer este público (o desaparecer)** — `--feed-root-radius`, `--feed-article-radius`
- **el privado `--_feed-font-size` debe pasar a leer este público (o desaparecer)** — `--feed-root-font-size-md`, `--feed-root-font-size-xs`, `--feed-root-font-size-sm`, `--feed-root-font-size-lg`, `--feed-root-font-size-xl`
- **el privado `--_feed-gap` debe pasar a leer este público (o desaparecer)** — `--feed-gap`, `--feed-thread-gap`
- **el privado `--_feed-article-padding-block` debe pasar a leer este público (o desaparecer)** — `--feed-article-padding-block-md`, `--feed-article-padding-block-xs`, `--feed-article-padding-block-sm`, `--feed-article-padding-block-lg`, `--feed-article-padding-block-xl`
- **el privado `--_feed-article-padding-inline` debe pasar a leer este público (o desaparecer)** — `--feed-article-padding-inline-md`, `--feed-article-padding-inline-xs`, `--feed-article-padding-inline-sm`, `--feed-article-padding-inline-lg`, `--feed-article-padding-inline-xl`
- **el privado `--_feed-thread-indent` debe pasar a leer este público (o desaparecer)** — `--feed-thread-padding-inline-md`, `--feed-thread-padding-inline-xs`, `--feed-thread-padding-inline-sm`, `--feed-thread-padding-inline-lg`, `--feed-thread-padding-inline-xl`
### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)
el único patrón normativo; no hay librería headless de referencia que lo
@ -81,13 +81,45 @@ publique.
disparo del load-more pertenece al contrato del componente; el consumidor
solo escucha `onLoadMore`.
## Talla y tema
Contrato en `lib/recipes/base.ts > feed` (50 claves). Alcance del censo:
**94 %** (era 0 % antes del 2026-08-21), sin privados propios — sólo quedan los
dos forwards de paleta THM-2.
**El eje `size`** (xs..xl) se estampa en `[data-feed-root]`, así que los
resueltos declaran `parts: ['root']`. El feed **tipa por debajo del 1:1** de md
en adelante (`font-size-md` lee la coordenada `sm`): un flujo se lee en bloque,
igual que `table` y `tree-grid`. La coordenada nombra ese paso en vez de
esconderlo en un primitivo crudo.
**El título NO escala por talla: escala por NIVEL DE ENCABEZADO.** El informe
leyó sus cuatro valores como una colisión de talla, y son `aria-level` 3..6 —
un encabezado más profundo es más pequeño porque es más profundo. Por eso sus
tokens son `article-title-font-size-{3,4,5,6}` y no coordenadas de `size`.
**El ⚠ del `sentinel` eran DOS medidas**: la fila-sonda mantiene un alto mínimo
para que el observador tenga algo que intersecar, y el spinner que va dentro
tiene su propio diámetro. Dos tokens, no uno.
### Nota de medición
La sonda da diffs que NO son cambios, y conviene saberlo antes de perseguirlos:
el `box-shadow` del nodo `[data-busy]` está ANIMADO y la sonda congela
`transition` pero nunca `animation` —a propósito—, así que lo muestrea en una
fase distinta cada vez; dos corridas del mismo código dan cinco diffs. Y el
estado `hover` se mide después de haber forzado tallas, así que su altura
arrastra la última. **Las seis medidas que sí son estables —reposo y las cinco
tallas— salieron idénticas al byte** antes y después: 544,5 · 564,5 · 628,5 ·
792,5 · 966,5 px.
## Gaps
| Gap | Disposición | Detalle |
| --- | --- | --- |
| Estados de resultado del fetch (complete/fail) como eventos propios | **diferir** | Hoy son del consumidor (que posee el fetch); si el censo de "sustain" del catálogo se consolida, feed es candidato natural. |
| Skeleton integrado durante `busy` | **descartar** | Componer `<Skeleton>` dentro del feed — no absorber. |
| Tests del wrapper eidos (soma con suite ✓) | **diferir** | Pasada SYS-2. |
| Estados de resultado del fetch (complete/fail) como eventos propios | **diferir**| Hoy son del consumidor (que posee el fetch); si el censo de "sustain" del catálogo se consolida, feed es candidato natural. |
| Skeleton integrado durante `busy`| **descartar** | Componer `<Skeleton>` dentro del feed — no absorber. |
| Tests del wrapper eidos (soma con suite ✓) | **diferir** | Pasada SYS-2. |
<tr><tdclass="name">totalItems</td><tdclass="type">number | undefined</td><tdclass="default empty">—</td><td>Total across all pages. <code>undefined</code> = unknown (AT reads "n of unknown").</td></tr>
<tr><tdclass="name">busy</td><tdclass="type">boolean</td><tdclass="default">false</td><td>Sets <code>aria-busy</code> while loading.</td></tr>
<tr><tdclass="name">onLoadMore</td><tdclass="type">() => void</td><tdclass="default empty">—</td><td>Fired on PageDown-past-last or Sentinel intersection.</td></tr>
<tr><tdclass="name">color <spandata-uix-tagdata-kind="eidos">eidos</span></td><tdclass="type">ColorRole</td><tdclass="default">'primary'</td><td>Focus ring + sentinel spinner.</td></tr>
<tr><tdclass="name">block <spandata-uix-tagdata-kind="eidos">eidos</span></td><tdclass="type">boolean</td><tdclass="default">true</td><td>Stretch to container width.</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 feed in a scroll region.</td></tr>
<tr><tdclass="name">Article.posinset</td><tdclass="type">number</td><tdclass="default">auto</td><td>1-based position in full set. Auto when DOM order matches feed order.</td></tr>
<tr><tdclass="name">ArticleTitle.level</td><tdclass="type">1–6</td><tdclass="default">3 + threadDepth</td><td>Heading level. Auto-derived from enclosing Thread depth.</td></tr>
<tr><tdclass="name">Sentinel.onIntersect</td><tdclass="type">() => void</td><tdclass="default">Feed.onLoadMore</td><td>Defaults to firing the parent Feed's onLoadMore.</td></tr>
<tr><tdclass="name">[data-feed-root][data-variant] [data-feed-article]</td><td><spandata-uix-tagdata-kind="eidos">eidos</span></td><td>Article card chrome per variant.</td></tr>
<tr><tdclass="name">[data-feed-article-title][aria-level]</td><td><spandata-uix-tagdata-kind="soma">morfo</span></td><td>Font-size scaled by heading level.</td></tr>
<tr><tdclass="name">[data-feed-thread]</td><td><spandata-uix-tagdata-kind="eidos">eidos</span></td><td>Nested feed with accent border indent.</td></tr>
<tr><tdclass="name">[data-feed][data-busy] [data-feed-sentinel]::before</td><td><spandata-uix-tagdata-kind="eidos">eidos</span></td><td>Spinner during load.</td></tr>
<pdata-uix-section-desc>Follows the APG <ahref={feedMorfo.apg}target="_blank"rel="noreferrer">feed pattern</a>. Root is <code>role="feed"</code>, each child is <code>role="article"</code> with <code>aria-posinset</code> / <code>aria-setsize</code> auto-wired. <code>aria-setsize="-1"</code> when total is unknown (per APG).</p>
<pdata-uix-section-desc>
Follows the APG <ahref={feedMorfo.apg}target="_blank"rel="noreferrer">feed pattern</a>.
Root is <code>role="feed"</code>, each child is <code>role="article"</code> with
<code>aria-posinset</code>
/ <code>aria-setsize</code> auto-wired. <code>aria-setsize="-1"</code> when total is unknown (per