uix(theming): revisión adversarial de F2-B — 4 hipótesis, 3 refutadas, 1 token que mentía

El plan (§7.7) exige una revisión escéptica antes de cerrar un bloque, y F2-B
no la tenía. Molde F2-A: worktree en la base del bloque (`8400e5bdd`) con su
propio dev server, medición independiente.

**REFUTADA \u2014 «el default cambió en algún sitio que la demo no mira».** 77 casos
FORZADOS (variantes, formas, tallas y estados que las demos NO montan, en los
21 componentes) comparados base vs HEAD: **0 diferencias**. Era la sospecha más
fuerte, porque cuatro componentes miden UN nodo (`link`, `code`, `skeleton`,
`link-preview`) y sus tokens viven justo en las variantes que la demo no
renderiza.

**REFUTADA \u2014 «hay tokens huérfanos».** 339 públicos de los 21 bloques, 0 sin
consumo. El primer extractor daba «SIN BLOQUE» en los 21 \u2014 el mismo antipatrón
que audita este eje \u2014 y se rehízo contando llaves; muta-prueba: un token falso
inyectado sale detectado y el árbol se restaura.

**REFUTADA \u2014 «el gate de 0 diffs pasó sobre casi nada».** Contados los nodos que
ve cada sonda; los cuatro de un nodo se cubrieron forzando sus variantes arriba.

**HALLAZGO REAL \u2014 `drag-drop.preview-z` mentía, y lo escondía una adjudicación
mía sobre un nodo SINTÉTICO.** El ledger lo daba por vivo («forzado → alcanza,
100 → 4321»), pero ese nodo lo había creado yo con JS. El preview REAL que soma
monta durante un arrastre lleva `z-index: 9999` **INLINE**
(`drag-drop-provider.svelte.ts:663,671`): el token resuelve a 4321 y el nodo
computa 9999. Ninguna cascada puede ganarle. **Retirado con 0 diffs** contra el
snapshot original (1.827 valores × 7 estados), misma clase que el `item-gap*`
de carousel que retiró la revisión de F2-A. Era el único público que drag-drop
tenía antes del eje; su alcance se mantiene en 89 %.

**La lección que generaliza, y va al protocolo**: una adjudicación «forzado →
alcanza» vale lo que valga el nodo que se forzó \u2014 si lo creaste tú, no lleva lo
que soma le pone. Re-verificado el resto de la misma clase contra nodos reales:
`clipboard` (indicador tras un clic real) y `picker-shell` (pie real) coinciden
con lo adjudicado y no llevan inline; el inline de `skeleton-line` es de
`inline-size`, no del eje tokenizado.

DOS HALLAZGOS DE INSTRUMENTO en la propia revisión (→ §13):

- **El orden de carga NO es el mismo en dev que en producción.** Medido con
  `npm run build` + preview y CDP: en dev la receta de `link-preview` va ANTES
  del plano `overlay`; en producción queda EN MEDIO de sus dos reglas. El
  ganador coincide por casualidad. La fragilidad de §12.9 queda medida en los
  dos entornos, y confirma que restaurar el par fue lo correcto.
- **Un worktree cuyo `node_modules` es una junction fuera del root NO HIDRATA**:
  Vite responde 403 a `@fs/…` y SvelteKit no arranca, así que la página SSR se
  ve pero ningún componente monta. La primera pasada de esta revisión comparó
  contra una base MUERTA y devolvió «77 casos, 1 diferencia» sin que nada lo
  delatara. Se arregla con `server: { fs: { allow: [...] } }`; queda escrito en
  el handoff junto a la regla: **comprueba que un componente monta antes de
  comparar**.

Gates: censo global 50 % · `--names` DESVIADAS 0 · drag-drop 89 % ·
component:audit PASS · eidos-lint 0 invalid · R-5.4 verde · suite eidos sin
rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio.
Worktree eliminado quitando la junction ANTES del `remove --force`, con el
`node_modules` real verificado intacto (memoria
`git-worktree-remove-recurses-into-junctions`).

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

@ -8,10 +8,10 @@
> [`theming-audit.md`](../theming-audit.md); ésta es la deuda de adopción que
> aquélla dejó apuntada en su §5.3-3.
- **Medido**: 2026-08-21 · **162 recetas** con CSS + **8 componentes sin receta** = 170 fichas, el árbol entero de `eidos/components/`
- **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**: **50%** — 2331 de 4676 knobs pasan por un token público del componente
- **Reparto**: público 2331 · privado 625 · global 1156 · literal 564 · sistema transversal 509 _(fuera del ratio)_
- **Alcance global**: **50%** — 2333 de 4678 knobs pasan por un token público del componente
- **Reparto**: público 2333 · privado 625 · global 1156 · literal 564 · sistema transversal 509 _(fuera del ratio)_
- **Sin token público propio**: 29 · **alcance < 20 %**: 22 · **alcance 100 %**: 10 · **con eje `size`**: 59
## Cómo se lee
@ -231,7 +231,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [media-player](./media-player.md) | 88% | 49 | 43 | 0 | 0 | 6 | 0 | 61 | – |
| [tabs](./tabs.md) | 88% | 76 | 66 | 6 | 2 | 1 | 1 | 79 | 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 | 10 | – |
| [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 | – |
| [breadcrumb](./breadcrumb.md) | 90% | 23 | 18 | 2 | 0 | 0 | 3 | 28 | y |
| [card-group](./card-group.md) | 90% | 22 | 18 | 0 | 0 | 2 | 2 | 20 | – |
@ -246,7 +246,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [text](./text.md) | — | 24 | 0 | 0 | 0 | 0 | 24 | 0 | – |
| [chat-list](./chat-list.md) | 100% | 18 | 18 | 0 | 0 | 0 | 0 | 18 | – |
| [splitter](./splitter.md) | 100% | 18 | 17 | 0 | 0 | 0 | 1 | 15 | – |
| [link-preview](./link-preview.md) | 100% | 15 | 12 | 0 | 0 | 0 | 3 | 33 | – |
| [link-preview](./link-preview.md) | 100% | 17 | 14 | 0 | 0 | 0 | 3 | 35 | – |
| [code](./code.md) | 100% | 13 | 7 | 0 | 0 | 0 | 6 | 6 | – |
| [box](./box.md) | 100% | 9 | 9 | 0 | 0 | 0 | 0 | 43 | – |
| [label](./label.md) | 100% | 8 | 2 | 0 | 0 | 0 | 6 | 2 | – |

@ -5,9 +5,9 @@
> Vista de conjunto: [README](./README.md) · método y protocolo:
> [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7.
- **Medido**: 2026-08-21 · **Alcance**: **100%** — 12 de 12 knobs por token público
- **Knobs de apariencia**: 15 — público 12 · privado 0 · global 0 · literal 0 · sistema 3 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 33 pública(s) — `padding-block-xs`, `padding-block-sm`, `padding-block-md`, `padding-block-lg`, `padding-block-xl`, `padding-inline-xs`, `padding-inline-sm`, `padding-inline-md`, `padding-inline-lg`, `padding-inline-xl`, `font-size-xs`, `font-size-sm`, `font-size-md`, `font-size-lg`, `font-size-xl`, `min-width-xs`, `min-width-sm`, `min-width-md`, `min-width-lg`, `min-width-xl`, `padding-block`, `padding-inline`, `font-size`, `min-width`, `max-width`, `gap`, `radius`, `fg`, `font-weight`, `trigger-radius`, `trigger-fg`, `hover-trigger-fg`, `trigger-underline`
- **Medido**: 2026-08-22 · **Alcance**: **100%** — 14 de 14 knobs por token público
- **Knobs de apariencia**: 17 — público 14 · privado 0 · global 0 · literal 0 · sistema 3 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 35 pública(s) — `padding-block-xs`, `padding-block-sm`, `padding-block-md`, `padding-block-lg`, `padding-block-xl`, `padding-inline-xs`, `padding-inline-sm`, `padding-inline-md`, `padding-inline-lg`, `padding-inline-xl`, `font-size-xs`, `font-size-sm`, `font-size-md`, `font-size-lg`, `font-size-xl`, `min-width-xs`, `min-width-sm`, `min-width-md`, `min-width-lg`, `min-width-xl`, `padding-block`, `padding-inline`, `font-size`, `min-width`, `max-width`, `gap`, `radius`, `fg`, `font-weight`, `font-family`, `line-height`, `trigger-radius`, `trigger-fg`, `hover-trigger-fg`, `trigger-underline`
- **Eje `size`**: no · **ficheros**: `link-preview.css`
## 1. Knobs fuera de alcance
@ -31,8 +31,8 @@ Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `link-preview.css:41` | `[data-link-preview-trigger]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 2 | `link-preview.css:99` | `[data-link-preview-arrow] polygon` | `fill` | `var(--depth-overlay-surface)` |
| 3 | `link-preview.css:104` | `[data-link-preview-arrow] path` | `stroke` | `var(--depth-overlay-border)` |
| 2 | `link-preview.css:105` | `[data-link-preview-arrow] polygon` | `fill` | `var(--depth-overlay-surface)` |
| 3 | `link-preview.css:110` | `[data-link-preview-arrow] path` | `stroke` | `var(--depth-overlay-border)` |
## 3. Privados de la receta — ¿de dónde sale su valor?

@ -695,6 +695,40 @@ Lo que `textarea` añadió (2026-08-21):
adjudicados por eso. Misma clase que `date-range-picker` con
`kind='month'|'year'` (norma N-6).
Lo que la REVISIÓN ADVERSARIAL de F2-B destapó (2026-08-22):
- **`drag-drop.preview-z` era un token que MENTÍA, y lo escondió una
adjudicación hecha sobre un nodo sintético.** El ledger decía «forzado →
alcanza (100 → 4321)», pero ese nodo lo había creado yo con JS. El preview
REAL —el que soma monta durante un arrastre de verdad— lleva
`z-index: 9999` **INLINE** (`drag-drop-provider.svelte.ts:663,671`), así que
ninguna cascada puede ganarle: el token resolvía a 4321 y el nodo computaba
9999. Retirado con 0 diffs, que es la prueba. Misma clase que el `item-gap*`
de `carousel` que retiró la revisión de F2-A: **canal de valor de soma, no
superficie de tema**.
La lección para el protocolo: **una adjudicación «forzado → alcanza» vale lo
que valga el nodo que se forzó**. Si lo creaste tú, no lleva lo que soma le
pone. Verificado el resto de la misma clase: `clipboard` (indicador real tras
un clic real) y `picker-shell` (pie real) coinciden con lo adjudicado y no
llevan inline; `skeleton-line` lleva inline pero de `inline-size`, no del
eje tokenizado.
- **El orden de carga NO es el mismo en dev que en producción.** Medido con
`npm run build` + preview y CDP: en dev la receta de `link-preview` va
ANTES del plano `overlay`, en producción queda EN MEDIO de sus dos reglas.
El ganador coincide por casualidad —hay dos reglas del plano y una queda
siempre al final—, pero la fragilidad de §12.9 es real y ahora está medida en
los dos entornos, no supuesta.
- **⚠ Un worktree de comparación NO hidrata si su `node_modules` es una
junction fuera del root**: Vite responde **403** a `@fs/…` y SvelteKit no
arranca, así que la página SSR se ve pero ningún componente monta. La primera
pasada de esta revisión comparó contra una base muerta y dio «77 casos, 1
diferencia» sin que eso significara nada. Se arregla con
`server: { fs: { allow: [...] } }` en el `vite.config.ts` del worktree. Es la
memoria `worktree-devserver-fsallow`, y vuelve a morder cada vez.
Lo que la cola pequeña añadió (2026-08-22):
- **⚠ El censo no distingue «0 % por deuda» de «0 % POR NATURALEZA», y ya son

@ -1,7 +1,7 @@
# CONTINUE — eje Theming «theme-reach» (handoff, act. 2026-08-22)
**Estado: EN EJECUCIÓN — F0 y F1 CERRADAS · F2 en curso, bloque F2-B cerrado
salvo su revisión adversarial.** Alcance global **50 %** (33 % al abrir el eje,
**Estado: EN EJECUCIÓN — F0 y F1 CERRADAS · F2 en curso, bloque F2-B CERRADO
con su revisión adversarial pasada (2026-08-22).** Alcance global **50 %** (33 % al abrir el eje,
45 % al cerrar F2-A el día 21). Sin contrato **29**; al 100 % **10**; alcance
<20 % **22**.
@ -30,22 +30,21 @@ salvo su revisión adversarial.** Alcance global **50 %** (33 % al abrir el eje,
## QUÉ HACER AHORA
### 1. La revisión adversarial de F2-B — ES LO PRIMERO
El plan (§7.7) la exige antes de dar un bloque por cerrado y **F2-B no la
tiene**: sus 21 componentes llevan sólo la medición de quien los escribió.
Molde: la de F2-A (§7.7 del registro) — worktree con el CSS revertido a
`8400e5bdd`, medición independiente por componente que intente REFUTAR «el
default es idéntico» y «cada token alcanza».
**Dos hipótesis concretas que hay que atacar**, ya identificadas:
- El **orden de carga** de `[data-depth='overlay']` frente a las recetas se
midió sólo en DEV (estable en 5 recargas). **En un bundle de producción está
sin verificar**, y de ahí depende el par `font-family`/`line-height` de
`link-preview` (§12.9).
- Los componentes cuya sonda ve pocos nodos (`link` 1, `mark` 1, `code` 1,
`label` 1): comprobar que el gate de 0 diffs no pasó sobre casi nada.
### 1. F2-B está CERRADO — su revisión adversarial (§7.7) se hizo el 2026-08-22
**4 hipótesis · 3 refutadas · 1 hallazgo real.** Detalle en `PLAN §8`. Lo que
hay que recordar de ella:
- **`drag-drop.preview-z` mentió y se retiró**: soma escribe su `z-index`
INLINE, y la adjudicación que lo daba por vivo se había medido sobre un nodo
creado a mano. **Una adjudicación «forzado → alcanza» vale lo que valga el
nodo que se forzó** — si lo creaste tú, no lleva lo que soma le pone.
- **El orden de carga NO es el mismo en dev que en producción** (medido con
build + CDP). El empate de §12.9 sigue abierto, ahora con las dos medidas.
- **Un worktree cuyo `node_modules` es una junction fuera del root no hidrata**:
Vite responde 403 a `@fs/…`. La primera pasada comparó contra una base muerta
sin que nada lo delatara — añade `server: { fs: { allow: [...] } }` a su
`vite.config.ts` y **comprueba que un componente monta antes de comparar**.
### 2. La cola que queda, por alcance

@ -519,7 +519,34 @@ de commit. Sin artefacto, el paso no se ha hecho.
por `hover` para hover-cards, y `text-decoration-thickness` /
`-underline-offset` en la lista de propiedades.
**Pendiente de este bloque**: la revisión adversarial §7.7 (ver abajo).
**Revisión adversarial §7.7 — HECHA 2026-08-22.** Molde F2-A: worktree en la
base del bloque (`8400e5bdd`) con su propio dev server, medición
independiente. **4 hipótesis · 3 refutadas · 1 hallazgo real.**
- **REFUTADA «el default cambió en algún sitio que la demo no mira»**: 77
casos FORZADOS (variantes, formas, tallas y estados que las demos NO
montan, en los 21 componentes) comparados base vs HEAD → **0 diferencias**.
- **REFUTADA «hay tokens huérfanos»**: 339 públicos de los 21 bloques, 0 sin
consumo. Con muta-prueba: un token falso inyectado sale detectado, y el
árbol se restaura.
- **REFUTADA «el gate de 0 diffs pasó sobre casi nada»**: se contaron los
nodos que ve cada sonda. Cuatro componentes miden 1 nodo (`link`, `code`,
`skeleton`, `link-preview`), y por eso se forzaron sus variantes en el
punto anterior — ahí es donde viven sus tokens, y salieron idénticos.
- **HALLAZGO REAL: `drag-drop.preview-z` mentía.** El ledger lo daba por vivo
con «forzado → alcanza», pero el nodo se había creado a mano; el preview
REAL que soma monta durante un arrastre lleva `z-index: 9999` INLINE, así
que ninguna cascada puede ganarle. Retirado con 0 diffs. Misma clase que el
`item-gap*` de carousel en F2-A. **La lección: una adjudicación «forzado →
alcanza» vale lo que valga el nodo que se forzó.** Re-verificado el resto de
la clase contra nodos reales (`clipboard`, `picker-shell`): correctos.
Dos hallazgos de INSTRUMENTO en la propia revisión, ambos registrados en §13:
el orden de carga **no es el mismo en dev que en producción** (medido con
build + CDP; el ganador coincide por casualidad), y un **worktree cuyo
`node_modules` es una junction fuera del root no hidrata** — Vite responde 403
a `@fs/…` y la primera pasada de esta revisión comparó contra una base
muerta sin que nada lo delatara.
- 2026-08-21 — **La capa `calendar-surface`** (firma 1 del acta, diseño
presentado y firmado el mismo día). La medición desmontó el encargo: la capa

@ -133,8 +133,6 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (14 → 99px)',
'preview-shadow':
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (0 0 0 7px rgb(7,8,9))',
'preview-z':
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (100 → 4321)'
},
link: {
'external-icon-size':

@ -76,8 +76,17 @@ y los diez adjudicados con su medición en
sólo existe mientras hay un arrastre en curso (0 nodos en reposo, contados).
Forzando el nodo, los diez mueven su propiedad. Corrido 2026-08-21.
Contrato en `lib/recipes/base.ts > drag-drop` (10 claves; `preview-z` ya
estaba). Alcance del censo: **89 %** (era 0 %).
Contrato en `lib/recipes/base.ts > drag-drop` (9 claves). Alcance del censo:
**89 %** (era 0 %).
**`preview-z` se RETIRÓ** en la revisión adversarial (2026-08-22): soma escribe
`z-index: 9999` **inline** sobre el preview
(`drag-drop-provider.svelte.ts:663,671`), así que ninguna cascada podía
ganarle — el token resolvía a 4321 y el nodo computaba 9999. Era el único
público que este componente tenía antes del eje, y llevaba una adjudicación
que lo daba por vivo porque se había medido sobre un nodo creado a mano, no
sobre el que soma monta durante un arrastre real. La z del preview es canal de
valor de soma.
**Siete de sus dieciséis knobs YA eran sistema** y no entran al ratio: las
opacidades de fantasma y deshabilitado, los dos anillos de foco y el anillo

@ -83,7 +83,9 @@
/* ── Preview (floating pointer follower) ─ */
[data-drag-drop-preview] {
pointer-events: none;
z-index: var(--drag-drop-preview-z);
/* z-index is soma's: it writes 9999 INLINE on this node, so a declaration
here can never win. Measured on the real node mid-drag (F2-B adversarial
review, 2026-08-22); the token it used to read was retired with it. */
padding: var(--drag-drop-preview-padding-block) var(--drag-drop-preview-padding-inline);
border-radius: var(--drag-drop-preview-radius);
border: var(--drag-drop-preview-border-width) solid var(--_drag-drop-palette-solid);

@ -4358,7 +4358,6 @@
--drag-drop-preview-font-family: var(--style-label-font-family);
--drag-drop-preview-font-size: var(--size-sm-font-size);
--drag-drop-preview-shadow: var(--shadow-raised);
--drag-drop-preview-z: var(--z-index-overlay-detached);
--navigation-menu-content-z: var(--z-index-overlay-inline);
--navigation-menu-indicator-w: 0px;
--navigation-menu-indicator-x: 0px;

@ -7191,7 +7191,11 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
// The lift under the ghost. The accent ring stacked on top of it comes from
// the palette forward and stays private.
'preview-shadow': 'var(--shadow-raised)',
'preview-z': 'var(--z-index-overlay-detached)',
// NO `preview-z`: soma writes `z-index: 9999` INLINE on the preview
// (`drag-drop-provider.svelte.ts:663,671`), so no cascade can win. Measured
// on the REAL node during an actual drag — the adversarial review of F2-B,
// 2026-08-22. Same class as carousel's retired `item-gap*`: a value channel
// of soma, not a theme surface (next-features §13).
// Selection palette (THM-2) — `data-color` sits on `[data-drag-drop-root]`.
'_palette-solid': {
parts: ['root'],

Loading…
Cancel
Save

Powered by TurnKey Linux.