You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/next-features.md

1560 lines
101 KiB

feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
---
title: Next features — initiative registry
type: registry
audience: human + agent
authority: backlog of user-decided future initiatives — records scope, sequencing and dependencies; it does NOT record verdicts (those live in the RFCs / changelog / book-deviations when executed)
status: living — items enter by explicit user decision during audits and sessions; remove an item when it ships (and link where it landed)
created: 2026-07-06
---
# Next features — initiative registry
Rules of this file: one entry per initiative, dated, with its origin (which
audit/session raised it), its scope in one paragraph, and its dependencies.
Entries are **registered, not started** — starting one is a session decision.
Detail lives in the linked doc, never copied here.
## 1. Tonal-ramp contrast parity (measure, then construct) — 2026-07-06 · Stage 1 DONE 2026-07-19 · Stage 2 CLOSED 2026-07-20
> **Stage 1 SHIPPED (2026-07-19).** Measured with `scripts/contrast-audit.ts`
> (33 scales × 2 modes); drift + user verdicts ratified as doctrine in
> [`theming/reference.md §40`](./theming/reference.md) + [`changelog.md §44`](./theming/changelog.md).
> Outcome: `text·11` = secondary tier, `text-strong` = AA-guaranteed; the border
> vocabulary is Radix-subtle by design (only `solid·9` clears 3:1 by
> construction) with a decorative-vs-load-bearing split; the focus ring is a
> config axis (`primitives.focusRing` + `color.focus`, single `outline` per §32)
> whose shipped default is soft — hardening is a config-value choice, kept as-is.
> **Stage 2 CLOSED (2026-07-20) — no solver needed.** The 6 decisions were
> locked (D1 band · D2 measured-pass · D3 flip-only · D4 opt-in · D5 runtime-first
> · D6 now), then **execution disproved the premise**: the template morph does not
> _compute_ text contrast, it **inherits** it (steps 11/12 copy the donor's L-curve
> verbatim; L-driven contrast is ~chroma-invariant under gamut-mapping), so any
> scale generated from a §40-compliant donor library clears the ratified text
> floors **by construction**. Verified on three banks — authored base, leave-one-out
> regeneration, and 45 out-of-distribution seeds — **0 hard-gate failures**
> (min WCAG 9.7:1). The luminance solver would have had nothing to resolve for
> realistic inputs → dropped as speculative. **Delivered instead:** the ratified
> pair table as shared data (`$color` → `CONTRAST_PAIRS`), the audit refactored to
> consume it + a morph-generated regression bank (`scripts/contrast-audit.ts`), and
> a CI guard that locks the inheritance (`eidos/lib/contrast-invariant.test.ts` —
> fails if a future donor/generator breaks it). With D2, the base stays verbatim
> ground-truth; **no base→seeds migration**.
> **Plan + decision + outcome record:**
> [`process/contrast-stage2-plan-2026-07.md`](./process/contrast-stage2-plan-2026-07.md);
> chronicle [`changelog.md §45`](./theming/changelog.md).
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
**Origin**: A.7 follow-up (theming audit F). The on-solid pair is now computed
at generation with a single criterion ([`rfcs/rfc-color-engine.md`](./rfcs/rfc-color-engine.md)
§8, `eidos/lib/on-solid.ts`). M3 still guarantees more: contrast **by
construction across its whole tonal ramp** (container/on-container pairs at
fixed tone deltas — ΔT 40 ≈ 3:1, ΔT 50 ≈ 4.5:1). Our equivalent promises
(text-11 on bg-2/element-3, border-7 vs bg-2 at the 3:1 non-text floor, text
over composited soft surfaces a2/a3) rest on Radix-style curation, unmeasured.
**Stage 1 — pair-contract verification** (the A.7 pattern generalized): a
canonical table of slot pairs + floors (a user-decided doctrine session),
a measurement pass over pairs × 33 scales × 2 modes at generation
(`on-solid.ts` generalizes; the compositing math exists in `$color`), and the
resulting drift list goes to user verdicts (retune vs annotated exception).
Infrastructure is small; the fallout is the unknown.
**Stage 2 — by-construction generator (CLOSED 2026-07-20, solver dropped).** The
plan proposed a luminance solver that resolves each step to satisfy Stage 1's
pair table for a seed. Execution disproved its premise: the morph **inherits**
text contrast from the donor (steps 11/12 copy the donor's L-curve verbatim;
L-driven contrast is ~chroma-invariant under gamut-mapping), so every scale from
a §40-compliant donor library clears the ratified floors by construction. Measured
across three banks (authored base, leave-one-out regeneration, 45 out-of-distribution
seeds): **0 hard-gate failures, min WCAG 9.7:1.** The solver had nothing to
resolve for realistic inputs → dropped (speculative). What shipped: the pair
table as shared data (`$color` `CONTRAST_PAIRS`), the audit consuming it + a
morph-generated regression bank, and a CI guard (`eidos/lib/contrast-invariant.test.ts`)
that locks the inheritance. The BASE stays verbatim ground-truth (D2); no
base→seeds migration.
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
**Sequence**: Stage 1 first (it is the spec for Stage 2). No M3-style `on-*`
vocabulary expansion — our slots already encode the pairs; the table
formalizes them without inflating the namespace.
## 2. Per-theme palette-contrast cascade emission — 2026-07-06
**Origin**: A.7 execution. The per-instance `palette-contrast` cascade lives
in the STATIC foundation — one polarity for all themes. Today polarity is
computed per configured theme and unanimous (33/33 scales agree across
base-light/base-dark); on disagreement the `*-light` theme wins
(`computeLightSolidScales`, documented in
[`rfcs/rfc-color-engine.md`](./rfcs/rfc-color-engine.md) §8). **Trigger**: the
day a real configured theme disagrees on a scale's polarity, emit the cascade
per-theme instead of statically. Not before — no speculative machinery.
## 3. Component audit (post-system) — 2026-07-06
**Origin**: user decision during the theming audit — "de momento no tocamos
los componentes, merecen una auditoría adicional una vez que se haya resuelto
estas". Gate: the system-level audit (F/G) closes first. Registered items so
far, to seed its scope:
- **Named-styles adoption by controls** (from A.3): controls/recipes that
hand-declare typography instead of consuming `--style-*` /
size-bundle typography — likely by error, to be confirmed per component.
- **Letter-spacing bundle adoption**: ~34 recipes consume `font-size` /
`line-height` from the size bundle but not `letter-spacing`; plus ~7 raw
letter-spacing literals in component CSS.
- **Tier-A `data-depth` stamping coverage**: overlays that should stamp their
plane (dialog / drawer / tooltip / menubar / combobox) per
[`rfcs/rfc-depth.md`](./rfcs/rfc-depth.md) §5.
- **Calendar family → shared layer/archetype** (user design verdict,
2026-07-07): the calendar-grid vocabulary (accent set, day geometry, the
holiday/event marks fixed in audit B.2) becomes a FIRST-CLASS shared layer
— today the family borrows it informally (measured: range-calendar
consumes 79 `--calendar-*` tokens, month-grid 65, year-grid 65,
date-range-picker 38, date-picker 4 — ~250 cross-component borrows), and
**chronos will draw from it too** when its track lands. Materialization to
decide at execution: a morfo part-archetype for the day cell (the 26-set
grows) and/or a `calendar-surface` shared recipe à la list-surface
(`--list-item-*` precedent). This supersedes "per-component composition
repairs" for the B.2 borrowers: the marks reach the whole family through
the shared layer instead.
- **Field family composition — segments under `[data-field]`** (user design
verdict, 2026-07-07, audit B.1): segment height derives from the SIZE axis
at the FIELD family level — `field` recipe declares `segment-height` with
size-scoped declarations (`calc(var(--field-control-height-{k}) -
var(--space-2))`; the TSC `size:` scope axis exists, unused so far), and
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
the per-component copies die (each x-field today re-declares the whole
`height-{xs..xl}` family the field recipe already has). **MANDATED by the
B.1-(i) verdict**: date/time/color-field MUST incorporate the Field
wrapper (segments under `[data-field][data-size]` — probed live: today
they don't) as part of the component audit; the three broken
per-component `segment-height` tokens and their consumers were removed
(they had computed `auto` since birth — zero visual change).
- Whatever F/G registers as "component-level adoption gap" in
`docs/audit/theming-audit.md` when that report lands (deliverable G of the
2026-07 theming audit).
## 4. `architecture/sema.md` hold-sync — 2026-07-06
**Origin**: A.4/A.5 execution (holds single-source). `SEMA_HOLDS_BY_INTENT`
(`src/uix/sema/holds.ts`) is now THE only hold table (resolver + visual
channel consume it; `sema-map.ts`'s per-family hold column died). The chapter
[`architecture/sema.md`](./architecture/sema.md) still narrates per-family
holds in ~34 places. **Blocked**: the file is the author's WIP (uncommitted
edits) — sync it when the author's pass lands, not before.
## 6. THM-2 rollout — per-instance palette to all surfaces — SHIPPED 2026-07-12; scope boundaries REVERTED by the open-color-cage initiative (2026-07-18/19)
Shipped in `05f0031c` (media-player Batch-4 + 14 surfaces + vocab + 4 props)
and `cc7e9a16` (avatar root). Every per-component palette cascade now collapses
into the shared `--palette-*` layer (size win, behavior-preserving for roles);
the vocabulary gained the alpha `surface`/`surface-hover` + `hover` slots.
> **REVERSED (2026-07-18/19) — "abrir la jaula del color".** THM-2 originally
> kept semantic controls (checkbox/radio/stepper/editable/select/file-upload)
> and `Avatar.Badge` role-restricted "by design". A later user decision reverses
> that: `color` accepts the FULL system (role / intent / 33 scales / raw CSS =
> `ComponentColorProp`) on **every** component, no exceptions — content-ink
> primitives keep their axis as an additive union. Enforced by two guards in
> `recipe-css-contract.test.ts`. Full record + the closed queue (incl. the
> chart SVG resolver and the Card/Avatar custom-color fix):
> [`process/open-color-cage-2026-07.md`](./process/open-color-cage-2026-07.md).
> Only `qr-code` stays out (its `color` is the QR module ink, not a scale).
feat(theming+soma): media-player Batch-4 (SOM-4) + THM-2 shared-palette rollout to 14 surfaces media-player Batch-4 (closes SOM-4): the 10 sub-parts migrate to renderProps() (35 attr-dup hits -> 0). Register the sources the morfo reads (label propRef, on stateRef, direction/type) so renderProps emits type/role/aria-*/data-state; keep only contract-only soma-owned attrs. Pin the soma-attr-audit ceiling to 0. THM-2 rollout - collapse each per-component palette cascade into the shared per-instance layer (size win, behavior-preserving for roles, adds the 33 scales): - Rename palette tokens to canonical _palette-{slot} + route via the presence-guarded forward: tags-input, stepper, badge, card, select (parts-aware trigger+content), file-upload, editable, tag-group. - Extend the shared vocabulary with alpha surface(a2)/surface-hover(a3) slots (soft variant of variant-aware recipes) and hover(4) - the latter also fixes Toggle's latent scale-hover. - tag-group: variant-aware migration (solid/outline -> canonical, soft -> alpha surface; merge outline-fg into text, prune orphans). - toggle-group: replace the 4-role cross-recipe composition with a hand-authored presence-scoped route to the shared layer (the composition DSL can't presence-scope); covers roles + 33 scales. - Widen color to ComponentColor (roles + 33 scales) where arbitrary color is meaningful: badge, tag-group, tags-input, toggle-group, and add the scale picker to their demos. Semantic controls stay restricted. Verified end-to-end with the REAL components (drive the demo color control -> the component emits data-color -> the shared layer resolves the exact scale). Gates: recipe-css-contract/config/contracts/ soma-attr-audit/media-player suites green; 0 orphan tokens. avatar remains for a focused pass: its variant-resolved matrix and badge sub-part don't fit a mechanical migration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
## 5. Book editorial note — "el hold es suelo, no tijera" — 2026-07-06
**Origin**: A.4/A.5 verdict. The mechanism (hold = floor for the expression,
never a scissor; expression waits animation-finish after the hold, capped by
an absolute `MAX_EXPRESSION_WAIT_MS`, never derived from the hold) is recorded
in [`decisions/book-deviations.md`](./decisions/book-deviations.md) D.12 as a
candidate **editorial appendix** for the book (_Diseñando lo que ocurre_,
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
HOMOGENEIZADO edition) — the book gives qualitative regions and this is the
materialization doctrine worth feeding back to the source.
uix(chat): bloque de mensajería chat-* v1 (log · message · composer · typing) Familia chat-* completa, componiendo el ecosistema (Feed + VirtualList anclado, Textarea, FileUpload, Avatar, Popover, Icon). Transporte-agnóstica: el modelo de datos es de la app, los componentes reciben props + snippets. - chat-log: Feed + VirtualList(anchor:'end') en triple registro; day separators, unread divider, filas de sistema (Feed.Article), pill "N nuevos" + notifyIncoming (announce a live-region), to-latest distance-aware. - chat-message: raíz Feed.Article; runs ($libs/chat), ticks de entrega, reply embebido, reacciones (chip pressed + tapback panel), acciones hover. - chat-composer: Textarea (doble registro) + FileUpload; teclado Enter/Shift+Enter/Esc/ArrowUp; typing saliente debounced (uix.timers); barra reply/edit. - chat-typing: sustain-streaming (visual-only, loop del sistema); aggregateTypers 1/2/3+ con plantillas plurales. - $libs/chat: groupIntoRuns (ventana 8 min estilo Discord) + aggregateTypers. - VirtualList: extensión anchor:'end' (followOutput, prepend por clave estable, sticky monótono, onAtEndChange) — sin scroller paralelo. Diseño de referencia (WhatsApp/Telegram, pase lado-a-lado): burbujas suaves (out = primary-element + texto normal; in = surface card + sombra) sobre lienzo muted del log, meta dentro de la burbuja, tapback bar horizontal, iconos lucide del ecosistema (nada de SVG a mano). Contrato de tamaños: size="md" = 16px canónico. Fixes de framework (afectaban más allá de chat): - EngineLangs.t() ahora pela el prefijo '#?': los idlangrefs imperativos (pagination/file-upload/chronos/chat) caían al fallback EN en locale es. Fix parseLangRef ?? parsePathFallback + test de regresión (132/132). - virtual-list commit-set-resize con channels:[]: es bookkeeping de ResizeObserver, no gesto → dejaba de emitir beep/vibrate pre-interacción (interventions de navigator.vibrate / AudioContext). Escaparate messenger en /uix/demos/chat (bot de eco con uix.timers; variante $connection señalada). Gaps v2 registrados en next-features §7. Pase: component:audit 4×PASS · eidos-lint invalid=0 · recipe-contract 29/29 · Playwright escaparate PASS end-to-end · consola limpia · npm run check en baseline (76, cero errores nuevos). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
## 7. Chat block v2 — 2026-07-18
**Origin**: the `chat-*` v1 initiative (comparative study + plan, session
2026-07-18; user scoped v1 to the room core). v1 shipped `chat-log`
(Feed + end-anchored VirtualList), `chat-message`, `chat-composer`
(Textarea + FileUpload composition) and `chat-typing`, plus the
`$libs/chat` helpers and the VirtualList `anchor: 'end'` extension. The
deferred surface, registered with disposition (each item also lives in its
component README §Gaps):
uix(chat): v2 · chat-list — panel de conversaciones (compone Listbox) Componente nuevo del bloque chat v2: el panel de lista de salas/DMs. Compone el Listbox del ecosistema (doble registro id/ref compartido): selección única + roving-tabindex + typeahead + role=option vienen de Listbox; chat-list aporta la disposición de fila (avatar · título/preview · meta) y el estado visual data-unread (negrita + Badge compuesto). - morfo: expression 'delegated', sin eventos propios (D.1 — el actor es el Listbox compuesto); parts provider/item(data-unread)/avatar/title/preview/meta. - soma: ChatListProvider crea ListboxProvider dentro (value string <-> string[]); ChatListItemProvider per-instance (unread = boolean crudo); 4 clases de contenido explicitas. - eidos: rejilla avatar | (titulo/preview) | meta, avatar y meta abarcan las dos filas; truncado; unread -> titulo en negrita. No neutraliza el arquetipo item (la conversacion ES una opcion seleccionable). Sin @keyframes; ejes logicos. - demo: 5 salas, seleccion limpia unread, typing-en-lista (compone ChatTyping), Badge de no-leidos; tabs live/system/api/morfo/a11y. - READMEs dobles (eidos con Passive justification, delegated); next-features 7 marca chat-list SHIPPED. Verificacion: check baseline 76 (0 nuevos) · component:audit PASS · eidos-lint invalid=0 · recipe-contract 30/30 · morfo:vocabulary 0 · Playwright PASS (role=listbox, 5 salas, aria-selected, unread=2, badges=2, typing-en-lista, click + teclado seleccionan). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
- ~~**`chat-list`** — the conversation-list pane (rooms, unread badges,
typing-in-list).~~ **✅ SHIPPED 2026-07-19** — composes `Listbox`
(single-select + keyboard) with an avatar/title/preview/meta row; unread
bold + `Badge`, typing-in-list composes `ChatTyping`. Virtualization for
very long lists deferred (pairs with the VirtualList bidirectional item).
uix(chat): v2 · emoji-picker (picker completo) + escaparate rico del bloque Nuevo componente emoji-picker y rediseño del escaparate de mensajeria. emoji-picker (ruta 9 fases): - Compone Popover (open/close/foco/portal) + Command (busqueda + grid + nav de teclado + a11y combobox/listbox, su "grid mode" documentado) + dos ToggleGroup (categorias + tono de piel). Delegated: sin eventos propios (D.1); el grid y las celdas doble-registran Command.List/Item, conservando la a11y pero con contrato visual propio (gana especificidad con selectores de 2 niveles, sin tocar attrs [data-command-*] ajenos — eidos-lint limpio). - Datos: $libs/emoji, dataset vendorizado de emojibase (MIT) horneado por scripts/generate-emoji-data.ts (precedente lucide: JSON crudo en static/ + script generador). ~1906 emoji con nombre CLDR + keywords, 316 con tono de piel single-person. Grid acotado (categoria activa o resultados de busqueda), sin virtualizacion (refinamiento v2). Helpers puros + test (12/12). - morfo delegated (7 partes + header/footer de layout); soma (root + trigger + content + grid + option + preview, provider-test 4/4); eidos (panel empaquetado, receta con tokens --emoji-picker-*); langs es/en; demo 5 tabs (excepcion delegated documentada); READMEs dobles. Escaparate del bloque (web/routes/uix/demos/chat) — rehecho rico y correcto: - Avatares por-autor con color solido (Avatar del ecosistema): antes gris neutro indefinido, ahora teal/ambar/verde/indigo/primary bien contrastados. - Dos columnas: chat-list (sidebar con badges + typing-en-lista) + sala. - Conversacion rica: separador de dia, run agrupado, reaccion, reply citado, recibos de lectura, typing con avatar, y el emoji-picker integrado en el composer (inserta en el draft). - Solape de burbujas (transitorio, estimateSize=48 con mensajes ~90-126px) mitigado subiendo el estimate a 76. Auditoria de tamanos/espacios: recipes del bloque limpios (todo con tokens --space-*/--size-*/--color-*); unico literal (punto tono-capable) tokenizado. Verificacion: check baseline 76 (0 nuevos) · component:audit emoji-picker PASS · eidos-lint invalid=0 · recipe-contract 30/30 · morfo:vocabulary 0 · emoji lib 12/12 · provider-test 4/4 · Playwright PASS (busqueda 'rocket'->rocket+ astronautas, click->onSelect+cierra, teclado, tono oscuro 👋->👋🏿, picker abre en el composer). Capturas claro miradas. next-features §7 marca emoji-picker SHIPPED. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
- ~~**`emoji-picker`** — a real picker component (color-picker scale:
categories, search, skin tones).~~ **✅ SHIPPED 2026-07-21** — composes
`Popover` + `Command` (search + grid keyboard + combobox/listbox a11y, its
documented "grid mode") + two `ToggleGroup`s (categories + skin tone). Data is
the vendored `$libs/emoji` (baked from emojibase, MIT — `scripts/generate-emoji-data.ts`):
~1,900 emoji, 316 tone-capable. The grid double-registers Command's
List/Item so it keeps the a11y but wears its own grid-cell styling. Delegated
(no own events). Full-grid virtualization + per-emoji tone + image emoji
(Twemoji/Noto) deferred. The quick-set Popover in `ChatMessage.ReactionAdd`
stays as the inline tapback bar (different UX).
uix(chat): bloque de mensajería chat-* v1 (log · message · composer · typing) Familia chat-* completa, componiendo el ecosistema (Feed + VirtualList anclado, Textarea, FileUpload, Avatar, Popover, Icon). Transporte-agnóstica: el modelo de datos es de la app, los componentes reciben props + snippets. - chat-log: Feed + VirtualList(anchor:'end') en triple registro; day separators, unread divider, filas de sistema (Feed.Article), pill "N nuevos" + notifyIncoming (announce a live-region), to-latest distance-aware. - chat-message: raíz Feed.Article; runs ($libs/chat), ticks de entrega, reply embebido, reacciones (chip pressed + tapback panel), acciones hover. - chat-composer: Textarea (doble registro) + FileUpload; teclado Enter/Shift+Enter/Esc/ArrowUp; typing saliente debounced (uix.timers); barra reply/edit. - chat-typing: sustain-streaming (visual-only, loop del sistema); aggregateTypers 1/2/3+ con plantillas plurales. - $libs/chat: groupIntoRuns (ventana 8 min estilo Discord) + aggregateTypers. - VirtualList: extensión anchor:'end' (followOutput, prepend por clave estable, sticky monótono, onAtEndChange) — sin scroller paralelo. Diseño de referencia (WhatsApp/Telegram, pase lado-a-lado): burbujas suaves (out = primary-element + texto normal; in = surface card + sombra) sobre lienzo muted del log, meta dentro de la burbuja, tapback bar horizontal, iconos lucide del ecosistema (nada de SVG a mano). Contrato de tamaños: size="md" = 16px canónico. Fixes de framework (afectaban más allá de chat): - EngineLangs.t() ahora pela el prefijo '#?': los idlangrefs imperativos (pagination/file-upload/chronos/chat) caían al fallback EN en locale es. Fix parseLangRef ?? parsePathFallback + test de regresión (132/132). - virtual-list commit-set-resize con channels:[]: es bookkeeping de ResizeObserver, no gesto → dejaba de emitir beep/vibrate pre-interacción (interventions de navigator.vibrate / AudioContext). Escaparate messenger en /uix/demos/chat (bot de eco con uix.timers; variante $connection señalada). Gaps v2 registrados en next-features §7. Pase: component:audit 4×PASS · eidos-lint invalid=0 · recipe-contract 29/29 · Playwright escaparate PASS end-to-end · consola limpia · npm run check en baseline (76, cero errores nuevos). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
- **Mention autocomplete (`@`)** — combobox-in-textarea for
`chat-composer`; pairs with the future `words`/`palabras` integration.
- **Threads UI** — the side-panel composition over the already-shipped
`Feed.Thread` semantics.
- ~~**Jump-to-message highlight** — `signal-emphasize-target` on
`chat-message` (the morfo event was deliberately left out of v1).~~
**✅ SHIPPED 2026-07-19** — `emphasized` prop → `data-emphasized` row flash
(visual-only, no sema event: reference apps flash silently; the app scrolls
via a reply's `onJump` then toggles `emphasized` for ~1.4s).
- ~~**Read-by list** — "seen by N" avatar row (Teams/Messenger pattern) on
`chat-message`.~~ **✅ SHIPPED 2026-07-19** — `ChatMessage.ReadBy` part
(the app composes `AvatarGroup`; the sender's `onMessageSeen` feeds it).
uix(chat): bloque de mensajería chat-* v1 (log · message · composer · typing) Familia chat-* completa, componiendo el ecosistema (Feed + VirtualList anclado, Textarea, FileUpload, Avatar, Popover, Icon). Transporte-agnóstica: el modelo de datos es de la app, los componentes reciben props + snippets. - chat-log: Feed + VirtualList(anchor:'end') en triple registro; day separators, unread divider, filas de sistema (Feed.Article), pill "N nuevos" + notifyIncoming (announce a live-region), to-latest distance-aware. - chat-message: raíz Feed.Article; runs ($libs/chat), ticks de entrega, reply embebido, reacciones (chip pressed + tapback panel), acciones hover. - chat-composer: Textarea (doble registro) + FileUpload; teclado Enter/Shift+Enter/Esc/ArrowUp; typing saliente debounced (uix.timers); barra reply/edit. - chat-typing: sustain-streaming (visual-only, loop del sistema); aggregateTypers 1/2/3+ con plantillas plurales. - $libs/chat: groupIntoRuns (ventana 8 min estilo Discord) + aggregateTypers. - VirtualList: extensión anchor:'end' (followOutput, prepend por clave estable, sticky monótono, onAtEndChange) — sin scroller paralelo. Diseño de referencia (WhatsApp/Telegram, pase lado-a-lado): burbujas suaves (out = primary-element + texto normal; in = surface card + sombra) sobre lienzo muted del log, meta dentro de la burbuja, tapback bar horizontal, iconos lucide del ecosistema (nada de SVG a mano). Contrato de tamaños: size="md" = 16px canónico. Fixes de framework (afectaban más allá de chat): - EngineLangs.t() ahora pela el prefijo '#?': los idlangrefs imperativos (pagination/file-upload/chronos/chat) caían al fallback EN en locale es. Fix parseLangRef ?? parsePathFallback + test de regresión (132/132). - virtual-list commit-set-resize con channels:[]: es bookkeeping de ResizeObserver, no gesto → dejaba de emitir beep/vibrate pre-interacción (interventions de navigator.vibrate / AudioContext). Escaparate messenger en /uix/demos/chat (bot de eco con uix.timers; variante $connection señalada). Gaps v2 registrados en next-features §7. Pase: component:audit 4×PASS · eidos-lint invalid=0 · recipe-contract 29/29 · Playwright escaparate PASS end-to-end · consola limpia · npm run check en baseline (76, cero errores nuevos). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
- **VirtualList chat-mode refinements** — bidirectional sparse ranges for
jump-into-history far from the loaded window.
- ~~**Typing avatars** — faces next to the `chat-typing` dots (Messenger).~~
**✅ SHIPPED 2026-07-19** — `ChatTyping.Avatars` slot (compose `AvatarGroup`).
uix(chat): bloque de mensajería chat-* v1 (log · message · composer · typing) Familia chat-* completa, componiendo el ecosistema (Feed + VirtualList anclado, Textarea, FileUpload, Avatar, Popover, Icon). Transporte-agnóstica: el modelo de datos es de la app, los componentes reciben props + snippets. - chat-log: Feed + VirtualList(anchor:'end') en triple registro; day separators, unread divider, filas de sistema (Feed.Article), pill "N nuevos" + notifyIncoming (announce a live-region), to-latest distance-aware. - chat-message: raíz Feed.Article; runs ($libs/chat), ticks de entrega, reply embebido, reacciones (chip pressed + tapback panel), acciones hover. - chat-composer: Textarea (doble registro) + FileUpload; teclado Enter/Shift+Enter/Esc/ArrowUp; typing saliente debounced (uix.timers); barra reply/edit. - chat-typing: sustain-streaming (visual-only, loop del sistema); aggregateTypers 1/2/3+ con plantillas plurales. - $libs/chat: groupIntoRuns (ventana 8 min estilo Discord) + aggregateTypers. - VirtualList: extensión anchor:'end' (followOutput, prepend por clave estable, sticky monótono, onAtEndChange) — sin scroller paralelo. Diseño de referencia (WhatsApp/Telegram, pase lado-a-lado): burbujas suaves (out = primary-element + texto normal; in = surface card + sombra) sobre lienzo muted del log, meta dentro de la burbuja, tapback bar horizontal, iconos lucide del ecosistema (nada de SVG a mano). Contrato de tamaños: size="md" = 16px canónico. Fixes de framework (afectaban más allá de chat): - EngineLangs.t() ahora pela el prefijo '#?': los idlangrefs imperativos (pagination/file-upload/chronos/chat) caían al fallback EN en locale es. Fix parseLangRef ?? parsePathFallback + test de regresión (132/132). - virtual-list commit-set-resize con channels:[]: es bookkeeping de ResizeObserver, no gesto → dejaba de emitir beep/vibrate pre-interacción (interventions de navigator.vibrate / AudioContext). Escaparate messenger en /uix/demos/chat (bot de eco con uix.timers; variante $connection señalada). Gaps v2 registrados en next-features §7. Pase: component:audit 4×PASS · eidos-lint invalid=0 · recipe-contract 29/29 · Playwright escaparate PASS end-to-end · consola limpia · npm run check en baseline (76, cero errores nuevos). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
- **Delegate-family ↔ agent chat** — the Aura reservation (§F6 of the
scene/pack initiative) once agent conversations land.
**Deps**: none to start `chat-list`/`emoji-picker`; mentions depend on the
`words` integration decision. The transport stays app-land (`$connection`).
feat(eidos): `Barcode` — el código de barras 1D, con encoder propio y cero dependencias Ocho simbologías desde su estándar ISO publicado (Code 128 con auto-switching A/B/C, EAN-13/8, UPC-A/E, Code 39, ITF/ITF-14) en `$libs/barcode`: tablas transcritas de la especificación o derivadas de su regla de construcción, sin una sola dependencia npm añadida. JsBarcode y bwip-js son referencia de corrección, jamás import — la misma doctrina con la que `$libs/qr` usó a Nayuki. Ningún headless del mercado trae barcode: el terreno lo ocupan encoders imperativos sin anatomía ni a11y. - **La física manda sobre la ergonomía visual.** La dimensión X (`moduleWidth`) es el ancla de escaneabilidad y la altura de barra un eje libre — un escáner lee una franja horizontal. Por eso la geometría es px absolutos en el viewBox (precedente: la familia `chart`) y no el token `size` cuadrado del QR: colapsar los dos ejes en uno haría que la X dependiera de la altura, que es justo lo que no puede flotar. La zona muda va en módulos, con el mínimo de cada estándar. - **Lo que hace reconocible a la familia.** Las guardas EAN/UPC bajan hasta la línea base y los dígitos se imprimen dígito-bajo-dígito sobre su celda de 7 módulos, en mono (la HRI del estándar es OCR-B, de ancho fijo). ITF-14 lleva su barra portadora por fuera de la zona muda, que no puede comerse. - **Un valor no codificable es un ESTADO, no una excepción.** `encode` lanza un `BarcodeError` tipado; el wrapper lo captura, marca `data-invalid`, avisa por `onInvalid` (deduplicado por razón: `encoded` es un objeto nuevo por pulsación) y deja un marco placeholder decorativo. Un EAN a medio teclear es un paso normal de edición; dejarlo reventar dentro de un `$derived` tumbaría la página. - **`'text'` entra en `MorfoElement`** — y también en el schema de sium, que tiene su propia lista literal. Añadir solo la unión de TypeScript compila y falla en runtime; lo cazó `morfo:check`, no `npm run check`. Verificación: 48 tests de encoder en tres capas (invariantes estructurales de cada tabla · vectores publicados transcritos aparte · decoders independientes que releen la retícula), la lectura real confirmada con escáner por el usuario, y la retícula decodificada desde el SVG ya pintado en navegador. `component:audit` PASS con 0 errores; los 2 warnings (D-1.5 / D-4.3) son los mismos que da `button`, la demo canaria: sus regex buscan la forma v1 inline del MutationObserver y del `emit`, que la v2 movió al harness compartido. Demo v2 canónica de 9 pestañas. El alcance diferido (Codabar/MSI/Pharmacode, GS1-128, add-ons EAN-2/5, y las 2D como componentes aparte) queda registrado en `next-features.md` §10 — el hueco de numeración se cierra cuando el track de blocks commitee sus §8/§9, hoy sin commitear. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## 8. `blocks` tier — reusable page-function compositions — 2026-07-21
**Origin**: ecosystem analysis session 2026-07-21; user decision: a new tier
for **blocks** — named compositions of canon components performing a page
function (sticky site header, hero, footer, app shell, docs shell…). Scope:
tier infrastructure (`src/uix/blocks/` + `$blocks`, doctrine at
`architecture/blocks.md`, B contract, `blocks-check` guard), **7 canon base
components first** (`sticky` · `empty-state` · `result` · `callout` ·
`prose` · `anchor-nav` · `sidebar` — full 9-phase route each), then the
block catalog (10 site + 10 app + 3 docs). Admission rule mirrors packs:
any behavior with contract surface is promoted to canon BEFORE the block
composes it. Detail + per-item fiches: the execution plan at
[`process/PLAN-blocks.md`](./process/PLAN-blocks.md).
**Status**: D-BLK decisions SIGNED 2026-07-21 (D-BLK.1 amended by the user:
the tier lives at `src/uix/blocks/`, not `src/blocks/`). **F0 SHIPPED
2026-07-21** (`069759604`: doctrine + `$blocks` + `blocks:check` + gallery).
**Reference study SHIPPED 2026-07-21** (`aff42fee0`:
[`process/RESEARCH-blocks-references.md`](./process/RESEARCH-blocks-references.md),
6 research tracks; every phase-0 now checks against it). Amendments E-1…E-5
SIGNED 2026-07-21: F1 = **8** canon components (+`nav-tree`, data-driven docs
navigation tree); parity-floor = v1 scope policy; F2 = **14** site blocks
(+banner · team · contact · content-section); distribution registered as §9;
⌘K stays app-land.
**F3 STARTED 2026-08-19** — `app-shell` shipped with a signed phase 0 (four
decisions: the block wires no services, two scroll models by prop, the top bar
inside the inset, generated bypass links) and a new canon component in front of
it, `SkipLink` (WCAG 2.4.1). It closes ledger row **A-95**, the one thing the
tier could not fix from inside any block: with the shell owning the page's
heights, an in-flow notice and a pinned header overlap by **0px** (measured;
before: 49px, with the hit test landing on the strip).
**Deps**: `scheduler` block gated on `chronos` landing; chat-room block
explicitly NOT here (canon `chat-*` family, §7 above).
## 9. Blocks distribution — registry + llms.txt + MCP — 2026-07-21
**Origin**: the blocks reference study (E-4 signed 2026-07-21; dossier
[`process/RESEARCH-blocks-references.md`](./process/RESEARCH-blocks-references.md)
§P6). The ecosystem's 2026 table stakes for component/block distribution are
agent-enumerable catalogs: a registry protocol (shadcn `registry.json` /
`registry-item.json`), `llms.txt` (+ `llms-full.txt` / `.md` content
negotiation) over the docs corpus, and an MCP server so agents can list and
install blocks (sv-blocks precedent). Scope when started: (a) `llms.txt`
generation from the docs corpus (cheap — the corpus is already Markdown,
pairs with F4 docs blocks); (b) a blocks/components registry manifest; (c)
an MCP server exposing catalog + morfo contracts (pairs with the
contract-introspection superation angle and the palabras F-B agentive
track). Registered, not started.
**Deps**: F2 catalog existing (something to distribute); F4.3 props-table
work shares the morfo-introspection substrate.
feat(eidos): `Barcode` — el código de barras 1D, con encoder propio y cero dependencias Ocho simbologías desde su estándar ISO publicado (Code 128 con auto-switching A/B/C, EAN-13/8, UPC-A/E, Code 39, ITF/ITF-14) en `$libs/barcode`: tablas transcritas de la especificación o derivadas de su regla de construcción, sin una sola dependencia npm añadida. JsBarcode y bwip-js son referencia de corrección, jamás import — la misma doctrina con la que `$libs/qr` usó a Nayuki. Ningún headless del mercado trae barcode: el terreno lo ocupan encoders imperativos sin anatomía ni a11y. - **La física manda sobre la ergonomía visual.** La dimensión X (`moduleWidth`) es el ancla de escaneabilidad y la altura de barra un eje libre — un escáner lee una franja horizontal. Por eso la geometría es px absolutos en el viewBox (precedente: la familia `chart`) y no el token `size` cuadrado del QR: colapsar los dos ejes en uno haría que la X dependiera de la altura, que es justo lo que no puede flotar. La zona muda va en módulos, con el mínimo de cada estándar. - **Lo que hace reconocible a la familia.** Las guardas EAN/UPC bajan hasta la línea base y los dígitos se imprimen dígito-bajo-dígito sobre su celda de 7 módulos, en mono (la HRI del estándar es OCR-B, de ancho fijo). ITF-14 lleva su barra portadora por fuera de la zona muda, que no puede comerse. - **Un valor no codificable es un ESTADO, no una excepción.** `encode` lanza un `BarcodeError` tipado; el wrapper lo captura, marca `data-invalid`, avisa por `onInvalid` (deduplicado por razón: `encoded` es un objeto nuevo por pulsación) y deja un marco placeholder decorativo. Un EAN a medio teclear es un paso normal de edición; dejarlo reventar dentro de un `$derived` tumbaría la página. - **`'text'` entra en `MorfoElement`** — y también en el schema de sium, que tiene su propia lista literal. Añadir solo la unión de TypeScript compila y falla en runtime; lo cazó `morfo:check`, no `npm run check`. Verificación: 48 tests de encoder en tres capas (invariantes estructurales de cada tabla · vectores publicados transcritos aparte · decoders independientes que releen la retícula), la lectura real confirmada con escáner por el usuario, y la retícula decodificada desde el SVG ya pintado en navegador. `component:audit` PASS con 0 errores; los 2 warnings (D-1.5 / D-4.3) son los mismos que da `button`, la demo canaria: sus regex buscan la forma v1 inline del MutationObserver y del `emit`, que la v2 movió al harness compartido. Demo v2 canónica de 9 pestañas. El alcance diferido (Codabar/MSI/Pharmacode, GS1-128, add-ons EAN-2/5, y las 2D como componentes aparte) queda registrado en `next-features.md` §10 — el hueco de numeración se cierra cuando el track de blocks commitee sus §8/§9, hoy sin commitear. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## 10. Barcode — deferred symbologies + GS1 profiles — 2026-07-29
**Origin**: the `Barcode` component (shipped 2026-07-29; design record +
dispositions in
[`eidos/components/barcode/README.md`](../src/uix/eidos/components/barcode/README.md)
§Gaps). v1 covers the Retail + Logistics set — Code 128 (A/B/C auto), EAN-13/8,
UPC-A/E, Code 39, ITF/ITF-14 — from their published ISO standards, in the
zero-dependency `$libs/barcode` encoder.
**Shipped the same day, out of this registry: the ISBN input profile.** An ISBN
barcode IS an EAN-13 (ISO 2108, Bookland 978/979), so it entered as a profile
over `ean13` — separators tolerated in the numeric symbologies, ISBN-10 read and
converted after validating its own base-11 check digit — and explicitly NOT as a
ninth `symbology` value, which would make `data-symbology` claim a standard that
does not exist. The doctrine is recorded in the component README §ISBN.
Four dispositions stay **diferir**, each a self-contained follow-up over the
same encoder:
feat(eidos): `Barcode` — el código de barras 1D, con encoder propio y cero dependencias Ocho simbologías desde su estándar ISO publicado (Code 128 con auto-switching A/B/C, EAN-13/8, UPC-A/E, Code 39, ITF/ITF-14) en `$libs/barcode`: tablas transcritas de la especificación o derivadas de su regla de construcción, sin una sola dependencia npm añadida. JsBarcode y bwip-js son referencia de corrección, jamás import — la misma doctrina con la que `$libs/qr` usó a Nayuki. Ningún headless del mercado trae barcode: el terreno lo ocupan encoders imperativos sin anatomía ni a11y. - **La física manda sobre la ergonomía visual.** La dimensión X (`moduleWidth`) es el ancla de escaneabilidad y la altura de barra un eje libre — un escáner lee una franja horizontal. Por eso la geometría es px absolutos en el viewBox (precedente: la familia `chart`) y no el token `size` cuadrado del QR: colapsar los dos ejes en uno haría que la X dependiera de la altura, que es justo lo que no puede flotar. La zona muda va en módulos, con el mínimo de cada estándar. - **Lo que hace reconocible a la familia.** Las guardas EAN/UPC bajan hasta la línea base y los dígitos se imprimen dígito-bajo-dígito sobre su celda de 7 módulos, en mono (la HRI del estándar es OCR-B, de ancho fijo). ITF-14 lleva su barra portadora por fuera de la zona muda, que no puede comerse. - **Un valor no codificable es un ESTADO, no una excepción.** `encode` lanza un `BarcodeError` tipado; el wrapper lo captura, marca `data-invalid`, avisa por `onInvalid` (deduplicado por razón: `encoded` es un objeto nuevo por pulsación) y deja un marco placeholder decorativo. Un EAN a medio teclear es un paso normal de edición; dejarlo reventar dentro de un `$derived` tumbaría la página. - **`'text'` entra en `MorfoElement`** — y también en el schema de sium, que tiene su propia lista literal. Añadir solo la unión de TypeScript compila y falla en runtime; lo cazó `morfo:check`, no `npm run check`. Verificación: 48 tests de encoder en tres capas (invariantes estructurales de cada tabla · vectores publicados transcritos aparte · decoders independientes que releen la retícula), la lectura real confirmada con escáner por el usuario, y la retícula decodificada desde el SVG ya pintado en navegador. `component:audit` PASS con 0 errores; los 2 warnings (D-1.5 / D-4.3) son los mismos que da `button`, la demo canaria: sus regex buscan la forma v1 inline del MutationObserver y del `emit`, que la v2 movió al harness compartido. Demo v2 canónica de 9 pestañas. El alcance diferido (Codabar/MSI/Pharmacode, GS1-128, add-ons EAN-2/5, y las 2D como componentes aparte) queda registrado en `next-features.md` §10 — el hueco de numeración se cierra cuando el track de blocks commitee sus §8/§9, hoy sin commitear. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Codabar · MSI · Pharmacode** — the niche tail JsBarcode still ships. Pure
table work: each is a symbology module + its vectors.
- **GS1-128 (FNC1 + Application Identifier parsing)** — a _profile_ of Code 128,
feat(eidos): `Barcode` — el código de barras 1D, con encoder propio y cero dependencias Ocho simbologías desde su estándar ISO publicado (Code 128 con auto-switching A/B/C, EAN-13/8, UPC-A/E, Code 39, ITF/ITF-14) en `$libs/barcode`: tablas transcritas de la especificación o derivadas de su regla de construcción, sin una sola dependencia npm añadida. JsBarcode y bwip-js son referencia de corrección, jamás import — la misma doctrina con la que `$libs/qr` usó a Nayuki. Ningún headless del mercado trae barcode: el terreno lo ocupan encoders imperativos sin anatomía ni a11y. - **La física manda sobre la ergonomía visual.** La dimensión X (`moduleWidth`) es el ancla de escaneabilidad y la altura de barra un eje libre — un escáner lee una franja horizontal. Por eso la geometría es px absolutos en el viewBox (precedente: la familia `chart`) y no el token `size` cuadrado del QR: colapsar los dos ejes en uno haría que la X dependiera de la altura, que es justo lo que no puede flotar. La zona muda va en módulos, con el mínimo de cada estándar. - **Lo que hace reconocible a la familia.** Las guardas EAN/UPC bajan hasta la línea base y los dígitos se imprimen dígito-bajo-dígito sobre su celda de 7 módulos, en mono (la HRI del estándar es OCR-B, de ancho fijo). ITF-14 lleva su barra portadora por fuera de la zona muda, que no puede comerse. - **Un valor no codificable es un ESTADO, no una excepción.** `encode` lanza un `BarcodeError` tipado; el wrapper lo captura, marca `data-invalid`, avisa por `onInvalid` (deduplicado por razón: `encoded` es un objeto nuevo por pulsación) y deja un marco placeholder decorativo. Un EAN a medio teclear es un paso normal de edición; dejarlo reventar dentro de un `$derived` tumbaría la página. - **`'text'` entra en `MorfoElement`** — y también en el schema de sium, que tiene su propia lista literal. Añadir solo la unión de TypeScript compila y falla en runtime; lo cazó `morfo:check`, no `npm run check`. Verificación: 48 tests de encoder en tres capas (invariantes estructurales de cada tabla · vectores publicados transcritos aparte · decoders independientes que releen la retícula), la lectura real confirmada con escáner por el usuario, y la retícula decodificada desde el SVG ya pintado en navegador. `component:audit` PASS con 0 errores; los 2 warnings (D-1.5 / D-4.3) son los mismos que da `button`, la demo canaria: sus regex buscan la forma v1 inline del MutationObserver y del `emit`, que la v2 movió al harness compartido. Demo v2 canónica de 9 pestañas. El alcance diferido (Codabar/MSI/Pharmacode, GS1-128, add-ons EAN-2/5, y las 2D como componentes aparte) queda registrado en `next-features.md` §10 — el hueco de numeración se cierra cuando el track de blocks commitee sus §8/§9, hoy sin commitear. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
not a new encoder: the FNC1 code value plus an AI grammar so the HRI can print
the bracketed form. The natural pair for a logistics-facing app, and the same
shape the ISBN profile already established.
- **EAN-2 / EAN-5 add-ons + the upper `ISBN 978-…` line** — the periodical /
book supplements and the book-cover convention; they extend the EAN geometry
(a second text band, a composed add-on symbol) rather than replacing it. Hard
constraint recorded with them: **we do not hyphenate**. The hyphen positions
are not computable from the number — they need the International ISBN
Agency's prefix-range tables, versioned data with an expiry date, which is a
maintenance clock a zero-dependency library must not swallow. The hyphenated
string comes from the consumer's catalogue.
feat(eidos): `Barcode` — el código de barras 1D, con encoder propio y cero dependencias Ocho simbologías desde su estándar ISO publicado (Code 128 con auto-switching A/B/C, EAN-13/8, UPC-A/E, Code 39, ITF/ITF-14) en `$libs/barcode`: tablas transcritas de la especificación o derivadas de su regla de construcción, sin una sola dependencia npm añadida. JsBarcode y bwip-js son referencia de corrección, jamás import — la misma doctrina con la que `$libs/qr` usó a Nayuki. Ningún headless del mercado trae barcode: el terreno lo ocupan encoders imperativos sin anatomía ni a11y. - **La física manda sobre la ergonomía visual.** La dimensión X (`moduleWidth`) es el ancla de escaneabilidad y la altura de barra un eje libre — un escáner lee una franja horizontal. Por eso la geometría es px absolutos en el viewBox (precedente: la familia `chart`) y no el token `size` cuadrado del QR: colapsar los dos ejes en uno haría que la X dependiera de la altura, que es justo lo que no puede flotar. La zona muda va en módulos, con el mínimo de cada estándar. - **Lo que hace reconocible a la familia.** Las guardas EAN/UPC bajan hasta la línea base y los dígitos se imprimen dígito-bajo-dígito sobre su celda de 7 módulos, en mono (la HRI del estándar es OCR-B, de ancho fijo). ITF-14 lleva su barra portadora por fuera de la zona muda, que no puede comerse. - **Un valor no codificable es un ESTADO, no una excepción.** `encode` lanza un `BarcodeError` tipado; el wrapper lo captura, marca `data-invalid`, avisa por `onInvalid` (deduplicado por razón: `encoded` es un objeto nuevo por pulsación) y deja un marco placeholder decorativo. Un EAN a medio teclear es un paso normal de edición; dejarlo reventar dentro de un `$derived` tumbaría la página. - **`'text'` entra en `MorfoElement`** — y también en el schema de sium, que tiene su propia lista literal. Añadir solo la unión de TypeScript compila y falla en runtime; lo cazó `morfo:check`, no `npm run check`. Verificación: 48 tests de encoder en tres capas (invariantes estructurales de cada tabla · vectores publicados transcritos aparte · decoders independientes que releen la retícula), la lectura real confirmada con escáner por el usuario, y la retícula decodificada desde el SVG ya pintado en navegador. `component:audit` PASS con 0 errores; los 2 warnings (D-1.5 / D-4.3) son los mismos que da `button`, la demo canaria: sus regex buscan la forma v1 inline del MutationObserver y del `emit`, que la v2 movió al harness compartido. Demo v2 canónica de 9 pestañas. El alcance diferido (Codabar/MSI/Pharmacode, GS1-128, add-ons EAN-2/5, y las 2D como componentes aparte) queda registrado en `next-features.md` §10 — el hueco de numeración se cierra cuando el track de blocks commitee sus §8/§9, hoy sin commitear. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **2D — DataMatrix · PDF417 · Aztec** — explicitly NOT this component: each is a
whole different encoder (own Reed–Solomon, matrix layout). They belong beside
`QrCode` as their own components, sharing the display-primitive shape.
**Deps**: none — the encoder's `BarcodeResult` (module lattice + guards + HRI
groups in module space) already generalises; the 2D entries would instead reuse
the `$libs/qr` shape.
uix(background): el fondo se mueve con el scroll, y con lo que el lector no pidió no F3 (parallax) + F4 (demo y registro documental) + las correcciones de sus dos auditorías, en un commit porque viven en los mismos ficheros: la demo enseña los ejes que F3 añade, y separarlas dejaría un estado que nunca se probó. Cinco maneras de que una capa deje de estarse quieta: `speed` (cuánto del token de travel cubre mientras el anfitrión cruza el viewport), `bleed` (crece más allá del anfitrión para que el viaje no arrastre su propio borde), `depth` (la deriva contra el puntero), `spotlight` y `attach='fixed'`. El travel es CSS: `animation-timeline: view()` lo gobierna desde la posición de scroll, sin listener ni rAF. Sólo donde el motor no lo trae, la pila arranca `ScrollProgress` y escribe `--background-progress`, que la rama `@supports not` mete en la MISMA declaración; los dos caminos no pueden estar vivos a la vez porque el JS comprueba la condición idéntica con `CSS.supports`, y ambos se paran bajo `prefers-reduced-motion` — el parallax es movimiento atado al scroll del propio lector, que es justo la clase que provoca síntomas vestibulares. **La decisión que no estaba prevista.** El scroll y el puntero quieren mover la MISMA capa, y una animación sobre `translate` gana a cualquier declaración estática: el puntero habría dejado de existir sin más. Así que el scroll anima una custom property REGISTRADA (`@property`, o interpolaría a saltos) y un único `translate` compone los dos términos. Medido: parallax solo → `0px 30px`; con el puntero arriba-derecha y `depth: 20px` → `20px 10px`. Es `translate` y nunca el shorthand `transform`, la misma ley que sigue el lift del draggable con `scale`. El precio, dicho porque en la primera redacción escribí lo contrario tres veces: una custom property NO se puede compositar, así que el navegador recalcula estilo cada frame. Para una decoración es el intercambio correcto —una property por capa que viaja, ninguna bajo reduced motion— pero «va en el compositor» era falso y ahora el código dice lo que ocurre. **Dos footguns cerrados por forma, no por disciplina:** - `attach='fixed'` se DECLARA desde la capa y la pila se recorta sola. Antes había que escribirlo también en la pila, y olvidarlo dejaba la capa `position: fixed` pintando a sangre por todo el viewport, detrás de todo y sin error (un hijo fijo se escapa de `overflow: clip`; sólo un `clip-path` lo trae de vuelta). La prop de la pila desaparece: no hay nada que olvidar. - Un `speed` negativo —una capa que se mueve contra el scroll— invertía el bleed: la capa ENCOGÍA y enseñaba justo los bordes que el bleed tapa. Ahora usa la magnitud. **Lo que costó medición**: el shorthand `animation` pone `duration: 0s` y una línea de tiempo de progreso necesita el `auto` inicial, así que con el shorthand la capa no se movía nunca (van longhands, con el porqué escrito) · mi listener de puntero pedía un frame y no lo liberaba si el rect salía degenerado, matando el puntero para el resto de la sesión (reescrito sin frame, con el rect cacheado e invalidado por `pointerenter` y `observeResize`) · las cuatro registraciones —`animated`, `pointer`, `scroll`, `fixed`— comparten un solo sitio, `declare.svelte.ts`, donde vive la regla A30 y su segunda mitad: registrar desde el init, y seguir el prop sin escribir en la primera pasada. **La demo** (`/uix/components/background`, v2, nueve pestañas) monta un ANFITRIÓN de verdad en el escenario, porque este componente es invisible por sí solo y sin padre no se puede enseñar lo único que importa: que el padre se adopta y el layout no se mueve. Los chips son uniones completas verificadas por el TIPO (`Record<Union, 0>`): un miembro que falte es error de compilación. Y fue la demo la que destapó que, con A30, encender `animate` en caliente no hacía aparecer el control de pausa — el registro era un hecho de montaje. Invisible en una sonda, obvio con un interruptor. Registro documental (D-BG.11): `next-features.md` §11 · la frase en `design-text-effects.md` (el mismo corte canon/pack leído desde el otro lado) · `PLAN-blocks-quality.md` Q0.3 → sucesor · `surface/README.md` §Gaps «scrim de autoría» CERRADO por `Background.Scrim` · glosario con entrada `Background` y `Aura` corregida (decía «Not built yet» y está construido) · y en `motion-guide.md` §8 + el RFC: el travel ligado al scroll no es un preset —un preset nombra una transición discreta CON duración, y esto es modulación continua sin ninguna— y sólo se replantea como dominio con un segundo consumidor. Verificado en Chrome real: el puntero mueve `depth` y `spotlight` con los valores exactos y vuelven al centro al salir · `attach='fixed'` estampa y retira el recorte de la pila · el bleed aguanta el speed negativo · RTL: el `translate` del puntero se mantiene FÍSICO y el bleed en el eje de bloque · cada control de la demo cambia algo (los de `spotlight` y `depth` no llegaban a tres de las cuatro clases de capa hasta la segunda auditoría). ⚠️ SIN VERIFICAR, y no lo doy por bueno: el travel real al hacer scroll, los 60 fps y el detector de reflow. El panel del navegador va oculto con viewport 0×0 y ahí las animaciones scroll-driven declaradas en CSS no se activan — comprobado que es del ENTORNO con un caso mínimo inyectado (un `div` pelado con `animation-timeline: view()` sale inactivo mientras una `ViewTimeline` creada por API sobre el mismo sujeto marca 68%). Necesita una pasada con Chrome visible. Gates: audit `--only background` PASS 0 errores · eidos-lint invalid 0 · `rtl:check` 0/180 · `docs:check` 0/0 en 634 docs · `blocks:check` 0/18 · `morfo:check` PASS · smoke PASS · 441/442 (el fallo es el `skin-media-player` de siempre) · `check` 0 errores propios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## 11. Background — parallax, the scrim scale, and the pack seam — 2026-08-18
**Origin**: the `Background` component (design record, measurements and
dispositions in
[`eidos/components/background/README.md`](../src/uix/eidos/components/background/README.md);
plan and signed decisions in
[`process/PLAN-background.md`](./process/PLAN-background.md)). It ships as the
HOST of a surface's background layers — a child that pins itself behind its
parent's content, with the parent adopted by a foundation rule rather than
wrapped. Patterns, gradients, scrims, images and video are in; so is the WCAG
2.2.2 pause control.
**Scope of this registry entry** — what the component deliberately does NOT own
yet, each a self-contained follow-up:
- **Parallax and the scroll seam** — `animation-timeline: view()` with a
`ScrollProgress` fallback, `attach='fixed'`, the pointer `depth` axis. The
infrastructure landed with the component (`$adom`'s `ScrollProgress` and
`prefersReducedData`); the layers that consume it did not. It is the one
follow-up with a mechanism already chosen and measured against browser
support, so it is also the one most likely to move first.
- **The scrim `strength` scale is not ordered by weight** — measured: `subtle`
veils MORE than `overlay`, and `overlay` and `muted` resolve to the same
number. The names come from the semantic opacity scale, which says how opaque
an ELEMENT is; borrowing them for a VEIL imports an ordering that means
something else. Renaming or renumbering is an API change, not a fix in
passing.
- **A bright photograph is illegible at every scrim weight** — white copy over
the heaviest veil measures below AA. Either the scale gains a step above
`--opacity-subtle`, or the doctrine becomes "the answer is a graded scrim",
stated once and demonstrated.
- **A shared `<video>` state port** — the framework has a load-state contract
for `<img>` (`ImageProvider` in `soma/layers`) and none for `<video>`:
`ScrollFrames` listens by hand and so does `Background.Video`. Two consumers
is the threshold this codebase uses to extract a layer.
- **The `Ambient` pack honouring the pause** — the canon publishes the context
(`paused`, `reduced`, `seen`); the pack reads it. A task OF the pack, because
the dependency runs pack → framework and never back.
**Deps**: the parallax entry needs no new service — the two `$adom` ports it
consumes already shipped. The scrim entries are the author's call on the token
vocabulary, so they block on a decision, not on code.
docs(next-features): el registro de incidencias del eje theme-reach Encargo del autor: anotar todas las incidencias de esta clase para resolverlas después. Van al registro canónico (`next-features.md`), no a un sitio nuevo, y con su MEDICIÓN — no como impresión. **§12 — El contrato de cascada del velo de estado.** Seis incidencias que resultaron ser la misma: nadie fijó nunca cómo compone el velo de `archetypes.css` con las reglas de una receta. Y la raíz explica por qué se manifiestan de formas tan distintas: el velo se pinta con DOS pesos según el arquetipo — `:where()` (0,0,0) para `trigger`, especificidad plena (0,5,0) para `item`/`option`. 131 declaraciones-atajo en 45 componentes cancelan el velo (66 BASE · 32 ACENTO · 23 HOVER · 10 OTRA; cero valores con gradiente, así que el paso a longhand no tiene riesgo técnico; 32 nodos velados que hoy no reaccionan al ratón pasarían a hacerlo) · la prop `hoverable` de table no suprime nada · en tree-grid el empate (0,5,0) lo decide el ORDEN DE CARGA y varía entre recargas · la banda de tree-grid mata el hover en las filas pares · un token de tinta no puede ganar al arquetipo (`selected-row-fg` se retiró por eso) · el thumb de scroll-area no tiene velo al que migrar. Esto explica de paso la adopción 21/135 que midió la auditoría del 2026-07-01: el velo estaba escrito y estructuralmente derrotado. **§13 — Huecos de instrumento y de demo.** Lo que hizo que una medición mintiera o no existiera, que importa porque el eje entero se apoya en ellas: falsos negativos del centinela sobre propiedades transicionadas, pseudo-elementos y componentes compuestos que no ve, la sonda que no pasa el ratón por un `<tr>`, y la lección general —**una sonda sobre una parte que la demo no monta compara CERO valores y pasa**—, con la lista de partes condicionales afectadas. El handoff gana la regla para las sesiones del eje: si una incidencia mueve píxel o toca morfo, se mide, se anota ahí y se sigue; no se arregla dentro del commit del componente. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## 12. El contrato de cascada del velo de estado — 2026-08-20
**Origen**: el eje `theme-reach` (F2-A, sesión del 2026-08-20). Al tokenizar
componente a componente aparecieron, medidas una a una, seis incidencias que
son **la misma**: nadie fijó nunca cómo compone el velo de `archetypes.css` con
las reglas de una receta. No se tocan dentro del eje de theming — mueven píxel
y son decisión aparte — pero cada una está medida y localizada.
**La raíz**: `archetypes.css` pinta el velo neutro como
`background-image: linear-gradient(var(--state-hover), var(--state-hover))`, y
lo hace con DOS pesos distintos según el arquetipo — `:where(...)` (0,0,0) para
`trigger`, y especificidad PLENA (0,5,0) para `item` / `option`, «INTENTIONAL
cross-component accent», dice su comentario. Una receta que declare fondo sobre
el mismo nodo entra en una relación que nunca se escribió.
### Las seis, con su medición
1. **El atajo `background:` cancela el velo — 131 declaraciones en 45
componentes.** El atajo fija `background-image: none`; sobre un arquetipo
velado por `:where()` eso lo mata para siempre, no sólo en hover. Medido con
`scripts/__statelayer-analysis.ts`, que comprueba contra el morfo compilado
que el atajo cae sobre el nodo VELADO y no sobre cualquiera. Por clase de
regla: **66 BASE** (mecánicas: pasar a `background-color` no mueve el
reposo), **32 ACENTO** (selected/checked/open: §38 pide `background-color`
para que el velo COMPONGA encima), **23 HOVER** (la migración de la firma 3)
y **10 OTRA** (disabled, dragging, variantes). **Cero valores con
gradiente**, así que el único riesgo técnico del paso a longhand no existe
en este catálogo. El píxel que se mueve: **32 nodos velados que hoy no
reaccionan al ratón** pasarían a hacerlo. Esto explica la adopción 21/135
que midió la auditoría del 2026-07-01: el velo estaba escrito y
estructuralmente derrotado.
2. **La prop `hoverable` de `table` no suprime nada.** El velo de `item` es
incondicional y a (0,5,0), así que una fila se ilumina al pasar el ratón
aunque `hoverable` esté apagada — verificado con hover real, y ocurre desde
antes de tocar la receta. O el arquetipo decide y la prop sobra, o la fila
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4 Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba más dura que la del autor), sonda x2 por componente para fijar el suelo de ruido. 34 hipótesis · 26 refutadas · 8 reales. Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs; command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 · listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano» de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación; eidos-lint 0 invalid. Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command 8.091 y carousel 2.842 valores, 0 diffs): 1. command: el re-point de `--command-radius` dentro del Dialog era una declaración muerta con comentario falso — la misma regla pone el radio a 0 y el input lee `--command-input-radius`, nunca este token (medido: `--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px` movía la paleta inline y no la del dialog). Retirado; comentarios y README reescritos con lo medido. 2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo mientras la horizontal leía `active-indicator-scale` — el token alcanzaba media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora lo leen las dos. 3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap` INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es la prueba. Canal de valor de soma, como --gp-current-gradient. Censo carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto (5.190 · 2.102). 4. table: `striped-row-bg` no alcanza NADA en la demo publicada — `:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel → PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`); adjudicado en el ledger mientras tanto. El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas medidas, y la cuarta era de la sonda: - el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el token de reposo leía muerto (era el «falso negativo sin causa» de §13); - el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de `{c}-root` no lo recibían; - `::before`/`::after` eran invisibles (el aro de buffering, el spinner); - la no-determinación de feed no era una animación sin localizar: era la demo aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma `commit-settle` anima box-shadow sobre el nodo medido. Con la espera de asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas. Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y que carousel convirtió en patrón — un estilo inline de otra capa es invisible para todo análisis estático): `npm run theming:sentinel -- <c> <url>` (scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en todos los nodos del componente, pseudos, pase de hover, settle) + ledger scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 · gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46 excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró; y dos muertos nuevos los produjeron los propios arreglos (el blur apaga focus-input-border; el settle desmonta el spinner de feed) — adjudicados con su porqué. Doctrina en recipe-contract §4. También: tres cifras de especificidad de next-features §12 recontadas (la banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0); y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el velo en la celda), el comentario de listbox que mandaba pisar el privado `--_listbox-max-height` apunta ahora al público, y quedan registrados en §13 los dos `header-z` con literal '2' y el spinner de media-player a 720ms mientras el gemelo de feed lleva tokens. Guards: censo global 43 % intacto · audit --only command+carousel PASS · eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs 0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en HEAD y no se tocan). Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el arquetipo en fila+celda, y que soma lea el gap de un token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
no es un `item` para el sistema. Decisión de MORFO. **Precisión medida
2026-08-21** (revisión adversarial, `elementsFromPoint`): en `table` el
arquetipo `item` está declarado en la fila Y EN LA CELDA
(`morfo/components/table.ts:132` y `:155`), y el nodo que recibe el velo
bajo el puntero es la CELDA — la decisión de morfo debe contemplar los dos
portadores, no sólo la fila.
docs(next-features): el registro de incidencias del eje theme-reach Encargo del autor: anotar todas las incidencias de esta clase para resolverlas después. Van al registro canónico (`next-features.md`), no a un sitio nuevo, y con su MEDICIÓN — no como impresión. **§12 — El contrato de cascada del velo de estado.** Seis incidencias que resultaron ser la misma: nadie fijó nunca cómo compone el velo de `archetypes.css` con las reglas de una receta. Y la raíz explica por qué se manifiestan de formas tan distintas: el velo se pinta con DOS pesos según el arquetipo — `:where()` (0,0,0) para `trigger`, especificidad plena (0,5,0) para `item`/`option`. 131 declaraciones-atajo en 45 componentes cancelan el velo (66 BASE · 32 ACENTO · 23 HOVER · 10 OTRA; cero valores con gradiente, así que el paso a longhand no tiene riesgo técnico; 32 nodos velados que hoy no reaccionan al ratón pasarían a hacerlo) · la prop `hoverable` de table no suprime nada · en tree-grid el empate (0,5,0) lo decide el ORDEN DE CARGA y varía entre recargas · la banda de tree-grid mata el hover en las filas pares · un token de tinta no puede ganar al arquetipo (`selected-row-fg` se retiró por eso) · el thumb de scroll-area no tiene velo al que migrar. Esto explica de paso la adopción 21/135 que midió la auditoría del 2026-07-01: el velo estaba escrito y estructuralmente derrotado. **§13 — Huecos de instrumento y de demo.** Lo que hizo que una medición mintiera o no existiera, que importa porque el eje entero se apoya en ellas: falsos negativos del centinela sobre propiedades transicionadas, pseudo-elementos y componentes compuestos que no ve, la sonda que no pasa el ratón por un `<tr>`, y la lección general —**una sonda sobre una parte que la demo no monta compara CERO valores y pasa**—, con la lista de partes condicionales afectadas. El handoff gana la regla para las sesiones del eje: si una incidencia mueve píxel o toca morfo, se mide, se anota ahí y se sigue; no se arregla dentro del commit del componente. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
3. **En `tree-grid` el empate lo decide el ORDEN DE CARGA.** El velo de `item`
y el hover de la receta pesan EXACTAMENTE lo mismo (0,5,0), así que gana la
hoja que cargue después — y en el dev server varía entre recargas: seis
corridas de la misma configuración dieron cuatro «sin velo» y dos «con
velo». Un empate resuelto por orden es la misma fragilidad que el canon ya
documenta para las bandas de `z-index`.
4. **En `tree-grid` la banda mata el hover.** La regla de `striped` pesa
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4 Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba más dura que la del autor), sonda x2 por componente para fijar el suelo de ruido. 34 hipótesis · 26 refutadas · 8 reales. Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs; command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 · listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano» de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación; eidos-lint 0 invalid. Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command 8.091 y carousel 2.842 valores, 0 diffs): 1. command: el re-point de `--command-radius` dentro del Dialog era una declaración muerta con comentario falso — la misma regla pone el radio a 0 y el input lee `--command-input-radius`, nunca este token (medido: `--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px` movía la paleta inline y no la del dialog). Retirado; comentarios y README reescritos con lo medido. 2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo mientras la horizontal leía `active-indicator-scale` — el token alcanzaba media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora lo leen las dos. 3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap` INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es la prueba. Canal de valor de soma, como --gp-current-gradient. Censo carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto (5.190 · 2.102). 4. table: `striped-row-bg` no alcanza NADA en la demo publicada — `:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel → PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`); adjudicado en el ledger mientras tanto. El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas medidas, y la cuarta era de la sonda: - el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el token de reposo leía muerto (era el «falso negativo sin causa» de §13); - el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de `{c}-root` no lo recibían; - `::before`/`::after` eran invisibles (el aro de buffering, el spinner); - la no-determinación de feed no era una animación sin localizar: era la demo aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma `commit-settle` anima box-shadow sobre el nodo medido. Con la espera de asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas. Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y que carousel convirtió en patrón — un estilo inline de otra capa es invisible para todo análisis estático): `npm run theming:sentinel -- <c> <url>` (scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en todos los nodos del componente, pseudos, pase de hover, settle) + ledger scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 · gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46 excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró; y dos muertos nuevos los produjeron los propios arreglos (el blur apaga focus-input-border; el settle desmonta el spinner de feed) — adjudicados con su porqué. Doctrina en recipe-contract §4. También: tres cifras de especificidad de next-features §12 recontadas (la banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0); y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el velo en la celda), el comentario de listbox que mandaba pisar el privado `--_listbox-max-height` apunta ahora al público, y quedan registrados en §13 los dos `header-z` con literal '2' y el spinner de media-player a 720ms mientras el gemelo de feed lleva tokens. Guards: censo global 43 % intacto · audit --only command+carousel PASS · eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs 0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en HEAD y no se tocan). Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el arquetipo en fila+celda, y que soma lea el gap de un token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
(0,8,0) — siete atributos más el `:nth-of-type` (la cifra (0,7,0) que se
anotó primero contaba uno de menos; recontada 2026-08-21) — contra los
(0,5,0) del hover y usa el atajo: en las filas pares no ocurre NADA al
pasar el ratón. El hover del componente es inconsistente entre filas.
docs(next-features): el registro de incidencias del eje theme-reach Encargo del autor: anotar todas las incidencias de esta clase para resolverlas después. Van al registro canónico (`next-features.md`), no a un sitio nuevo, y con su MEDICIÓN — no como impresión. **§12 — El contrato de cascada del velo de estado.** Seis incidencias que resultaron ser la misma: nadie fijó nunca cómo compone el velo de `archetypes.css` con las reglas de una receta. Y la raíz explica por qué se manifiestan de formas tan distintas: el velo se pinta con DOS pesos según el arquetipo — `:where()` (0,0,0) para `trigger`, especificidad plena (0,5,0) para `item`/`option`. 131 declaraciones-atajo en 45 componentes cancelan el velo (66 BASE · 32 ACENTO · 23 HOVER · 10 OTRA; cero valores con gradiente, así que el paso a longhand no tiene riesgo técnico; 32 nodos velados que hoy no reaccionan al ratón pasarían a hacerlo) · la prop `hoverable` de table no suprime nada · en tree-grid el empate (0,5,0) lo decide el ORDEN DE CARGA y varía entre recargas · la banda de tree-grid mata el hover en las filas pares · un token de tinta no puede ganar al arquetipo (`selected-row-fg` se retiró por eso) · el thumb de scroll-area no tiene velo al que migrar. Esto explica de paso la adopción 21/135 que midió la auditoría del 2026-07-01: el velo estaba escrito y estructuralmente derrotado. **§13 — Huecos de instrumento y de demo.** Lo que hizo que una medición mintiera o no existiera, que importa porque el eje entero se apoya en ellas: falsos negativos del centinela sobre propiedades transicionadas, pseudo-elementos y componentes compuestos que no ve, la sonda que no pasa el ratón por un `<tr>`, y la lección general —**una sonda sobre una parte que la demo no monta compara CERO valores y pasa**—, con la lista de partes condicionales afectadas. El handoff gana la regla para las sesiones del eje: si una incidencia mueve píxel o toca morfo, se mide, se anota ahí y se sigue; no se arregla dentro del commit del componente. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
5. **Un token de receta no puede ganar al arquetipo.** La regla de `item` fija
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4 Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba más dura que la del autor), sonda x2 por componente para fijar el suelo de ruido. 34 hipótesis · 26 refutadas · 8 reales. Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs; command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 · listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano» de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación; eidos-lint 0 invalid. Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command 8.091 y carousel 2.842 valores, 0 diffs): 1. command: el re-point de `--command-radius` dentro del Dialog era una declaración muerta con comentario falso — la misma regla pone el radio a 0 y el input lee `--command-input-radius`, nunca este token (medido: `--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px` movía la paleta inline y no la del dialog). Retirado; comentarios y README reescritos con lo medido. 2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo mientras la horizontal leía `active-indicator-scale` — el token alcanzaba media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora lo leen las dos. 3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap` INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es la prueba. Canal de valor de soma, como --gp-current-gradient. Censo carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto (5.190 · 2.102). 4. table: `striped-row-bg` no alcanza NADA en la demo publicada — `:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel → PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`); adjudicado en el ledger mientras tanto. El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas medidas, y la cuarta era de la sonda: - el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el token de reposo leía muerto (era el «falso negativo sin causa» de §13); - el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de `{c}-root` no lo recibían; - `::before`/`::after` eran invisibles (el aro de buffering, el spinner); - la no-determinación de feed no era una animación sin localizar: era la demo aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma `commit-settle` anima box-shadow sobre el nodo medido. Con la espera de asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas. Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y que carousel convirtió en patrón — un estilo inline de otra capa es invisible para todo análisis estático): `npm run theming:sentinel -- <c> <url>` (scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en todos los nodos del componente, pseudos, pase de hover, settle) + ledger scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 · gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46 excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró; y dos muertos nuevos los produjeron los propios arreglos (el blur apaga focus-input-border; el settle desmonta el spinner de feed) — adjudicados con su porqué. Doctrina en recipe-contract §4. También: tres cifras de especificidad de next-features §12 recontadas (la banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0); y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el velo en la celda), el comentario de listbox que mandaba pisar el privado `--_listbox-max-height` apunta ahora al público, y quedan registrados en §13 los dos `header-z` con literal '2' y el spinner de media-player a 720ms mientras el gemelo de feed lleva tokens. Guards: censo global 43 % intacto · audit --only command+carousel PASS · eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs 0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en HEAD y no se tocan). Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el arquetipo en fila+celda, y que soma lea el gap de un token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
también `color` en el estado seleccionado, a (0,4,0) contra los (0,3,0) de
la receta (la cifra (0,5,0) anotada primero era la de la variante `:hover`
de la misma lista; recontada 2026-08-21): un `table.selected-row-fg` no
podía mover nada nunca. Se retiró
docs(next-features): el registro de incidencias del eje theme-reach Encargo del autor: anotar todas las incidencias de esta clase para resolverlas después. Van al registro canónico (`next-features.md`), no a un sitio nuevo, y con su MEDICIÓN — no como impresión. **§12 — El contrato de cascada del velo de estado.** Seis incidencias que resultaron ser la misma: nadie fijó nunca cómo compone el velo de `archetypes.css` con las reglas de una receta. Y la raíz explica por qué se manifiestan de formas tan distintas: el velo se pinta con DOS pesos según el arquetipo — `:where()` (0,0,0) para `trigger`, especificidad plena (0,5,0) para `item`/`option`. 131 declaraciones-atajo en 45 componentes cancelan el velo (66 BASE · 32 ACENTO · 23 HOVER · 10 OTRA; cero valores con gradiente, así que el paso a longhand no tiene riesgo técnico; 32 nodos velados que hoy no reaccionan al ratón pasarían a hacerlo) · la prop `hoverable` de table no suprime nada · en tree-grid el empate (0,5,0) lo decide el ORDEN DE CARGA y varía entre recargas · la banda de tree-grid mata el hover en las filas pares · un token de tinta no puede ganar al arquetipo (`selected-row-fg` se retiró por eso) · el thumb de scroll-area no tiene velo al que migrar. Esto explica de paso la adopción 21/135 que midió la auditoría del 2026-07-01: el velo estaba escrito y estructuralmente derrotado. **§13 — Huecos de instrumento y de demo.** Lo que hizo que una medición mintiera o no existiera, que importa porque el eje entero se apoya en ellas: falsos negativos del centinela sobre propiedades transicionadas, pseudo-elementos y componentes compuestos que no ve, la sonda que no pasa el ratón por un `<tr>`, y la lección general —**una sonda sobre una parte que la demo no monta compara CERO valores y pasa**—, con la lista de partes condicionales afectadas. El handoff gana la regla para las sesiones del eje: si una incidencia mueve píxel o toca morfo, se mide, se anota ahí y se sigue; no se arregla dentro del commit del componente. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
con su declaración muerta (un token que no mueve nada es un token que
miente). Mientras el arquetipo pinte tinta, ningún componente puede
tematizar la suya en ese estado.
6. **`scroll-area.thumb-bg-hover` no tiene velo al que migrar.** Su thumb lleva
`archetype: 'thumb'`, que `archetypes.css` no vela, así que la firma 3 no
puede ejecutarse ahí sin cambiar el arquetipo — decisión de MORFO. Es el
único knob de los seis de la cola que sigue sin resolver.
uix(grid-list): temable — 0 % → 85 %, y una selección que nunca pintó 38 claves nuevas en el bloque `grid-list` de `recipes/base.ts` (que ya existía con los dos forwards de paleta: se FUSIONA, no se añade un segundo). Alcance del censo 0 % → 85 % (22 de 26 puntuables); global 45 % → 46 %, sin contrato 49 → 48. El eje `size` se estampa en el PROVIDER —el wrapper de eidos no tiene parte `root`—, así que los tres nombres resueltos van SIN `parts`: molde `command`, no `table`/`tree-grid`. Fuera los cuatro bloques `[data-size]` del CSS; los emite el TSC. Altura de fila 1:1 con el bundle, `row-padding-inline` con ritmo propio, y la tipografía un paso por debajo de md en adelante — verbatim, como table y tree-grid. LO QUE LA MEDICIÓN CAMBIÓ (la §4 generada tenía un error de doctrina y dos defectos que sólo aparecieron midiendo; el §5 Veredicto se escribió con la sonda delante, porque estaba vacío): 1. La selección de fila NUNCA HA PINTADO. La receta seleccionaba `[data-grid-list-row][data-selected]`, un atributo que ni el morfo declara ni soma estampa (estampa `data-state='selected'` + `aria-selected`): la regla no casaba jamás y su acento `--_grid-list-palette-element` no llegó nunca al píxel — una fila seleccionada muestra el velo NEUTRO del arquetipo, indistinguible del hover (capturado). Retirada la declaración muerta y su forward huérfano: 0 diffs, que es la prueba. Repararlo mueve píxel y choca con §12.5 (el arquetipo fija también `color`) → §13, espera firma. El acento del checkbox (`_palette-solid`) sí vive y se conserva. 2. NO se acuña `hover-row-bg` aunque la propuesta lo pedía. La fila es `archetype: 'item'`: el velo del sistema la pinta a especificidad plena y el plano de la receta pierde — medido, el hover pinta DOS VECES sobre el MISMO nodo (a diferencia de table/tree-grid, donde velo y plano caen en nodos distintos). Sería un token que no puede ganar; mismo criterio que listbox.highlighted → §12.7. Y fila Y celda llevan ambas `item`, así que bajo el puntero se apilan tres capas. 3. «Consume la capa compartida `menu-indicator`» es FALSO. Sale de `SHARED_LAYERS` (`theming-census.ts:432`), pero esa capa no tiene un solo selector de grid-list, la receta no la importa y soma no renderiza indicator: el checkbox ESPEJA el visual pintándolo por su cuenta. Entrada desviada del censo → §13 (listbox / menubar / navigation-menu están en la misma entrada y tampoco aparecen en el fichero). Nombres, sobre la propuesta: `selection-checkbox-{width,height}` son UN knob → `selection-checkbox-size`; `on-selection-checkbox-bg` → `checked-selection-checkbox-fg` (es la tinta del glifo, y `on-` es prefijo reservado); `border` partido en `border-width` + color (molde `command.input-border*`), que el checkbox comparte; `padding` uniforme se queda `padding` (precedente `command.viewport-padding`); `max-block` entra al contrato (molde `command.viewport-max-block`). Artefactos y gates: - Sonda antes/después = 0 diffs en 6.090 valores × 7 estados. Determinismo verificado: dos corridas del mismo código, 0 diffs. - R-5.4: 37/38 tokens mueven un computed. `invalid-border` adjudicado EN EL LEDGER con su razón medida (la demo arranca con su control en off; activado a mano alcanza: oklch(0.9555 0.0207 13.86) → rgb(1,2,3)). - censo --only 85 % · component:audit PASS (R-5.3 incluida) · eidos-lint invalid 0, class-hooks 0 · suite eidos sin rojos nuevos (el único es 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 (38 claves, leídas en vivo). El tab `Recipe` documentaba la regla retirada: corregido. Las fichas de calendar / carousel / command / month-grid / range-calendar / table / year-grid entran porque `--report` regenera el informe entero: sólo actualizan NÚMEROS DE LÍNEA de commits ya hechos que no lo regeneraron. Cero cambios de alcance. Las otras 161, que sólo cambiaban la fecha, se revirtieron. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
7. **En `grid-list` el hover pinta DOS VECES sobre el MISMO nodo** (medido
2026-08-21 al tokenizarlo). A diferencia de `table` / `tree-grid` —donde el
velo cae en la celda y el plano en la fila, nodos distintos— aquí el plano
de la receta (`background:` atajo, (0,3,0) → `background-color`) y el velo
del arquetipo ((0,5,0) → `background-image`) caen ambos en
`[data-grid-list-row]`: el atajo fija `background-image: none` pero pierde,
así que se ven los dos. Por eso el commit de `grid-list` **no acuñó**
`hover-row-bg`: sería un token que no puede ganar (mismo criterio que
`listbox.highlighted`). Y como en `table`, **fila Y celda llevan
`archetype: 'item'`** (`morfo/components/grid-list.ts:118` y `:141`), así
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í.
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>
2 months ago
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.
uix(link-preview): temable — 0 % → 100 %, y el plano overlay que traía tipografía 33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → **100 %**: no queda un knob de apariencia fuera del contrato (los tres restantes son sistema — anillo de foco y los dos del plano de profundidad que pintan la flecha). Global 48 %, sin contrato 42 → 41, al 100 % 7 → 8. 1. **Los per-talla llevan `parts: ['content']`**, no el host: el panel está PORTALADO y su `data-size` vive en el content, así que una cascada colgada del host no lo alcanza nunca. La talla mueve sólo dimensiones físicas (padding, min-width, tipo); familia, peso e interlineado son constantes a propósito, como ya decía la receta. 2. **La flecha no se tokeniza**: su fill y su stroke consumen el plano `overlay` para casar siempre con el panel; acuñar `arrow-fg` rompería justo esa garantía. El subrayado del trigger es un `color-mix` sobre `currentColor` — la mezcla ES el aspecto, así que el knob es la declaración entera, no el 35 %. Y el fallback encadenado del color del trigger viaja verbatim: la coma es parte del valor, no un segundo knob. EL HALLAZGO — el plano `overlay` impone TIPOGRAFÍA (→ next-features §12.9): `[data-depth='overlay']` (`generated/base.css:7088`) no declara sólo fondo, borde y sombra: también `font-family` y `line-height`. El panel estampa ese plano para su elevación, así que la declaración del plano empata a (0,1,0) con la de la receta y **gana por orden de carga** (la hoja generada va después del chunk de la receta). Medido con CDP `getMatchedStylesForNode`: el par `font-family` / `line-height` de esta receta **no había pintado nunca**. Retirado con **0 diffs**, que es la prueba. Afecta a los **veinte** componentes que estampan el plano. Lo que hay que decidir —aparte, mueve píxel— es si un plano de profundidad tiene competencia sobre la tipografía o sólo sobre la superficie. Cuarta ocurrencia de la clase «empate resuelto por orden de carga». CÓMO SE MIDIÓ, porque el instrumento estándar tampoco servía aquí: El panel abre en HOVER con retardo y está portalado → la sonda genérica ve **1 nodo**, y una sonda que compara un nodo pasa en falso. Sonda dirigida (abre por hover, captura los cinco nodos reales en las cinco tallas): determinismo verificado y **gate antes/después = 0 diffs sobre 550 valores**, repetido tras la retirada. El guard R-5.4 pasó de **3/35 a 31/33** con dos extensiones: `openBy: 'hover'` para este componente (un hover card no se abre con clic — clicar su `<a>` navega) y `text-decoration-color` en la lista de propiedades, que faltaba pese a estar en `KNOB_PROPS` del censo. Verificado que los otros QUINCE componentes con ledger no se mueven ni una cifra. Los dos tokens que siguen mudos son un conflicto ESTRUCTURAL, no un hueco de demo: para abrir el panel hay que tener el puntero en el trigger, y el hover es justo el estado cuya regla pisa la tinta y el subrayado de reposo — el token de hover lee vivo en la misma corrida. Adjudicados con esa razón. Gates: censo --only 100 % · 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 · README con «Talla y tema» y tab `Tokens` en la demo (33 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
9. **El plano de profundidad `overlay` impone TIPOGRAFÍA, no sólo elevación**
— medido con CDP el 2026-08-21 al tokenizar `link-preview`.
`[data-depth='overlay']` (`generated/base.css:7088`) declara
`font-family: var(--style-label-font-family)` y
`line-height: var(--leading-ui)` junto al fondo, el borde y la sombra. Un
componente que estampe el plano para su elevación —**veinte lo hacen**—
hereda además esa tipografía, y su propia declaración empata a **(0,1,0)**
y **pierde por orden de carga** (la hoja generada va después del chunk de la
receta). En `link-preview` el par `font-family` / `line-height` de la receta
uix(theming): correcciones de la revisión de F2-B — el empate no se resuelve de oficio Auditoría de la sesión con medición. Cinco correcciones; la primera toca código y es la que importa. 1. **`link-preview`: RESTAURADAS `font-family` y `line-height`.** Las retiré como «declaraciones muertas» apoyándome en los precedentes de `table.selected-row-fg` y del trigger de `gradient-picker`, pero **no son el mismo caso**: allí perdían por ESPECIFICIDAD, aquí empatan a (0,1,0) con el plano `[data-depth='overlay']` y decide el ORDEN DE CARGA. Retirarlas no es neutral — **fija el empate a favor del plano** y con él su 1.25 donde la receta pide `--leading-normal` (1.5), que es justo la decisión que §12.9 tiene pendiente para VEINTE componentes. Un empate por orden de carga se registra, no se resuelve dentro de un commit de tokenización. Y la medición era más débil de lo que el commit daba a entender: estable en cinco recargas **en DEV**, sin verificar en un bundle de producción. Restauradas → **0 diffs contra el estado PRE-SESIÓN** (550 valores × 5 tallas), censo de vuelta a 100 %, y las dos adjudicadas en el ledger con esa razón. §12.9 y el README dicen ahora que el par se deja declarado A PROPÓSITO. 2. **Marcas `R-5.x exception:` en 16 READMEs.** El gate del plan es «censo al 100 % o excepción escrita», y la válvula que `component-audit` lee es esa marca (`readmeException`), no la prosa. Tenía la razón documentada en veredicto, README y commit, pero sin la marca: cuando F3 active R-5.1/5.2, los 16 habrían dado error. Una marca por clase medida — forward de paleta THM-2 · conmutador de dos fuentes · eje propiedad de una capa · identidad de layout. Los tres al 100 % (`code`, `label`, `link-preview`) no la necesitan. 3. **Continuidad restaurada.** El handoff seguía diciendo «45 %» y «empieza por grid-list», y `PLAN §8` no tenía una sola línea de la sesión pese a que el protocolo lo pide por componente: una sesión nueva habría arrancado con el mapa viejo. Reescrita la agenda de `CONTINUE-theming.md` (cifras, la revisión adversarial pendiente COMO PRIMERA TAREA, la cola restante, las 8 firmas), registrado el bloque en `PLAN §8` con su tabla de 22 commits y sus seis lecciones, y actualizada la memoria del eje. 4. **`recipe-contract §4`** documenta las SEIS capacidades que R-5.4 ganó en el bloque, cada una nombrando la forma en que el instrumento mentía, más la lección que generaliza: una sonda que inspecciona pocos nodos pasa en falso — `picker-shell` medía CERO y reportaba éxito. 5. **«un paso por debajo desde lg» → «desde md»** en `tree-view`, `virtual-list` y `virtual-grid` (README + ficha). El escalón empieza en md (md→sm); los valores eran correctos (0 diffs), el texto no. Lo que esta revisión NO arregla, y queda como primera tarea del eje: **F2-B sigue sin su revisión adversarial §7.7**. Sus 21 componentes llevan sólo la medición de quien los escribió, y el plan la exige antes de cerrar un bloque. El handoff nombra las dos hipótesis concretas que hay que atacar. Gates: censo global 50 % · `--names` DESVIADAS 0 sobre 3.993 claves · `component:audit` 162 PASS / 4 NEEDS-WORK (los cuatro preexistentes, ninguno con un commit de esta sesión en su código) · R-5.4 verde en los 22 · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
NO pinta: el plano impone 1.25 donde ella pide `--leading-normal` (1.5).
Cuarta ocurrencia de la clase «empate resuelto por orden de carga» (§12.3, y
las dos del calendario y de `tree-grid`).
⚠ **El par se deja DECLARADO a propósito.** Se retiró primero —daba 0 diffs,
porque no pintaba— y se RESTAURÓ el 2026-08-22 al revisar la sesión:
retirarlo no es neutral, **fija el empate a favor del plano** y con él el
1.25, que es justo la decisión que este punto tiene pendiente. Un empate por
orden de carga se registra, no se resuelve dentro de un commit de
tokenización. Los dos tokens quedan adjudicados en el ledger de R-5.4 con esa
razón, y la medición del orden se hizo en DEV (estable en cinco recargas);
en un bundle de producción está sin verificar.
uix(link-preview): temable — 0 % → 100 %, y el plano overlay que traía tipografía 33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → **100 %**: no queda un knob de apariencia fuera del contrato (los tres restantes son sistema — anillo de foco y los dos del plano de profundidad que pintan la flecha). Global 48 %, sin contrato 42 → 41, al 100 % 7 → 8. 1. **Los per-talla llevan `parts: ['content']`**, no el host: el panel está PORTALADO y su `data-size` vive en el content, así que una cascada colgada del host no lo alcanza nunca. La talla mueve sólo dimensiones físicas (padding, min-width, tipo); familia, peso e interlineado son constantes a propósito, como ya decía la receta. 2. **La flecha no se tokeniza**: su fill y su stroke consumen el plano `overlay` para casar siempre con el panel; acuñar `arrow-fg` rompería justo esa garantía. El subrayado del trigger es un `color-mix` sobre `currentColor` — la mezcla ES el aspecto, así que el knob es la declaración entera, no el 35 %. Y el fallback encadenado del color del trigger viaja verbatim: la coma es parte del valor, no un segundo knob. EL HALLAZGO — el plano `overlay` impone TIPOGRAFÍA (→ next-features §12.9): `[data-depth='overlay']` (`generated/base.css:7088`) no declara sólo fondo, borde y sombra: también `font-family` y `line-height`. El panel estampa ese plano para su elevación, así que la declaración del plano empata a (0,1,0) con la de la receta y **gana por orden de carga** (la hoja generada va después del chunk de la receta). Medido con CDP `getMatchedStylesForNode`: el par `font-family` / `line-height` de esta receta **no había pintado nunca**. Retirado con **0 diffs**, que es la prueba. Afecta a los **veinte** componentes que estampan el plano. Lo que hay que decidir —aparte, mueve píxel— es si un plano de profundidad tiene competencia sobre la tipografía o sólo sobre la superficie. Cuarta ocurrencia de la clase «empate resuelto por orden de carga». CÓMO SE MIDIÓ, porque el instrumento estándar tampoco servía aquí: El panel abre en HOVER con retardo y está portalado → la sonda genérica ve **1 nodo**, y una sonda que compara un nodo pasa en falso. Sonda dirigida (abre por hover, captura los cinco nodos reales en las cinco tallas): determinismo verificado y **gate antes/después = 0 diffs sobre 550 valores**, repetido tras la retirada. El guard R-5.4 pasó de **3/35 a 31/33** con dos extensiones: `openBy: 'hover'` para este componente (un hover card no se abre con clic — clicar su `<a>` navega) y `text-decoration-color` en la lista de propiedades, que faltaba pese a estar en `KNOB_PROPS` del censo. Verificado que los otros QUINCE componentes con ledger no se mueven ni una cifra. Los dos tokens que siguen mudos son un conflicto ESTRUCTURAL, no un hueco de demo: para abrir el panel hay que tener el puntero en el trigger, y el hover es justo el estado cuya regla pisa la tinta y el subrayado de reposo — el token de hover lee vivo en la misma corrida. Adjudicados con esa razón. Gates: censo --only 100 % · 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 · README con «Talla y tema» y tab `Tokens` en la demo (33 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
Lo que hay que decidir: si un PLANO DE PROFUNDIDAD tiene competencia sobre
la tipografía o sólo sobre la superficie. Si sólo sobre la superficie, esas
dos líneas salen del plano y cada componente recupera las suyas; si el plano
manda, entonces ningún overlay del catálogo debe declarar familia ni
interlineado y hay que barrer los que lo intentan. Mueve píxel en los veinte
en cuanto se toque.
docs(next-features): el registro de incidencias del eje theme-reach Encargo del autor: anotar todas las incidencias de esta clase para resolverlas después. Van al registro canónico (`next-features.md`), no a un sitio nuevo, y con su MEDICIÓN — no como impresión. **§12 — El contrato de cascada del velo de estado.** Seis incidencias que resultaron ser la misma: nadie fijó nunca cómo compone el velo de `archetypes.css` con las reglas de una receta. Y la raíz explica por qué se manifiestan de formas tan distintas: el velo se pinta con DOS pesos según el arquetipo — `:where()` (0,0,0) para `trigger`, especificidad plena (0,5,0) para `item`/`option`. 131 declaraciones-atajo en 45 componentes cancelan el velo (66 BASE · 32 ACENTO · 23 HOVER · 10 OTRA; cero valores con gradiente, así que el paso a longhand no tiene riesgo técnico; 32 nodos velados que hoy no reaccionan al ratón pasarían a hacerlo) · la prop `hoverable` de table no suprime nada · en tree-grid el empate (0,5,0) lo decide el ORDEN DE CARGA y varía entre recargas · la banda de tree-grid mata el hover en las filas pares · un token de tinta no puede ganar al arquetipo (`selected-row-fg` se retiró por eso) · el thumb de scroll-area no tiene velo al que migrar. Esto explica de paso la adopción 21/135 que midió la auditoría del 2026-07-01: el velo estaba escrito y estructuralmente derrotado. **§13 — Huecos de instrumento y de demo.** Lo que hizo que una medición mintiera o no existiera, que importa porque el eje entero se apoya en ellas: falsos negativos del centinela sobre propiedades transicionadas, pseudo-elementos y componentes compuestos que no ve, la sonda que no pasa el ratón por un `<tr>`, y la lección general —**una sonda sobre una parte que la demo no monta compara CERO valores y pasa**—, con la lista de partes condicionales afectadas. El handoff gana la regla para las sesiones del eje: si una incidencia mueve píxel o toca morfo, se mide, se anota ahí y se sigue; no se arregla dentro del commit del componente. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### Qué habría que decidir
En una frase: **quién manda cuando el arquetipo y la receta pintan el mismo
eje**. Las tres piezas concretas: si el velo de `item` baja a `:where()` como
el de `trigger` (y entonces la receta manda, y `hoverable` vuelve a significar
algo); si las recetas pasan a longhand en bloque (66 BASE + 32 ACENTO, mecánico
y sin gradientes de por medio); y si un arquetipo puede fijar `color` sobre un
componente que quiere tematizarlo.
**Deps**: ninguna de código. Es decisión del autor sobre píxel, y el
instrumental para medirla ya existe (`__statelayer-analysis.ts` +
`__theming-probe.ts` + `__tg-rowhover.ts`).
## 13. Huecos de instrumento y de demo del eje theme-reach — 2026-08-20
**Origen**: la misma sesión. Cosas que hicieron que una medición mintiera o no
existiera. Se anotan porque el eje entero se apoya en esas mediciones.
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4 Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba más dura que la del autor), sonda x2 por componente para fijar el suelo de ruido. 34 hipótesis · 26 refutadas · 8 reales. Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs; command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 · listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano» de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación; eidos-lint 0 invalid. Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command 8.091 y carousel 2.842 valores, 0 diffs): 1. command: el re-point de `--command-radius` dentro del Dialog era una declaración muerta con comentario falso — la misma regla pone el radio a 0 y el input lee `--command-input-radius`, nunca este token (medido: `--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px` movía la paleta inline y no la del dialog). Retirado; comentarios y README reescritos con lo medido. 2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo mientras la horizontal leía `active-indicator-scale` — el token alcanzaba media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora lo leen las dos. 3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap` INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es la prueba. Canal de valor de soma, como --gp-current-gradient. Censo carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto (5.190 · 2.102). 4. table: `striped-row-bg` no alcanza NADA en la demo publicada — `:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel → PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`); adjudicado en el ledger mientras tanto. El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas medidas, y la cuarta era de la sonda: - el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el token de reposo leía muerto (era el «falso negativo sin causa» de §13); - el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de `{c}-root` no lo recibían; - `::before`/`::after` eran invisibles (el aro de buffering, el spinner); - la no-determinación de feed no era una animación sin localizar: era la demo aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma `commit-settle` anima box-shadow sobre el nodo medido. Con la espera de asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas. Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y que carousel convirtió en patrón — un estilo inline de otra capa es invisible para todo análisis estático): `npm run theming:sentinel -- <c> <url>` (scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en todos los nodos del componente, pseudos, pase de hover, settle) + ledger scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 · gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46 excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró; y dos muertos nuevos los produjeron los propios arreglos (el blur apaga focus-input-border; el settle desmonta el spinner de feed) — adjudicados con su porqué. Doctrina en recipe-contract §4. También: tres cifras de especificidad de next-features §12 recontadas (la banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0); y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el velo en la celda), el comentario de listbox que mandaba pisar el privado `--_listbox-max-height` apunta ahora al público, y quedan registrados en §13 los dos `header-z` con literal '2' y el spinner de media-player a 720ms mientras el gemelo de feed lleva tokens. Guards: censo global 43 % intacto · audit --only command+carousel PASS · eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs 0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en HEAD y no se tocan). Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el arquetipo en fila+celda, y que soma lea el gap de un token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- ~~**El centinela da falsos negativos sobre una propiedad transicionada.**~~
**CAUSA ENCONTRADA Y ARREGLADA 2026-08-21** (revisión adversarial): no era la
transición. El paso «abrir lo que se pueda abrir» hacía CLIC en
`[data-command-input]`, y un clic de ratón sobre un input de texto SÍ casa
`:focus-visible` — la regla de foco (0,2,0) se quedaba con `border-color` y
el token de reposo leía muerto. El guard (`theming-sentinel.ts`) ahora hace
`blur()` tras abrir. La revisión midió la tasa completa del instrumento
viejo: **22 falsos negativos de 26 «no effect»** en F2-A, con tres causas
(el clic-foco, escribir sólo en `[data-{c}]` cuando las partes cuelgan de
`{c}-root`, y no leer `::before`/`::after`). Las tres, arregladas.
uix(textarea): temable — 0 % → 80 %, y el contador que casi pierde su 0.85 37 claves en el bloque `textarea` de `recipes/base.ts` (fusionadas con el forward de paleta que ya estaba). Censo 0 % → 80 %, con **cero globales y cero literales de valor**: los 16 privados leen ya su público. Global 46 %, sin contrato 48 → 47. LO QUE LA MEDICIÓN CAMBIÓ. La §4 generada proponía 35 tokens; cuatro eran colisiones y uno habría movido el default: 1. `count-font-size-{k}` × 5 BORRABA el factor 0.85. El CSS calcula `calc(var(--_textarea-font-size) * 0.85)` y la propuesta ponía el tipo del input crudo en las cinco tallas: el contador habría crecido un 18 %. D-TH.5 lo prohíbe. Queda en UN token con el `calc` sobre el público del input — la escala se escribe una vez y la talla la resuelve el input. Pide `scope: 'host'`: emitido en `:root` su dependencia no existe, y el contrato de scope de la receta lo caza (lo cazó). 2. `input-fg` ⚠ eran DOS knobs (texto y placeholder) → `input-fg` + `input-placeholder-fg`. Igual `count-fg` → `count-fg` + `overflow-count-fg`. 3. `input-border` ⚠ NO eran dos knobs sino UNA variable con su swap de paleta, y el nombre estaba mal: es el borde de FOCO → `focus-input-border`. El de reposo es otro token, que la propuesta ni listó por viajar en el atajo. 4. `ghost-input-border` se RETIRA: la regla ghost lee ese mismo conmutador — sería un valor con dos nombres. Bundle, medido: la tipografía casa 1:1 en las cinco (lee `--size-{k}-font-size`); el spacing NO — `padding-inline` casa salvo en xs (8px vs 6px) y `padding-block` sólo en xs. Verbatim y documentado: un campo multi-línea respira más, y el spacing queda fuera del guard del bundle. Resueltos sin `parts` (el `data-size` va en `[data-textarea]` y el input hereda); fuera los cuatro `[data-size]`. DOS ARREGLOS DEL INSTRUMENTO, los dos por medición y no por sospecha: - **El guard medía con el PUNTERO encima.** `reopen()` corre antes de CADA token y hace clic en `[data-{c}-input]` cuando el componente no tiene parte `content`; el `blur()` de la revisión adversarial quitaba el foco pero no el puntero, así que `:hover` casaba toda la corrida y toda regla de hover pisaba a sus vecinas. Costaba TRES falsos negativos aquí. Con `mouse.move(0,0)` tras cada blur, `input-border` revive solo (31 → 32). - **`::placeholder` SÍ se lee con `getComputedStyle`.** La nota de §13 que decía lo contrario nunca se comprobó; medido, el color centinela vuelve tal cual. Entra en el snapshot (32 → 33) y deja STALE la entrada `command.input-placeholder-fg`, que el guard señaló y se ha borrado. Corridos los DIEZ componentes con ledger tras los dos arreglos: ningún otro cambia, un único STALE, el previsto. Y un hallazgo de píxel que NO se toca aquí (→ next-features §13): el borde de foco sólo se ve con Tab. Las tres reglas de `border-color` del input están ordenadas al revés de lo que significan — hover (0,4,0) > foco (0,3,0) > invalid (0,2,0) — y hacer clic deja el puntero encima por definición, así que gana el hover; un campo inválido pierde su borde rojo al enfocarlo o al pasar el ratón. El anillo de foco del sistema sí se ve siempre, así que no es fallo de accesibilidad. Arreglarlo mueve píxel. El 20 % que falta son las dos declaraciones que consumen `--_textarea-border-focus`, privado a propósito: es el conmutador de una variable con dos fuentes (el público y el forward THM-2), y aplanarlo exigiría duplicar la regla por color. Mismo techo que `listbox` paga por consumir bien su capa. Mi propio veredicto predecía ~100 % y la medición lo corrigió a 80 %. Artefactos y gates: - Sonda antes/después = 0 diffs en 1.247 valores × 8 estados (el 0.85 intacto, que era el riesgo). Determinismo verificado: dos corridas, 0 diffs. - R-5.4: 33/37; los 4 mudos adjudicados en el ledger con su medición (foco, invalid y las dos del contador rebasado — estados que la demo no monta). - censo --only 80 % · 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 (37 claves; el panel muestra el contador computando `calc(calc(1rem * 1) * 0.85)`). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- ~~**El centinela no ve `::placeholder`.**~~ **ERA FALSO, y ARREGLADO
2026-08-21** (al tokenizar `textarea`): `getComputedStyle(el, '::placeholder')`
SÍ devuelve el valor — medido, el color centinela volvió tal cual. La
afirmación anterior nunca se comprobó y le costó a
`command.input-placeholder-fg` una entrada de ledger «verificada a mano» que
ha salido STALE y se ha borrado. El pseudo entra ahora en el snapshot del
guard, junto a `::before`/`::after`.
uix(avatar): temable — 75 % → 90 %, y su contrato era INVISIBLE para los dos instrumentos Protocolo PLAN-theming §7 sobre `avatar` (incluye `AvatarGroup`, que comparte receta y fichero). Censo 75 % → **90 %**, contrato 84 → 88 claves, centinela **83/88** con 5 adjudicadas. El default NO se mueve. ## Lo primero: el instrumento no veía el contrato La entrada `avatar` de `recipes/base.ts` es la ÚNICA construida por una IIFE (un helper local genera sus 24 ámbitos compuestos), así que su mapa vive en el `return {`, un tabulador más adentro. Consecuencia medida: - el censo la leía como «sin entrada en base.ts» — 84 claves invisibles; - el centinela **moría** con `no recipe block for avatar`: el componente no se podía medir, y su gate no significaba nada. Los dos lectores leen ya la IIFE (dedentan el `return`). Sin este arreglo, todo lo demás de este commit habría sido humo. ## Lo que entra al contrato (6 claves) `group-overlap-{xs,sm,md,lg,xl,xxl}` — la escala de solape de `AvatarGroup`. Había UNA clave, `--avatar-group-overlap`, y la receta la RE-DECLARABA en seis bloques `[data-size]`: sentada en el elemento, ganaba siempre al `:root` donde escribe un tema. Medido: `37px` desde el asiento del tema no movía el margen; el mismo valor escrito sobre el nodo, sí. Ahora el paso viaja por `--_avatar-group-overlap` y los seis alcanzan desde `:root` (−8,4 · −11,2 · −14 · −16,8 · −22,4 · −33,6 px → 37 px, uno a uno). ## Lo que se retira (2 declaraciones muertas, diff 0 las dos) - `group-max`: el envoltorio escribía `--avatar-group-max` INLINE y la receta declaraba su default `99`. **No lo leía nadie** — el tope se aplica con `data-has-max` + `:nth-child(n + M)` porque una variable no entra en `:nth-child()`, y lo dice el propio comentario del CSS. Retirado de los dos sitios; el `+3` del grupo sigue exactamente donde estaba. - el respaldo `, white` de `--_avatar-badge-fg`: el contrato ya declara `--avatar-badge-color-custom-contrast: white`, así que era inalcanzable y sólo podía envejecer contra su token. Comprobado en la rama custom: la tinta sigue computando `rgb(255, 255, 255)`. ## Seis literales firmados (salen del ratio) Los cuatro `100 %` de Image y Fallback son IDENTIDAD — la parte ES la superficie del avatar, no una talla propia — y los dos `line-height: 1` mantienen el glifo centrado por la caja flex. ## Lo que se queda privado (el techo real es 90 %) `--_avatar-bg`, `--_avatar-fg` y `--_avatar-badge-bg` son un CONMUTADOR: cambian de fuente con la variante (solid · soft · outline) y su valor sale del forward de paleta THM-2 que la capa de color alimenta por instancia desde `[data-color]`. Un público encima dejaría que un tema los fijara y matara el `color=` de cada avatar. ## Artefactos - Sonda: **0 diffs** en TRES bases — demo de avatar con insignia y anillo (576 valores), la misma en modo fallback (384) y demo de AvatarGroup (3.072). - Capturas 2× antes/después de las dos demos: **idénticas al byte**. - Centinela R-5.4: 83/88, 5 adjudicadas con su medida en el ledger (`size-xxl` y `font-size-xxl` viven en el paso `xxl`, que el barrido del guard no alcanza — el límite que `metrics` ya registró; `radius-none` y `ring-width-sm` son «sólo el paso en vigor pinta»; `group-overlap-xxl` junta las dos cosas). - El guard aprende `avatar`: la insignia y el anillo son OPT-IN, el barrido de `data-variant` destapa las 16 claves `*-outline-*`, y `AvatarGroup` se mide en su propia ruta (21 muertos → 5). - `eidos-lint` 0 invalid · `vitest src/uix/eidos` sin rojos nuevos (el conocido `skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `npm run check` COMPLETED sin un solo error en `avatar`. - README «Talla y tema» + pestaña `Tokens` (88 filas, verificada en el navegador con `networkidle` y clic real). ## Lo que NO se arregla aquí (anotado) - **R-5.3 preexistente**: cuatro claves `*-color-custom*` donde la gramática exige `fg`. Son escotillas que el envoltorio escribe INLINE, así que renombrarlas cambia el contrato público de tres canales: se lista, el audit sigue en NEEDS-WORK por esto y sólo por esto. - **Falso POSITIVO nuevo del centinela** (next-features §13): escribe el token en `:root` y también sobre cada nodo, y para una propiedad personalizada que la receta re-declara en el ELEMENTO ese inline gana justo lo que un tema pierde. Es lo que hacía que `--avatar-group-overlap` leyera vivo. - **El barrido de tallas del guard para en `xl`**: añadir `xxl` dejaría STALE las seis excepciones de `metrics`, así que se hace con la re-verificación del ledger entero. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **El centinela da FALSOS POSITIVOS cuando la receta re-declara el público en
el ELEMENTO** (medido 2026-08-23 en `avatar`). El guard escribe el token en
`:root` **y sobre cada nodo del componente** —para alcanzar un panel portalado
que no ve la raíz— y el comentario que justifica eso dice que escribir en
todas partes «no puede fingir una victoria a nivel de PROPIEDAD». Cierto para
una propiedad; **falso para una propiedad personalizada** que la receta vuelve
a declarar en un bloque `[data-size]`: ahí el inline del guard gana justo lo
que un tema pierde. `--avatar-group-overlap` leía VIVO y desde `:root` no
movía un píxel. No hay arreglo obvio (quitar la escritura sobre el nodo
resucita el punto ciego del portal); mientras tanto, **un público que la
receta re-declare sobre el elemento se mide a mano desde `:root`**.
- **El barrido de tallas del centinela para en `xl`.** Dos componentes tienen
paso `xxl` (`metrics`, `avatar`) y sus claves leen muertas: ocho excepciones
de ledger escritas por ese motivo. Añadir el paso es una línea, pero deja
STALE las seis de `metrics`, así que se hace con la re-verificación del
ledger entero, no en el commit de un componente.
docs(next-features): el registro de incidencias del eje theme-reach Encargo del autor: anotar todas las incidencias de esta clase para resolverlas después. Van al registro canónico (`next-features.md`), no a un sitio nuevo, y con su MEDICIÓN — no como impresión. **§12 — El contrato de cascada del velo de estado.** Seis incidencias que resultaron ser la misma: nadie fijó nunca cómo compone el velo de `archetypes.css` con las reglas de una receta. Y la raíz explica por qué se manifiestan de formas tan distintas: el velo se pinta con DOS pesos según el arquetipo — `:where()` (0,0,0) para `trigger`, especificidad plena (0,5,0) para `item`/`option`. 131 declaraciones-atajo en 45 componentes cancelan el velo (66 BASE · 32 ACENTO · 23 HOVER · 10 OTRA; cero valores con gradiente, así que el paso a longhand no tiene riesgo técnico; 32 nodos velados que hoy no reaccionan al ratón pasarían a hacerlo) · la prop `hoverable` de table no suprime nada · en tree-grid el empate (0,5,0) lo decide el ORDEN DE CARGA y varía entre recargas · la banda de tree-grid mata el hover en las filas pares · un token de tinta no puede ganar al arquetipo (`selected-row-fg` se retiró por eso) · el thumb de scroll-area no tiene velo al que migrar. Esto explica de paso la adopción 21/135 que midió la auditoría del 2026-07-01: el velo estaba escrito y estructuralmente derrotado. **§13 — Huecos de instrumento y de demo.** Lo que hizo que una medición mintiera o no existiera, que importa porque el eje entero se apoya en ellas: falsos negativos del centinela sobre propiedades transicionadas, pseudo-elementos y componentes compuestos que no ve, la sonda que no pasa el ratón por un `<tr>`, y la lección general —**una sonda sobre una parte que la demo no monta compara CERO valores y pasa**—, con la lista de partes condicionales afectadas. El handoff gana la regla para las sesiones del eje: si una incidencia mueve píxel o toca morfo, se mide, se anota ahí y se sigue; no se arregla dentro del commit del componente. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **El centinela no ve lo que pinta un componente COMPUESTO.** Los tokens que
media-player reenvía al Slider (`--media-player-track` →
`--slider-track-bg`) pintan en nodos `[data-slider-*]`, fuera del espacio de
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4 Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba más dura que la del autor), sonda x2 por componente para fijar el suelo de ruido. 34 hipótesis · 26 refutadas · 8 reales. Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs; command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 · listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano» de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación; eidos-lint 0 invalid. Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command 8.091 y carousel 2.842 valores, 0 diffs): 1. command: el re-point de `--command-radius` dentro del Dialog era una declaración muerta con comentario falso — la misma regla pone el radio a 0 y el input lee `--command-input-radius`, nunca este token (medido: `--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px` movía la paleta inline y no la del dialog). Retirado; comentarios y README reescritos con lo medido. 2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo mientras la horizontal leía `active-indicator-scale` — el token alcanzaba media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora lo leen las dos. 3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap` INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es la prueba. Canal de valor de soma, como --gp-current-gradient. Censo carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto (5.190 · 2.102). 4. table: `striped-row-bg` no alcanza NADA en la demo publicada — `:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel → PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`); adjudicado en el ledger mientras tanto. El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas medidas, y la cuarta era de la sonda: - el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el token de reposo leía muerto (era el «falso negativo sin causa» de §13); - el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de `{c}-root` no lo recibían; - `::before`/`::after` eran invisibles (el aro de buffering, el spinner); - la no-determinación de feed no era una animación sin localizar: era la demo aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma `commit-settle` anima box-shadow sobre el nodo medido. Con la espera de asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas. Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y que carousel convirtió en patrón — un estilo inline de otra capa es invisible para todo análisis estático): `npm run theming:sentinel -- <c> <url>` (scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en todos los nodos del componente, pseudos, pase de hover, settle) + ledger scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 · gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46 excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró; y dos muertos nuevos los produjeron los propios arreglos (el blur apaga focus-input-border; el settle desmonta el spinner de feed) — adjudicados con su porqué. Doctrina en recipe-contract §4. También: tres cifras de especificidad de next-features §12 recontadas (la banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0); y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el velo en la celda), el comentario de listbox que mandaba pisar el privado `--_listbox-max-height` apunta ahora al público, y quedan registrados en §13 los dos `header-z` con literal '2' y el spinner de media-player a 720ms mientras el gemelo de feed lleva tokens. Guards: censo global 43 % intacto · audit --only command+carousel PASS · eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs 0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en HEAD y no se tocan). Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el arquetipo en fila+celda, y que soma lea el gap de un token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
atributos del componente medido. Sigue vivo; adjudicado por token en el
ledger de R-5.4.
- ~~**La sonda no pasa el ratón por un `<tr>`.**~~ **ARREGLADO 2026-08-21**:
`tr` entra en el filtro de hover de la sonda, y el guard R-5.4 añade un pase
de hover propio para los tokens `hover-*` (así adjudicaron
`table.hover-row-bg` y `gradient-picker.hover-preset-border`).
- **La sonda medía una demo aún CARGANDO.** El auto load-more de `feed`
mantiene `[data-busy]` ~3 s y su firma `commit-settle` anima `box-shadow`
sobre el nodo medido — los «5 diffs entre dos corridas del mismo código» del
handoff eran esto, no una animación sin localizar. **ARREGLADO 2026-08-21**:
sonda y guard esperan a que `[data-busy]` caiga; dos corridas del mismo
código dan 0 diffs y 0 nodos ausentes (6.264 valores).
docs(next-features): el registro de incidencias del eje theme-reach Encargo del autor: anotar todas las incidencias de esta clase para resolverlas después. Van al registro canónico (`next-features.md`), no a un sitio nuevo, y con su MEDICIÓN — no como impresión. **§12 — El contrato de cascada del velo de estado.** Seis incidencias que resultaron ser la misma: nadie fijó nunca cómo compone el velo de `archetypes.css` con las reglas de una receta. Y la raíz explica por qué se manifiestan de formas tan distintas: el velo se pinta con DOS pesos según el arquetipo — `:where()` (0,0,0) para `trigger`, especificidad plena (0,5,0) para `item`/`option`. 131 declaraciones-atajo en 45 componentes cancelan el velo (66 BASE · 32 ACENTO · 23 HOVER · 10 OTRA; cero valores con gradiente, así que el paso a longhand no tiene riesgo técnico; 32 nodos velados que hoy no reaccionan al ratón pasarían a hacerlo) · la prop `hoverable` de table no suprime nada · en tree-grid el empate (0,5,0) lo decide el ORDEN DE CARGA y varía entre recargas · la banda de tree-grid mata el hover en las filas pares · un token de tinta no puede ganar al arquetipo (`selected-row-fg` se retiró por eso) · el thumb de scroll-area no tiene velo al que migrar. Esto explica de paso la adopción 21/135 que midió la auditoría del 2026-07-01: el velo estaba escrito y estructuralmente derrotado. **§13 — Huecos de instrumento y de demo.** Lo que hizo que una medición mintiera o no existiera, que importa porque el eje entero se apoya en ellas: falsos negativos del centinela sobre propiedades transicionadas, pseudo-elementos y componentes compuestos que no ve, la sonda que no pasa el ratón por un `<tr>`, y la lección general —**una sonda sobre una parte que la demo no monta compara CERO valores y pasa**—, con la lista de partes condicionales afectadas. El handoff gana la regla para las sesiones del eje: si una incidencia mueve píxel o toca morfo, se mide, se anota ahí y se sigue; no se arregla dentro del commit del componente. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Una sonda sobre una demo que no monta la parte compara CERO valores y
pasa.** Ocurrió con el skin de audio: `/uix/components/audio-player` da 404
porque la demo es la de `media-player` con el chip `media`, y la sonda cargaba
en modo vídeo. Resuelto con `scripts/__probe-audio-skin.ts`, pero la clase de
fallo es general: **una parte condicional necesita que la sonda la monte**.
Afecta al menos a captions, buffering y paneles portalados de media-player, al
estado vacío y la barra de carga de `command`, y al overview de
`date-range-picker`.
- **`date-range-picker` no expone `kind='month'` / `'year'` en su demo**
(norma N-6), así que la mitad de su API no se ve ni se mide.
feat(gradient-picker): temable — y su trigger resultó estar muerto entero Quinto del bloque F2-A. censo 0 % → 88 % · global 42 % computed 609 valores en 8 estados: 0 diffs **El hallazgo llegó por el centinela: 36 de 45 tokens no movían nada.** Con esa cifra no se sigue adelante. El trigger es un POPOVER TRIGGER, y `popover.css` le pinta el cromo entero —altura, padding inline, tipografía, color, fondo, borde, radio, hover y foco— desde `[data-popover-trigger]:not([data-archetype='field-trigger'])`, que gana a `[data-gradient-picker-trigger]` en base (0,2,0 contra 0,1,0) y en los estados (0,3,0 contra 0,2,0). Esta receta re-declaraba TODO eso y nada pintaba: la altura, el padding, el tamaño de letra y el radio venían de popover, y con valores distintos —12px contra el `space-2-5` de la receta, 14px contra su `font-size-md`—. Llevaba así desde que el trigger se volvió trigger de popover. Así que no se tokeniza: **se retira**. Un token sobre una declaración muerta es un token que miente, y este eje ya retiró uno por lo mismo en `table`. Retirarlo dio **0 diffs**, que es la prueba de que estaba muerto. Un tema viste este trigger por el contrato de POPOVER, que es composición funcionando. Lo que sí es del picker se queda y alcanza (verificado a mano, porque el chip usa un hook de CLASE que el centinela no ve): el `gap` de la fila, el chip del degradado (tamaño por talla, radio, borde), las tres anchuras del panel, la lista de paradas y la rejilla de presets. **Correcciones al veredicto**, ambas medidas: `--gp-current-gradient` NO lo estampa el wrapper sino SOMA (`gradient-picker-provider.svelte.ts:197`), así que renombrarlo toca otra capa y no es de este commit; y no es un knob de tema sino un canal de valor —el degradado que eligió el usuario—, sobre el que un tema no tiene nada que decir. Las tres anchuras del panel se quedan como tokens planos leídos por tres reglas, no como cascada del TSC: el content va por PORTAL y dimensiona con `data-picker-size` (el atributo de picker-shell), y el vocabulario de scopes no tiene palabra para el atributo de otro componente. Al registro (`next-features.md` §13) van cuatro incidencias nuevas: que un componente compuesto pueda tener el cromo entero muerto sin que nada avise —merece guard, y el instrumento ya existe—, el nombre abreviado en soma, los dos hooks por CLASE que `eidos-lint` cuenta, y el foco opaco del trigger contra la mezcla suave del sistema. audit PASS · eidos-lint 0 invalid · rtl 0 · docs 0 · check 0 en tocados · suite: sólo el rojo conocido Demo con tab Tokens: 25 filas, 0 sin computar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Un componente COMPUESTO puede tener su cromo entero muerto sin que nada
avise.** `gradient-picker` re-declaraba altura, padding, tipografía, color,
fondo, borde, radio, hover y foco de su trigger, y NADA de ello pintaba:
`popover.css` gana con `[data-popover-trigger]:not([data-archetype='field-trigger'])`
(0,2,0 contra 0,1,0). Lo cazó el centinela —36 de 45 tokens sin efecto— y
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4 Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba más dura que la del autor), sonda x2 por componente para fijar el suelo de ruido. 34 hipótesis · 26 refutadas · 8 reales. Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs; command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 · listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano» de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación; eidos-lint 0 invalid. Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command 8.091 y carousel 2.842 valores, 0 diffs): 1. command: el re-point de `--command-radius` dentro del Dialog era una declaración muerta con comentario falso — la misma regla pone el radio a 0 y el input lee `--command-input-radius`, nunca este token (medido: `--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px` movía la paleta inline y no la del dialog). Retirado; comentarios y README reescritos con lo medido. 2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo mientras la horizontal leía `active-indicator-scale` — el token alcanzaba media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora lo leen las dos. 3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap` INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es la prueba. Canal de valor de soma, como --gp-current-gradient. Censo carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto (5.190 · 2.102). 4. table: `striped-row-bg` no alcanza NADA en la demo publicada — `:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel → PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`); adjudicado en el ledger mientras tanto. El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas medidas, y la cuarta era de la sonda: - el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el token de reposo leía muerto (era el «falso negativo sin causa» de §13); - el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de `{c}-root` no lo recibían; - `::before`/`::after` eran invisibles (el aro de buffering, el spinner); - la no-determinación de feed no era una animación sin localizar: era la demo aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma `commit-settle` anima box-shadow sobre el nodo medido. Con la espera de asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas. Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y que carousel convirtió en patrón — un estilo inline de otra capa es invisible para todo análisis estático): `npm run theming:sentinel -- <c> <url>` (scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en todos los nodos del componente, pseudos, pase de hover, settle) + ledger scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 · gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46 excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró; y dos muertos nuevos los produjeron los propios arreglos (el blur apaga focus-input-border; el settle desmonta el spinner de feed) — adjudicados con su porqué. Doctrina en recipe-contract §4. También: tres cifras de especificidad de next-features §12 recontadas (la banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0); y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el velo en la celda), el comentario de listbox que mandaba pisar el privado `--_listbox-max-height` apunta ahora al público, y quedan registrados en §13 los dos `header-z` con literal '2' y el spinner de media-player a 720ms mientras el gemelo de feed lleva tokens. Guards: censo global 43 % intacto · audit --only command+carousel PASS · eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs 0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en HEAD y no se tocan). Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el arquetipo en fila+celda, y que soma lea el gap de un token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
retirarlo dio 0 diffs. **EL GUARD EXISTE desde 2026-08-21: R-5.4**
(`npm run theming:sentinel -- <c> <url>`, `scripts/theming-sentinel.ts` +
ledger `theming-sentinel-exceptions.ts`, doctrina en recipe-contract §4).
Nació con la segunda ocurrencia de la clase: el `item-gap` de `carousel`
contra un estilo INLINE de soma, invisible para todo análisis estático.
Los ocho componentes de F2-A pasan con su adjudicación escrita.
feat(gradient-picker): temable — y su trigger resultó estar muerto entero Quinto del bloque F2-A. censo 0 % → 88 % · global 42 % computed 609 valores en 8 estados: 0 diffs **El hallazgo llegó por el centinela: 36 de 45 tokens no movían nada.** Con esa cifra no se sigue adelante. El trigger es un POPOVER TRIGGER, y `popover.css` le pinta el cromo entero —altura, padding inline, tipografía, color, fondo, borde, radio, hover y foco— desde `[data-popover-trigger]:not([data-archetype='field-trigger'])`, que gana a `[data-gradient-picker-trigger]` en base (0,2,0 contra 0,1,0) y en los estados (0,3,0 contra 0,2,0). Esta receta re-declaraba TODO eso y nada pintaba: la altura, el padding, el tamaño de letra y el radio venían de popover, y con valores distintos —12px contra el `space-2-5` de la receta, 14px contra su `font-size-md`—. Llevaba así desde que el trigger se volvió trigger de popover. Así que no se tokeniza: **se retira**. Un token sobre una declaración muerta es un token que miente, y este eje ya retiró uno por lo mismo en `table`. Retirarlo dio **0 diffs**, que es la prueba de que estaba muerto. Un tema viste este trigger por el contrato de POPOVER, que es composición funcionando. Lo que sí es del picker se queda y alcanza (verificado a mano, porque el chip usa un hook de CLASE que el centinela no ve): el `gap` de la fila, el chip del degradado (tamaño por talla, radio, borde), las tres anchuras del panel, la lista de paradas y la rejilla de presets. **Correcciones al veredicto**, ambas medidas: `--gp-current-gradient` NO lo estampa el wrapper sino SOMA (`gradient-picker-provider.svelte.ts:197`), así que renombrarlo toca otra capa y no es de este commit; y no es un knob de tema sino un canal de valor —el degradado que eligió el usuario—, sobre el que un tema no tiene nada que decir. Las tres anchuras del panel se quedan como tokens planos leídos por tres reglas, no como cascada del TSC: el content va por PORTAL y dimensiona con `data-picker-size` (el atributo de picker-shell), y el vocabulario de scopes no tiene palabra para el atributo de otro componente. Al registro (`next-features.md` §13) van cuatro incidencias nuevas: que un componente compuesto pueda tener el cromo entero muerto sin que nada avise —merece guard, y el instrumento ya existe—, el nombre abreviado en soma, los dos hooks por CLASE que `eidos-lint` cuenta, y el foco opaco del trigger contra la mezcla suave del sistema. audit PASS · eidos-lint 0 invalid · rtl 0 · docs 0 · check 0 en tocados · suite: sólo el rojo conocido Demo con tab Tokens: 25 filas, 0 sin computar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **`--gp-current-gradient` es un nombre ABREVIADO en SOMA.** Lo estampa
`gradient-picker-provider.svelte.ts` (no el wrapper de eidos, como decía la
ficha), y viola theming §6 r5. No es knob de tema —es un canal de valor— pero
el nombre es deuda; renombrarlo toca soma, otra capa.
- **Hooks por CLASE en una receta de eidos.** `gradient-picker` usa
`.gradient-picker-trigger-swatch` y `.gradient-picker-trigger-label` en vez
de `data-*`; `eidos-lint` los cuenta (`class-hooks: 2`) y el centinela no los
ve, porque filtra nodos por atributo.
- **El foco del trigger de `gradient-picker` es opaco** (`--color-primary-solid`)
mientras el resto del catálogo lleva la mezcla suave del sistema
(`--focus-ring-color`, 52 %). Quedó sin tocar porque alinearlo mueve píxel —
y de hecho quedó sin token, porque la regla entera está muerta bajo popover.
feat(calendar-surface): la capa que ya existía, con nombre, casa y su agujero tapado Firma 1 del acta, diseño presentado y firmado. **La medición desmontó el encargo**: el handoff la vendía como una capa NUEVA de ~220 knobs, y la capa ya existía de hecho, sin nombre. `--calendar-*` se emite en `:root` (76 claves) y sus consumidores no acuñan NADA — medido: `range-calendar` 114 referencias prestadas y 0 propias, `month-grid` 77/0, `year-grid` 77/0, y lo único ajeno que leen es sistema puro (`--focus-ring-*`, `--state-hover`). Su 0 % era el artefacto de `listbox` (§13), pero total. **`lib/calendar-surface.css`** (hook `data-calendar-surface`): - cuatro coordenadas por talla — `padding`, `control-size`, `day-size`, `font-size` — xs..lg, porque la familia NO tiene xl, y con la celda DOS pasos por debajo del bundle de control. Esa desviación estaba escondida en cuatro bloques `[data-size]` idénticos, uno por receta, cada uno puenteando a un privado con otro nombre; ahora se lee en un sitio. - la FORMA del anillo de evento y de la marca de festivo. **Capa HÍBRIDA, y es lo que la distingue de sus hermanas**: `list-surface` y `viewport-placement` componen primitivos del sistema, así que declaran sus públicos en el fichero y no tienen entrada de receta. Ésta no puede: su vocabulario son 76 claves SEMÁNTICAS que un tema alcanza una a una por config, así que la entrada `calendar` de `recipes/base.ts` pasa a ser la de la FAMILIA y la capa posee sólo lo que una entrada de receta no sabe expresar. **El defecto que la justificaba, medido**: `--calendar-event-shadow` y `--calendar-day-holiday-shadow` se emitían con ámbito `[data-calendar]` (audit B.2 los host-scopeó por buenas razones) mientras `range-calendar`, `month-grid` y `year-grid` los leían desde hosts que nunca llevan ese atributo: variable VACÍA, `box-shadow` inválido en computed, **el anillo sema de `commit-select` / `commit-set` no pintaba jamás en tres componentes**. Un token prestado cuyo ÁMBITO no te cubre no es un préstamo, es un agujero silencioso, y ningún guard lo veía. Ahora la forma vive en la capa y el acento entra por `--_calendar-surface-accent`, que cada superficie alimenta con su propio forward de paleta THM-2. **Siete wrappers estampan, no cuatro** — y esto casi se me cuela: `DatePicker` y `DateRangePicker` renderizan la superficie soma por sus PROPIOS wrappers (`date-picker-calendar`, `-month-view`, `-year-view`, `date-range-picker-calendar`) y un panel portalado no hereda nada del root del picker. Con sólo los cuatro standalone sellando, ambos quedaban con `--calendar-padding` VACÍA y el panel a padding 0 (medido). La tentación era enganchar la capa a las cuatro identidades de componente: eso viola la regla 1 de capas compartidas, y la respuesta correcta es un sello por wrapper. computed 0 diffs en range-calendar (19.285 valores) · month-grid (3.451) · year-grid (3.451) · date-picker (464) · date-range-picker (406). `calendar` da 12, y son del INSTRUMENTO: dos corridas del MISMO código dan 24 en los mismos nodos y las mismas dos propiedades. Los «missing node» son el propio sello entrando en la clave. **Una incidencia nueva, medida y NO arreglada aquí** (§13): el font-size de los selectores month/year es una moneda al aire — `[data-calendar-month-select][data-button]` (0,2,0) empata con `[data-popover-trigger]:not([data-archetype='field-trigger'])` (0,2,0), la MISMA regla de popover que dejó muerto el cromo de `gradient-picker`, y gana la hoja que cargue después: 16px o 14px según la recarga. Arreglarlo fija el píxel en un lado ⇒ decisión. De paso, `calendar-select.css` deja de puentear un privado que sólo `[data-calendar]` declaraba: en range / date-range corría SIEMPRE por el fallback, clavado a md fuera cual fuera la talla. **El censo deja de penalizar hacer lo correcto**: `LAYER_VOCABULARY` en `theming-census.ts`, mismo precedente que D-TH.2-b con `--style-*`. Global 43 % → **45 %**, `calendar` 75 % → **84 %**. `list-surface` NO se registra: sus consumidores puentean por privados, otra forma, y mueve diez componentes de golpe. Y aparece el techo de debajo, anotado: a los tres consumidores sólo les quedan los forwards de paleta THM-2 —que el censo cuenta como `private` en TODO el catálogo— así que siguen leyendo 0 %. `recipe-css-contract` aprende que una CAPA también declara públicos (antes sólo miraba la receta, y una capa que comparte prefijo con un componente la hacía fallar). Sin debilitarla: un nombre que no declara nadie sigue en rojo. eidos-lint 0 invalid (calendar 29/8 · range-calendar 41/12 · los grids 23/5) · audit --only calendar PASS · vitest eidos 434/435 (el rojo conocido) · rtl 0/181 · docs 0/813 · check 0 errores en tocados · prettier: revertido el reformateo en masa que se coló en cuatro README, el test y el censo (churn ajeno, no mío) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- ~~**El censo PENALIZA consumir una capa compartida correctamente.**~~
**RESUELTO PARA LA FAMILIA CALENDAR 2026-08-21** y abierto para el resto: el
censo tiene ahora `LAYER_VOCABULARY` (mismo precedente que D-TH.2-b), así que
el `--calendar-*` que leen `range-calendar` / `month-grid` / `year-grid`
cuenta como `system`. **`list-surface` NO está registrado a propósito**: sus
consumidores puentean por PRIVADOS (`--_listbox-item-*: var(--list-item-*)`),
otra forma, y activarlo mueve diez componentes de golpe — sigue siendo
decisión aparte. Y aparece un techo NUEVO debajo: con el vocabulario de la
capa fuera del denominador, a los tres sólo les quedan los **forwards de
paleta THM-2** (`--_x-palette-*`, que el censo cuenta como `private` en TODO
el catálogo) y literales de layout, así que siguen leyendo 0 %. Reclasificar
el forward de paleta es la misma pregunta, un piso más abajo, y afecta a
todos.
feat(listbox): temable hasta donde le toca — 0 % → 68 % Sexto del bloque F2-A, y el primero cuyo techo es DELIBERADO. censo 0 % → 68 % · global 42 % computed 2.813 valores en 7 estados: 0 diffs **Este componente no puede acuñar su ritmo de fila, y esa es la regla de oro de las capas compartidas.** Altura, padding inline y block, gap y tamaño de letra los posee `lib/list-surface.css`, por talla, para TODAS las superficies de lista —select, combobox, command y los menús—. Acuñar `--listbox-item-height` sería el vocabulario paralelo que las reglas de esa capa prohíben, y encima ganaría a la capa para todas las demás. Los cinco privados ADOPTAN la capa y se quedan privados; un tema mueve ese ritmo por `--list-*`, que es donde vive. Consecuencia honesta: el censo cuenta esos cinco como privados y el alcance se queda en 68 % en vez de ~86 %. **La métrica penaliza hacer lo correcto** — el mismo defecto de medición que D-TH.2-b arregló para los primitivos tipográficos (`--style-*` pasó a `system`). Registrado en `next-features.md` §13; extenderlo a las capas compartidas es decisión, no corrección al paso. **Dos cosas medidas que NO se tocaron:** - El `highlighted` pinta DOS veces: la receta pone un `--color-surface-overlay` plano y encima el arquetipo `item` añade su velo del 8 %. El plano es el duplicado que §38 deprecó, así que se dejó SIN token — bendecir con un nombre público algo condenado a morir es el error que este eje ya evitó en el codemod. Retirarlo mueve píxel. - La tinta de la fila seleccionada era código MUERTO y se retiró: el arquetipo la fija a (0,4,0) contra los (0,3,0) de la receta. Mismo hallazgo que en `table`, verificado igual, 0 diffs al quitarlo. Lo que sí es suyo y ahora es público: la superficie que sostiene las filas (fondo, borde, radio, padding, gap, tipografía, alto máximo), la esquina y la tinta de la fila, el glifo de selección y el grupo con su etiqueta. audit PASS · eidos-lint 0 invalid · rtl 0 · docs 0 · check 0 en tocados · suite: sólo el rojo conocido Demo con tab Tokens: 21 filas, 0 sin computar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **El censo PENALIZA consumir una capa compartida correctamente.** `listbox`
adopta los cinco ejes de ritmo que `list-surface` posee — que es exactamente
lo que la doctrina de capas manda — y el censo los cuenta como `private`, así
que su alcance queda en 68 % en vez de ~86 %. Es el mismo defecto de medición
que D-TH.2-b arregló para los primitivos tipográficos (`--style-*` pasó a
contar como `system`): la capa ES la superficie de tema de ese eje. Afecta a
los consumidores de `list-surface` (select · combobox · command · listbox ·
los menús) y de `viewport-placement` (affix · fab · menu-dial). Extenderlo es
una decisión, no una corrección al paso.
- **`listbox` pinta el `highlighted` DOS veces**: la receta pone un
`--color-surface-overlay` plano y encima el arquetipo `item` añade su velo del
8 % — medido. El plano es el duplicado que §38 deprecó; se dejó sin token para
no bendecirlo, pero retirarlo mueve píxel.
docs(next-features): el registro de incidencias del eje theme-reach Encargo del autor: anotar todas las incidencias de esta clase para resolverlas después. Van al registro canónico (`next-features.md`), no a un sitio nuevo, y con su MEDICIÓN — no como impresión. **§12 — El contrato de cascada del velo de estado.** Seis incidencias que resultaron ser la misma: nadie fijó nunca cómo compone el velo de `archetypes.css` con las reglas de una receta. Y la raíz explica por qué se manifiestan de formas tan distintas: el velo se pinta con DOS pesos según el arquetipo — `:where()` (0,0,0) para `trigger`, especificidad plena (0,5,0) para `item`/`option`. 131 declaraciones-atajo en 45 componentes cancelan el velo (66 BASE · 32 ACENTO · 23 HOVER · 10 OTRA; cero valores con gradiente, así que el paso a longhand no tiene riesgo técnico; 32 nodos velados que hoy no reaccionan al ratón pasarían a hacerlo) · la prop `hoverable` de table no suprime nada · en tree-grid el empate (0,5,0) lo decide el ORDEN DE CARGA y varía entre recargas · la banda de tree-grid mata el hover en las filas pares · un token de tinta no puede ganar al arquetipo (`selected-row-fg` se retiró por eso) · el thumb de scroll-area no tiene velo al que migrar. Esto explica de paso la adopción 21/135 que midió la auditoría del 2026-07-01: el velo estaba escrito y estructuralmente derrotado. **§13 — Huecos de instrumento y de demo.** Lo que hizo que una medición mintiera o no existiera, que importa porque el eje entero se apoya en ellas: falsos negativos del centinela sobre propiedades transicionadas, pseudo-elementos y componentes compuestos que no ve, la sonda que no pasa el ratón por un `<tr>`, y la lección general —**una sonda sobre una parte que la demo no monta compara CERO valores y pasa**—, con la lista de partes condicionales afectadas. El handoff gana la regla para las sesiones del eje: si una incidencia mueve píxel o toca morfo, se mide, se anota ahí y se sigue; no se arregla dentro del commit del componente. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **La demo del bloque F2-A ya enseña los tokens** (`TokensPanel`), pero los
~155 componentes sin contrato siguen sin superficie donde verlos hasta que se
tokenicen.
feat(calendar-surface): la capa que ya existía, con nombre, casa y su agujero tapado Firma 1 del acta, diseño presentado y firmado. **La medición desmontó el encargo**: el handoff la vendía como una capa NUEVA de ~220 knobs, y la capa ya existía de hecho, sin nombre. `--calendar-*` se emite en `:root` (76 claves) y sus consumidores no acuñan NADA — medido: `range-calendar` 114 referencias prestadas y 0 propias, `month-grid` 77/0, `year-grid` 77/0, y lo único ajeno que leen es sistema puro (`--focus-ring-*`, `--state-hover`). Su 0 % era el artefacto de `listbox` (§13), pero total. **`lib/calendar-surface.css`** (hook `data-calendar-surface`): - cuatro coordenadas por talla — `padding`, `control-size`, `day-size`, `font-size` — xs..lg, porque la familia NO tiene xl, y con la celda DOS pasos por debajo del bundle de control. Esa desviación estaba escondida en cuatro bloques `[data-size]` idénticos, uno por receta, cada uno puenteando a un privado con otro nombre; ahora se lee en un sitio. - la FORMA del anillo de evento y de la marca de festivo. **Capa HÍBRIDA, y es lo que la distingue de sus hermanas**: `list-surface` y `viewport-placement` componen primitivos del sistema, así que declaran sus públicos en el fichero y no tienen entrada de receta. Ésta no puede: su vocabulario son 76 claves SEMÁNTICAS que un tema alcanza una a una por config, así que la entrada `calendar` de `recipes/base.ts` pasa a ser la de la FAMILIA y la capa posee sólo lo que una entrada de receta no sabe expresar. **El defecto que la justificaba, medido**: `--calendar-event-shadow` y `--calendar-day-holiday-shadow` se emitían con ámbito `[data-calendar]` (audit B.2 los host-scopeó por buenas razones) mientras `range-calendar`, `month-grid` y `year-grid` los leían desde hosts que nunca llevan ese atributo: variable VACÍA, `box-shadow` inválido en computed, **el anillo sema de `commit-select` / `commit-set` no pintaba jamás en tres componentes**. Un token prestado cuyo ÁMBITO no te cubre no es un préstamo, es un agujero silencioso, y ningún guard lo veía. Ahora la forma vive en la capa y el acento entra por `--_calendar-surface-accent`, que cada superficie alimenta con su propio forward de paleta THM-2. **Siete wrappers estampan, no cuatro** — y esto casi se me cuela: `DatePicker` y `DateRangePicker` renderizan la superficie soma por sus PROPIOS wrappers (`date-picker-calendar`, `-month-view`, `-year-view`, `date-range-picker-calendar`) y un panel portalado no hereda nada del root del picker. Con sólo los cuatro standalone sellando, ambos quedaban con `--calendar-padding` VACÍA y el panel a padding 0 (medido). La tentación era enganchar la capa a las cuatro identidades de componente: eso viola la regla 1 de capas compartidas, y la respuesta correcta es un sello por wrapper. computed 0 diffs en range-calendar (19.285 valores) · month-grid (3.451) · year-grid (3.451) · date-picker (464) · date-range-picker (406). `calendar` da 12, y son del INSTRUMENTO: dos corridas del MISMO código dan 24 en los mismos nodos y las mismas dos propiedades. Los «missing node» son el propio sello entrando en la clave. **Una incidencia nueva, medida y NO arreglada aquí** (§13): el font-size de los selectores month/year es una moneda al aire — `[data-calendar-month-select][data-button]` (0,2,0) empata con `[data-popover-trigger]:not([data-archetype='field-trigger'])` (0,2,0), la MISMA regla de popover que dejó muerto el cromo de `gradient-picker`, y gana la hoja que cargue después: 16px o 14px según la recarga. Arreglarlo fija el píxel en un lado ⇒ decisión. De paso, `calendar-select.css` deja de puentear un privado que sólo `[data-calendar]` declaraba: en range / date-range corría SIEMPRE por el fallback, clavado a md fuera cual fuera la talla. **El censo deja de penalizar hacer lo correcto**: `LAYER_VOCABULARY` en `theming-census.ts`, mismo precedente que D-TH.2-b con `--style-*`. Global 43 % → **45 %**, `calendar` 75 % → **84 %**. `list-surface` NO se registra: sus consumidores puentean por privados, otra forma, y mueve diez componentes de golpe. Y aparece el techo de debajo, anotado: a los tres consumidores sólo les quedan los forwards de paleta THM-2 —que el censo cuenta como `private` en TODO el catálogo— así que siguen leyendo 0 %. `recipe-css-contract` aprende que una CAPA también declara públicos (antes sólo miraba la receta, y una capa que comparte prefijo con un componente la hacía fallar). Sin debilitarla: un nombre que no declara nadie sigue en rojo. eidos-lint 0 invalid (calendar 29/8 · range-calendar 41/12 · los grids 23/5) · audit --only calendar PASS · vitest eidos 434/435 (el rojo conocido) · rtl 0/181 · docs 0/813 · check 0 errores en tocados · prettier: revertido el reformateo en masa que se coló en cuatro README, el test y el censo (churn ajeno, no mío) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Lo que la capa `calendar-surface` añadió (2026-08-21):
- **⚠ El font-size de los selectores month/year del calendario es una MONEDA AL
AIRE.** `[data-calendar-month-select][data-button]` (0,2,0) empata con
`[data-popover-trigger]:not([data-archetype='field-trigger'])` (0,2,0) — la
MISMA regla de `popover.css` que dejó muerto el cromo entero del trigger de
`gradient-picker` — y gana la hoja que cargue después: dos corridas del mismo
código dan 16px o 14px en los mismos nodos (24 diffs de instrumento, medidos
con la sonda dos veces). El pin del calendario existe justamente para que
caja, etiqueta y chevron lean UNA talla, así que perderlo es visible.
Arreglarlo es subir el peso del pin de forma determinista — decisión, porque
fija el píxel en un lado. Segunda ocurrencia de la clase §12.3 (empate por
orden de carga) y segunda víctima de la misma regla de popover.
- **Los tres consumidores de la capa siguen leyendo 0 % de alcance**, ahora por
los forwards de paleta THM-2 — ver el punto del censo más arriba.
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4 Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba más dura que la del autor), sonda x2 por componente para fijar el suelo de ruido. 34 hipótesis · 26 refutadas · 8 reales. Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs; command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 · listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano» de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación; eidos-lint 0 invalid. Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command 8.091 y carousel 2.842 valores, 0 diffs): 1. command: el re-point de `--command-radius` dentro del Dialog era una declaración muerta con comentario falso — la misma regla pone el radio a 0 y el input lee `--command-input-radius`, nunca este token (medido: `--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px` movía la paleta inline y no la del dialog). Retirado; comentarios y README reescritos con lo medido. 2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo mientras la horizontal leía `active-indicator-scale` — el token alcanzaba media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora lo leen las dos. 3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap` INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es la prueba. Canal de valor de soma, como --gp-current-gradient. Censo carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto (5.190 · 2.102). 4. table: `striped-row-bg` no alcanza NADA en la demo publicada — `:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel → PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`); adjudicado en el ledger mientras tanto. El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas medidas, y la cuarta era de la sonda: - el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el token de reposo leía muerto (era el «falso negativo sin causa» de §13); - el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de `{c}-root` no lo recibían; - `::before`/`::after` eran invisibles (el aro de buffering, el spinner); - la no-determinación de feed no era una animación sin localizar: era la demo aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma `commit-settle` anima box-shadow sobre el nodo medido. Con la espera de asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas. Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y que carousel convirtió en patrón — un estilo inline de otra capa es invisible para todo análisis estático): `npm run theming:sentinel -- <c> <url>` (scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en todos los nodos del componente, pseudos, pase de hover, settle) + ledger scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 · gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46 excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró; y dos muertos nuevos los produjeron los propios arreglos (el blur apaga focus-input-border; el settle desmonta el spinner de feed) — adjudicados con su porqué. Doctrina en recipe-contract §4. También: tres cifras de especificidad de next-features §12 recontadas (la banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0); y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el velo en la celda), el comentario de listbox que mandaba pisar el privado `--_listbox-max-height` apunta ahora al público, y quedan registrados en §13 los dos `header-z` con literal '2' y el spinner de media-player a 720ms mientras el gemelo de feed lleva tokens. Guards: censo global 43 % intacto · audit --only command+carousel PASS · eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs 0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en HEAD y no se tocan). Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el arquetipo en fila+celda, y que soma lea el gap de un token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Lo que la revisión adversarial de F2-A añadió (2026-08-21):
fix(table): el striped cuenta filas de DATOS — la banda vuelve a existir Firmado por el autor tras la revisión adversarial: el hallazgo 4 (679dfdb29) midió que `:nth-of-type(even)` cuenta los <tr> hermanos, y con un RowDetail intercalado tras cada fila las filas de datos quedan todas impares — la demo publicada no bandeaba NADA (diez tr inmunes al token, medido uno a uno). El selector pasa a `:nth-child(even of [data-table-row])`, con dos decisiones de peso dentro: - **conserva el (0,6,0) del viejo**: el pseudo más el argumento `of` pesan exactamente lo que pesaba `[data-table-row]:nth-of-type()`, así que el hover —posterior en el fichero— sigue ganando su empate y la banda NO mata el hover. Subirlo de peso habría importado a table el defecto §12.4 de tree-grid. - **`:where(:not([data-selected]))` a peso CERO**: la fila seleccionada siempre estuvo prometida al acento («Overrides hover + striped», dice la regla de al lado a (0,3,0)) pero la banda vieja, de haber casado, se la habría comido por especificidad. El :where excluye sin pesar. Medido (sonda antes/después, 13.195 valores en 7 estados): **12 diffs exactos** — 2 filas pares × 6 estados, backgroundColor 0.9911 → 0.9551 — y nada más se movió. Interacciones verificadas en vivo: selected par conserva el acento (0.9588 con croma) · hover sobre par pinta su tinte (0.931). El ledger de R-5.4 pierde la excepción de `striped-row-bg` (era «genuinely unreachable, pending signature»): el guard adjudica ahora table 42/45 con 3 excepciones, exit 0, sin STALE. eidos-lint 0 invalid · audit --only table PASS · censo table 86 % intacto · vitest eidos 434/435 (el rojo conocido) · rtl 0 · docs 0 · check 0 en tocados · prettier limpio en lo tocado (el wrap de la regla del hover ya estaba desviado en HEAD y no se toca) Incidencia operativa, reparada y anotada: al retirar el worktree de medición de la revisión, `git worktree remove --force` recursó A TRAVÉS de la junction de node_modules y borró parte del real (node_modules/.bin incluido) antes de fallar; `npm install` lo reconstruyó y la suite volvió a 434/435. El árbol git nunca se tocó. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- ~~**PENDIENTE DE FIRMA — el `striped` de `table` es inerte con RowDetail.**~~
**FIRMADO Y EJECUTADO 2026-08-21**: `:nth-child(even of [data-table-row])`
cuenta filas de DATOS y la paridad ya no cae en los `<tr>` de detalle (la
demo no bandeaba NADA; ahora las filas 2 y 4 tiñen — 12 diffs medidos, 2
filas × 6 estados, y nada más se movió en 13.195 valores). El selector
conserva el (0,6,0) del viejo para que el hover siga ganando su empate (no
se importa el defecto §12.4 de tree-grid) y un `:where(:not([data-selected]))`
de peso cero deja el acento seleccionado al mando — verificado los tres.
La excepción del ledger se retiró; `striped-row-bg` adjudica VIVO (42/45).
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4 Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba más dura que la del autor), sonda x2 por componente para fijar el suelo de ruido. 34 hipótesis · 26 refutadas · 8 reales. Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs; command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 · listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano» de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación; eidos-lint 0 invalid. Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command 8.091 y carousel 2.842 valores, 0 diffs): 1. command: el re-point de `--command-radius` dentro del Dialog era una declaración muerta con comentario falso — la misma regla pone el radio a 0 y el input lee `--command-input-radius`, nunca este token (medido: `--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px` movía la paleta inline y no la del dialog). Retirado; comentarios y README reescritos con lo medido. 2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo mientras la horizontal leía `active-indicator-scale` — el token alcanzaba media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora lo leen las dos. 3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap` INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es la prueba. Canal de valor de soma, como --gp-current-gradient. Censo carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto (5.190 · 2.102). 4. table: `striped-row-bg` no alcanza NADA en la demo publicada — `:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel → PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`); adjudicado en el ledger mientras tanto. El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas medidas, y la cuarta era de la sonda: - el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el token de reposo leía muerto (era el «falso negativo sin causa» de §13); - el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de `{c}-root` no lo recibían; - `::before`/`::after` eran invisibles (el aro de buffering, el spinner); - la no-determinación de feed no era una animación sin localizar: era la demo aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma `commit-settle` anima box-shadow sobre el nodo medido. Con la espera de asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas. Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y que carousel convirtió en patrón — un estilo inline de otra capa es invisible para todo análisis estático): `npm run theming:sentinel -- <c> <url>` (scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en todos los nodos del componente, pseudos, pase de hover, settle) + ledger scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 · gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46 excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró; y dos muertos nuevos los produjeron los propios arreglos (el blur apaga focus-input-border; el settle desmonta el spinner de feed) — adjudicados con su porqué. Doctrina en recipe-contract §4. También: tres cifras de especificidad de next-features §12 recontadas (la banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0); y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el velo en la celda), el comentario de listbox que mandaba pisar el privado `--_listbox-max-height` apunta ahora al público, y quedan registrados en §13 los dos `header-z` con literal '2' y el spinner de media-player a 720ms mientras el gemelo de feed lleva tokens. Guards: censo global 43 % intacto · audit --only command+carousel PASS · eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs 0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en HEAD y no se tocan). Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el arquetipo en fila+celda, y que soma lea el gap de un token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **El gap entre diapositivas de `carousel` es un canal de valor de soma.**
Soma lo estampa INLINE desde la prop `gap` (necesita el número para el
`flex-basis`), así que ninguna declaración de receta puede ganarle — el trío
`item-gap*` se retiró con 0 diffs. Si algún día debe ser temable, es soma
quien tendría que leer un token; decisión de otra capa.
- **Dos `header-z` llevan el literal `'2'`** (`table`, `tree-grid`) mientras los
otros catorce `*-z` del catálogo leen la escalera `--z-index-*`. Alinearlos es
decidir qué peldaño significa «cabecera pegajosa dentro de un scroller».
- **El spinner de `media-player` conserva `720ms` a pelo** mientras el spinner
idéntico de `feed` ganó dos tokens públicos de duración
(`sentinel-spinner-duration` / `-reduced-duration`). El mismo trato o una
anotación `functional:`, pero no las dos varas.
uix(grid-list): temable — 0 % → 85 %, y una selección que nunca pintó 38 claves nuevas en el bloque `grid-list` de `recipes/base.ts` (que ya existía con los dos forwards de paleta: se FUSIONA, no se añade un segundo). Alcance del censo 0 % → 85 % (22 de 26 puntuables); global 45 % → 46 %, sin contrato 49 → 48. El eje `size` se estampa en el PROVIDER —el wrapper de eidos no tiene parte `root`—, así que los tres nombres resueltos van SIN `parts`: molde `command`, no `table`/`tree-grid`. Fuera los cuatro bloques `[data-size]` del CSS; los emite el TSC. Altura de fila 1:1 con el bundle, `row-padding-inline` con ritmo propio, y la tipografía un paso por debajo de md en adelante — verbatim, como table y tree-grid. LO QUE LA MEDICIÓN CAMBIÓ (la §4 generada tenía un error de doctrina y dos defectos que sólo aparecieron midiendo; el §5 Veredicto se escribió con la sonda delante, porque estaba vacío): 1. La selección de fila NUNCA HA PINTADO. La receta seleccionaba `[data-grid-list-row][data-selected]`, un atributo que ni el morfo declara ni soma estampa (estampa `data-state='selected'` + `aria-selected`): la regla no casaba jamás y su acento `--_grid-list-palette-element` no llegó nunca al píxel — una fila seleccionada muestra el velo NEUTRO del arquetipo, indistinguible del hover (capturado). Retirada la declaración muerta y su forward huérfano: 0 diffs, que es la prueba. Repararlo mueve píxel y choca con §12.5 (el arquetipo fija también `color`) → §13, espera firma. El acento del checkbox (`_palette-solid`) sí vive y se conserva. 2. NO se acuña `hover-row-bg` aunque la propuesta lo pedía. La fila es `archetype: 'item'`: el velo del sistema la pinta a especificidad plena y el plano de la receta pierde — medido, el hover pinta DOS VECES sobre el MISMO nodo (a diferencia de table/tree-grid, donde velo y plano caen en nodos distintos). Sería un token que no puede ganar; mismo criterio que listbox.highlighted → §12.7. Y fila Y celda llevan ambas `item`, así que bajo el puntero se apilan tres capas. 3. «Consume la capa compartida `menu-indicator`» es FALSO. Sale de `SHARED_LAYERS` (`theming-census.ts:432`), pero esa capa no tiene un solo selector de grid-list, la receta no la importa y soma no renderiza indicator: el checkbox ESPEJA el visual pintándolo por su cuenta. Entrada desviada del censo → §13 (listbox / menubar / navigation-menu están en la misma entrada y tampoco aparecen en el fichero). Nombres, sobre la propuesta: `selection-checkbox-{width,height}` son UN knob → `selection-checkbox-size`; `on-selection-checkbox-bg` → `checked-selection-checkbox-fg` (es la tinta del glifo, y `on-` es prefijo reservado); `border` partido en `border-width` + color (molde `command.input-border*`), que el checkbox comparte; `padding` uniforme se queda `padding` (precedente `command.viewport-padding`); `max-block` entra al contrato (molde `command.viewport-max-block`). Artefactos y gates: - Sonda antes/después = 0 diffs en 6.090 valores × 7 estados. Determinismo verificado: dos corridas del mismo código, 0 diffs. - R-5.4: 37/38 tokens mueven un computed. `invalid-border` adjudicado EN EL LEDGER con su razón medida (la demo arranca con su control en off; activado a mano alcanza: oklch(0.9555 0.0207 13.86) → rgb(1,2,3)). - censo --only 85 % · component:audit PASS (R-5.3 incluida) · eidos-lint invalid 0, class-hooks 0 · suite eidos sin rojos nuevos (el único es 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 (38 claves, leídas en vivo). El tab `Recipe` documentaba la regla retirada: corregido. Las fichas de calendar / carousel / command / month-grid / range-calendar / table / year-grid entran porque `--report` regenera el informe entero: sólo actualizan NÚMEROS DE LÍNEA de commits ya hechos que no lo regeneraron. Cero cambios de alcance. Las otras 161, que sólo cambiaban la fecha, se revirtieron. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
Lo que `grid-list` añadió (2026-08-21):
- **⚠ PENDIENTE DE FIRMA — la selección de fila de `grid-list` nunca ha
pintado.** La receta seleccionaba `[data-grid-list-row][data-selected]`, un
atributo que ni el morfo declara ni soma estampa: soma estampa
`data-state='selected'` + `aria-selected='true'`. La regla no casaba jamás,
así que su acento (`--_grid-list-palette-element`) no llegó nunca al píxel y
una fila seleccionada muestra sólo el velo NEUTRO del arquetipo —
visualmente indistinguible de una fila con el ratón encima (capturado). La
declaración muerta y su forward huérfano se retiraron en el commit de
tokenización con **0 diffs de computed**, que es la prueba de que estaban
muertos. **Repararlo mueve píxel** (re-apuntar a `[data-state='selected']`
haría aparecer un acento que el componente nunca tuvo), y choca de frente con
§12.5: el arquetipo `item` fija también `color` a (0,4,0) sobre el estado
seleccionado. Es la misma decisión del velo, no una corrección al paso.
El acento del SelectionCheckbox (`_palette-solid`) sí vive y se conservó.
- **`SHARED_LAYERS` declara a `grid-list` consumidor de `menu-indicator`, y no
lo es.** `theming-census.ts:432` lo lista, pero `lib/menu-indicator.css` no
tiene un solo selector que apunte a grid-list (sus 134 líneas targetean
`[data-context-menu-*]` / `[data-dropdown-menu-*]`), la receta no importa la
capa y soma no renderiza indicator: el checkbox **espeja** ese visual
pintándolo por su cuenta. La entrada hace que el informe generado escriba
«Consume la capa compartida `menu-indicator`» en la §4 de la ficha, que es
falso y dirige mal a quien la lea. Revisar las seis entradas de esa lista
contra los selectores reales de cada capa (`listbox`, `menubar` y
`navigation-menu` están en la misma entrada y tampoco aparecen en el fichero).
uix(textarea): temable — 0 % → 80 %, y el contador que casi pierde su 0.85 37 claves en el bloque `textarea` de `recipes/base.ts` (fusionadas con el forward de paleta que ya estaba). Censo 0 % → 80 %, con **cero globales y cero literales de valor**: los 16 privados leen ya su público. Global 46 %, sin contrato 48 → 47. LO QUE LA MEDICIÓN CAMBIÓ. La §4 generada proponía 35 tokens; cuatro eran colisiones y uno habría movido el default: 1. `count-font-size-{k}` × 5 BORRABA el factor 0.85. El CSS calcula `calc(var(--_textarea-font-size) * 0.85)` y la propuesta ponía el tipo del input crudo en las cinco tallas: el contador habría crecido un 18 %. D-TH.5 lo prohíbe. Queda en UN token con el `calc` sobre el público del input — la escala se escribe una vez y la talla la resuelve el input. Pide `scope: 'host'`: emitido en `:root` su dependencia no existe, y el contrato de scope de la receta lo caza (lo cazó). 2. `input-fg` ⚠ eran DOS knobs (texto y placeholder) → `input-fg` + `input-placeholder-fg`. Igual `count-fg` → `count-fg` + `overflow-count-fg`. 3. `input-border` ⚠ NO eran dos knobs sino UNA variable con su swap de paleta, y el nombre estaba mal: es el borde de FOCO → `focus-input-border`. El de reposo es otro token, que la propuesta ni listó por viajar en el atajo. 4. `ghost-input-border` se RETIRA: la regla ghost lee ese mismo conmutador — sería un valor con dos nombres. Bundle, medido: la tipografía casa 1:1 en las cinco (lee `--size-{k}-font-size`); el spacing NO — `padding-inline` casa salvo en xs (8px vs 6px) y `padding-block` sólo en xs. Verbatim y documentado: un campo multi-línea respira más, y el spacing queda fuera del guard del bundle. Resueltos sin `parts` (el `data-size` va en `[data-textarea]` y el input hereda); fuera los cuatro `[data-size]`. DOS ARREGLOS DEL INSTRUMENTO, los dos por medición y no por sospecha: - **El guard medía con el PUNTERO encima.** `reopen()` corre antes de CADA token y hace clic en `[data-{c}-input]` cuando el componente no tiene parte `content`; el `blur()` de la revisión adversarial quitaba el foco pero no el puntero, así que `:hover` casaba toda la corrida y toda regla de hover pisaba a sus vecinas. Costaba TRES falsos negativos aquí. Con `mouse.move(0,0)` tras cada blur, `input-border` revive solo (31 → 32). - **`::placeholder` SÍ se lee con `getComputedStyle`.** La nota de §13 que decía lo contrario nunca se comprobó; medido, el color centinela vuelve tal cual. Entra en el snapshot (32 → 33) y deja STALE la entrada `command.input-placeholder-fg`, que el guard señaló y se ha borrado. Corridos los DIEZ componentes con ledger tras los dos arreglos: ningún otro cambia, un único STALE, el previsto. Y un hallazgo de píxel que NO se toca aquí (→ next-features §13): el borde de foco sólo se ve con Tab. Las tres reglas de `border-color` del input están ordenadas al revés de lo que significan — hover (0,4,0) > foco (0,3,0) > invalid (0,2,0) — y hacer clic deja el puntero encima por definición, así que gana el hover; un campo inválido pierde su borde rojo al enfocarlo o al pasar el ratón. El anillo de foco del sistema sí se ve siempre, así que no es fallo de accesibilidad. Arreglarlo mueve píxel. El 20 % que falta son las dos declaraciones que consumen `--_textarea-border-focus`, privado a propósito: es el conmutador de una variable con dos fuentes (el público y el forward THM-2), y aplanarlo exigiría duplicar la regla por color. Mismo techo que `listbox` paga por consumir bien su capa. Mi propio veredicto predecía ~100 % y la medición lo corrigió a 80 %. Artefactos y gates: - Sonda antes/después = 0 diffs en 1.247 valores × 8 estados (el 0.85 intacto, que era el riesgo). Determinismo verificado: dos corridas, 0 diffs. - R-5.4: 33/37; los 4 mudos adjudicados en el ledger con su medición (foco, invalid y las dos del contador rebasado — estados que la demo no monta). - censo --only 80 % · 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 (37 claves; el panel muestra el contador computando `calc(calc(1rem * 1) * 0.85)`). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
Lo que `textarea` añadió (2026-08-21):
- **⚠ El borde de foco de `textarea` no se ve al hacer CLIC — sólo con Tab.**
Medido: la regla de hover `[data-textarea-input]:hover:not([data-disabled]):not([data-readonly])`
pesa **(0,4,0)** y la de foco `[data-textarea][data-focused] [data-textarea-input]`
**(0,3,0)**, así que con foco y puntero a la vez gana el hover — y hacer clic
deja el puntero encima por definición. Verificado con las dos reglas
apuntando a colores distintos: sale el del hover. Debajo, `[data-invalid]`
pesa **(0,2,0)**: un campo inválido pierde su borde rojo tanto al enfocarlo
como al pasar el ratón. El anillo de foco del SISTEMA (`outline`) sí se ve
siempre, así que no es un fallo de accesibilidad, pero el orden de las tres
reglas de `border-color` está invertido respecto a lo que significan.
Arreglarlo mueve píxel: no entra en el commit de tokenización.
- ~~**El guard mide con el PUNTERO encima del componente.**~~ **ARREGLADO
2026-08-21**: `reopen()` corre antes de CADA token y hace clic en
`[data-{c}-input]` cuando el componente no tiene parte `content` — el
`blur()` que añadió la revisión adversarial quita el foco pero **no el
puntero**, así que `:hover` seguía casando durante toda la corrida y toda
regla de hover pisaba a sus vecinas de reposo. Es la mitad que aquel arreglo
dejó atrás. En `textarea` costaba tres falsos negativos
(`input-border`, `focus-input-border`, `invalid-input-border`); con
`page.mouse.move(0, 0)` tras cada blur, `input-border` revive solo. Corridos
los diez componentes con ledger: ningún otro cambia, un único STALE
(`command.input-placeholder-fg`, por el arreglo del `::placeholder`).
uix(card-group): temable — 0 % → 90 %, y dos privados abreviados que mueren 20 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 90 %, cero globales y cero privados. Global 47 %, sin contrato 45 → 44. Es un CASCO de composición y su propia cabecera ya lo decía: compone ToggleGroup, Collapsible, Card y Button, y la receta posee «ONLY the genuinely CardGroup-level visual». Eso acota el contrato antes de nombrar nada — el cromo de las tarjetas es de Card, el del título de disclosure es de Button. 1. **`--_cg-pad` y `--_cg-card-radius` eran privados ABREVIADOS** (§6 r5 los prohíbe: `cg` por `card-group`), el mismo defecto que los `--_mp-*` de media-player. Pasar a públicos con el nombre completo es a la vez el renombrado que la regla pedía y la tokenización. La §3 de la ficha decía «la receta no declara privados propios»: los declaraba en su bloque raíz (`card-group.css:15-16`), y el escáner no los veía por buscarlos con el prefijo completo del componente. 2. **`--shape-outer-radius` NO se acuña: se CALCULA.** La receta lo compone como `card-radius + padding` para que las tarjetas aniden concéntricamente (§30). Un tema mueve las dos coordenadas y el radio exterior se recompone solo; el censo ya lo clasifica como sistema. 3. **El eje `size` es sm|md|lg, no las cinco tallas** (`Extract<Size, 'sm' | 'md' | 'lg'>`): seis coordenadas por talla, no diez. Escribir xs/xl habría sido vocabulario muerto. 4. Los tres `padding` son shorthand con una parte fija y otra variable; lo que varía y no es el inset del grupo es el hueco inferior → `title-padding-block-end` y `description-padding-block-end`. 5. El chevron es geometría propia (dos bordes girados 45°): `chevron-size` cubre las dos dimensiones y `chevron-width` el trazo. Su `currentColor` hereda del título a propósito y no es knob. 6. Los `font-size` van al BUNDLE (`--size-{k}-font-size`, alias 1:1) y no al primitivo crudo — misma trampa que cayó en code-block: la propuesta copia el CSS verbatim y hereda el incumplimiento cuando el valor de partida ya violaba `recipe-css-contract`. Aquí se aplicó de entrada. Artefactos y gates: - Sonda antes/después = 0 diffs en 2.581 valores × 7 estados. Determinismo verificado: dos corridas, 0 diffs. - R-5.4: 15/20. Los cinco mudos son los del encabezado ESTÁTICO, que la demo no monta (0 nodos `[data-static]` contra 1 `[data-button]`, CONTADOS); forzado `data-static` alcanzan los cinco, y cada uno lleva su medición en el ledger. El hueco de demo queda registrado en next-features §13, misma clase que el `kind='month'|'year'` de date-range-picker. - censo --only 90 % · 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. - README con su «Talla y tema» y tab `Tokens` en la demo (20 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- **Hueco de demo: `card-group` no monta su encabezado ESTÁTICO.** La página
renderiza el título sólo en su forma de disclosure (`[data-button]`), así que
la mitad `[data-static]` del cromo del título —tinta, familia, tamaño,
interlineado y el hueco inferior— no se ve ni se mide: cinco tokens
adjudicados por eso. Misma clase que `date-range-picker` con
`kind='month'|'year'` (norma N-6).
test(theming): el suelo del alcance, con sus dos muta-pruebas — cierra F0.d `src/uix/eidos/theming-reach-floor.test.ts`. El plan lo encargaba en F0 («un test que fije el suelo: el número SUBE o el test falla») y nunca se escribió, así que hasta hoy **nada impedía que el alcance BAJARA entre sesiones**: una receta podía cambiar un token público por un primitivo y sólo se vería leyendo el censo a mano. Cuatro trinquetes, medidos el 2026-08-22 tras el endurecimiento del censo: alcance global ≥ **56 %** · literales ≤ **564** · knobs sobre global crudo ≤ **1.156** · componentes al 100 % ≥ **14**. Cuando el eje avanza, se suben en el mismo commit que se lo gana. **LAS DOS MUTA-PRUEBAS** (memoria `a-guard-that-inspects-nothing-passes`: un guard sólo vale cuando lo has VISTO rojo). No se dan por supuestas — se ejecutaron: 1. Inyectar UN literal (`border-radius: 7px` en `mark`) → rojo en `maxLiteral` y en la sonda del clasificador. Revertido, verde, árbol idéntico. 2. Cambiar CINCO públicos de `tree-view` por primitivos crudos (92 % → 73 % en ese componente) → rojo en `maxGlobal`. Revertido, vuelve a 92 %. Y de ahí sale una observación que queda escrita en el propio test: **los techos son el detector FINO y el porcentaje el grueso.** Cinco knobs de ~4.700 no mueven un porcentaje redondeado, pero sí un techo — una regresión demasiado pequeña para esconderse de `reachPct` sigue disparando `maxGlobal`. Lo que el test NO asegura, a propósito: el alcance POR COMPONENTE, que ya gobiernan el censo `--only` y las excepciones R-5.x — un número bajo puede ser doctrina bien aplicada (`menu-dial` paga por consumir bien una capa compartida, `link` por heredar la tipografía ambiente). Y `system`, que es temable a nivel de sistema por diseño y queda fuera del ratio. Gates: los 5 tests en verde · suite eidos 439/440 (el único rojo sigue siendo el conocido `skin-media-player`) · prettier sobre el fichero nuevo · docs:check 0 · check limpio. `PLAN §5 F0.d` y la entrada de §13 quedan cerradas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- ~~**El test del SUELO del censo que F0 pedía no existe.**~~ **ESCRITO
2026-08-22** — `src/uix/eidos/theming-reach-floor.test.ts`: alcance global
≥ 56 %, literales ≤ 564, globales ≤ 1.156 y ≥ 14 componentes al 100 %, con
las dos muta-pruebas que el plan exige (un literal inyectado y cinco públicos
cambiados por primitivos, cada uno con su rojo). Medido de paso: **los techos
son el detector FINO y el porcentaje el grueso** — cinco knobs de ~4.700 no
mueven un porcentaje redondeado, pero sí un techo. La entrada original decía: El plan (§5, F0.d)
uix(theming): el censo aprende que un privado DERIVADO sí alcanza — 50 % → 56 % Seis puntos de alcance sin tocar una línea de componente: **la medición estaba mal, no el código**. EL DEFECTO. F0 encargaba «detección "privado que deriva de público" (leer `--_c-x: var(--c-y)` en la misma receta y reclasificar)». Se implementó a medias: el censo CALCULA `derives` y lo imprime en la columna «¿deriva de un público?» de la §3 de cada ficha, pero **el contador nunca lo usó** — esos knobs seguían puntuando como deuda. Medido: **240 privados en 59 componentes** estaban en ese estado, es decir, haciendo exactamente lo que la doctrina prescribe («privados sólo si derivan de públicos») y penalizados por ello. Lo destapó tokenizar `avatar`: sus 17 privados YA leían públicos (`--_avatar-size: var(--avatar-size-md)`), así que no había nada que tokenizar — había que arreglar el instrumento. LA CORRECCIÓN. Segunda pasada tras recoger privados y knobs: un knob que lee `var(--_c-x)` pasa a `public` si ese privado alcanza un público. El cierre es **TRANSITIVO** (`--_a: calc(var(--_b) * .3)` alcanza si `--_b` alcanza) con conjunto de visitados para que un ciclo no lo cuelgue. **Muta-prueba** (obligatoria, memoria `a-guard-that-inspects-nothing-passes`): apuntar `--_avatar-radius` al primitivo crudo baja avatar de 75 % a 72 % y el knob vuelve a `private`; revertirlo lo devuelve a 75 % y deja el árbol idéntico. EFECTO MEDIDO: - Global **50 % → 56 %** · privados 625 → 341 · al 100 % 10 → 14 · <20 % 22 → 20. - `avatar` 36 % → **75 %**, `picker-shell` 76 % → **95 %**, `menu-dial` 25 % → 38 %, y otras 51 fichas con cifras nuevas — **ninguna por cambio de código**. - Los READMEs de `picker-shell` y `menu-dial` se actualizan con la cifra nueva y con la razón, para que nadie crea que el componente cambió. ⚠ **Cualquier cifra anterior a este commit no es comparable con las de ahora.** Queda escrito en el handoff. Y un hueco de F0 que sale al hacerlo, registrado en §13: **el test del SUELO que el plan pedía NO EXISTE** («alcance global ≥ el de hoy y literales ≤ 614 — el número sube o el test falla»). Hoy nada impide que el alcance BAJE entre sesiones: sólo se vería mirando la cifra a mano. Con el censo ya endurecido, es el momento de escribirlo. Gates: `component:audit` 162 PASS / 4 NEEDS-WORK (preexistentes) · `--names` DESVIADAS 0 · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · el censo compila. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
encarga «un test que fije el suelo: alcance global ≥ el de hoy y literales ≤
614 — el número SUBE o el test falla», con su muta-prueba. No hay
`theming-census.test.ts` ni equivalente bajo `src/uix/eidos/`, así que hoy
**nada impide que el alcance BAJE** entre sesiones: sólo se detectaría
mirando la cifra a mano. Con el censo ya endurecido (ver arriba) es el
momento de escribirlo.
- **⚠ `knob.arc-width` significaba DOS cosas con DOS defaults distintos** —
medido 2026-08-22 al tokenizarlo: el inset de la cara del dial (`--space-3`)
y la separación superior del puntero (`--space-2`). Un tema que lo escribiera
movía las tres declaraciones a la vez, pero SIN tema cada una valía algo
distinto. Separado en `arc-width` + `pointer-inset` con 0 diffs. Queda como
aviso de clase: **un mismo nombre con defaults distintos por sitio es una
colisión aunque el override funcione** — es la definición que el propio
generador usa para marcar `⚠`, y conviene barrer el catálogo buscando más.
- **⚠ `mockup` no tiene ruta de demo** (`/uix/components/mockup` da 404), como
le pasaba a `picker-shell`. Con 29 knobs al 38 % es el mayor de los que
quedan sin tocar de su tramo, y sin demo no hay sonda ni guard: habría que
medirlo prestado o darle página.
- **⚠ R-5.1 y R-5.2 no están implementadas** en `component-audit` (0 disparos
hoy; sólo existe R-5.3 de gramática y R-5.4 de alcance). El plan las da por
hechas en F0 «en `warn`» y F3 las quiere en `error`, así que **F3 no puede
cerrarse sin escribirlas primero**. Cuando lleguen, los componentes con
alcance parcial necesitan su marca `R-5.x exception:` en el README — ya
puestas en los 16 de F2-B que no llegan al 100 %.
- **Cuatro componentes salen NEEDS-WORK en `component:audit`** y son
PREEXISTENTES (ninguno tiene un commit de F2-B en su código): `badge`,
`gradient-picker`, `mockup` y `motion`. Anotado para que nadie los atribuya
al eje de theming al leer el informe.
- **⚠ 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. Mordió en
la revisión adversarial de F2-B, que comparó contra una base muerta sin que
nada lo delatara. Se arregla con `server: { fs: { allow: [...] } }` en el
`vite.config.ts` del worktree, y la regla que generaliza es **comprobar que
un componente monta ANTES de comparar**. (Memoria
`worktree-devserver-fsallow`; vuelve a morder cada vez.)
uix(text-focus): temable — 63 % → 100 %, y un primitivo FANTASMA que pintaba por accidente 8 claves en una entrada NUEVA de `recipes/base.ts`. Censo 63 % → **100 %**. Mismo patrón que text-circular: cinco públicos «de facto» consumidos con fallback y ninguno declarado, más tres que faltaban — los tres en la ESQUINA del marco de foco. **La esquina es una escuadra dibujada con bordes**: 1rem × 1rem con dos de sus cuatro lados a `0` según la posición. El tamaño es un knob, el grosor del trazo otro, el radio el tercero. Los desplazamientos `-0.625rem` NO son knobs: colocan la escuadra fuera del marco y se derivan del tamaño y el trazo. `border-color` y `glow-color` son el mismo acento en dos capas y se declaran aparte a propósito: un tema puede querer el halo de otro tono que el borde. EL HALLAZGO — **`--radius-xs` NO EXISTE** (→ next-features §13): La escala de radios del sistema es `sm | md | lg | xl | xxl` (+ none, full, default), sin `xs`. Esta receta escribía `border-radius: var(--radius-xs, 3px)` y **pintaba el fallback**, así que nadie lo notó nunca. Al tokenizarlo tal cual, la variable quedó vacía, la declaración inválida y las cuatro esquinas CUADRADAS: **48 diffs** — exactamente para lo que existe el gate. El contrato guarda `3px` verbatim, el valor que de verdad pintaba, y el diff vuelve a 0. Lo que abre: **un `var(--fantasma, fallback)` es una referencia muerta que funciona por accidente**, y el guard G2 que el plan menciona («referencias-fantasma contra el contrato derivado») o no existe o no cubre los fallbacks. Barrer el catálogo buscando `var(--…, …)` cuyo primer nombre no esté en el contrato derivado es un pase mecánico que se paga solo. Y un arreglo de instrumento: **`filter` y `backdrop-filter` faltaban en la lista de propiedades del guard**. `glow-color` vive dentro de un `drop-shadow()` y leía «no effect» estando vivo — `KNOB_PROPS` del censo sí las tenía, así que el censo contaba el knob y el guard no podía verlo. Con ellas, text-focus pasa a **8/8 sin una sola excepción**. Verificados los 30 componentes con ledger: cero regresiones, cero STALE. (De paso se retiró una adjudicación que había escrito con una razón equivocada antes de medir: el token sí movía el filter.) Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo --only 100 % · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- **⚠ `--radius-xs` NO EXISTE, y una receta lo referenciaba.** La escala de
radios del sistema es `sm | md | lg | xl | xxl` (+ `none`, `full`,
`default`), sin `xs`. `text-focus` escribía
`border-radius: var(--radius-xs, 3px)`: **pintaba el fallback**, así que
nadie lo notó — hasta que tokenizarlo sin fallback dejó la variable vacía, la
declaración inválida y las cuatro esquinas cuadradas (**48 diffs**, medidos
2026-08-22). Corregido a `3px` verbatim, el valor que de verdad pintaba.
Lo que esto dice del catálogo: **un `var(--fantasma, fallback)` es una
referencia muerta que funciona por accidente**, y el guard G2 que el plan
menciona («referencias-fantasma contra el contrato derivado») o no existe o
no cubre los fallbacks. Barrer el catálogo buscando `var(--…, …)` cuyo primer
nombre no esté en el contrato derivado es un pase mecánico que se paga solo.
- **⚠ `filter` y `backdrop-filter` faltaban en la lista de propiedades del
guard** — arreglado 2026-08-22. `text-focus.glow-color` vive dentro de un
`drop-shadow()` y leía «no effect» estando vivo; `KNOB_PROPS` del censo sí
las tenía, así que el censo contaba el knob y el guard no podía verlo.
Verificados los 30 componentes con ledger tras el cambio: cero regresiones,
cero STALE.
uix(text-circular): temable — 83 % → 100 %, declarando lo que ya consumía 6 claves en una entrada NUEVA de `recipes/base.ts`. Censo 83 % → **100 %**. Ya consumía CINCO públicos «de facto» (`var(--text-circular-x, <default>)`) pero **sin declarar ninguno**: un tema no los veía en `getRecipeTokens()` ni el panel de la demo los listaba. Tokenizar ha sido sobre todo DECLARAR lo que ya se consumía, más el `radius` que faltaba. - **Aquí declarar es seguro, al revés que en `knob`**: no hay privado por talla detrás del fallback, así que el público no puede matar una escala. - `color: currentColor` es correcto como token: una custom property no resuelve `currentColor` hasta el punto de uso, así que sigue tomando la tinta del nodo y no la del `:root` — verificado con el diff. - `char-transition` guarda una transición COMPLETA (`all 0.5s cubic-bezier(0, 0, 0, 1)`): el gesto del carácter al girar, verbatim, no cuatro ejes sueltos. EL HALLAZGO (→ next-features §13): **el guard congela las transiciones, y eso esconde los tokens cuyo trabajo ES la transición.** La congelación existe por una razón buena y documentada —una propiedad transicionada devuelve su valor inicial justo tras escribir el token, y un token vivo parecería muerto— pero deja ciego al guard ante `char-transition`. Medido sin congelar: alcanza (0.5s cubic-bezier → 9.5s linear). Si aparecen más tokens de esta clase —transición, animación, easing— merecen un pase propio sin congelación, como el que ya tiene el hover. Gates: sonda antes/después = 0 diffs en 5.916 valores × 7 estados · censo --only 100 % · R-5.4 5/6 con el mudo adjudicado · rtl:check 0 · docs:check 0 · check por fichero limpio · ledger verificado con import real (27 componentes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- **⚠ El guard CONGELA las transiciones, y eso esconde los tokens cuyo trabajo
ES la transición.** La congelación existe por una razón buena y documentada
(una propiedad transicionada devuelve su valor INICIAL justo tras escribir el
token, y un token vivo parecía muerto), pero deja ciego al guard ante
`text-circular.char-transition`, que guarda una transición completa. Medido
sin congelar: alcanza. Si aparecen más tokens de este tipo —transición,
animación, easing— merecen un pase propio sin congelación, como el que ya
tiene el hover.
uix(skin-media-player): temable — 0 % → 22 %, y un techo que es NATURALEZA 10 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 22 %, y ese techo bajo es lo correcto: **es un componente de TONO FIJO**. Sus cuatro objetos son cosas físicas —caoba, aluminio cepillado, cromo, teclas de marfil, una etiqueta de vino, un eje dorado— y esos tonos son la DEPICCIÓN, no roles de tema: «un tocadiscos no se vuelve azul porque el tema lo haga», dice su cabecera. La pintura vive en el SVG bajo la excepción que `recipe-css-contract.test.ts` ya reconoce para natural-time-picker, color-picker y proof-of-human. Lo que SÍ entra es el TAMAÑO, que la propia receta llamaba «design values: retune here, in one place»: - **El ancho base es POR SKIN**, no un número: un casete no mide lo que un tocadiscos (300 / 340 / 480 px). Mi primera lectura del CSS se quedó corta y escribió un solo `base-width`; el assert del script lo cazó al encontrar dos declaraciones, y la corrección salió de volver a leer el fichero entero. - La escala sigue el eje `size` del REPRODUCTOR (0.7 / 1 / 1.35 / 1.7), que él estampa en su raíz y el skin lee desde ahí — sin prop propia ni segundo vocabulario. - Más la opacidad de la lámpara apagada (0.18: «una bombilla apagada sigue enseñando su vidrio») y el radio del área de pulsación de las teclas. Fuera a propósito: el `aspect-ratio` de cada skin (proporción del objeto dibujado — cambiarla lo deforma), el `drop-shadow` del casete (su marrón es madera) y los cuatro `100%` de layout. EL HALLAZGO DE INSTRUMENTO (→ next-features §13): **un valor centinela GRANDE no prueba nada contra una propiedad capada**. El ancho es `min(100%, base × scale)`, así que el `1234px` del guard se capa al contenedor y los siete tokens de tamaño leen «no effect» aunque estén vivos. Con valores PEQUEÑOS (111/122/133 px y escalas 0.11/0.22/0.33) alcanzan EXACTAMENTE (133 × 0.11 = 14.625px). Es primo del caso ya registrado «un valor centinela igual al real lee como no efecto»: el centinela tiene que ser imposible **en la dirección en que la propiedad puede moverse**, y un guard que probara los dos extremos cazaría ambas familias. Y un error de proceso repetido, ya corregido: el fichero de excepciones volvió a romperse por comillas simples dentro de cadenas de comillas simples (`data-skin='record'`). Reescrito con comillas dobles, verificado con un import real (26 componentes en el ledger) y **re-corridos los 26 comprobando el código de salida REAL**: cero fallos. Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo --only 22 % · suite eidos 439/440 (el rojo conocido de este mismo componente, que no tiene morfo — preexistente y ajeno a este commit) · rtl:check 0 · docs:check 0 · check por fichero limpio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- **⚠ El valor centinela GRANDE no prueba nada contra una propiedad capada.**
`skin-media-player` calcula su ancho como `min(100%, base × scale)`: el
`1234px` del guard se capa al contenedor y los siete tokens de tamaño leen
«no effect» aunque estén vivos. Con valores PEQUEÑOS (111/122/133px y escalas
0.11/0.22/0.33) alcanzan exactamente. Es primo del caso ya registrado «un
valor centinela igual al real lee como no efecto» (`9999px` contra un
`--radius-full` que ya era 9999px): **el centinela tiene que ser imposible
EN LA DIRECCIÓN en que la propiedad puede moverse**. Un guard que probara
ambos extremos —uno muy grande y uno muy pequeño— cazaría las dos familias.
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>
2 months ago
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
uix(navigation-menu): temable — 44 % → 100 %, y el guard cerraba el panel que medía Bloque B1 de D-TH.4. Contrato 26 → 44 claves: 19 nuevas y UNA retirada. Diff de computed 0 sobre 2.752 valores en 8 estados; R-5.4 43/44 con una adjudicación escrita. Global 65 % (al 100 % 25 → 26). LOS NOMBRES LOS DIO EL CATÁLOGO, NO YO Ninguno se inventó. Los gemelos cerrados el mismo día (dropdown-menu 96 %, context-menu 87 %) dan content-{min-width,padding,radius,fg} y chevron-{size,stroke}; accordion da trigger-font-family y disabled-trigger-fg. Las cuatro recetas que declaran un chevron lo dimensionan con un LITERAL (0.42em · 0.5em · 0.625rem): un glifo no es un paso de la escala de espacio. Al revés con el subrayado del indicador: su 2px ES --border-width-medium, y dropdown-menu.separator-thickness dice que un grosor decorativo se nombra por esa familia. LA §4 GENERADA FALLA DE CINCO MANERAS, UNA NUEVA Llamaba trigger-width / trigger-height al CHEVRON — nombrando por el primer selector de la regla ([…-trigger]::after) y colisionando con trigger-height, que YA EXISTE y es la altura del control. Fundía en content-radius dos nodos con dos valores. Cruzaba los nombres: content-padding-* para lo que es de la FILA, y el padding real del panel a «sin nombre mecánico». Llamaba content-width a un min-inline-size. Y la nueva: abría exigiendo «consume la capa compartida menu-indicator» — HOMONIMIA, esa capa es el check de los CheckboxItem de dropdown/context-menu y no toca el subrayado de esta barra (LAYER_VOCABULARY sólo registra calendar-surface). LAS FILAS DEL PANEL NO SON item- NI link- En los gemelos, `item` ES la fila. Aquí `item` es el <li> de la barra (parte del morfo) y el Link de barra viste el cromo del trigger: --…-item-radius o --…-link-radius prometerían a un tema un nodo que no alcanzan. Se acuñan content-link-*, que nombra el nodo por donde vive. Su tinta sigue leyendo trigger-fg, como venía. EL PRIVADO ERA UNA ESCALA, NO UN CONMUTADOR --_navigation-menu-list-gap tenía tres fuentes (0 / --space-1 / --space-2) por data-size: sube al TSC como list-gap-{sm,md,lg} + el resuelto, el molde que el bundle del trigger ya tenía al lado. Los dos bloques [data-size] del CSS se retiran — los emite el TSC, y dejarlos vivos da un diff de 0 por la ruta vieja. indicator-x ERA DECLARACIÓN MUERTA Soma escribe las cuatro coordenadas del rect INLINE sobre el indicador (-x, -y, -w, -h) y la receta lee sólo -w y -h; su propio comentario registra que sumar translateX(var(--…-x)) DUPLICABA el desplazamiento. No la leía nadie. Retirada, 0 diffs — la prueba de que estaba muerta (precedente: el re-point de --command-radius, el trío item-gap* de carousel). -w se queda: vive, y es canal de valor, no superficie de tema. EL PANEL NO ACUÑA TIPOGRAFÍA, A PROPÓSITO Lleva [data-depth='overlay'], y ese plano declara font-family / line-height a la misma especificidad (0,1,0) y más tarde en la cascada: un token ahí leería muerto. Es §12.9, pendiente de firma — la misma que hizo retirar claves en menubar, dropdown-menu, context-menu y tooltip. Sí se acuña la familia de la BARRA (trigger-font-family), que no es overlay y alcanza. TRES COSAS QUE APRENDIÓ EL INSTRUMENTO (registradas en next-features §13) 1. Sonda y centinela eran CIEGOS A LA MITAD DEL COMPONENTE. Las filas del mega-menú son los <a> DESNUDOS del consumidor, estilados por descendencia: el filtro data-{c}-* veía 10 nodos y NINGUNO era el panel ni una fila — 9 de los 19 knobs fuera del diff. Con EXTRA_NODES / extraNodes la sonda pasa a 16 nodos abiertos. Y el paso `open` de la sonda no honraba EXTRA_NODES. 2. EL GUARD CERRABA EL PANEL QUE MEDÍA. Este mega-menú abre con pointerenter y pointerleave programa el cierre; el guard abría con clic y luego aparcaba el puntero en (0,0) — justo el gesto que lo cierra. Dos corridas del mismo código discrepaban. Y daba las DOS caras del error: falso negativo en los tokens de la fila, falso positivo en content-link-padding-inline, que leía «vivo» por ensanchar el panel que sí se medía. Con openBy: 'hover', dos corridas idénticas. 3. `color` está EN TRANSICIÓN en esta parte: forzar data-disabled y leer en el acto devuelve el valor VIEJO — tres diagnósticos falsos («ni el inline lo mueve») hasta congelar. La trampa del cuentagotas del color-picker, en otro componente. Congelado: reposo oklch(0.2435 0 0) → disabled oklch(0.61 0 0). Y una de contabilidad, del instrumento y no del componente: el censo decide el eje `size` grepeando data-size en el CSS, y el protocolo §7.4 ORDENA retirar esos bloques. Todo componente que lo aplique bien aparece «sin eje». Ya le pasa a sidebar y nav-tree. En §13. VERIFICACIÓN Sonda antes/después 0 diffs (2.752 valores · 8 estados · 16 nodos con el panel abierto) · R-5.4 43/44 en dos corridas idénticas, adjudicación única (disabled-trigger-fg: la demo no monta el estado; forzado alcanza en trigger Y link, rgb(1,2,3)) · component:audit PASS · censo --only 100 % · eidos-lint 30 morfo-backed / 3 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido) · check sin error nuevo en los ficheros tocados · Chrome con el panel abierto: panel 8/10px/224px, fila 8/12/6px, trigger 36px/6px/gap 4px, indicador 2px. Tras tocar el guard, command, table y tooltip re-verificados sin regresión ni STALE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
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*`
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>
2 months ago
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.
uix(theming): cinco 0 % que NO son deuda — veredictos, no tokens inventados `aspect-ratio`, `text-blur`, `cascade`, `motion` y `date-picker` figuran en el censo con alcance 0 % y sin entrada de contrato. Medidos uno a uno: **ninguno tiene contrato que escribir**. Este commit escribe sus cinco veredictos §5 en vez de fabricarles tokens. - **`aspect-ratio`** no es un componente con cromo: es una FACETA de `Box` (`[data-box][data-aspect-ratio]`). Su único knob global está **prestado de `box`** — la propia ficha lo marca ⤴ — y un token prestado trae la semántica de su dueño: acuñar `--aspect-ratio-width` sería un segundo nombre para el mismo eje. `--aspect-ratio` es canal de valor por instancia (16/9, 1…), no tema; los `100%` y el `object-fit` del hijo son identidad de layout. - **`text-blur`**: sus dos knobs son el `1px` de la técnica sr-only, la receta de accesibilidad que permite al lector de pantalla leer el texto entero mientras la versión animada se parte en segmentos. Moverlos rompe la técnica. - **`cascade`** y **`motion`**: su knob es el `opacity` del gate antiparpadeo (`[data-reveal-pending]` / `[data-animation-pending]`), mecánica del canal de motion. Un tema no puede querer que ese `0` sea otra cosa sin romper el propósito del gate; lo tematizable de motion (duraciones, easings, presets, escalonado) vive en `EidosConfig.motion`. - **`date-picker`**: su propia cabecera lo dice — «This recipe must NOT re-declare any of those… it owns ONLY the popover / calendar layout». Es un DateField compuesto cuyo cromo entero viene de `field.css` / `date-field.css`. Sus dos knobs son la corrección `max-content` que impide que el pie del popover (Clear / Cancel / Save) desborde: un único valor correcto, no una preferencia. REGISTRADO EN next-features §13: **el censo no distingue «0 % por deuda» de «0 % por naturaleza»**, y ya son cinco. Mientras los cuente igual que a un componente con deuda real, la cifra global miente por abajo y el gate de F3 («censo 100 %») es inalcanzable por construcción. Hace falta una clase más —`structural`, junto a `system`— o una marca en la ficha que los saque del denominador, como `LAYER_VOCABULARY` hizo con la familia calendar. Es la misma pregunta ya abierta para las capas compartidas, un piso más abajo. Sin cambios de código: cinco fichas y el registro. `docs:check` 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
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
cinco componentes.** Medidos uno a uno el 2026-08-22, `aspect-ratio`,
`text-blur`, `cascade`, `motion` y `date-picker` tienen alcance 0 % y
**ninguno tiene contrato que escribir**: el knob de `aspect-ratio` está
PRESTADO de `box` (la ficha ya lo marca ⤴) porque es una faceta suya, no un
componente; los de `text-blur` son el `1px` de la técnica sr-only; los de
`cascade` y `motion` son el `opacity` del gate antiparpadeo, mecánica del
canal de motion cuyo valor tematizable vive en `EidosConfig.motion`; y los de
`date-picker` son la corrección `max-content` que impide que el pie del
popover desborde, con un único valor correcto. Sus veredictos §5 quedan
escritos.
Mientras el censo los cuente igual que a un componente con deuda real, la
cifra global miente por abajo y el gate de F3 («censo 100 %») es inalcanzable
por construcción. Lo que hace falta es una clase más —`structural`, junto a
`system`— o una marca en la ficha que los saque del denominador, como
`LAYER_VOCABULARY` hizo con la familia calendar. Es la misma pregunta que ya
está abierta para las capas compartidas, un piso más abajo.
uix(picker-shell): temable — 0 % → 76 %, y el componente que el instrumento no veía 31 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 76 %. Global 48 %, sin contrato 43 → 42. La cola avisaba «es CAPA de los pickers: mirar antes qué posee y qué presta». Mirarlo cambió tres cosas del encargo: 1. **NO es una capa compartida: es un COMPONENTE.** A diferencia de list-surface o calendar-surface no hay hook de capa ni vocabulario prestado — color-picker, date-picker y chronos importan sus `.svelte` directamente. No toca `LAYER_VOCABULARY` y el censo ya lo puntuaba bien. 2. **Su eje de talla no es `data-size`, es `data-picker-size` sobre `[data-popover-content]`**, un ancestro PORTALADO. El TSC no puede emitir esa cascada (`scope: 'size:xs'` generaría `[data-picker-shell][data-size='xs']`, que no casa nunca), así que las coordenadas por talla quedan como públicos PLANOS y la receta las consume desde sus propios selectores. Es el único componente del eje donde el patrón de nombres resueltos no aplica; la regla base es el default `sm` y `md` sólo re-declara lo que cambia. La fila de tiempo lleva un TERCER eje, `data-size` sobre su propia clase. 3. **El instrumental del eje estaba CIEGO aquí, y la sonda genérica devolvió 0 nodos** — el antipatrón de comparar cero valores y pasar. Dos causas a la vez: `picker-shell` no tiene demo propia (404, se mide dentro de un picker anfitrión y detrás de su popover), y sus partes se llaman `data-picker-header` / `-body` / `-footer` en vez de `data-picker-shell-{parte}` — genéricas A PROPÓSITO, dice la receta, «so every picker gets the same visual contract for free» —, así que todo filtro por prefijo del componente ve sólo la raíz. CÓMO SE VERIFICÓ, ya que el instrumento estándar no servía: - Sonda DIRIGIDA (abre el popover del date-picker, captura los seis nodos reales en las cuatro picker-sizes): determinismo comprobado (576 valores, 0 diffs entre dos corridas del mismo código) y **gate antes/después = 0 diffs**. - Los knobs que la demo no monta —el header entero y la fila de tiempo— se midieron FORZANDO el nodo: los quince mueven su propiedad, y el font-size del header se comprobó talla a talla (12/14/16/16px → 102px, cada uno en su picker-size). - El guard R-5.4 pasó de **0/31 a 6/31** al ganar un `COMPONENT_OVERRIDES` (prefijo de atributo + selector de apertura) acotado a este componente. Verificado que los otros CATORCE con ledger no se mueven ni una cifra. Adjudicar 31 excepciones sin ese arreglo habría sido escribir una mentira en el ledger; los 25 que siguen mudos llevan cada uno su razón medida. Header y footer comparten la regla que los separa del cuerpo → UN par de knobs (`border-width` + `border`), no dos. Sin tab `Tokens`: este componente no tiene página donde ponerlo. Registrado en next-features §13 junto con la pregunta de fondo — si el precio de los nombres genéricos (un contrato visual común para todos los pickers) compensa la ceguera del instrumental — y con el eje portalado como caso de prueba si el TSC llega a ganar un scope configurable. Gates: censo --only 76 % · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio · `component:audit` no lo cubre (eidos-only, sin morfo) y `eidos-lint` tampoco. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
Lo que `picker-shell` añadió (2026-08-21):
- **⚠ `picker-shell` no tiene ruta de demo propia** (`/uix/components/picker-shell`
da 404) y sus partes se llaman `data-picker-header` / `-body` / `-footer` en
vez de `data-picker-shell-{parte}` — genéricas **a propósito**, dice su
receta, «so every picker gets the same visual contract for free». La suma de
las dos cosas deja al instrumental ciego: la sonda genérica devolvió **0
nodos**, que es el antipatrón de comparar cero valores y pasar. Se tokenizó
con sonda dirigida (abre el popover del `date-picker`, captura los seis nodos
reales en las cuatro `data-picker-size`: 576 valores, 0 diffs) y el guard
R-5.4 ganó un `COMPONENT_OVERRIDES` para poder abrirlo y filtrarlo — acotado
a este componente, con los otros catorce verificados sin cambio. Lo de fondo
sigue abierto: **una demo propia**, y decidir si el precio de los nombres
genéricos (un contrato visual común para todos los pickers) vale la ceguera
del instrumental.
- **El eje de talla de `picker-shell` es `data-picker-size` sobre un ancestro
PORTALADO**, así que el TSC no puede emitir su cascada (`scope: 'size:xs'`
generaría `[data-picker-shell][data-size='xs']`, que no casa nunca). Es el
único componente del eje cuyas coordenadas por talla quedan como públicos
planos que la receta consume desde sus propios selectores. Si algún día el
TSC gana un eje de scope configurable, éste es su caso de prueba.
uix(code-block): temable — 0 % → 81 %, y la demo que no enseña el componente 20 claves en una entrada NUEVA de `recipes/base.ts` (no tenía). Censo 0 % → 81 %. Global 46 % → 47 %, sin contrato 46 → 45. El handoff lo llamaba «el más mecánico de los que quedan». No lo es: sus 26 knobs se parten en dos grupos con doctrinas distintas. 1. **El nodo del código NO acuña nada, y la §4 pedía que lo hiciera.** `[data-code-block-code]` consume la capa tipográfica `--style-code-*` en sus cinco declaraciones, y las dos que pasan por privado usan la forma canónica de la escotilla por instancia (`var(--_code-block-font-size, var(--style-code-font-size))`, escrita por el wrapper desde la prop `size` y sólo si llega). Es el patrón que D-TH.2-b fijó para heading/text/code. Acuñar `code-font-family` y compañía sería el vocabulario paralelo que la regla 2 de capas prohíbe. Todo lo que entra al contrato es el CASCO alrededor. 2. **Los tres `font-size` iban al primitivo crudo, y lo cazó un guard.** La propuesta copia el valor del CSS verbatim, y ese valor ya violaba `recipe-css-contract` («recipes consume the size bundle, not the raw size-coordinate primitives»): `title/lang/copy-font-size` corregidos a `var(--size-{k}-font-size)`. Los alias son 1:1 → 0 diffs tras aplicarlo. Aviso general: cuando el CSS de partida ya incumple el contrato, la propuesta generada hereda el incumplimiento. 3. Shorthand partidos: los dos `border` y el `border-bottom` de la cabecera comparten anchura y color → `border-width` + `border`; el `padding` del `<pre>` → `pre-padding`. Los `transparent` de outline/ghost y el `border: 0` de ghost son identidad de variante. El 19 % que falta son esas cinco de la capa, que el censo cuenta como `global` porque `code-block` no está en `TYPOGRAPHIC_PRIMITIVES` — es un compuesto que CONTIENE un primitivo tipográfico, no uno de ellos. Extender la clase `system` del censo a las capas sigue pendiente de tu firma; la métrica penaliza hacer lo correcto y no se fuerza de oficio. DOS HALLAZGOS QUE NO SE ARREGLAN AQUÍ (→ next-features §13): - **La demo de `code-block` no enseña el `code-block`.** `[data-uix-docs] pre` (`web/routes/uix/uix.css:801`) pesa (0,1,1) contra los (0,1,0) de `[data-code-block-pre]`, y `[data-uix-docs]` es el shell de TODAS las páginas. Siete propiedades del `<pre>` las pinta el harness y no la receta: padding (16px 20px contra 12px), border (1px contra ninguno), radius (8px contra 0), background (gris contra transparente), font-family, font-size (13 contra 14) y line-height (18.2 contra 20.3). El fondo, el borde y el radio que se ven ahí son del sitio. Por eso `pre-padding` lee muerto en el guard, adjudicado con esa medición. Acotar la regla toca el CSS del SITIO, que sirve a ~162 páginas: decisión aparte. - **El guard de claves duplicadas del handoff inspeccionaba el VACÍO.** El comando documentado usa `grep -oE "^\t…"`, y `grep -E` lee `\t` como una `t` literal: 0 coincidencias sobre 130 bloques reales, así que salía «vacío = OK» sin mirar nada — el antipatrón que el proyecto tiene registrado, dentro del propio handoff. Comprobado con un contador fiable: cero duplicados en el árbol, así que no hay daño en los commits que confiaron en él. CONTINUE queda corregido con una versión que CUENTA los bloques y avisa de que «0 bloques» significa guard roto, no fichero limpio. Artefactos y gates: - Sonda antes/después = 0 diffs en 1.305 valores × 7 estados (dos veces: tras la tokenización y tras la corrección del bundle). Determinismo verificado. - R-5.4: 19/20; `pre-padding` adjudicado con las siete propiedades medidas. - censo --only 81 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0 · suite eidos de vuelta al único rojo conocido (skin-media-player) tras corregir el bundle · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con su «Talla y tema» y tab `Tokens` en la demo (20 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
Lo que `code-block` añadió (2026-08-21):
- **⚠ La demo de `code-block` NO ENSEÑA el `code-block`: el harness del sitio
de documentación le pinta el `<pre>` por encima.**
`[data-uix-docs] pre` (`web/routes/uix/uix.css:801`) pesa **(0,1,1)** y gana
a `[data-code-block-pre]` **(0,1,0)** — y `[data-uix-docs]` es el shell de
TODAS las páginas, así que la regla alcanza cualquier demo. Medidas **siete
propiedades pisadas**: `padding` (16px 20px del harness contra los 12px de la
receta), `border` (1px sólido contra ninguno), `border-radius` (8px contra
0), `background-color` (gris contra transparente), `font-family`,
`font-size` (13px contra 14px) y `line-height` (18.2px contra 20.3px). El
fondo, el borde y el radio que se ven en esa página son del sitio, no del
componente. Es la clase «la demo puede tapar el componente» (precedente
`proof-of-human`), pero aquí son siete propiedades y le toca justo al
componente cuya razón de ser es mostrar código. Por eso `pre-padding` lee
muerto en el guard y está adjudicado con esa medición.
Acotar la regla del harness es tocar el CSS del SITIO, que sirve a las ~162
páginas y a los bloques de código de las fichas: no entra en el commit de un
componente. Decisión aparte.
uix(code): temable — 0 % → 100 %, y el harness que también le pisa el radio 6 claves fusionadas en el bloque `code` de `recipes/base.ts` (donde ya vivía el forward de paleta). Censo 0 % → **100 %**: no queda un knob de apariencia fuera del contrato. **Seis de sus trece knobs son SISTEMA y no se acuñan.** `code` es el primitivo tipográfico canónico: sus seis ejes de tipo consumen `--style-code-*` con la escotilla por instancia (`var(--_code-{eje}, var(--style-code-{eje}))`), la forma que D-TH.2-b fijó, y un primitivo no re-declara el vocabulario de su capa. Lo que entra al contrato es el CROMO de las variantes `soft` y `outline`. - El padding es em-relativo a propósito (la píldora crece con el código que envuelve) y lo declaran IGUAL las dos variantes con cromo: un knob por eje, no cuatro. - `--_code-color` sigue privado: conmutador de dos fuentes (tinta de contenido en línea / forward de paleta cuando llega `data-color`), mismo patrón que el borde de foco de textarea. - El `margin: 0` / `padding: 0` / `background: transparent` / `border: 0` de la base es RESET: la variante `plain` es texto desnudo por identidad. EL HALLAZGO — el harness pisa también a `code` (→ next-features §13, ampliado): Medido con CDP: `[data-uix-docs] code` declara `font-family`, `font-size`, `padding` y `border-radius` y gana **(0,1,1)** contra la regla BASE de la receta **(0,1,0)**. En la página de documentación el radio del componente es el del sitio (3px), no el suyo (4px). Sus reglas de VARIANTE (0,2,0) sí ganan, así que el efecto es selectivo y por eso sólo `radius` lee muerto. **No es un caso aislado**: es la misma familia de reglas del harness que tapa el `<pre>` de code-block con siete propiedades. Cualquier decisión sobre esa regla cubre los dos componentes. Artefactos y gates: - Sonda antes/después = 0 diffs en 174 valores × 7 estados. Determinismo verificado. - R-5.4: 0/6, los seis adjudicados con su medición — `radius` por el harness (CDP), los otros cinco porque la demo monta sólo la variante `plain`, sin cromo; forzando las variantes, los cinco alcanzan. - censo --only 100 % · component:audit PASS · rtl:check 0 · docs:check 0 · check por fichero limpio · suite eidos sin rojos nuevos. - README con «Talla y tema» y tab `Tokens` en la demo (6 claves). Nota de proceso: el fichero de excepciones llegó a quedar con sintaxis TS inválida por un apóstrofe sin escapar, y la comprobación que hice lo dio por bueno porque el `$?` capturado era el del `grep` de la tubería, no el del guard. Corregido, y re-verificados los ONCE componentes con ledger comprobando el código de salida REAL: cero fallos. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
**No es un caso aislado — la misma familia de reglas pisa también a `code`**
(medido 2026-08-21 al tokenizarlo, con CDP): `[data-uix-docs] code` declara
`font-family`, `font-size`, `padding` y `border-radius`, y gana (0,1,1)
contra la regla BASE del componente (0,1,0). Sus reglas de VARIANTE (0,2,0)
sí ganan, así que el efecto es selectivo y por eso sólo su `radius` lee
muerto — 3px del sitio donde el componente pide 4px. Cualquier decisión sobre
la regla del harness cubre los dos componentes.
uix(code-block): temable — 0 % → 81 %, y la demo que no enseña el componente 20 claves en una entrada NUEVA de `recipes/base.ts` (no tenía). Censo 0 % → 81 %. Global 46 % → 47 %, sin contrato 46 → 45. El handoff lo llamaba «el más mecánico de los que quedan». No lo es: sus 26 knobs se parten en dos grupos con doctrinas distintas. 1. **El nodo del código NO acuña nada, y la §4 pedía que lo hiciera.** `[data-code-block-code]` consume la capa tipográfica `--style-code-*` en sus cinco declaraciones, y las dos que pasan por privado usan la forma canónica de la escotilla por instancia (`var(--_code-block-font-size, var(--style-code-font-size))`, escrita por el wrapper desde la prop `size` y sólo si llega). Es el patrón que D-TH.2-b fijó para heading/text/code. Acuñar `code-font-family` y compañía sería el vocabulario paralelo que la regla 2 de capas prohíbe. Todo lo que entra al contrato es el CASCO alrededor. 2. **Los tres `font-size` iban al primitivo crudo, y lo cazó un guard.** La propuesta copia el valor del CSS verbatim, y ese valor ya violaba `recipe-css-contract` («recipes consume the size bundle, not the raw size-coordinate primitives»): `title/lang/copy-font-size` corregidos a `var(--size-{k}-font-size)`. Los alias son 1:1 → 0 diffs tras aplicarlo. Aviso general: cuando el CSS de partida ya incumple el contrato, la propuesta generada hereda el incumplimiento. 3. Shorthand partidos: los dos `border` y el `border-bottom` de la cabecera comparten anchura y color → `border-width` + `border`; el `padding` del `<pre>` → `pre-padding`. Los `transparent` de outline/ghost y el `border: 0` de ghost son identidad de variante. El 19 % que falta son esas cinco de la capa, que el censo cuenta como `global` porque `code-block` no está en `TYPOGRAPHIC_PRIMITIVES` — es un compuesto que CONTIENE un primitivo tipográfico, no uno de ellos. Extender la clase `system` del censo a las capas sigue pendiente de tu firma; la métrica penaliza hacer lo correcto y no se fuerza de oficio. DOS HALLAZGOS QUE NO SE ARREGLAN AQUÍ (→ next-features §13): - **La demo de `code-block` no enseña el `code-block`.** `[data-uix-docs] pre` (`web/routes/uix/uix.css:801`) pesa (0,1,1) contra los (0,1,0) de `[data-code-block-pre]`, y `[data-uix-docs]` es el shell de TODAS las páginas. Siete propiedades del `<pre>` las pinta el harness y no la receta: padding (16px 20px contra 12px), border (1px contra ninguno), radius (8px contra 0), background (gris contra transparente), font-family, font-size (13 contra 14) y line-height (18.2 contra 20.3). El fondo, el borde y el radio que se ven ahí son del sitio. Por eso `pre-padding` lee muerto en el guard, adjudicado con esa medición. Acotar la regla toca el CSS del SITIO, que sirve a ~162 páginas: decisión aparte. - **El guard de claves duplicadas del handoff inspeccionaba el VACÍO.** El comando documentado usa `grep -oE "^\t…"`, y `grep -E` lee `\t` como una `t` literal: 0 coincidencias sobre 130 bloques reales, así que salía «vacío = OK» sin mirar nada — el antipatrón que el proyecto tiene registrado, dentro del propio handoff. Comprobado con un contador fiable: cero duplicados en el árbol, así que no hay daño en los commits que confiaron en él. CONTINUE queda corregido con una versión que CUENTA los bloques y avisa de que «0 bloques» significa guard roto, no fichero limpio. Artefactos y gates: - Sonda antes/después = 0 diffs en 1.305 valores × 7 estados (dos veces: tras la tokenización y tras la corrección del bundle). Determinismo verificado. - R-5.4: 19/20; `pre-padding` adjudicado con las siete propiedades medidas. - censo --only 81 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0 · suite eidos de vuelta al único rojo conocido (skin-media-player) tras corregir el bundle · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con su «Talla y tema» y tab `Tokens` en la demo (20 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- **⚠ El guard de claves duplicadas del handoff INSPECCIONA EL VACÍO.** El
comando documentado en `CONTINUE-theming.md` («Trampas que costaron un commit
cada una») es
`grep -oE "^ '?[a-z0-9-]+'?: \{" src/uix/eidos/lib/recipes/base.ts | …`,
y **`grep -E` no interpreta ` ` como tabulador** — lo lee como una `t`
literal, así que el patrón no casa NADA: 0 coincidencias sobre 130 bloques
reales. El guard salía siempre «vacío = OK» sin haber mirado nada, que es la
definición del antipatrón que el propio proyecto tiene registrado. Con un tab
real en el patrón encuentra los 130 (comprobado: cero duplicados en el árbol,
así que no hay daño que reparar). El handoff queda corregido con la versión
que sí mira.
Lo que `text-gradient` destapó (2026-08-22) — **PENDIENTE DE FIRMA, apuntado
para resolver después** (autor, 2026-08-22):
- **⚠ La familia text-effects ACUÑA tipografía propia contra el canon.** Cinco
claves del contrato: `text-focus.word-size` (`var(--font-size-4xl, 3rem)` —
`4xl` es un paso que la escala canónica `xxs..xxxl` de
`lib/primitives/typography.ts` NO tiene: idioma Tailwind del import de julio
`63b371d5c`, pinta el fallback) y `word-weight` (900) ·
`text-circular.weight` (900) y `font-size` (1.5rem) ·
`text-gradient.font-weight` (500, subida el 22 en `97a6f03d7`). Son opiniones
del seed (react-bits) que el protocolo «valor verbatim» convirtió en contrato
sin la pregunta previa: ¿un tratamiento de pintura sobre texto real tiene
tipografía propia? La doctrina fijada en F2-B (regla 2, cinco precedentes:
`label`, `code`, `kbd`, `announce`, `code-block`) dice que una receta
CONSUME la capa tipográfica (`--style-*`) o HEREDA (`link` → `inherit`):
un TextGradient dentro de un `h1` ES un h1.
uix(theming): text-scramble no tiene contrato que escribir — y el barrido de fantasmas, hecho Sin cambios de código: su veredicto §5 y el registro. Su 50 % no es ni deuda ni logro, es la suma de dos cosas que no son knobs: - **Los dos literales son la técnica sr-only** (`width`/`height: 1px` en `[data-text-scramble-sr]`), idéntica a `text-blur` y al resto del catálogo: existe para que el lector de pantalla lea el texto entero mientras la versión animada se parte en caracteres. Moverlos rompe la técnica. - **Los dos «públicos» son tipografía que el componente no debería acuñar** (`--text-scramble-font` / `-size`, con fallback y sin declarar): es la firma 1.bis pendiente. Escribirles contrato ahora sería acuñar exactamente lo que esa firma propone retirar. Con esto, 1.bis son **7 claves en 4 de los 5** componentes de la familia; `text-blur` es el único limpio: hereda. Y el `font` apunta a un FANTASMA, medido en navegador: `var(--font-mono, …)` no existe —el canónico es `--font-family-mono`— así que pinta `ui-monospace` e **ignora la mono del tema** (`Azeret Mono`, que está ahí al lado). EL BARRIDO MECÁNICO QUE PEDÍAN `--radius-xs` Y `--font-size-4xl`, HECHO: 5.793 custom properties declaradas en todo `src/uix/eidos` (fundación + capas + recetas + `generated/`) contra los `var(--x, fallback)` de las recetas cuyo `--x` sea vocabulario de SISTEMA → **9 referencias fantasma**. (Las que van sin fallback ya las guarda `recipe-css-contract`; el hueco eran justo las que «funcionan por accidente».) Tabla completa en next-features §13: - `--color-content-tertiary` ×3 (float-panel, proof-of-human clock y rotate-align) — **es el caso histórico** que `recipe-contract` §1 cita como origen de su propia regla, y sigue vivo en tres recetas. - `--style-{body,prose,label,caption}-font-variation-settings` ×4 (`text.css`) — los estilos con nombre emiten 6 claves y ésta no está: **el eje de fuente variable no llega nunca al primitivo `Text`**. - `--font-family-sans` (palabras, track WIP) y `--font-mono` (text-scramble). Ninguna se arregla aquí. El barrido debería ser un guard: misma lógica que el test de fantasmas sin fallback que ya existe, con el filtro de vocabulario de sistema encima. Guards: censo `--only` sin cambio (50 %, es el veredicto lo que cambia) · component-audit PASS · docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Son SIETE claves en CUATRO de los cinco componentes** (medido 2026-08-22 al
adjudicar `text-scramble`): las cinco de arriba más `text-scramble.font` y
`.size`, éstas aún sin declarar en `base.ts`. Y la de `text-scramble` es la
peor de todas porque además apunta fuera del canon: `var(--font-mono, …)`,
nombre que **no existe** —el canónico es `--font-family-mono`—, así que pinta
`ui-monospace` y **el componente ignora la mono del tema** (`Azeret Mono`).
Medido en navegador. `text-blur` es el único limpio de la familia: hereda.
**Resolución propuesta**: retirar las cinco claves y heredar del contexto.
Mueve defaults (TextFocus deja de imponer 3rem/900, TextCircular 1.5rem/900,
TextGradient 500) → firma del autor. Con ello muere el fantasma `4xl` sin
tocar el módulo de tipografía, que es canon y está **prohibido modificar**
(autor, 2026-08-22). Refuerza además el barrido mecánico que `--radius-xs`
ya pedía: `var(--nombre, …)` cuyo nombre no emite ningún generador.
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES 7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %** (10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos. Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres literales. Sin eje `size` ni privados propios, así que declarar no puede matar una escala. LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO, no contra el CSS de partida: - **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight` contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya pintaba: `500` ES `var(--font-weight-medium)`. - **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo del marco asomando a cada lado. El knob es `border-width` —con `var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de la escala— y el hueco DERIVA de él. - **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje `scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es `calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110. Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter. Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el 2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro. FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B aplicada por adelantado, no después): - La demo arranca con `showBorder=false`, así que sonda y centinela veían **2 nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa `DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una vez por talla) y el centinela una entrada `openWith` que no abre un panel sino que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica. - La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el centinela ya leía: un token de fondo podía moverse entre las dos corridas sin que el diff lo viera. Con la propiedad y la variante: **4 nodos**. Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el único efecto de hover es `animation-play-state`, que no es knob. LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene `2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token `text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y `--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit. Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y este renombrado la dejaba obsoleta al instante. Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) · centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**. Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene 2.392), no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
uix(theming): text-scramble no tiene contrato que escribir — y el barrido de fantasmas, hecho Sin cambios de código: su veredicto §5 y el registro. Su 50 % no es ni deuda ni logro, es la suma de dos cosas que no son knobs: - **Los dos literales son la técnica sr-only** (`width`/`height: 1px` en `[data-text-scramble-sr]`), idéntica a `text-blur` y al resto del catálogo: existe para que el lector de pantalla lea el texto entero mientras la versión animada se parte en caracteres. Moverlos rompe la técnica. - **Los dos «públicos» son tipografía que el componente no debería acuñar** (`--text-scramble-font` / `-size`, con fallback y sin declarar): es la firma 1.bis pendiente. Escribirles contrato ahora sería acuñar exactamente lo que esa firma propone retirar. Con esto, 1.bis son **7 claves en 4 de los 5** componentes de la familia; `text-blur` es el único limpio: hereda. Y el `font` apunta a un FANTASMA, medido en navegador: `var(--font-mono, …)` no existe —el canónico es `--font-family-mono`— así que pinta `ui-monospace` e **ignora la mono del tema** (`Azeret Mono`, que está ahí al lado). EL BARRIDO MECÁNICO QUE PEDÍAN `--radius-xs` Y `--font-size-4xl`, HECHO: 5.793 custom properties declaradas en todo `src/uix/eidos` (fundación + capas + recetas + `generated/`) contra los `var(--x, fallback)` de las recetas cuyo `--x` sea vocabulario de SISTEMA → **9 referencias fantasma**. (Las que van sin fallback ya las guarda `recipe-css-contract`; el hueco eran justo las que «funcionan por accidente».) Tabla completa en next-features §13: - `--color-content-tertiary` ×3 (float-panel, proof-of-human clock y rotate-align) — **es el caso histórico** que `recipe-contract` §1 cita como origen de su propia regla, y sigue vivo en tres recetas. - `--style-{body,prose,label,caption}-font-variation-settings` ×4 (`text.css`) — los estilos con nombre emiten 6 claves y ésta no está: **el eje de fuente variable no llega nunca al primitivo `Text`**. - `--font-family-sans` (palabras, track WIP) y `--font-mono` (text-scramble). Ninguna se arregla aquí. El barrido debería ser un guard: misma lógica que el test de fantasmas sin fallback que ya existe, con el filtro de vocabulario de sistema encima. Guards: censo `--only` sin cambio (50 %, es el veredicto lo que cambia) · component-audit PASS · docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠ El barrido mecánico está HECHO: 9 referencias fantasma con fallback en el
catálogo de componentes** (2026-08-22). Es lo que pedían el hallazgo de
`--radius-xs` y el de `--font-size-4xl`, y ya no hay que suponer cuántas
quedan. Método: recoger las **5.793** custom properties declaradas en TODO
`src/uix/eidos` (fundación + capas + recetas + `generated/`) y buscar en las
recetas los `var(--x, fallback)` cuyo `--x` sea vocabulario de SISTEMA y no
esté declarado — el sistema nunca lo escribe en runtime, así que un nombre
ausente es una errata. (Los `var(--x)` SIN fallback ya los guarda
`recipe-css-contract`; el hueco eran justo los que llevan fallback, que
«funcionan por accidente».)
uix(navigation-menu): temable — 44 % → 100 %, y el guard cerraba el panel que medía Bloque B1 de D-TH.4. Contrato 26 → 44 claves: 19 nuevas y UNA retirada. Diff de computed 0 sobre 2.752 valores en 8 estados; R-5.4 43/44 con una adjudicación escrita. Global 65 % (al 100 % 25 → 26). LOS NOMBRES LOS DIO EL CATÁLOGO, NO YO Ninguno se inventó. Los gemelos cerrados el mismo día (dropdown-menu 96 %, context-menu 87 %) dan content-{min-width,padding,radius,fg} y chevron-{size,stroke}; accordion da trigger-font-family y disabled-trigger-fg. Las cuatro recetas que declaran un chevron lo dimensionan con un LITERAL (0.42em · 0.5em · 0.625rem): un glifo no es un paso de la escala de espacio. Al revés con el subrayado del indicador: su 2px ES --border-width-medium, y dropdown-menu.separator-thickness dice que un grosor decorativo se nombra por esa familia. LA §4 GENERADA FALLA DE CINCO MANERAS, UNA NUEVA Llamaba trigger-width / trigger-height al CHEVRON — nombrando por el primer selector de la regla ([…-trigger]::after) y colisionando con trigger-height, que YA EXISTE y es la altura del control. Fundía en content-radius dos nodos con dos valores. Cruzaba los nombres: content-padding-* para lo que es de la FILA, y el padding real del panel a «sin nombre mecánico». Llamaba content-width a un min-inline-size. Y la nueva: abría exigiendo «consume la capa compartida menu-indicator» — HOMONIMIA, esa capa es el check de los CheckboxItem de dropdown/context-menu y no toca el subrayado de esta barra (LAYER_VOCABULARY sólo registra calendar-surface). LAS FILAS DEL PANEL NO SON item- NI link- En los gemelos, `item` ES la fila. Aquí `item` es el <li> de la barra (parte del morfo) y el Link de barra viste el cromo del trigger: --…-item-radius o --…-link-radius prometerían a un tema un nodo que no alcanzan. Se acuñan content-link-*, que nombra el nodo por donde vive. Su tinta sigue leyendo trigger-fg, como venía. EL PRIVADO ERA UNA ESCALA, NO UN CONMUTADOR --_navigation-menu-list-gap tenía tres fuentes (0 / --space-1 / --space-2) por data-size: sube al TSC como list-gap-{sm,md,lg} + el resuelto, el molde que el bundle del trigger ya tenía al lado. Los dos bloques [data-size] del CSS se retiran — los emite el TSC, y dejarlos vivos da un diff de 0 por la ruta vieja. indicator-x ERA DECLARACIÓN MUERTA Soma escribe las cuatro coordenadas del rect INLINE sobre el indicador (-x, -y, -w, -h) y la receta lee sólo -w y -h; su propio comentario registra que sumar translateX(var(--…-x)) DUPLICABA el desplazamiento. No la leía nadie. Retirada, 0 diffs — la prueba de que estaba muerta (precedente: el re-point de --command-radius, el trío item-gap* de carousel). -w se queda: vive, y es canal de valor, no superficie de tema. EL PANEL NO ACUÑA TIPOGRAFÍA, A PROPÓSITO Lleva [data-depth='overlay'], y ese plano declara font-family / line-height a la misma especificidad (0,1,0) y más tarde en la cascada: un token ahí leería muerto. Es §12.9, pendiente de firma — la misma que hizo retirar claves en menubar, dropdown-menu, context-menu y tooltip. Sí se acuña la familia de la BARRA (trigger-font-family), que no es overlay y alcanza. TRES COSAS QUE APRENDIÓ EL INSTRUMENTO (registradas en next-features §13) 1. Sonda y centinela eran CIEGOS A LA MITAD DEL COMPONENTE. Las filas del mega-menú son los <a> DESNUDOS del consumidor, estilados por descendencia: el filtro data-{c}-* veía 10 nodos y NINGUNO era el panel ni una fila — 9 de los 19 knobs fuera del diff. Con EXTRA_NODES / extraNodes la sonda pasa a 16 nodos abiertos. Y el paso `open` de la sonda no honraba EXTRA_NODES. 2. EL GUARD CERRABA EL PANEL QUE MEDÍA. Este mega-menú abre con pointerenter y pointerleave programa el cierre; el guard abría con clic y luego aparcaba el puntero en (0,0) — justo el gesto que lo cierra. Dos corridas del mismo código discrepaban. Y daba las DOS caras del error: falso negativo en los tokens de la fila, falso positivo en content-link-padding-inline, que leía «vivo» por ensanchar el panel que sí se medía. Con openBy: 'hover', dos corridas idénticas. 3. `color` está EN TRANSICIÓN en esta parte: forzar data-disabled y leer en el acto devuelve el valor VIEJO — tres diagnósticos falsos («ni el inline lo mueve») hasta congelar. La trampa del cuentagotas del color-picker, en otro componente. Congelado: reposo oklch(0.2435 0 0) → disabled oklch(0.61 0 0). Y una de contabilidad, del instrumento y no del componente: el censo decide el eje `size` grepeando data-size en el CSS, y el protocolo §7.4 ORDENA retirar esos bloques. Todo componente que lo aplique bien aparece «sin eje». Ya le pasa a sidebar y nav-tree. En §13. VERIFICACIÓN Sonda antes/después 0 diffs (2.752 valores · 8 estados · 16 nodos con el panel abierto) · R-5.4 43/44 en dos corridas idénticas, adjudicación única (disabled-trigger-fg: la demo no monta el estado; forzado alcanza en trigger Y link, rgb(1,2,3)) · component:audit PASS · censo --only 100 % · eidos-lint 30 morfo-backed / 3 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido) · check sin error nuevo en los ficheros tocados · Chrome con el panel abierto: panel 8/10px/224px, fila 8/12/6px, trigger 36px/6px/gap 4px, indicador 2px. Tras tocar el guard, command, table y tooltip re-verificados sin regresión ni STALE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| referencia inexistente | dónde | qué pinta en su lugar |
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--color-content-tertiary` ×3 | `float-panel.css:227` · `proof-of-human/clock.css:87` · `proof-of-human/rotate-align.css:114` | `--color-content-secondary`. **Es el caso histórico** que `recipe-contract` §1 cita como origen de la regla («shipped a month that way») y sigue vivo en tres recetas |
| `--style-{body,prose,label,caption}-font-variation-settings` ×4 | `text.css:32,55,65,75` | `normal` — los estilos con nombre emiten 6 claves y ésta no está, así que **el eje variable no llega nunca al primitivo `Text`** |
| `--font-family-sans` | `palabras.css:296` | `sans-serif` del sistema. El canónico es `--font-family-primary` (palabras es track WIP) |
| `--font-mono` | `text-scramble.css:11` | `ui-monospace`. El canónico es `--font-family-mono` |
uix(theming): text-scramble no tiene contrato que escribir — y el barrido de fantasmas, hecho Sin cambios de código: su veredicto §5 y el registro. Su 50 % no es ni deuda ni logro, es la suma de dos cosas que no son knobs: - **Los dos literales son la técnica sr-only** (`width`/`height: 1px` en `[data-text-scramble-sr]`), idéntica a `text-blur` y al resto del catálogo: existe para que el lector de pantalla lea el texto entero mientras la versión animada se parte en caracteres. Moverlos rompe la técnica. - **Los dos «públicos» son tipografía que el componente no debería acuñar** (`--text-scramble-font` / `-size`, con fallback y sin declarar): es la firma 1.bis pendiente. Escribirles contrato ahora sería acuñar exactamente lo que esa firma propone retirar. Con esto, 1.bis son **7 claves en 4 de los 5** componentes de la familia; `text-blur` es el único limpio: hereda. Y el `font` apunta a un FANTASMA, medido en navegador: `var(--font-mono, …)` no existe —el canónico es `--font-family-mono`— así que pinta `ui-monospace` e **ignora la mono del tema** (`Azeret Mono`, que está ahí al lado). EL BARRIDO MECÁNICO QUE PEDÍAN `--radius-xs` Y `--font-size-4xl`, HECHO: 5.793 custom properties declaradas en todo `src/uix/eidos` (fundación + capas + recetas + `generated/`) contra los `var(--x, fallback)` de las recetas cuyo `--x` sea vocabulario de SISTEMA → **9 referencias fantasma**. (Las que van sin fallback ya las guarda `recipe-css-contract`; el hueco eran justo las que «funcionan por accidente».) Tabla completa en next-features §13: - `--color-content-tertiary` ×3 (float-panel, proof-of-human clock y rotate-align) — **es el caso histórico** que `recipe-contract` §1 cita como origen de su propia regla, y sigue vivo en tres recetas. - `--style-{body,prose,label,caption}-font-variation-settings` ×4 (`text.css`) — los estilos con nombre emiten 6 claves y ésta no está: **el eje de fuente variable no llega nunca al primitivo `Text`**. - `--font-family-sans` (palabras, track WIP) y `--font-mono` (text-scramble). Ninguna se arregla aquí. El barrido debería ser un guard: misma lógica que el test de fantasmas sin fallback que ya existe, con el filtro de vocabulario de sistema encima. Guards: censo `--only` sin cambio (50 %, es el veredicto lo que cambia) · component-audit PASS · docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Ninguna se arregla aquí: dos son tipografía y esperan la firma 1.bis, y las
otras dos familias son decisión de diseño (¿existe un slot `tertiary`?
¿el eje variable debe emitirse por estilo?). El barrido, en cambio, **debería
ser un guard**: la lógica es la misma que la del test de fantasmas sin
fallback que ya existe, con el filtro de vocabulario de sistema encima.
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor 22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart 9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18). LO QUE DECIDIÓ LA MEDICIÓN, no la ficha: Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9× `content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6× `font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`. Nombrar por parte habría dado cinco claves para el pelo que separa marcas y nueve para la tinta de anotación: la inflación de `spinner` con otra cara. **El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar `--chart-label-font-size` sería tipografía por componente, prohibido el mismo día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`, `announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a `var(--style-caption|label-font-size)`, mismo valor. **La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento), `smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment). **Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA `var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract` prohíbe— con un respaldo muerto. El contrato guarda el bundle `var(--size-sm-control-height)`, 1:1 con él. **Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el `min-inline-size: max-content` del tooltip, misma clase ya adjudicada en `date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5. EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir: 1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo: `/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de 0/31 con seis sin adjudicar a 6/31 sin tener que saberlo. 2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` / `strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un componente que dibuja en SVG leían muertas estando vivas. Misma clase que el hueco de `filter` / `backdrop-filter` de ayer. 3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía `rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía muerta. La dimensión se decide ahora por el FINAL del nombre. Los 28 componentes con ledger re-verificados tras los tres cambios: cero regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.) Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados, 892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease` adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier → steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y 0 en los ficheros tocados. Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica** si no eres un primitivo —las doce declaraciones correctas siguen contando `global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número del gauge no tiene estilo con nombre que lo represente (un readout de dato no es un `h2`), así que sigue leyendo `--font-size-xl`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Lo que `chart` destapó (2026-08-22):
uix(chart): la COSTURA del texto — 49 % → 89 %, y `global` a cero Seis claves. El valor sigue siendo el del canon; lo que faltaba era el knob. CORRIGE MI LECTURA DE HACE DOS HORAS. Al tokenizar chart di por doctrina que «el texto no recibe tokens del componente» porque su valor viene de la capa de estilos con nombre, y dejé el CSS leyendo `var(--style-caption-font-size)` y `var(--color-content-*)` A PELO. Eso es media doctrina: PLAN §2-A pide que TODO knob de apariencia lea `--{c}-{slot}`. Sin la costura, un tema no puede aclarar las anotaciones de un gráfico denso sin mover `caption` en toda la app, y `getRecipeTokens()` / `/temas` no ven esa superficie. El molde ya estaba escrito en la vertebración tipográfica (`--accordion-trigger-font-family: var(--style-label-font-family)`), y D-TH.2 aclara que sólo los SEIS primitivos quedan fuera: «un componente que sólo LEE un named style para un eje no es un primitivo y no está en el conjunto». POR ROL, NO POR PARTE — y se decidió midiendo: --chart-caption-font-size var(--style-caption-font-size) --chart-caption-fg var(--style-caption-color) --chart-label-font-size var(--style-label-font-size) --chart-label-fg var(--style-label-color) --chart-label-font-weight var(--style-label-font-weight) --chart-gauge-label-font-size var(--size-xl-font-size) Dos roles × tres ejes = 6 claves para 19 nodos de texto; nombrarlo por parte habrían sido ~30 claves con dos valores. **Tamaño y tinta son ortogonales a propósito**: `empty` y `bar-list-value` son tamaño `label` con tinta `caption`, y el CSS lo dice. El readout del gauge no tiene rol en el canon —un `h2` es un encabezado, no un dato— así que su costura toma el bundle de talla; con eso se cierra la escotilla que dejé abierta en el commit anterior. Tres nombres de mi primera propuesta no aguantaron la revisión y no entran: `label-font-size` a secas chocaba con las seis partes `*-label` del componente (se acepta el choque, pero nombrando por el ROL del canon, que dice la verdad: las etiquetas de eje SON captions), `emphasis-*` inventaba vocabulario fuera de §1/§6.7, y `fg-muted` se apoyaba en un único precedente. La tinta lee el color DEL ROL (`var(--style-caption-color)`), no la ranura a pelo. Y RETIRA UNA NOTA EQUIVOCADA DE §13: escribí que el censo penalizaba consumir la capa tipográfica cuando no eres un primitivo. El censo medía bien; faltaba la costura. Con ella, `global` cae a CERO y el alcance a 89 % — el techo, porque lo que queda son los diez literales de identidad (nueve `100%` + el `max-content` del tooltip). La lección queda escrita: cuando la métrica y la doctrina discrepan, sospecha de la lectura antes que del instrumento. `listbox` (68 %, ritmo de fila prestado de `list-surface`) es la misma pregunta un piso más arriba y queda pendiente de revisar con este molde. Gates: sonda antes/después **0 diffs en 161.850 valores computados** (16 rutas × 7 estados) · centinela **43/45**, las dos de transición adjudicadas · censo --only **89 %** con `global` 36 → 0 · --names 45/45 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y 0 en los ficheros tocados. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **~~El censo penaliza consumir la capa tipográfica~~ — RESUELTO el mismo día,
y la nota estaba equivocada.** Escribí que las doce declaraciones de `chart`
que pasaban a `var(--style-caption|label-font-size)` seguían contando
`global` porque el censo sólo exime `--style-*` a los seis primitivos. El
censo medía BIEN: lo que faltaba era la **costura**. PLAN §2-A pide que todo
knob lea `--{c}-{slot}`, y leer el canon a pelo desde el CSS deja a un tema
sin poder retocar ESE componente sin mover el rol en toda la app. Con seis
claves `--chart-{caption,label}-*` cuyo VALOR es `var(--style-{rol}-*)`,
`global` cayó a cero y el alcance a 89 %. **La lección: cuando la métrica y
la doctrina discrepan, sospecha de la lectura antes que del instrumento.**
El molde canónico ya estaba escrito en la vertebración tipográfica
(`--accordion-trigger-font-family: var(--style-label-font-family)`), y D-TH.2
aclara que sólo los PRIMITIVOS quedan fuera. `listbox` (68 %, ritmo de fila
de `list-surface`) es la misma pregunta un piso más arriba: **una capa
compartida no se lee a pelo, se cose** — pendiente de revisar con este molde.
- **⚠ Un READOUT de dato no tiene estilo con nombre en el canon.** El número del
gauge (`--font-size-xl`), el total de un funnel, el valor de un `Stat`: no
son encabezados (`h2` está en ese tamaño pero significa otra cosa) ni texto de
cuerpo. Su costura toma el bundle de talla, que es correcto pero no dice nada
del ROL. Si aparecen más, la respuesta es un estilo con nombre nuevo en el
módulo de tipografía — firma del autor, y el módulo no se toca sin ella.
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor 22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart 9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18). LO QUE DECIDIÓ LA MEDICIÓN, no la ficha: Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9× `content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6× `font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`. Nombrar por parte habría dado cinco claves para el pelo que separa marcas y nueve para la tinta de anotación: la inflación de `spinner` con otra cara. **El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar `--chart-label-font-size` sería tipografía por componente, prohibido el mismo día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`, `announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a `var(--style-caption|label-font-size)`, mismo valor. **La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento), `smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment). **Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA `var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract` prohíbe— con un respaldo muerto. El contrato guarda el bundle `var(--size-sm-control-height)`, 1:1 con él. **Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el `min-inline-size: max-content` del tooltip, misma clase ya adjudicada en `date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5. EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir: 1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo: `/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de 0/31 con seis sin adjudicar a 6/31 sin tener que saberlo. 2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` / `strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un componente que dibuja en SVG leían muertas estando vivas. Misma clase que el hueco de `filter` / `backdrop-filter` de ayer. 3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía `rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía muerta. La dimensión se decide ahora por el FINAL del nombre. Los 28 componentes con ledger re-verificados tras los tres cambios: cero regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.) Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados, 892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease` adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier → steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y 0 en los ficheros tocados. Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica** si no eres un primitivo —las doce declaraciones correctas siguen contando `global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número del gauge no tiene estilo con nombre que lo represente (un readout de dato no es un `h2`), así que sigue leyendo `--font-size-xl`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
uix(theming): tres techos que no son deuda — el PUENTE DE PALETA no se acuña Sin cambios de código: los veredictos §5 de `tag-group` (65 %), `stepper` (79 %) y `timeline` (73 %), y la clase registrada. Los tres tienen `global` a CERO. Lo único fuera de alcance son sus privados `--_{c}-palette-*`: el puente THM-2 que la capa compartida alimenta POR INSTANCIA desde `[data-color]`. **Un público encima del puente (`--tag-group-selected-bg: var(--_tag-group-palette-surface)`) dejaría que un tema lo fijara y matara en silencio el `color=` de cada instancia** — la misma clase de daño que «declarar un público de facto mata una escala» (`knob`). Y no hace falta: los componentes ya exponen sus nueve roles × variante como claves públicas (91 en `tag-group`). Ésa ES su superficie de color, y está completa. `timeline` suma una segunda razón, también firmada: sus `--_timeline-hue-*` son un CONMUTADOR —cada `data-intent` los re-apunta a las ranuras de rol y el intent por defecto al puente—, y aplanarlo obligaría a duplicar cada regla por intent (doctrina F2-B, cinco precedentes). MEDIDO, para no adjudicar de oído: de las **687** declaraciones del catálogo que leen un privado, **130 leen un `--_{c}-palette-*`**, y son las que dominan la columna `private` de todo componente con paleta — tag-group 14/14, stepper 11/11, radio-group 10, checkbox 8, file-upload 8, calendar / editable / los tres grids 7 cada uno. Consecuencia que queda escrita en §13: **el techo de un componente con paleta no es el 100 %**, y su cifra no compara con la de uno sin ella, porque el denominador incluye un puente que por diseño no se acuña. Si algún día se quiere comparabilidad, la salida es una clase `bridge` en el censo — no acuñar los tokens. Los otros seis heredan la razón cuando les toque, pero ojo: ésos tienen ADEMÁS knobs `global` y sí les toca trabajo real. Los cuatro literales de los tres son identidad anotada: el `line-height: 1` del botón de quitar y el del glifo del marcador, el `100%` de layout del stepper y los dos `1em` que hacen que título y descripción del timeline hereden el tamaño escalado del item en vez del tamaño UA del encabezado. docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠ La clase `private` del censo ES, en su mayor parte, el PUENTE DE PALETA
THM-2** (medido 2026-08-22). De las 687 declaraciones que leen un privado en
el catálogo, **130 leen un `--_{c}-palette-*`**, y son las que dominan la
columna `private` de los componentes con paleta: `tag-group` 14/14,
`stepper` 11/11, `radio-group` 10, `checkbox` 8, `file-upload` 8,
`calendar`/`editable`/los tres grids 7 cada uno.
**No es deuda y no debe acuñarse**: el puente lo alimenta la capa compartida
por instancia desde `[data-color]`, y un público encima
(`--{c}-selected-bg: var(--_{c}-palette-surface)`) dejaría que un tema lo
fijara y **matara en silencio el `color=` de cada instancia** — la misma clase
de daño que «declarar un público de facto mata una escala» (`knob`). El
componente ya expone sus nueve roles × variante como claves públicas: ésa es
su superficie de color.
Consecuencia para la métrica: **el techo de un componente con paleta no es el
100 %**, y su cifra no compara con la de uno sin ella — el denominador incluye
un puente que por diseño no se acuña. Tres ya adjudicados con esta razón
(`tag-group` 65 %, `stepper` 79 %, `timeline` 73 %); los demás la heredan
cuando les toque. Si algún día se quiere una cifra comparable, la respuesta es
una clase `bridge` en el censo, no acuñar los tokens.
uix(listbox): 68 % es su techo — coserlo como sus hermanos habría mentido Sin cambios de código: su veredicto §5 y la incidencia registrada. Cinco de sus nueve privados ADOPTAN la capa `list-surface` (el ritmo de fila que `lib/list-surface.css` resuelve por talla sobre `data-list-surface`, compartido con select, combobox, command y los tres menús); dos son el puente de paleta THM-2 (misma razón que `tag-group`) y el noveno sí deriva de un público. La cola decía «cose como sus hermanos», porque `dropdown-menu` y `context-menu` sí acuñan el puente (`--dropdown-menu-item-height: var(--list-item-height)`). Medido antes de copiarlo, con el menú abierto y la fila a 36 px: tema escribe --dropdown-menu-item-height: 1234px en :root -> 36px tema escribe --list-item-height: 1234px en :root -> 36px **Ninguno alcanza.** Es cascada: la capa declara sobre `[data-list-surface][data-size]` y el puente sobre `[data-{c}-content]`; las dos ganan a `:root` siempre. Copiar el puente aquí habría subido la cifra sin dar alcance a nadie — un token que miente, que es justo la clase que R-5.4 existe para cazar. Se arregla EN LA CAPA, no en el consumidor: dándole a `list-surface` una entrada de receta como se hizo con `calendar-surface` (el gancho resuelve por talla, el vocabulario vive en la receta). Toca diez componentes de golpe → §13, eje propio. Y deja un aviso sobre el instrumento, también en §13: **el centinela da por vivos los tokens de esta forma** porque escribe inline en el nodo, que gana a la regla del gancho. Para un token que un tema sólo puede escribir en `:root`, «mueve un computed» no equivale a «un tema lo alcanza». Lo demás de su 32 %: el `background` del item resaltado es la incidencia ya escrita del velo de estado (§12) y el `100%` de `[data-block]` es identidad. docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠ La capa `list-surface` no es alcanzable por un tema — MEDIDO
2026-08-22.** Ni por su propio nombre ni por el puente que acuñan sus
consumidores. Con el menú abierto y la fila a 36 px: un tema que escribe
`--list-item-height: 1234px` en `:root` no mueve nada, y uno que escribe
`--dropdown-menu-item-height` tampoco. Es cascada: la capa declara sobre
`[data-list-surface][data-size]` y el puente sobre `[data-{c}-content]`; las
dos ganan a `:root`.
Por eso `listbox` **no** se cosió como sus hermanos: copiar el puente habría
subido la cifra sin dar alcance (un token que miente). La salida es la de
`calendar-surface`: que el vocabulario de la capa viva en una entrada de
receta y el gancho sólo lo RESUELVA por talla. Toca los diez consumidores de
golpe → eje propio.
Y deja un aviso sobre el instrumento: **el centinela da por vivos los tokens
de esta forma** porque escribe inline en el nodo, que gana a la regla del
gancho. Para un token que un tema sólo puede escribir en `:root`, «mueve un
computed» no equivale a «un tema lo alcanza».
uix(popover): temable — 46 % → 67 %, y un huérfano que el guard de huérfanos no veía Once claves para el disparador + una RETIRADA (36 → 46). Global del eje 60 %. EL DISPARADOR GENÉRICO ES CROMO PROPIO. El que no lleva el arquetipo `field-trigger` —que tiene su tratamiento aparte— montaba alto, padding, borde, radio, superficie y tinta sobre primitivos crudos. Dos notas: `trigger-height` lee el BUNDLE de talla (`--size-md-control-height`) y no el primitivo que el CSS traía, y hover y abierto llevan CADA UNO su par de claves aunque hoy compartan valor — son dos estados, y un tema puede querer el abierto más marcado. `bg` RETIRADO: ninguna regla CSS lo consumía. El único sitio del corpus que lo nombraba era un COMENTARIO de `popover-arrow.svelte` («fill: var(--popover-bg)»), y el test `does not leave declared public recipe variables orphaned` busca el nombre como TEXTO en los ficheros del componente — así que un comentario mantiene vivo un token para siempre. La superficie del panel la pinta el plano de profundidad (`--depth-overlay-surface`), que es lo que un plano debe poseer; el comentario queda corregido y el hueco del test, en §13. LOS DOCE QUE SIGUEN FUERA SON LA FIRMA DE SALIDA, y no se cosen aquí: `data-last-action` × 3 intents × 4 lados tiñendo el borde durante el cierre. La misma matriz está repetida en `drawer` (22 reglas) y `dialog` (9) — y ya ha derivado: dialog tiñe sólo `border-top-color` mientras popover y drawer usan el shorthand con `--border-width-medium`, así que en dos de tres el GROSOR también cambia al salir. La tabla morfo→eidos la sitúa como mapeo transversal («`events[].prewrite` → tinting the exit anim by cause»), misma fila que los arquetipos: es canal visual de sema, no cromo de componente. Coserlo tres veces sería triplicar un vocabulario que debe vivir una vez → §13. DOS CEGUERAS DEL INSTRUMENTO, arregladas: 1. La página tiene CINCO disparadores y el guard abría el primero del documento —un ejemplo de más abajo—, no el del escenario: las partes que el escenario monta nunca entraban. `openWith` apuntando al escenario. 2. `content-z` se consume sobre el ENVOLTORIO FLOTANTE (`:has(> [data-popover-content])`), un nodo sin atributo `data-popover*` que el filtro no veía: leía muerto moviendo 80 → 4321. TRECE ADJUDICACIONES, TODAS MEDIDAS: nueve son partes que la demo no monta (Title, Description) y dos el overlay opcional —montados en el panel ABIERTO con los atributos que el CSS selecciona, alcanzan—. **Las dos que no son de montaje**: `font-family` y `line-height` no pueden ganar porque el plano `overlay` impone la tipografía del panel — el hallazgo §12.9, que espera tu firma y afecta a VEINTE componentes. Aquí queda medido otra vez. Gates: sonda antes/después **0 diffs en 420 valores** (8 estados; el escenario monta un disparador y el panel portala fuera, así que el estado `abierto` es el que mide el panel: 7 nodos) · centinela 33/46 · censo --only 67 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠ La FIRMA DE SALIDA está escrita a mano tres veces** (medido 2026-08-22).
`data-last-action` × 3 intents × 4 lados tiñe el borde durante la animación
de cierre, y la misma matriz vive en `popover` (22 reglas), `drawer` (22) y
`dialog` (9), leyendo `--color-{fulfill,threat,neutral}-element`. **Y ya ha
derivado**: dialog tiñe sólo `border-top-color` mientras popover y drawer usan
el shorthand con `--border-width-medium`, así que en dos de tres el GROSOR
también cambia al salir.
La tabla morfo→eidos la sitúa como mapeo TRANSVERSAL (« `events[].prewrite` →
tinting the exit anim by cause»), misma fila que los arquetipos y los
selectores `[data-event=…]`: es el canal visual de sema materializado por
eidos, como la capa de estado vive en `archetypes.css`. Coserlo por componente
sería triplicar un vocabulario que debería vivir UNA vez; **la salida es una
regla transversal sobre el arquetipo `content`**, con los colores de intent
(que son canon) — 53 knobs a `system` y un tema que retoca la firma una sola
vez. Lleva una firma dentro: ¿la salida tiñe sólo el color o también el
grosor?
- **⚠ El test de huérfanos cuenta un COMENTARIO como consumo.**
`--popover-bg` no lo leía ninguna regla; el único sitio del corpus que lo
nombraba era un comentario de `popover-arrow.svelte`. El test
`does not leave declared public recipe variables orphaned` busca el nombre
como TEXTO en los ficheros del componente, así que un comentario lo mantiene
«vivo» para siempre. Retirado el token y corregido el comentario
(2026-08-22); el test ganaría con despojar comentarios antes de buscar,
igual que hace el censo.
uix(color-picker): temable — 51 % → 75 %, y un prefijo ABREVIADO fuera de todo el árbol 20 claves de costura (54 → 71) y cinco nombres renombrados. Global del eje 60 % → **61 %**. `--cp-*` VIOLABA theming §6 r5 («no abrevies el nombre del componente»). Cinco nombres —`checker`, `checker-cell`, `current-color`, `channel-gradient`, `trigger-color`— y **45 ocurrencias en 8 ficheros**, tres de ellos de SOMA: el provider los estampa inline, su test los lee y sus tipos los declaran. Renombrados a `--_color-picker-*` con `git ls-files` delante; dos de ellos —el damero y el gradiente de canal— ya derivaban de un público, así que el censo los reclasifica solo. Precedente: los `--gb-*` / `--_ntp-*` de F2-A. Cosido: el ritmo del componente, la tinta de la etiqueta, la tipografía del disparador y la del VALOR —que es metadato: mono, regular, tinta e interlineado propios—, el borde de la muestra, el raíl del canal y su pulgar, el hueco del anillo de la muestra y el borde del cuentagotas. UNA SOMBRA QUE SON DOS Y HACEN UNA COSA: el pulgar del canal lleva contorno + elevación en un solo `box-shadow`, así que se acuña como UN token y la regla de foco lo compone con su tercer anillo, en vez de repetir las dos sombras. EL INSTRUMENTO NO VEÍA DOS FAMILIAS DE NODO, y las dos se arreglaron midiendo: las muestras del picker **son `<ColorSwatch>`** (llevan `data-color-swatch`, no `data-color-picker-*`) y sus sliders de canal **son SLIDERS** (el pulgar y el raíl llevan `data-slider-*`). Cuatro tokens vivos leían muertos por eso. LO ROJO QUE QUEDA ES ANTERIOR A ESTE COMMIT: el guard reporta 23 sin adjudicar —las muestras, la rueda de tono (`hue-0..360` + `saturation-floor`), el ancho del panel por talla, el cuentagotas y las dos de transición—, todos del bloque que ya existía. Antes eran **24**, así que la cifra baja. Su pase de medición queda anotado en §13 con dos causas ya descartadas: el panel SÍ abre y sobrevive al blur, al parqueo del ratón y al forzado de `data-size`, y el guard ya ve sus nodos. Lo que este commit añadió está medido: dos adjudicadas montando el disparador de muestra dentro del panel abierto, el resto en vivo. Gates: sonda antes/después **0 diffs en 1.350 valores** (8 estados), medida DOS veces —una tras el renombrado, otra tras la costura— · tests de soma del color-picker **7/7** tras el renombrado · censo --only 75 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y 0 en los ficheros tocados. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠ `color-picker` arrastra 23 tokens sin adjudicar, ANTERIORES al eje**
(2026-08-22). Su costura los dejó en 23 —eran 24 antes—, pero el bloque
viejo necesita su propio pase de medición: las muestras (7 claves), la rueda
de tono (`hue-0..360` + `saturation-floor`), el ancho del panel por talla,
el cuentagotas y las dos de transición. Dos causas ya descartadas: no es que
el panel no abra (abre y sobrevive al blur, al parqueo del ratón y al forzado
de `data-size`) ni que el guard no vea sus nodos (ya se le dieron las dos
familias que le faltaban).
uix(theming): revisión adversarial del bloque — la pasada de HOVER envenenaba a todos los tokens de reposo Cinco hipótesis, un hallazgo real, cuatro refutadas. Bloque de CATORCE componentes; global del eje 56 % → **62 %**. HALLAZGO REAL — la anomalía de `float-panel` tenía causa, y era del guard. `resize-grip-fg` movía aislado y leía muerto en una corrida completa. Instrumenté el propio guard para probar UNA clave y salió `moved=true`; luego, en la corrida entera, muerto. La causa: **la pasada de HOVER deja el puntero sobre el último nodo que tocó**, y una regla `:hover` gana a la de reposo con la que comparte nodo — el asa re-apunta su color al acento al pasar el ratón. Es decir: **cualquier token de reposo probado DESPUÉS de un token `hover-*` podía leer muerto**. Es exactamente la clase que F2-A arregló con el clic-foco, una pasada más tarde: allí se aparcó el puntero tras ABRIR, aquí faltaba aparcarlo tras HOVERAR. Arreglado. Y entonces el propio guard delató DOS excepciones STALE — `float-panel.resize-grip-fg` y `media-player.track` —, retiradas: dos tokens que el ledger daba por muertos están vivos. Se confirma la regla de F2-A: **un falso negativo siempre tiene causa**; registrarlo como «sin causa» era el paso previo, no el final. REFUTADA «hay tokens huérfanos»: comprobación ESTRICTA —comentarios despojados, que es el hueco por el que `popover.bg` sobrevivió, sobre todo el CSS del árbol— de las **553 claves** de los catorce bloques: ninguna declarada sin que una regla la lea. REFUTADA «el cambio de instrumento rompió algo»: los **27 componentes con ledger re-verificados** tras aparcar el puntero — cero regresiones. Los tres rojos que quedan (`drawer` 24, `metrics` 16, `color-picker` 23) son ANTERIORES al bloque y están anotados con lo que ya se descartó de cada uno. REFUTADA «quedan fantasmas del barrido en lo tocado»: pasa de 9 a **8** — el `--color-content-tertiary` de float-panel murió con su costura. Los ocho restantes son de componentes que este bloque no tocó. CONFIRMADO, y ya estaba escrito: hay gates FINOS. `popover` mide 1 nodo en reposo (7 en abierto), `drawer` 1, `context-menu` 1 + 14 en abierto, `color-picker` 5 + 12. Cada veredicto lo dice, y lo que sostiene esas cifras es el forzado a mano de sus estados, no la sonda. Guards: `--names` sin desviadas · docs:check 0 · censo 62 % (2.877 públicos de 5.137 knobs; `global` 1.145 → 912). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **~~Un falso negativo del centinela sin causa~~ — RESUELTO en la revisión
adversarial del bloque (2026-08-22).** `float-panel.resize-grip-fg` movía
aislado y leía muerto en una corrida completa. La causa: **la pasada de HOVER
deja el puntero sobre el último nodo que tocó**, y una regla `:hover` gana a
la de reposo con la que comparte nodo — el asa re-apunta su color al acento
al pasar el ratón. Así que CUALQUIER token de reposo probado después de un
token `hover-*` podía leer muerto. Es la misma clase que el envenenamiento
por clic-foco que F2-A arregló, una pasada más tarde. Arreglado aparcando el
puntero tras la pasada de hover; el propio guard delató entonces DOS
excepciones STALE (`float-panel.resize-grip-fg` y `media-player.track`),
retiradas. Confirma la regla de F2-A: **un falso negativo siempre tiene
causa.**
uix(float-panel): temable — 59 % → 84 %, muere un fantasma y queda una anomalía registrada 12 claves (28 → 40). Censo float-panel 59 % → **84 %** con `global` a CERO. Cosido: las dos VARIANTES que ajustan desde el default elevado (`outline-*`, `ghost-*`), los botones de su barra (`action-*`, `hover-action-*`) y el asa de redimensionado. MUERE UNO DE LOS NUEVE FANTASMAS DEL BARRIDO de esta mañana: el asa leía `var(--color-content-tertiary, var(--color-content-secondary))` y ese primitivo NO EXISTE, así que pintaba el respaldo. El contrato guarda lo que de verdad pintaba, con lo que la referencia muerta desaparece sin mover un píxel. ONCE ADJUDICACIONES MEDIDAS: la demo monta la variante por DEFECTO —ni outline ni ghost— y no tiene botón de acción genérico, sólo el control de cierre; se forzó el atributo de variante y se montó el botón donde el CSS lo selecciona, y el radio del asa sólo pinta bajo `:focus-visible`. Las once alcanzan. Y UNA ANOMALÍA REGISTRADA EN VEZ DE ESCONDIDA: `resize-grip-fg` **mueve** bajo una réplica a mano de la secuencia del propio guard —congelar transiciones, blur, aparcar el ratón, escribir en `:root` y en cada nodo con el prefijo, fotografiar los mismos props con `::before`/`::after`/`::placeholder`— (oklch(0.5032 0 0) → rgb(1,2,3)), y aun así el guard lo da por muerto. Descartado que el nodo no lleve el atributo (lo lleva), que la regla necesite un estado (es la regla base), que sea el valor centinela (es el mismo) y que sea HMR (dos corridas). Queda adjudicado CON la medición y marcado como anomalía en §13: el token está vivo, lo que está en duda es la lectura. Es por donde debe empezar la revisión adversarial de este bloque — el precedente de F2-A dice que estos casos sí tienen causa. Lo rojo que queda son 12 tokens anteriores, y seis de ellos —`bg`, `border`, `border-width`, `shadow`, `font-family`, `line-height`— son el mismo conjunto que en `popover`: el PLANO de profundidad gana sobre el panel (§12.9, pendiente de firma). Gates: sonda antes/después **0 diffs en 1.890 valores** (9 nodos × 7 estados) · censo --only 84 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura mirada (cabecera, botón de cierre y asa de la esquina en su sitio). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
uix(drawer): los 24 rojos medidos uno a uno — y uno era un DEFECTO REAL: el velo no pinta Sin cambios de código: 24 adjudicaciones medidas y dos hallazgos. El guard queda en VERDE. EL VELO DEL CAJÓN NO PINTA. Su regla es background: color-mix(in srgb, var(--drawer-overlay-bg) var(--drawer-overlay-opacity), transparent) y **soma escribe `--drawer-overlay-opacity: 1` INLINE** en el nodo del velo — es su progreso de arrastre, un número sin unidad. `color-mix` exige ahí un PORCENTAJE: con `1` la función entera es inválida y el fondo cae a `transparent`. Medido: el velo abierto computa `rgba(0, 0, 0, 0)`; escribiendo `80%` en el propio nodo, pinta. Y VISTO en captura: el fondo tras el cajón sale desenfocado pero **sin atenuar** — el `backdrop-filter` funciona y el tinte no existe. Un cajón modal sin velo. No se arregla aquí porque mueve píxel y toca soma (§7.8). Puede arreglarse de un lado o del otro —que soma escriba un porcentaje, o que la receta no use ese slot como porcentaje—, y la lección general queda escrita en §13: **un valor que soma escribe inline y una receta consume tiene que compartir UNIDAD**, o el fallo es silencioso. SEGUNDO HALLAZGO: las tallas son canal de valor en el eje que manda. Soma escribe `width` (o `height`, según el lado) inline en el contenido, así que `content-width-{sm,md,lg}` no puede ganar desde `:root` en un cajón izquierda/derecha; el eje cruzado sí alcanza (`height-sm` forzado: 260px → 976px). Misma clase que el `item-gap` de carousel y el `preview-z` de drag-drop, que se retiraron — pero aquí el token sigue siendo la fuente del valor que soma resuelve, así que retirarlo o no es firma tuya. Los otros 22 son lo esperable, y quedan medidos con su valor: el asa —que la demo no monta y cuyo tamaño INTERCAMBIA ejes según el lado (36px→1234px de largo, 4px→1234px de grosor)—, el anillo de arrastre, el padding `full`, la cabecera y el pie opcionales, y la z de la variante inline. Gates: centinela 29/54 con las 24 adjudicadas, exit 0 · docs:check 0 · captura de la página con el cajón abierto mirada. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠⚠ EL VELO DEL `drawer` NO PINTA — defecto real, medido y visto**
(2026-08-22). Su regla es
`background: color-mix(in srgb, var(--drawer-overlay-bg) var(--drawer-overlay-opacity), transparent)`,
y **soma escribe `--drawer-overlay-opacity: 1` INLINE** en el nodo del velo (es
su progreso de arrastre, un número sin unidad). `color-mix` exige un
PORCENTAJE ahí: con `1` la función entera es inválida y el fondo cae a
`transparent`. Computado en el velo abierto: `rgba(0, 0, 0, 0)`; escribiendo
`80%` en el propio nodo pinta. **En la captura se ve**: el fondo detrás del
cajón está DESENFOCADO pero no atenuado — el `backdrop-filter` funciona y el
tinte no existe.
No se arregla aquí porque mueve píxel y toca soma (§7.8). El arreglo puede
ser de un lado o del otro: que soma escriba un porcentaje, o que la receta
no use ese slot como porcentaje. Y la lección general: **un valor que soma
escribe inline y una receta consume tiene que compartir UNIDAD**, o el fallo
es silencioso.
- **⚠ Las tallas del `drawer` son canal de valor en el eje que manda**: soma
escribe `width` (o `height`, según el lado) INLINE en el contenido, así que
`content-width-{sm,md,lg}` no puede ganar desde `:root` para un cajón
izquierda/derecha —el eje cruzado sí alcanza (`height-sm` forzado: 260px →
976px)—. Misma clase que el `item-gap` de carousel y el `preview-z` de
drag-drop, que se RETIRARON; aquí no, porque el token sigue siendo la fuente
del valor que soma resuelve. Decidir si se retira o si soma lo lee es firma.
uix(theming): los dos rojos que quedaban — y el eje `size` del picker MUERE EN EL PORTAL Medidos uno a uno los 18 de `metrics` y los 25 de `color-picker`, los últimos del bloque viejo que el guard R-5.4 arrastraba sin adjudicar. Los dos quedan en verde. Sin cambios de código: esto es medición y registro. `metrics`: los 18 están VIVOS. Ninguno miente; lo que faltaba era el escenario. Seis piden el paso `xxl` (la demo llega a `xl`), dos el icono SIN `data-color` (su defecto sólo pinta cuando no hay color, y todos los de la demo llevan uno), dos las formas `circle`/`square`, cuatro la fila de acciones, uno el gráfico en `end`, uno el borde de la variante `outline`, y dos el sello de sema durante el hold (`data-event-family=signal` + `phase=active`): el destello del valor sólo se mide estampando el sello a mano, porque cuando el guard lee ya se ha ido. `color-picker`: 23 de 25 vivos, y los otros dos son un DEFECTO REAL. EL EJE `size` MUERE EN EL PORTAL, dos veces. `--_color-picker-content-width` se declara TRES veces y no lo consume NADIE: la anchura del panel la fija `--_popover-content-width-override`, cableada al paso `md`. Un picker `sm` y uno `lg` abren el MISMO panel de 344 px, y `content-width-sm`/`-lg` son dos públicos que no mueven nada. Lo mismo con las muestras dentro del panel (`--_color-picker-swatch-size` clavado a `md`): fuera del portal los tres pasos alcanzan (20/24/28 px → 1234 px), dentro sólo el `md`. La causa es una y vale como ley: el portal es una FRONTERA DE CASCADA — un privado declarado en la raíz del componente no la cruza. Misma familia que la capa `list-surface` inalcanzable desde `:root`. El arreglo honesto es una escala por talla en el ámbito del CONTENIDO (que sí lleva su `data-size`); mueve píxel en `sm` y `lg`, así que es firma → §13. Tres cegueras del instrumento, medidas: - El guard CONGELA las transiciones antes de medir —el arreglo que hizo medibles radio-cards y compañía— y `transition-duration`/`transition-ease` SON la transición: sin congelar alcanzan (0.12s → 11.5s). Es genérico: cualquier componente con esos dos públicos los reportará muertos. - Una lectura inmediata de una propiedad EN TRANSICIÓN devuelve el valor VIEJO. Es lo que hacía ilegible el fondo del cuentagotas: ni con `!important` en línea se movía, porque la transición aún no había corrido. - La DEMO pisa el cuentagotas con su regla de ámbito (`.cp-tools-row` le pone `inline-size:auto` y `background`), así que sus dos tokens sólo se miden devolviéndole el consumo del token. Gates: `theming:sentinel metrics` y `color-picker` en verde (54/72 y 46/71, el resto adjudicado por escrito) · `check` sin errores propios · `docs:check` 0/0 · formato sin regresión (los tres ficheros ya venían avisados en HEAD). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠ El eje `size` del `color-picker` MUERE EN EL PORTAL — dos veces**
(2026-08-23). El panel viaja en un portal, fuera de `[data-color-picker]`,
así que ningún privado declarado en la raíz llega hasta él. Consecuencias
medidas:
1. **`--_color-picker-content-width` se declara TRES veces y no lo consume
NADIE.** La anchura del panel la fija
`--_popover-content-width-override: calc(var(--color-picker-content-width-md) + 2rem)`,
cableada al paso `md`. Un picker `sm` y uno `lg` abren el MISMO panel de
344 px, y `content-width-sm` / `content-width-lg` son dos públicos que no
mueven nada (el `md` sí alcanza, por el override).
2. **El tamaño de las muestras dentro del panel está clavado a `md`**: la
regla del contenido redeclara `--_color-picker-swatch-size: var(--color-picker-swatch-size-md)`
sin variantes por talla. Fuera del portal los tres pasos alcanzan
(20 / 24 / 28 px → 1234 px); dentro, sólo el `md`.
El arreglo honesto es una escala por talla EN EL ÁMBITO DEL CONTENIDO (que sí
lleva su `data-size`), no en la raíz. Mueve píxel para `sm` y `lg`, así que
es firma. Misma familia que la capa `list-surface` inalcanzable desde
`:root`: **el portal es una frontera de cascada, y un privado declarado en la
raíz no la cruza.**
- **Punto ciego genérico del centinela: los tokens que SON la transición.** El
guard congela las transiciones antes de medir —el arreglo que hizo medibles
`radio-cards` y compañía— y con ello `transition-duration` / `transition-ease`
no pueden moverse jamás. Medidos sin congelar en `color-picker` alcanzan
(0.12s → 11.5s). Cualquier componente con esos dos públicos los reportará
muertos: o el guard los mide en una pasada sin congelar, o cada uno paga su
adjudicación escrita.
uix(menubar): temable — 15 % → 88 %, y el plano `overlay` GANA a la receta 30 claves nuevas (5 → 35): la barra (superficie, marco, radio —uno solo, la receta lo desacopla de la talla a propósito y el motor de forma deriva el del disparador de él— y las tres escalas `gap`/`padding`/`height`), el disparador (tinta, tipografía, escala de `font-size` y de `padding-inline`, el par de la entrada ABIERTA y la tinta deshabilitada) y el Panel `role=dialog` (tinta + escala). Global 62 % → 63 %. Los pasos leen el BUNDLE (`--size-{k}-control-height` / `--size-{k}-font-size`), no el primitivo crudo que el CSS venía consumiendo; y el `calc(var(--space-1) * 0.5)` del paso `sm` es `var(--space-0-5)` exacto. EL HUECO DEL PANEL DEJA DE REPETIR EL VALOR DEL PADDING. Las tres reglas de `--floating-gap` compensan el sangrado del disparador dentro de la barra con lo que era una COPIA del padding; ahora leen `--menubar-padding-{k}`, su fuente — si un tema ensancha el padding, el panel deja de quedar a ras de la barra. No pueden leer el privado `--_menubar-padding`: esas superficies viajan POR PORTAL y un privado declarado en la raíz no cruza esa frontera. ⚠ EL PLANO `overlay` GANA A LA RECETA — cuarta prueba de §12.9 y la primera con MECANISMO. Acuñé `panel-font-family` y `panel-line-height` sobre declaraciones que la receta YA tenía, y el centinela las dio muertas. Medido: `[data-depth='overlay']` declara las dos con la MISMA especificidad (0,1,0) y más tarde en la cascada, así que gana; del mismo bloque, `color` y `font-size` —que el plano no declara— sí alcanzan. Retiradas del contrato y devueltas a su fuente literal, con la razón escrita en el CSS. Mientras §12.9 no se firme, un token de tipografía en CUALQUIER superficie `overlay` miente, y son veinte. EL HOVER NO SE ACUÑA: ES DEL EJE navigation-menu. `background: var(--color-surface-overlay)` sobre el disparador mata la capa del sistema —medido: `background-image` computa `none` en hover con `--state-hover` vivo—, por las dos razones que `navigation-menu.css` ya documenta al retirar esta MISMA regla: el shorthand resetea `background-image`, y la regla del arquetipo va en `:where()`, especificidad CERO. Acuñar un `hover-trigger-bg` habría fosilizado la invención. Instrumento: la barra suelta DOS superficies y el Panel —el que lleva sus knobs— cuelga de la entrada «Format», no de la primera; sonda (`OPEN_TRIGGER`) y guard (`openWith`) aprenden a abrir la que toca. Y el guard necesitó `openMarker`: su comprobación de «ya está abierto» busca `[data-{c}-content]`, que aquí no existe, así que volvía a pulsar el disparador antes de cada token y CERRABA el panel — media corrida contra una superficie cerrada. Gates: sonda 0 diffs en 1.470 valores × 8 estados · centinela 34/35 (la deshabilitada, adjudicada) · censo 88 % · `--names` 0 desviadas · `component:audit` PASS · `rtl:check` 0 · `docs:check` 0 · suite eidos con el rojo conocido ajeno (`skin-media-player`) · `check` sin errores propios · capturas de barra, tallas sm/lg, menú abierto y panel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠⚠ §12.9 tiene MECANISMO: el plano `overlay` no «impone» tipografía donde
el componente calla — GANA donde el componente habla** (medido en `menubar`,
2026-08-23). Acuñadas `panel-font-family` y `panel-line-height` sobre una
declaración que la receta YA tenía, el centinela las dio muertas:
`[data-depth='overlay']` declara las dos con la MISMA especificidad (0,1,0) y
más tarde en la cascada. Del mismo bloque, `color` y `font-size` —que el
plano no declara— sí alcanzan. Consecuencia para la cola: **mientras §12.9 no
se firme, un token de `font-family` o `line-height` en cualquier superficie
`overlay` MIENTE**, y son veinte componentes. Retiradas del contrato de
menubar y devueltas a su fuente literal, con la razón escrita en el CSS.
- **⚠ El hover de `menubar` mata la capa del sistema — y es del eje
navigation-menu, no de éste** (medido 2026-08-23). `[data-menubar-trigger]:hover`
pinta `background: var(--color-surface-overlay)`: en hover el
`background-image` computa `none` con `--state-hover` vivo. Dos razones, las
dos ya escritas en `navigation-menu.css` cuando retiró esta MISMA regla: el
shorthand resetea `background-image`, y la regla del arquetipo va en
`:where()`, especificidad CERO. El eje navigation-menu tiene `menubar` en su
cola por esto; aquí no se acuña un `hover-trigger-bg` porque fosilizaría la
invención (doctrina: el hover es del SISTEMA).
uix(field): temable — 73 % → 98 %, y sus 18 rojos estaban VIVOS 8 claves nuevas (60 → 68) y 8 literales ANOTADOS. Lo que falta es el PUENTE DE PALETA (`_palette-text`, THM-2), que no es deuda. Global 63 %. Field es el componente más COMPUESTO del sistema: lo que otras fichas llaman «préstamo con dueño» se cobra aquí, así que su techo pesa más que el de nadie. Acuñados el DISPARADOR DE CONTROL —el icono que abre el calendario, el reloj o la muestra, y revela la contraseña: `size`, `radius`, `fg` y sus dos estados—, el indicador `optional` (gemelo del `required`, que ya tenía token) y el lateral de la etiqueta flotante, que es lo que CORTA el borde bajo el texto. La caja del disparador se mide en `em` a propósito, así que el token guarda la RAZÓN (`1.75em`) y no un píxel por talla. OCHO LITERALES PASAN A EXCEPCIÓN FIRMADA POR IDENTIDAD, no para inflar el ratio: cuatro `inline-size: 100%` (el campo, el control, el input y el control flotante llenan su fila), dos `line-height: 1` (un glifo suelto no tiene interlínea) y dos `1em` del icono dentro del disparador (el glifo ES el tamaño del texto). LOS 20 ROJOS DEL GUARD ESTABAN VIVOS, uno por uno, y tres enseñan algo: - `disabled-opacity` vive en el CONTROL, no en la raíz: el guard leía el nodo equivocado. - `control-placeholder-fg` no leía porque el input de la demo NO TIENE `placeholder`: sin el atributo no existe la caja `::placeholder`, y `getComputedStyle(nd, '::placeholder')` devuelve entonces el estilo del ELEMENTO. Con un placeholder puesto, alcanza. Es genérico → §13. - `segment-height` es un token de FAMILIA que field posee y los pickers consumen —en las rutas de field no lo lee nadie— y además es RESUELTO (declarado por `[data-field][data-size]`), así que escribirlo en `:root` pierde POR DISEÑO: el tema mueve la coordenada. Medido en `/time-picker`: desde `:root` nada, desde el host del campo 28px → 1234px, y moviendo `--field-control-height-md` 28px → 992px. El resto son estado (invalid, disabled, readonly, foco), variante ghost, orientación horizontal, la franja de segmentos, el texto de error —que sólo se RENDERIZA con el interruptor de la demo: forzar el atributo no hace que Svelte monte el nodo— y los dos de transición, que el guard congela. Instrumento: el disparador de control no lo monta NINGUNA demo de field, así que el guard mide ahora `field` sobre DOS rutas (`/field` + `/date-picker`). Gates: sonda 0 diffs en 2.280 valores × 8 estados · equivalencia del disparador comprobada aparte en `/date-picker` (28px = 1.75em con fuente 16px · 4px = `--radius-sm` · misma tinta) · centinela 48/68 con las 20 adjudicadas · censo 98 % · `--names` 0 · `component:audit` PASS · `rtl:check` 0 · `docs:check` 0 · suite eidos con el rojo conocido ajeno · `check` sin errores propios · capturas de reposo, invalid, disabled, etiqueta flotante y disparador. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠ Un token de `::placeholder` lee MUERTO sobre un input sin `placeholder`**
(medido en `field`, 2026-08-23). Sin el atributo no existe la caja del
pseudo-elemento, y `getComputedStyle(nd, '::placeholder')` devuelve entonces el
estilo del ELEMENTO — que no sigue al token. Poniendo un placeholder, alcanza
(`oklch(0.61 0 0)` → `rgb(1,2,3)`). Es genérico: cualquier componente con un
token de placeholder lo sufrirá si su demo arranca con el input vacío. El guard
podría sembrar un placeholder antes de esa medida.
uix(onion-menu): temable — 29 % → 84 %, y una regla que el navegador DESCARTA 7 claves nuevas (12 → 19) y 5 literales anotados. Lo que queda fuera son TRES filas y ninguna es deuda de tokenización: una es canal de valor y dos viven en una regla inválida. Acuñado el color del ANILLO DEL SECTOR —uno para los dos estados, porque el propio comentario del CSS dice que el hover usa «el mismo color que el anillo de selección, más claro» y lo que los separa es la opacidad, que ya tenía token—, la tipografía de la etiqueta, el radio y la tinta del disparador, y las dos piezas del glifo `+`→`×`. Dos limpiezas de paso: un `var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí mismo, el defecto que ya se cazó en context-menu— y dos `var(--radius-full, …)` con un respaldo que no puede ocurrir. Cinco literales pasan a excepción POR IDENTIDAD: dos `opacity: 1` que DESHACEN un atenuado (sin atenuar es 1 por definición) y la caja del icono. ⚠ `.onion-menu-icon :global(svg)` NO EXISTE. `:global()` es un envoltorio de SVELTE y esto es un CSS plano: el selector es inválido y el navegador descarta la regla entera. Medido: el glifo computa 14px —el tamaño propio del Icon— y el `70 %` que el fichero cree pintar no pinta. Acuñé un `icon-glyph-size`, el centinela lo delató y lo RETIRÉ; los literales se quedan con el defecto escrito encima, porque arreglar el selector empieza a pintar el 70 % y MUEVE PÍXEL. Mismo bug en `timeline.css:331`; `image-picker.css` ya documentaba la trampa —la lección estaba escrita y se repitió—. → §13. Canal de valor: el fondo del disparador lo escribe el componente INLINE desde el motor de color, como los rellenos de cada sector. No es superficie de tema. TRES CEGUERAS DEL INSTRUMENTO, dos nuevas y genéricas: 1. EL GUARD CERRABA LA SUPERFICIE. Este menú nace ABIERTO y el clic de «abrir» lo cerraba: 12 de 20 tokens leían muertos. Ahora, si hay `openMarker` y ya casa, no se pulsa nada (8 → 14 de golpe). 2. Sus tripas cuelgan de CLASES, no de `data-onion-*`: sonda y guard las miden con `extraNodes` — 37 nodos en reposo contra los 5 con atributo. 3. Congelar las transiciones NO BASTA cuando el nodo está ANIMADO: `rim-glow-opacity` leía 0,482956 —el valor animado— y parecía muerto; con la animación congelada computa 0,45 y sigue al token. → §13. Los cuatro tokens del rim son del look `luminous`, que la demo del componente no renderiza (es una prop de Svelte que emite OTRO marcado, así que no hay atributo que forzar): medidos en `/active/docs/agnt`, que sí lo usa. Gates: sonda 0 diffs en 7.328 valores × 8 estados (37 nodos) · centinela 14/19 con las cinco adjudicadas · censo 84 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · capturas de reposo, sector en hover y disparador. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠ DOS recetas escriben `:global()` en un CSS PLANO, y el navegador descarta
la regla entera** (medido 2026-08-23): `onion-menu.css` (`.onion-menu-icon
uix(navigation-menu): temable — 44 % → 100 %, y el guard cerraba el panel que medía Bloque B1 de D-TH.4. Contrato 26 → 44 claves: 19 nuevas y UNA retirada. Diff de computed 0 sobre 2.752 valores en 8 estados; R-5.4 43/44 con una adjudicación escrita. Global 65 % (al 100 % 25 → 26). LOS NOMBRES LOS DIO EL CATÁLOGO, NO YO Ninguno se inventó. Los gemelos cerrados el mismo día (dropdown-menu 96 %, context-menu 87 %) dan content-{min-width,padding,radius,fg} y chevron-{size,stroke}; accordion da trigger-font-family y disabled-trigger-fg. Las cuatro recetas que declaran un chevron lo dimensionan con un LITERAL (0.42em · 0.5em · 0.625rem): un glifo no es un paso de la escala de espacio. Al revés con el subrayado del indicador: su 2px ES --border-width-medium, y dropdown-menu.separator-thickness dice que un grosor decorativo se nombra por esa familia. LA §4 GENERADA FALLA DE CINCO MANERAS, UNA NUEVA Llamaba trigger-width / trigger-height al CHEVRON — nombrando por el primer selector de la regla ([…-trigger]::after) y colisionando con trigger-height, que YA EXISTE y es la altura del control. Fundía en content-radius dos nodos con dos valores. Cruzaba los nombres: content-padding-* para lo que es de la FILA, y el padding real del panel a «sin nombre mecánico». Llamaba content-width a un min-inline-size. Y la nueva: abría exigiendo «consume la capa compartida menu-indicator» — HOMONIMIA, esa capa es el check de los CheckboxItem de dropdown/context-menu y no toca el subrayado de esta barra (LAYER_VOCABULARY sólo registra calendar-surface). LAS FILAS DEL PANEL NO SON item- NI link- En los gemelos, `item` ES la fila. Aquí `item` es el <li> de la barra (parte del morfo) y el Link de barra viste el cromo del trigger: --…-item-radius o --…-link-radius prometerían a un tema un nodo que no alcanzan. Se acuñan content-link-*, que nombra el nodo por donde vive. Su tinta sigue leyendo trigger-fg, como venía. EL PRIVADO ERA UNA ESCALA, NO UN CONMUTADOR --_navigation-menu-list-gap tenía tres fuentes (0 / --space-1 / --space-2) por data-size: sube al TSC como list-gap-{sm,md,lg} + el resuelto, el molde que el bundle del trigger ya tenía al lado. Los dos bloques [data-size] del CSS se retiran — los emite el TSC, y dejarlos vivos da un diff de 0 por la ruta vieja. indicator-x ERA DECLARACIÓN MUERTA Soma escribe las cuatro coordenadas del rect INLINE sobre el indicador (-x, -y, -w, -h) y la receta lee sólo -w y -h; su propio comentario registra que sumar translateX(var(--…-x)) DUPLICABA el desplazamiento. No la leía nadie. Retirada, 0 diffs — la prueba de que estaba muerta (precedente: el re-point de --command-radius, el trío item-gap* de carousel). -w se queda: vive, y es canal de valor, no superficie de tema. EL PANEL NO ACUÑA TIPOGRAFÍA, A PROPÓSITO Lleva [data-depth='overlay'], y ese plano declara font-family / line-height a la misma especificidad (0,1,0) y más tarde en la cascada: un token ahí leería muerto. Es §12.9, pendiente de firma — la misma que hizo retirar claves en menubar, dropdown-menu, context-menu y tooltip. Sí se acuña la familia de la BARRA (trigger-font-family), que no es overlay y alcanza. TRES COSAS QUE APRENDIÓ EL INSTRUMENTO (registradas en next-features §13) 1. Sonda y centinela eran CIEGOS A LA MITAD DEL COMPONENTE. Las filas del mega-menú son los <a> DESNUDOS del consumidor, estilados por descendencia: el filtro data-{c}-* veía 10 nodos y NINGUNO era el panel ni una fila — 9 de los 19 knobs fuera del diff. Con EXTRA_NODES / extraNodes la sonda pasa a 16 nodos abiertos. Y el paso `open` de la sonda no honraba EXTRA_NODES. 2. EL GUARD CERRABA EL PANEL QUE MEDÍA. Este mega-menú abre con pointerenter y pointerleave programa el cierre; el guard abría con clic y luego aparcaba el puntero en (0,0) — justo el gesto que lo cierra. Dos corridas del mismo código discrepaban. Y daba las DOS caras del error: falso negativo en los tokens de la fila, falso positivo en content-link-padding-inline, que leía «vivo» por ensanchar el panel que sí se medía. Con openBy: 'hover', dos corridas idénticas. 3. `color` está EN TRANSICIÓN en esta parte: forzar data-disabled y leer en el acto devuelve el valor VIEJO — tres diagnósticos falsos («ni el inline lo mueve») hasta congelar. La trampa del cuentagotas del color-picker, en otro componente. Congelado: reposo oklch(0.2435 0 0) → disabled oklch(0.61 0 0). Y una de contabilidad, del instrumento y no del componente: el censo decide el eje `size` grepeando data-size en el CSS, y el protocolo §7.4 ORDENA retirar esos bloques. Todo componente que lo aplique bien aparece «sin eje». Ya le pasa a sidebar y nav-tree. En §13. VERIFICACIÓN Sonda antes/después 0 diffs (2.752 valores · 8 estados · 16 nodos con el panel abierto) · R-5.4 43/44 en dos corridas idénticas, adjudicación única (disabled-trigger-fg: la demo no monta el estado; forzado alcanza en trigger Y link, rgb(1,2,3)) · component:audit PASS · censo --only 100 % · eidos-lint 30 morfo-backed / 3 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido) · check sin error nuevo en los ficheros tocados · Chrome con el panel abierto: panel 8/10px/224px, fila 8/12/6px, trigger 36px/6px/gap 4px, indicador 2px. Tras tocar el guard, command, table y tooltip re-verificados sin regresión ni STALE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
:global(svg)`, que cree pintar el glifo al 70 % de su caja — computa 14px, el
uix(onion-menu): temable — 29 % → 84 %, y una regla que el navegador DESCARTA 7 claves nuevas (12 → 19) y 5 literales anotados. Lo que queda fuera son TRES filas y ninguna es deuda de tokenización: una es canal de valor y dos viven en una regla inválida. Acuñado el color del ANILLO DEL SECTOR —uno para los dos estados, porque el propio comentario del CSS dice que el hover usa «el mismo color que el anillo de selección, más claro» y lo que los separa es la opacidad, que ya tenía token—, la tipografía de la etiqueta, el radio y la tinta del disparador, y las dos piezas del glifo `+`→`×`. Dos limpiezas de paso: un `var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí mismo, el defecto que ya se cazó en context-menu— y dos `var(--radius-full, …)` con un respaldo que no puede ocurrir. Cinco literales pasan a excepción POR IDENTIDAD: dos `opacity: 1` que DESHACEN un atenuado (sin atenuar es 1 por definición) y la caja del icono. ⚠ `.onion-menu-icon :global(svg)` NO EXISTE. `:global()` es un envoltorio de SVELTE y esto es un CSS plano: el selector es inválido y el navegador descarta la regla entera. Medido: el glifo computa 14px —el tamaño propio del Icon— y el `70 %` que el fichero cree pintar no pinta. Acuñé un `icon-glyph-size`, el centinela lo delató y lo RETIRÉ; los literales se quedan con el defecto escrito encima, porque arreglar el selector empieza a pintar el 70 % y MUEVE PÍXEL. Mismo bug en `timeline.css:331`; `image-picker.css` ya documentaba la trampa —la lección estaba escrita y se repitió—. → §13. Canal de valor: el fondo del disparador lo escribe el componente INLINE desde el motor de color, como los rellenos de cada sector. No es superficie de tema. TRES CEGUERAS DEL INSTRUMENTO, dos nuevas y genéricas: 1. EL GUARD CERRABA LA SUPERFICIE. Este menú nace ABIERTO y el clic de «abrir» lo cerraba: 12 de 20 tokens leían muertos. Ahora, si hay `openMarker` y ya casa, no se pulsa nada (8 → 14 de golpe). 2. Sus tripas cuelgan de CLASES, no de `data-onion-*`: sonda y guard las miden con `extraNodes` — 37 nodos en reposo contra los 5 con atributo. 3. Congelar las transiciones NO BASTA cuando el nodo está ANIMADO: `rim-glow-opacity` leía 0,482956 —el valor animado— y parecía muerto; con la animación congelada computa 0,45 y sigue al token. → §13. Los cuatro tokens del rim son del look `luminous`, que la demo del componente no renderiza (es una prop de Svelte que emite OTRO marcado, así que no hay atributo que forzar): medidos en `/active/docs/agnt`, que sí lo usa. Gates: sonda 0 diffs en 7.328 valores × 8 estados (37 nodos) · centinela 14/19 con las cinco adjudicadas · censo 84 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · capturas de reposo, sector en hover y disparador. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
tamaño propio del Icon) y `timeline.css:331` (`[data-timeline-marker]
uix(navigation-menu): temable — 44 % → 100 %, y el guard cerraba el panel que medía Bloque B1 de D-TH.4. Contrato 26 → 44 claves: 19 nuevas y UNA retirada. Diff de computed 0 sobre 2.752 valores en 8 estados; R-5.4 43/44 con una adjudicación escrita. Global 65 % (al 100 % 25 → 26). LOS NOMBRES LOS DIO EL CATÁLOGO, NO YO Ninguno se inventó. Los gemelos cerrados el mismo día (dropdown-menu 96 %, context-menu 87 %) dan content-{min-width,padding,radius,fg} y chevron-{size,stroke}; accordion da trigger-font-family y disabled-trigger-fg. Las cuatro recetas que declaran un chevron lo dimensionan con un LITERAL (0.42em · 0.5em · 0.625rem): un glifo no es un paso de la escala de espacio. Al revés con el subrayado del indicador: su 2px ES --border-width-medium, y dropdown-menu.separator-thickness dice que un grosor decorativo se nombra por esa familia. LA §4 GENERADA FALLA DE CINCO MANERAS, UNA NUEVA Llamaba trigger-width / trigger-height al CHEVRON — nombrando por el primer selector de la regla ([…-trigger]::after) y colisionando con trigger-height, que YA EXISTE y es la altura del control. Fundía en content-radius dos nodos con dos valores. Cruzaba los nombres: content-padding-* para lo que es de la FILA, y el padding real del panel a «sin nombre mecánico». Llamaba content-width a un min-inline-size. Y la nueva: abría exigiendo «consume la capa compartida menu-indicator» — HOMONIMIA, esa capa es el check de los CheckboxItem de dropdown/context-menu y no toca el subrayado de esta barra (LAYER_VOCABULARY sólo registra calendar-surface). LAS FILAS DEL PANEL NO SON item- NI link- En los gemelos, `item` ES la fila. Aquí `item` es el <li> de la barra (parte del morfo) y el Link de barra viste el cromo del trigger: --…-item-radius o --…-link-radius prometerían a un tema un nodo que no alcanzan. Se acuñan content-link-*, que nombra el nodo por donde vive. Su tinta sigue leyendo trigger-fg, como venía. EL PRIVADO ERA UNA ESCALA, NO UN CONMUTADOR --_navigation-menu-list-gap tenía tres fuentes (0 / --space-1 / --space-2) por data-size: sube al TSC como list-gap-{sm,md,lg} + el resuelto, el molde que el bundle del trigger ya tenía al lado. Los dos bloques [data-size] del CSS se retiran — los emite el TSC, y dejarlos vivos da un diff de 0 por la ruta vieja. indicator-x ERA DECLARACIÓN MUERTA Soma escribe las cuatro coordenadas del rect INLINE sobre el indicador (-x, -y, -w, -h) y la receta lee sólo -w y -h; su propio comentario registra que sumar translateX(var(--…-x)) DUPLICABA el desplazamiento. No la leía nadie. Retirada, 0 diffs — la prueba de que estaba muerta (precedente: el re-point de --command-radius, el trío item-gap* de carousel). -w se queda: vive, y es canal de valor, no superficie de tema. EL PANEL NO ACUÑA TIPOGRAFÍA, A PROPÓSITO Lleva [data-depth='overlay'], y ese plano declara font-family / line-height a la misma especificidad (0,1,0) y más tarde en la cascada: un token ahí leería muerto. Es §12.9, pendiente de firma — la misma que hizo retirar claves en menubar, dropdown-menu, context-menu y tooltip. Sí se acuña la familia de la BARRA (trigger-font-family), que no es overlay y alcanza. TRES COSAS QUE APRENDIÓ EL INSTRUMENTO (registradas en next-features §13) 1. Sonda y centinela eran CIEGOS A LA MITAD DEL COMPONENTE. Las filas del mega-menú son los <a> DESNUDOS del consumidor, estilados por descendencia: el filtro data-{c}-* veía 10 nodos y NINGUNO era el panel ni una fila — 9 de los 19 knobs fuera del diff. Con EXTRA_NODES / extraNodes la sonda pasa a 16 nodos abiertos. Y el paso `open` de la sonda no honraba EXTRA_NODES. 2. EL GUARD CERRABA EL PANEL QUE MEDÍA. Este mega-menú abre con pointerenter y pointerleave programa el cierre; el guard abría con clic y luego aparcaba el puntero en (0,0) — justo el gesto que lo cierra. Dos corridas del mismo código discrepaban. Y daba las DOS caras del error: falso negativo en los tokens de la fila, falso positivo en content-link-padding-inline, que leía «vivo» por ensanchar el panel que sí se medía. Con openBy: 'hover', dos corridas idénticas. 3. `color` está EN TRANSICIÓN en esta parte: forzar data-disabled y leer en el acto devuelve el valor VIEJO — tres diagnósticos falsos («ni el inline lo mueve») hasta congelar. La trampa del cuentagotas del color-picker, en otro componente. Congelado: reposo oklch(0.2435 0 0) → disabled oklch(0.61 0 0). Y una de contabilidad, del instrumento y no del componente: el censo decide el eje `size` grepeando data-size en el CSS, y el protocolo §7.4 ORDENA retirar esos bloques. Todo componente que lo aplique bien aparece «sin eje». Ya le pasa a sidebar y nav-tree. En §13. VERIFICACIÓN Sonda antes/después 0 diffs (2.752 valores · 8 estados · 16 nodos con el panel abierto) · R-5.4 43/44 en dos corridas idénticas, adjudicación única (disabled-trigger-fg: la demo no monta el estado; forzado alcanza en trigger Y link, rgb(1,2,3)) · component:audit PASS · censo --only 100 % · eidos-lint 30 morfo-backed / 3 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido) · check sin error nuevo en los ficheros tocados · Chrome con el panel abierto: panel 8/10px/224px, fila 8/12/6px, trigger 36px/6px/gap 4px, indicador 2px. Tras tocar el guard, command, table y tooltip re-verificados sin regresión ni STALE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
:global(svg)`). `:global()` es un envoltorio de SVELTE: fuera de un
uix(onion-menu): temable — 29 % → 84 %, y una regla que el navegador DESCARTA 7 claves nuevas (12 → 19) y 5 literales anotados. Lo que queda fuera son TRES filas y ninguna es deuda de tokenización: una es canal de valor y dos viven en una regla inválida. Acuñado el color del ANILLO DEL SECTOR —uno para los dos estados, porque el propio comentario del CSS dice que el hover usa «el mismo color que el anillo de selección, más claro» y lo que los separa es la opacidad, que ya tenía token—, la tipografía de la etiqueta, el radio y la tinta del disparador, y las dos piezas del glifo `+`→`×`. Dos limpiezas de paso: un `var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí mismo, el defecto que ya se cazó en context-menu— y dos `var(--radius-full, …)` con un respaldo que no puede ocurrir. Cinco literales pasan a excepción POR IDENTIDAD: dos `opacity: 1` que DESHACEN un atenuado (sin atenuar es 1 por definición) y la caja del icono. ⚠ `.onion-menu-icon :global(svg)` NO EXISTE. `:global()` es un envoltorio de SVELTE y esto es un CSS plano: el selector es inválido y el navegador descarta la regla entera. Medido: el glifo computa 14px —el tamaño propio del Icon— y el `70 %` que el fichero cree pintar no pinta. Acuñé un `icon-glyph-size`, el centinela lo delató y lo RETIRÉ; los literales se quedan con el defecto escrito encima, porque arreglar el selector empieza a pintar el 70 % y MUEVE PÍXEL. Mismo bug en `timeline.css:331`; `image-picker.css` ya documentaba la trampa —la lección estaba escrita y se repitió—. → §13. Canal de valor: el fondo del disparador lo escribe el componente INLINE desde el motor de color, como los rellenos de cada sector. No es superficie de tema. TRES CEGUERAS DEL INSTRUMENTO, dos nuevas y genéricas: 1. EL GUARD CERRABA LA SUPERFICIE. Este menú nace ABIERTO y el clic de «abrir» lo cerraba: 12 de 20 tokens leían muertos. Ahora, si hay `openMarker` y ya casa, no se pulsa nada (8 → 14 de golpe). 2. Sus tripas cuelgan de CLASES, no de `data-onion-*`: sonda y guard las miden con `extraNodes` — 37 nodos en reposo contra los 5 con atributo. 3. Congelar las transiciones NO BASTA cuando el nodo está ANIMADO: `rim-glow-opacity` leía 0,482956 —el valor animado— y parecía muerto; con la animación congelada computa 0,45 y sigue al token. → §13. Los cuatro tokens del rim son del look `luminous`, que la demo del componente no renderiza (es una prop de Svelte que emite OTRO marcado, así que no hay atributo que forzar): medidos en `/active/docs/agnt`, que sí lo usa. Gates: sonda 0 diffs en 7.328 valores × 8 estados (37 nodos) · centinela 14/19 con las cinco adjudicadas · censo 84 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · capturas de reposo, sector en hover y disparador. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
`<style>` de componente el selector es inválido. `image-picker.css` ya
documenta la trampa en su propio comentario, así que la lección estaba escrita
y se repitió. Arreglar el selector EMPIEZA A PINTAR lo que la regla dice, así
que mueve píxel en los dos y es firma. Mientras tanto, los literales se quedan
con el defecto escrito encima y sin token (uno acuñado se retiró porque
mentía).
- **⚠ Congelar las transiciones no basta cuando el nodo está ANIMADO** (medido
en `onion-menu`, 2026-08-23). `rim-glow-opacity` leía 0,482956 —el valor que
la animación tenía en ese instante— y el token parecía muerto; con la
animación congelada también, computa 0,45 y sigue al token. El guard no puede
congelar animaciones en general (Presence necesita la suya para montar), así
que es una medida A MANO cuando el nodo pulsa.
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada, y no por deuda suya. Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO — `[data-rounded='full']` leía el primitivo global a pelo, así que la forma que más se retoca (la píldora) era el único escalón fuera del contrato. Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD (`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`, `outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían sido justo el antipatrón que la doctrina avisa: agrupa por VALOR. Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada, no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían inalcanzables mientras todas las demás ramas del mismo privado leen un público. ⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE. El último bloque del forward, `[data-button][data-color]`, resuelve `--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']` llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la misma especificidad (0,2,0). Medido sobre `data-color='risk'`: `--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras `--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete `primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`. No es de este componente: 49 recetas emiten el mismo forward genérico y el contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no movería un píxel, porque el valor por defecto de `--button-risk-solid` ES `var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es firma. → §13. El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el acabado de degradado que el generador deriva por instancia. Un error de método, anotado porque casi cuela: anclé la clave nueva en la primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se ancla en el BLOQUE del componente, nunca en una línea de clave. Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y a estampar el TONO que la clave nombra; y el fichero de excepciones gana PATRONES, para que una razón arquitectónica se escriba una vez y no 56. Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado · censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las seis variantes + píldora + tono `risk`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠⚠ LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE — 419 claves
públicas del catálogo** (medido 2026-08-23 en `button`, `badge` y `callout`).
El forward emite, por componente, un bloque por tono
(`[data-button][data-color='risk'] { --button-palette-solid: var(--button-risk-solid) }`)
y, **al final**, uno genérico
(`[data-button][data-color] { --button-palette-solid: var(--palette-solid, var(--button-primary-solid)) }`).
Los dos tienen especificidad (0,2,0), así que **gana el último**: cualquier
instancia con `data-color` resuelve por el `--palette-*` GLOBAL, que
`[data-color='{tono}']` llena desde `--color-{tono}-*`. Consecuencia medida
sobre `data-color='risk'`: `--button-risk-solid` no mueve nada ni en `:root`
ni en el nodo, mientras `--palette-solid` y `--color-risk-solid` en el nodo
repintan. Las siete `{c}-primary-*` son el RESPALDO de la regla genérica y
sólo actúan en una instancia SIN `data-color`.
**Tamaño**: 49 recetas emiten el forward genérico y el contrato tiene **419
claves de tono** (`{tono}-{track|element|border|solid|solid-hover|text|contrast}`),
repartidas entre ~13 componentes que además declaran reglas por tono. Son
públicas y no alcanzan: R-5.4 las llamaría mentiras, y no son deuda de nadie
en particular — es el orden de emisión.
**Arreglo propuesto (firma)**: emitir el bloque genérico ANTES de los de tono.
Entonces una instancia con tono lee `--{c}-{tono}-*` —cuyo valor por defecto
ES `var(--color-{tono}-*)`, así que **no movería un píxel**— y las escalas y
el custom siguen cayendo por el genérico. **Contrapartida**: cambia el
contrato de la jaula del color — un ancestro que inyecte `--palette-*` dejaría
de pisar un tono SEMÁNTICO en esos componentes (seguiría pisando los
personalizados). Por eso no se toca aquí.
Mientras tanto el guard lo adjudica con un PATRÓN
(`SENTINEL_PATTERN_EXCEPTIONS`), para que la razón se escriba una vez.
**Enmienda 2026-08-23 (`switch`): donde soma estampa `data-color` SIEMPRE, el
respaldo tampoco existe.** El párrafo de arriba dice que las `{c}-primary-*`
salvan a la instancia SIN `data-color` — pero hay componentes cuyo proveedor
estampa el atributo en toda instancia, porque para ellos el color es un valor
RESUELTO y `neutral` es su default, no su ausencia. En esos, el bloque
genérico casa siempre y **cae también el tono por defecto**: medido en
`switch` en estado checked, `--switch-neutral-solid` no mueve nada y
`--palette-solid` sobre el nodo repinta. Amplía el alcance de la firma: no es
«los tonos no canónicos», es **los tonos**.
uix(dropdown-menu): temable — 57 % → 96 %, y la tipografía del gemelo se RETIRA 9 claves nuevas (20 → 28) y 18 respaldos muertos retirados. Y una corrección a la baja, a propósito: `context-menu` 90 % → 87 %. Es el GEMELO de `context-menu`: mismos nombres, misma doctrina, mismas dos limpiezas de paso —el `font-family` era respaldo de SÍ MISMO y el peso del encabezado llevaba un `500` muerto detrás del token que ya vale 500—. Cosidos la tinta del panel y de la fila, su interlínea, el encabezado entero (tinta, peso y tracking) y el galón del submenú, que ahora tiene sus dos knobs: trazo y caja. EL FILO DEL SEPARADOR DEJA DE SER UN `1px` SUELTO y pasa a ser el trazo fino del sistema (`--border-width`, idéntico hoy): un tema que engorde el hairline lo engorda también aquí. Los 18 respaldos: cada `var(--dropdown-menu-x, …)` llevaba detrás un valor que NO puede ocurrir —las 18 claves están en el contrato— y sólo servía para envejecer contra él. Ninguno movía un píxel; dos ya discrepaban del contrato en la FUENTE. ⚠ `content-font-family` NO SE ACUÑA, Y LA DEL GEMELO SE RETIRA. El panel lleva `data-depth='overlay'` y el PLANO declara la familia con la misma especificidad y más tarde en la cascada: gana él. Quinta prueba de §12.9. `context-menu` la tenía acuñada desde esta misma sesión CON una adjudicación escrita, y eso era media doctrina: un público que no alcanza es un token que miente (R-5.4). Las dos recetas vuelven a su fuente literal con la razón encima. Un tercer caso —`combobox`, misma clave, mismo plano— queda medido y registrado en §13; no se toca aquí porque no es el componente en curso, y cuando §12.9 se firme la decisión será la misma para las cuatro. Gates: sonda 0 diffs en 928 valores × 8 estados (22 nodos con el panel abierto) · centinela 27/28 con la deshabilitada adjudicada · censo 96 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · captura del panel abierto. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠ `combobox.content-font-family` es el TERCER token de tipografía que el
plano `overlay` anula** (medido 2026-08-23). Su panel lleva
`data-depth='overlay'`, así que la clave no mueve nada, igual que las de
`menubar`, `dropdown-menu` y `context-menu` —las tres retiradas ya—. No se
toca aquí porque no es el componente en curso: cuando §12.9 se firme, o se
retira o empieza a pintar, y la decisión es la MISMA para las cuatro.
uix(audio-player): temable — 37 % → 56 %, y el instrumento medía CERO nodos 3 claves nuevas (5 → 8) y un literal anotado. El 44 % restante es PRÉSTAMO CON DUEÑO, no deuda: ocho filas que leen `--media-player-*` porque este componente ES la piel de audio de ese chasis, y una ficha nunca propone duplicar un token prestado con prefijo propio (§5.3-3). Acuñado lo único que ESTE reproductor añade sobre el chasis: el peso del título y la insignia LIVE en versalitas espaciadas (peso + tracking). Los dos `600` pasan a `var(--font-weight-semibold)`: el mismo valor, con nombre. Y EL INSTRUMENTO ESTABA CIEGO DEL TODO: MEDÍA 0 NODOS. Tres razones encadenadas: 1. `/uix/components/audio-player` es un 404 — el CUARTO canónico sin ruta propia (picker-shell, mockup, surface y éste). Se mide en la demo del chasis. 2. Sus partes no llevan `data-audio-player-*`: son `data-media-player-*`. Con `extraNodes`, la sonda pasa de 0 a 48 NODOS. 3. Sólo existe tras el chip `media: audio` de esa demo: sonda (`DEMO_VARIANTS`) y guard (`openWith` + `openMarker`) lo pulsan ahora, y el guard barre además las cuatro variantes (card/row/bar/inline). Con eso, 0/8 → 6/8. Las dos que quedan son la insignia LIVE, que sólo existe mientras el proveedor refleja un directo: montada con `[data-live]`, alcanza (600 → 333 y 1,28px → 7px). Gates: sonda con 48 nodos (antes 0) · centinela 6/8 con las dos adjudicadas · censo 56 % · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · equivalencia comprobada en el computado (título 600, tracking 1,28px). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠ Ya son CUATRO los componentes canónicos sin ruta de demo**: `picker-shell`,
`mockup`, `surface` y **`audio-player`** (404 los cuatro; los dos últimos
medidos el 2026-08-23). El de audio se mide dentro de `/uix/components/media-player`,
tras el chip `media: audio`, porque es la piel de ese chasis. Lo de abajo vale
igual para él: sin ruta no hay sonda ni guard, y el guard **miente en verde-rojo** (`/uix/components/surface` = 404, medido
2026-08-23). Sin ruta no hay sonda ni guard, y el guard **miente en verde-rojo**:
reportaba 0/25 sobre una página vacía, incluido un `radius` que está vivo. El
parche es el mismo que se le puso a picker-shell —medirlo donde SÍ se renderiza
(`urls` en el override)—, pero la solución de fondo es que un componente del
canon tenga su demo.
uix(tooltip): temable — 52 % → 75 %, y el plano ANULA SUS TRES VARIANTES 3 claves nuevas y 4 literales anotados. Subiría más, pero medir destapó que cinco de sus claves no pintan: el plano `overlay` le gana la superficie ENTERA. Acuñados el desenfoque del velo `ghost` y las dos tintas de la flecha `outline`, que se pintan por selector HERMANO desde el contenido. ⚠⚠ EL PLANO NO SÓLO SE QUEDA LA TIPOGRAFÍA: SE QUEDA EL FONDO, EL BORDE Y LA SOMBRA. `[data-depth='overlay']` declara las tres con la misma especificidad que `[data-tooltip-content]` y más tarde en la cascada. Medido: escribir `--tooltip-bg`, `--tooltip-border` o `--tooltip-shadow` no mueve NADA, mientras `--depth-overlay-surface` / `-border` / `-shadow` repintan el panel. Con `font-family` y `line-height`, son cinco públicos que no alcanzan. Y LA CONSECUENCIA VISUAL ES PEOR QUE LA CONTABLE: LAS TRES VARIANTES SON LA MISMA. Medidas `solid`, `outline` y `ghost` con el panel abierto: fondo, borde y sombra IDÉNTICOS en las tres. `outline` es indistinguible de `solid`; `ghost` sólo se diferencia por un `backdrop-filter` que no se ve porque la superficie que tiene delante es opaca. La máquina de variantes existe, alimenta privados, y el plano la anula entera. → §13. Por eso no se acuñó `ghost-bg`: lo intenté, el centinela lo delató y se retiró antes de commitear. Las cinco que ya estaban quedan ADJUDICADAS, no retiradas: son la FUENTE de esa máquina de variantes, y borrarlas dejaría inválidos los privados sin arreglar nada. La corrección de fondo es la precedencia del plano — la misma firma §12.9 que arrastran menubar, dropdown-menu y combobox. `content-z` sí está vivo, pero en otro nodo: aterriza en el ENVOLTORIO flotante (90 → 4321), que no lleva ningún `data-tooltip-*` y queda fuera del conjunto medido. Instrumento: un tooltip abre por HOVER, no por clic (`openBy: 'hover'`, como link-preview) — con eso pasó de 0/24 a 14/24. El resto se midió con el interruptor `open` de la demo: un panel abierto por puntero exige el ratón quieto sobre el disparador toda la pasada, y no es medida fiable. Gates: sonda 0 diffs en 256 valores × 8 estados · centinela 15/23 con las ocho adjudicadas · censo 75 % · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠⚠ EL PLANO `overlay` ANULA LAS TRES VARIANTES DE `tooltip`** (medido
2026-08-23). No es sólo la tipografía de §12.9: `[data-depth='overlay']`
declara `background`, `border` y `box-shadow` con la misma especificidad que
`[data-tooltip-content]` y más tarde en la cascada, así que gana. Medido con el
panel abierto: `--tooltip-bg` / `-border` / `-shadow` no mueven nada,
`--depth-overlay-surface` / `-border` / `-shadow` repintan.
**Consecuencia visual**: `solid`, `outline` y `ghost` computan el MISMO fondo,
el MISMO borde y la MISMA sombra. `outline` es indistinguible de `solid`;
`ghost` sólo aporta un `backdrop-filter` invisible tras una superficie opaca.
La receta tiene una máquina de variantes completa (privados por variante) que
el plano neutraliza entera.
Las cinco claves afectadas quedan ADJUDICADAS, no retiradas: son la fuente de
esa máquina y borrarlas dejaría los privados inválidos sin arreglar el fondo.
La decisión es la de §12.9 —qué manda, el plano o la receta— y aquí ya no es
sólo tipografía: es la superficie.
uix(navigation-menu): temable — 44 % → 100 %, y el guard cerraba el panel que medía Bloque B1 de D-TH.4. Contrato 26 → 44 claves: 19 nuevas y UNA retirada. Diff de computed 0 sobre 2.752 valores en 8 estados; R-5.4 43/44 con una adjudicación escrita. Global 65 % (al 100 % 25 → 26). LOS NOMBRES LOS DIO EL CATÁLOGO, NO YO Ninguno se inventó. Los gemelos cerrados el mismo día (dropdown-menu 96 %, context-menu 87 %) dan content-{min-width,padding,radius,fg} y chevron-{size,stroke}; accordion da trigger-font-family y disabled-trigger-fg. Las cuatro recetas que declaran un chevron lo dimensionan con un LITERAL (0.42em · 0.5em · 0.625rem): un glifo no es un paso de la escala de espacio. Al revés con el subrayado del indicador: su 2px ES --border-width-medium, y dropdown-menu.separator-thickness dice que un grosor decorativo se nombra por esa familia. LA §4 GENERADA FALLA DE CINCO MANERAS, UNA NUEVA Llamaba trigger-width / trigger-height al CHEVRON — nombrando por el primer selector de la regla ([…-trigger]::after) y colisionando con trigger-height, que YA EXISTE y es la altura del control. Fundía en content-radius dos nodos con dos valores. Cruzaba los nombres: content-padding-* para lo que es de la FILA, y el padding real del panel a «sin nombre mecánico». Llamaba content-width a un min-inline-size. Y la nueva: abría exigiendo «consume la capa compartida menu-indicator» — HOMONIMIA, esa capa es el check de los CheckboxItem de dropdown/context-menu y no toca el subrayado de esta barra (LAYER_VOCABULARY sólo registra calendar-surface). LAS FILAS DEL PANEL NO SON item- NI link- En los gemelos, `item` ES la fila. Aquí `item` es el <li> de la barra (parte del morfo) y el Link de barra viste el cromo del trigger: --…-item-radius o --…-link-radius prometerían a un tema un nodo que no alcanzan. Se acuñan content-link-*, que nombra el nodo por donde vive. Su tinta sigue leyendo trigger-fg, como venía. EL PRIVADO ERA UNA ESCALA, NO UN CONMUTADOR --_navigation-menu-list-gap tenía tres fuentes (0 / --space-1 / --space-2) por data-size: sube al TSC como list-gap-{sm,md,lg} + el resuelto, el molde que el bundle del trigger ya tenía al lado. Los dos bloques [data-size] del CSS se retiran — los emite el TSC, y dejarlos vivos da un diff de 0 por la ruta vieja. indicator-x ERA DECLARACIÓN MUERTA Soma escribe las cuatro coordenadas del rect INLINE sobre el indicador (-x, -y, -w, -h) y la receta lee sólo -w y -h; su propio comentario registra que sumar translateX(var(--…-x)) DUPLICABA el desplazamiento. No la leía nadie. Retirada, 0 diffs — la prueba de que estaba muerta (precedente: el re-point de --command-radius, el trío item-gap* de carousel). -w se queda: vive, y es canal de valor, no superficie de tema. EL PANEL NO ACUÑA TIPOGRAFÍA, A PROPÓSITO Lleva [data-depth='overlay'], y ese plano declara font-family / line-height a la misma especificidad (0,1,0) y más tarde en la cascada: un token ahí leería muerto. Es §12.9, pendiente de firma — la misma que hizo retirar claves en menubar, dropdown-menu, context-menu y tooltip. Sí se acuña la familia de la BARRA (trigger-font-family), que no es overlay y alcanza. TRES COSAS QUE APRENDIÓ EL INSTRUMENTO (registradas en next-features §13) 1. Sonda y centinela eran CIEGOS A LA MITAD DEL COMPONENTE. Las filas del mega-menú son los <a> DESNUDOS del consumidor, estilados por descendencia: el filtro data-{c}-* veía 10 nodos y NINGUNO era el panel ni una fila — 9 de los 19 knobs fuera del diff. Con EXTRA_NODES / extraNodes la sonda pasa a 16 nodos abiertos. Y el paso `open` de la sonda no honraba EXTRA_NODES. 2. EL GUARD CERRABA EL PANEL QUE MEDÍA. Este mega-menú abre con pointerenter y pointerleave programa el cierre; el guard abría con clic y luego aparcaba el puntero en (0,0) — justo el gesto que lo cierra. Dos corridas del mismo código discrepaban. Y daba las DOS caras del error: falso negativo en los tokens de la fila, falso positivo en content-link-padding-inline, que leía «vivo» por ensanchar el panel que sí se medía. Con openBy: 'hover', dos corridas idénticas. 3. `color` está EN TRANSICIÓN en esta parte: forzar data-disabled y leer en el acto devuelve el valor VIEJO — tres diagnósticos falsos («ni el inline lo mueve») hasta congelar. La trampa del cuentagotas del color-picker, en otro componente. Congelado: reposo oklch(0.2435 0 0) → disabled oklch(0.61 0 0). Y una de contabilidad, del instrumento y no del componente: el censo decide el eje `size` grepeando data-size en el CSS, y el protocolo §7.4 ORDENA retirar esos bloques. Todo componente que lo aplique bien aparece «sin eje». Ya le pasa a sidebar y nav-tree. En §13. VERIFICACIÓN Sonda antes/después 0 diffs (2.752 valores · 8 estados · 16 nodos con el panel abierto) · R-5.4 43/44 en dos corridas idénticas, adjudicación única (disabled-trigger-fg: la demo no monta el estado; forzado alcanza en trigger Y link, rgb(1,2,3)) · component:audit PASS · censo --only 100 % · eidos-lint 30 morfo-backed / 3 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido) · check sin error nuevo en los ficheros tocados · Chrome con el panel abierto: panel 8/10px/224px, fila 8/12/6px, trigger 36px/6px/gap 4px, indicador 2px. Tras tocar el guard, command, table y tooltip re-verificados sin regresión ni STALE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **El censo pierde el eje `size` DE PREMIO por aplicar bien el protocolo**
(medido 2026-08-23 en `navigation-menu`). `hasSize` se decide grepeando
`data-size` en el CSS de la receta (`theming-census.ts:287`), y el paso §7.4
del protocolo ORDENA retirar los bloques `[data-size]` del CSS cuando la
cascada la emite el TSC. Resultado: todo componente que haga lo correcto
aparece con «eje `size`: no» en su ficha y sin `y` en la tabla del censo.
Ya le pasa a `sidebar` y `nav-tree`, y a `navigation-menu` desde hoy — los
tres con su cascada por talla viva en `generated/base.css`. La columna es
informativa (no entra en el ratio), pero el conteo «con eje `size`: N» del
encabezado cuenta al revés de lo que quiere medir. El arreglo es leer el
eje del CONTRATO (una clave `-{sm,md,lg}` o un `scope: 'size:{k}'`), no del
CSS.
- **La sonda y el centinela no ven las partes que el CONSUMIDOR compone**
(medido 2026-08-23 en `navigation-menu`). Las filas de su mega-menú son `<a>`
desnudos que la receta estiliza por descendencia
(`[data-navigation-menu-content] :is(a, …)`): sin atributo del componente, el
filtro `data-{c}-*` veía 10 nodos y ninguno era el panel ni una fila — 9 de
19 knobs fuera del diff. Es la MISMA clase que `prose` y `onion-menu`, pero
por composición del consumidor, no por HTML crudo ni por hooks de clase.
Resuelto para este componente con `EXTRA_NODES` / `extraNodes` (y el paso
`open` de la sonda, que no los honraba). **Queda la pregunta general**: una
receta que estiliza nodos sin atributo propio no es medible por defecto, y
nada avisa — el gate sale verde.
uix(waveform): temable — 55 % → 86 %, y el playhead era el ÚNICO primitivo crudo UNA clave nueva, cuatro literales firmados, un préstamo adjudicado. Diff de computed 0 sobre 1.600 valores en 7 estados; R-5.4 11/11, CERO adjudicaciones. LA CLAVE NUEVA NO LA PIDIÓ EL CENSO, LA PIDIÓ EL FICHERO `playhead-bg`. Todas las demás líneas del re-tinte del Slider embebido leen ya un token propio (--slider-secondary-bg: var(--waveform-buffered-fg)) o una identidad (transparent / none / 0px / 1); el thumb era la ÚNICA que pasaba un primitivo CRUDO (--color-content-primary) a un componente compuesto. Con eso, repintar el playhead exigía mover --slider-thumb-bg (todos los sliders de la app) o --color-content-primary (todo). Ahora es el par natural de `playhead-width`, que ya existía. El molde es el re-tinte de media-player, que reenvía --media-player-* en cada línea. El censo no lo veía: una custom property reenviada a un componente compuesto no entra en KNOB_PROPS (D-TH.2). La clase entera queda anotada en §13 — toda receta que re-tinta un embebido puede pasarle primitivos crudos sin que nada lo vea. EL 14 % QUE QUEDA ES UNA FILA, Y ES DOCTRINA La onda deshabilitada lee var(--slider-disabled-opacity). La prueba de si un token es tuyo no es qué valor tiene sino QUÉ DECIDE: aquí decide cuánto se apaga un control deshabilitado, y eso es del Slider — la onda vive fuera de su caja pero es la misma superficie a la vista. Un --waveform-disabled-opacity daría un segundo mando para UNA decisión y permitiría desincronizar cromo y pintura. Préstamo con dueño: se tema en la ficha de slider, como el 82 % de time-picker se tema en la de field. LOS CUATRO LITERALES SON IDENTIDAD, Y AHORA LO DICEN La pintura ES la caja del provider (100% sobre un position:absolute; inset:0), el área de arrastre ES la caja entera («scrubbing means grabbing anywhere on it»), y un playhead abarca todo el alto por definición. Anotación /* literal: */ en su declaración: desviación firmada, fuera del ratio. EL INSTRUMENTO, CIEGO POR UNA CAUSA NUEVA: EL COMPONENTE COMPUESTO La sonda medía 4 nodos —provider, svg y los dos paths— y NINGUNO era el playhead: el cromo entero es un Slider embebido cuyos nodos llevan data-slider*. Con extraNodes, 8. Es la TERCERA cara del mismo agujero: HTML crudo (prose), partes que compone el consumidor (navigation-menu) y partes de un componente compuesto (waveform). En §13, con la pregunta de fondo: hoy hay que sospecharlo y contar los nodos, el instrumento no lo detecta solo. VERIFICACIÓN Sonda antes/después 0 diffs (1.600 valores · 7 estados · 8 nodos) · R-5.4 11/11 sin adjudicaciones · component:audit PASS · censo 86 % con la única fila restante explicada · eidos-lint 4 morfo-backed / 16 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido) · Chrome: playhead 2px × 40px, onda 544 × 40, trazo 0.6 — idénticos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **…y la TERCERA cara: las partes de un componente COMPUESTO** (medido
2026-08-23 en `waveform`). Su cromo entero es un `Slider` embebido que la
receta re-tinta —el playhead ES el thumb del slider—, así que esos nodos
llevan `data-slider*` y el filtro `data-waveform*` medía 4 nodos sin ninguno
de ellos. Con `extraNodes`, 8. Las tres caras del mismo agujero son ya: HTML
crudo (`prose`), partes que compone el CONSUMIDOR (`navigation-menu`) y partes
de un componente COMPUESTO (`waveform`, `picker-shell`). **Lo que falta es que
el instrumento lo DETECTE solo** — hoy hay que sospecharlo y contar los nodos.
- **El censo no ve los knobs que una receta reenvía a un componente compuesto.**
`--slider-thumb-bg: var(--color-content-primary)` en `waveform.css` es un
primitivo CRUDO pintando el playhead, y no aparece en ninguna columna: las
custom properties no están en `KNOB_PROPS` (D-TH.2). El caso se arregló a
mano al tokenizar (`playhead-bg`), pero **el perímetro deja fuera una clase
entera**: toda receta que re-tinta un componente embebido (media-player,
waveform, chronos, los pickers) puede pasarle primitivos crudos sin que el
censo ni `recipe-css-contract` lo vean. Medir cuántas hay antes de decidir si
D-TH.2 se ensancha.
docs(theming): la revisión adversarial §7.7 de los 26 commits — 5 hipótesis, 5 refutadas Cierra el punto P5 del plan de la revisión: lo ejecutado desde el 22 (33 componentes, dos bloques) por fin tiene su pasada escéptica, con medición propia y no leída de los commits. LA BASE, ANTES DE COMPARAR NADA Worktree en cd6a9f4e3 con dev server propio, node_modules por junction y `server: { fs: { allow: [...] } }` — sin eso Vite responde 403 a /@fs/… , la página NO hidrata y se compara contra una base muerta (la trampa que el handoff registra). Comprobado que los componentes MONTAN e hidratan antes de medir. H1 · «el default es idéntico» — 33 de 33 31 con 0 diffs, ~48.000 valores computados en 7-8 estados por componente. `surface` daba 0 valores por una URL mía equivocada: en su ruta real (/temas/gradientes) 0 diffs sobre 896. Y los dos con diffs son RUIDO DEL INSTRUMENTO, probado corriendo dos veces el MISMO código: `image` reproduce su único diff y `audio-player` da ONCE (más que la comparación base↔HEAD). Las dos propiedades son estado vivo — la opacity de un <img> con animación de entrada (la sonda congela transition, nunca animation) y el ancho del rango buffered de un audio. En §13, con la recomendación de excluirlas del snapshot. H2 · «el velo cae donde debe» — verde en los cuatro que tocaron :hover Píxel hacia arriba: dropdown-menu y context-menu pintan el velo en su item (radio 4px, el nodo CON forma) y el list-surface de debajo queda limpio; menubar pinta el abierto en su propio <button> sin derramar al contenedor, y la fila de su panel en el item; anchor-nav no tiene velo en ninguna capa, que es lo correcto — su hover es sólo tinta. Medido sobre un enlace NO activo: el primero que casa el selector lleva data-active y pinta otra cosa. H3 · «no hay doble animación» — no aplica Ningún commit de los 26 toca animation, @keyframes, data-event ni motion-* en components/. No hay sello movido que pueda duplicarse. H4 · «cada token alcanza» — rehecho, y destapó float-panel El barrido del centinela sobre los 59 con ledger: 0 STALE, 0 errores, un solo rojo (float-panel), ya arreglado en 233ef989b. H5 · «el contrato no tiene duplicados ni huérfanos» — limpio 144 bloques sin duplicados, sin claves repetidas dentro de un bloque, y el test oficial de huérfanos en verde. ⚠ Y aquí mintió mi propio instrumento: un guard estricto de un solo uso reportó 185 huérfanos —los pasos de escala por talla y todos los tonos— porque excluía `generated/`, que es exactamente donde el TSC emite el nombre resuelto y el forward de paleta lee los tonos. Un guard de huérfanos que no mira el CSS GENERADO ve huérfano todo lo que el generador resuelve. En §13. VEREDICTO: 0 hallazgos nuevos. Los reales de esta revisión salieron del análisis ESTÁTICO (E1-E5) y ya estaban arreglados antes de esta pasada. El handoff abre ahora con las cuatro lecciones que cambian cómo se trabaja: correr el guard sobre el ledger entero · el nombre de un token no es el de la propiedad · si un paso del protocolo no está en el commit, no se hizo · y comprobar que la base MONTA antes de comparar contra ella. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Dos propiedades que la sonda NO puede medir: la `opacity` de un `<img>` con
animación de entrada y el `inline-size` del rango *buffered* de un audio**
(medido 2026-08-23 en `image` y `audio-player`). La sonda congela `transition`
pero **nunca `animation`** —congelarla impide que Presence monte los paneles—,
así que la imagen se muestrea a mitad de su fundido y da un valor distinto en
cada corrida; el *buffered* depende de lo que el audio lleve descargado.
Probado: dos corridas sobre el MISMO código dan 1 diff en `image` y **11** en
`audio-player`. Mientras no se resuelva, **un diff en esas dos propiedades se
repite antes de creerlo**, y lo honesto sería excluirlas del snapshot para
esos dos componentes.
- **Un guard de huérfanos que no mira `generated/` ve huérfano casi todo**
(medido 2026-08-23): un script estricto de un solo uso reportó 185 claves «no
leídas» que eran los pasos de escala por talla y los tonos de paleta — los lee
el CSS GENERADO, donde el TSC emite el nombre resuelto y el forward de paleta
resuelve los tonos. El test oficial (`recipe-css-contract.test.ts`) sí los ve.
- **El escenario de la demo de `background` cambia de ANCHO entre corridas**
(medido 2026-08-23, dos veces): 870 px una corrida, 1.350 px la siguiente, con
el mismo código. Produce 2 diffs de `inlineSize` en la sonda —del contenedor,
no del componente— que un lector apresurado leería como regresión. Mientras no
se estabilice, **una corrida de sonda con diffs SÓLO de `inlineSize` sobre el
provider/capa de background se repite antes de creerla**.
- **`audio-player` no tiene README propio** (`src/uix/eidos/components/audio-player/`
tiene css, svelte, index y types y nada más). Es el cuarto canónico sin ruta de
demo y además sin documento: cuando se le escriba, le toca su «Talla y tema»
con las 8 claves que tiene desde `83a897b92`.
uix(emoji-picker): temable — 61 % → 100 %, y el cromo del trigger NO era suyo Siete claves nuevas, TRES RETIRADAS por muertas, tres literales firmados y cinco adjudicaciones medidas. Contrato 17 → 21. Diff de computed 0 sobre 6.080 valores en 8 estados (176 nodos con el panel abierto), capturas 2× idénticas. LOS NOMBRES LOS DIO EL CATÁLOGO `open-trigger-fg` (accordion lo nombra igual), grid-padding-inline + grid-padding-block-end (el eje lógico completo, como card-group / combobox / command) y las tres del marcador de tono, la familia timeline.marker-{size,radius,bg}. Se llama tone-marker-* y no marker-* porque el pie lleva además un selector de TONO: dentro del propio componente, `marker` a secas sería ambiguo. TRES CLAVES RETIRADAS — EL CROMO DE REPOSO DEL TRIGGER NO ES SUYO El trigger lleva también data-popover-trigger, y [data-popover-trigger]:not([data-archetype='field-trigger']) es (0,2,0) contra el (0,1,0) de esta receta: el radio, la tinta y el tamaño de letra los pinta popover.css. Medido: mover --emoji-picker-trigger-{fg,radius,font-size} no cambiaba NADA. Retiradas con sus declaraciones, 0 diffs — la prueba de que estaban muertas. Es el defecto que gradient-picker documentó (36 de sus 45 tokens) y la misma respuesta. Sobreviven dos por razones distintas: trigger-size, porque popover dimensiona con `height` y no con el par lógico, y open-trigger-fg, porque su selector añade [data-state='open'] y gana por orden. trigger-fg no llegó a existir: se acuñó y se midió muerta en el mismo pase. CINCO ADJUDICACIONES, TODAS SOBRE NODOS REALES Las tres del marcador de tono (la demo monta CERO celdas data-tone-capable; forzado el atributo sobre una celda real, el ::after alcanza: 4px → 37px, 9999px → 37px, y el fondo), preview-name-fg (los hijos de la vista previa son <span> desnudos, sin atributo del componente, fuera del conjunto medido) y open-trigger-fg. ⚠ open-trigger-fg OSCILA ENTRE CORRIDAS: 17/21, 16/21, 16/21 con el mismo código. Medido vivo CUATRO veces —la última replicando paso a paso el staticPass del propio guard, que movió a la primera talla—, así que el token no miente: lo que falla es el instrumento. La causa: el panel se cierra en algún punto de la pasada y, durante la animación de SALIDA, el content sigue en el DOM mientras el trigger ya perdió data-state='open', que es lo que la regla selecciona; reopen() mira el CONTENT, cree que está abierto y no reabre. Probé un openMarker sobre el trigger y NO basta (el clic de reapertura cae en esa misma ventana), así que lo retiré en vez de dejar un override que no arregla. El arreglo real es que reopen() espere a la CONDICIÓN en vez de dormir un número fijo — §13. Su entrada de ledger saldrá STALE en las corridas donde alcanza, y está escrito en la razón para que nadie la borre por eso. Y EL INSTRUMENTO DE CAPTURA ESTABA ROTO DESDE SIEMPRE __shot.ts tenía el puerto 5173 CABLEADO, así que cualquier sesión en otro puerto moría con ERR_EMPTY_RESPONSE. Es probablemente por lo que los pasos 6 y 13 del protocolo se saltaron en 33 componentes sin que nadie lo notara. Ahora toma la URL como argumento —como la sonda y el guard— y un --open opcional para los componentes cuya superficie sólo existe abierta. Estrenado aquí: capturas antes y después, miradas y idénticas. VERIFICACIÓN Sonda antes/después 0 diffs (6.080 valores · 8 estados) · R-5.4 17/21 con las cinco adjudicadas, exit 0 · component:audit PASS · censo --only 100 % · eidos-lint 17 morfo-backed / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido) · el ledger sigue en sus 63 hunks de prettier preexistentes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **`reopen()` mira el CONTENT, y el estado vive en el TRIGGER** (medido
2026-08-23 en `emoji-picker`). Su `open-trigger-fg` oscila corrida a corrida
(17/21 · 16/21 · 16/21 con el mismo código): el panel se cierra en algún punto
de la pasada y, durante la animación de SALIDA, el content sigue en el DOM
mientras el trigger ya perdió `data-state='open'` — que es lo que la regla
selecciona. `reopen()` ve el content, cree que está abierto y no reabre.
Probado un `openMarker` sobre el trigger: **no basta**, porque el clic de
reapertura cae dentro de esa misma ventana y sus 250 ms de espera no llegan.
El arreglo es que `reopen()` **espere a la condición** (`waitForSelector` del
marcador tras el clic) en vez de dormir un número fijo. Mientras tanto, su
entrada de ledger sale STALE en las corridas donde alcanza: está escrito en la
propia razón para que nadie la borre por eso.
uix(form): temable — 71 % → 86 %, y 24 claves suyas NO PINTAN por una cascada Diez claves nuevas (54 → 64), un literal firmado, veinte adjudicaciones medidas y un hallazgo que espera firma. Diff de computed 0 sobre 832 valores en 7 estados; capturas 2× antes/después. LAS DIEZ IBAN A LOS PRIMITIVOS A PELO El resumen de errores (error-summary-{bg,fg,border}) —es el señalizador de nivel de página y su superficie, su tinta y su borde SON el tono risk—, el acento de formulario inválido (invalid-accent, un privado con UNA sola fuente, no un conmutador), las tres superficies del árbol de AutoFields, las dos del widget y el subrayado a media tinta de los enlaces del resumen (link-underline-fg, el par del -offset que ya existía). ⚠ R-5.3 CAZÓ MI NOMBRE EN EL ACTO: lo acuñé como link-underline-color y la tinta es `fg` (theming §6.7 r7, firmado el 20). Renombrado antes de commitear. El guard de nombres funciona — es el vocabulario DIMENSIONAL el que no cubre (ver badge, hoy mismo). ⚠⚠ VEINTICUATRO CLAVES DEL CONTRATO NO PINTAN, Y ES UNA CASCADA [data-form-submit], [data-form-reset] y los dos botones del array llevan también data-button porque COMPONEN el Button canónico, y [data-button] casa con la MISMA especificidad (0,1,0) pero carga DESPUÉS: gana button.css. Medido propiedad a propiedad sobre el nodo real —altura, padding-inline, tamaño y peso de letra, interlineado, hueco, radio, grosor de borde, el par de transición y la opacidad deshabilitada—: NINGUNA se mueve. Es el defecto que gradient-picker documentó (36 de sus 45 claves) y que emoji-picker encontró hoy en su trigger. Allí la respuesta fue RETIRAR las declaraciones muertas; aquí son 24 claves públicas, y eso es una decisión del autor, no del que ejecuta. Quedan adjudicadas POR PATRÓN (una razón arquitectónica escrita una vez) y anotadas en §13 con la pregunta de fondo, que es la misma de §12.9 con el plano overlay: qué manda, el componente compuesto o la receta que lo compone. Si mandan las recetas, se sube la especificidad y no se retira nada; si mandan los compuestos, hay ~63 claves públicas que retirar en tres componentes. LAS DEMÁS ADJUDICACIONES SON RAMAS QUE LA DEMO NO MONTA El layout grid (forzado alcanza — y ojo: su grid-template-columns sólo CAMBIA si el valor altera el número de columnas; con 90px pasa de una columna de 502px a cuatro de 110,5), el resumen de errores VACÍO (sin <ul> que indentar ni <a> que subrayar) y toda la rama de ARRAY de AutoFields: el modo `auto` de la demo monta group y field, nunca array, item ni widget. Fuera por doctrina: los tres privados del panel son un CONMUTADOR (variante plain contra panel) y los cuatro --_form-action-* leen el forward de paleta THM-2. VERIFICACIÓN Sonda antes/después 0 diffs (832 valores · 7 estados) · R-5.4 22/64 con las 20 + el patrón adjudicados, exit 0 · component:audit PASS (tras el renombrado) · --names 0 desviadas · censo --only 86 % · eidos-lint 36 morfo-backed / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo conocido) · el ledger sigue en sus 63 hunks de prettier previos. Rama con cuatro sesiones en paralelo: índice construido desde HEAD + sólo mis hunks (base.ts por bloque), verificado con `git diff --cached`. No entra docs/audit/theming/README.md — es un agregado global que ahora mismo mezcla trabajo en vuelo de otras sesiones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠⚠ UN COMPONENTE COMPUESTO GANA A LA RECETA QUE LO COMPONE, y ya van TRES**
(medido 2026-08-23). Cuando una receta estiliza un nodo que también lleva
`data-button` / `data-popover-trigger`, los dos selectores casan a la MISMA
especificidad (0,1,0) y decide el ORDEN DE CARGA — que gana el componente
canónico. Censo de lo medido: `gradient-picker` 36 claves de 45 (F2-A, se
retiraron), `emoji-picker` 3 (retiradas hoy) y **`form` 24** (adjudicadas, no
retiradas: son demasiadas para decidirlo sin firma). La pregunta de fondo es
la misma de §12.9 con el plano `overlay`: **qué manda, el componente compuesto
o la receta que lo compone**. Si la respuesta es «la receta», el arreglo es
subir la especificidad de esas reglas y no retirar nada; si es «el compuesto»,
hay ~63 claves públicas que retirar en tres componentes.
fix(badge): la clave se llamaba como la PROPIEDAD — min-block-size-{k} → min-height-{k} Corrección de nombre, sin píxel: la clave por talla que entró esta tarde salió como `min-block-size-{k}` —el nombre de la propiedad CSS— cuando la ranura del vocabulario para ella es `height`. LO DICE EL CANON Y LO DICE EL PROPIO CENSO recipe-contract §1: «Control height per size → control-height-{size} (o {part}-height-{size} cuando la parte no es el control) · antipatrón: inventar un tercer nombre por componente». Y `theming-census.ts` mapea, en su tabla de propiedades, `min-block-size → height`: la herramienta que mide ya sabía cuál era la ranura. MEDIDO EN EL CATÁLOGO, NO SUPUESTO 24 componentes dimensionan por `…-height-{k}` (accordion, field, table, tree-grid, sidebar, navigation-menu, select, tabs…) y `min-block-size-{k}` existía SÓLO en badge. El gemelo con la forma exacta —raíz, por talla, sobre `--size-{k}-control-height`— es `proof-of-human.min-height-{k}`, que además resuelve igual (clave `min-height` con `declarations` por talla). R-5.3 NO LO CAZA, Y ESO ES LO INTERESANTE El guard de nombres cubre la ranura de tinta (`fg` vs `color`) y la posición del modificador, pero no el vocabulario DIMENSIONAL: una clave con el nombre de la propiedad pasa. Anotado en next-features §13 con el arreglo propuesto (extender R-5.3 con la tabla propiedad → ranura que el censo ya tiene). Verificación: sonda antes/después **0 diffs** (992 valores · 7 estados · 5 nodos) · R-5.4 29/71 idéntico, exit 0 · censo --only 75 % sin cambio · `--names` 0 desviadas · docs:check 0 · suite eidos 439/440 (el rojo conocido) · `component:audit` NEEDS-WORK preexistente (R-1.5, sin foco). Sale de la revisión de hoy (`REVIEW-theming-2026-08-23.md`, punto P1). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **R-5.3 no guarda el vocabulario DIMENSIONAL** (medido 2026-08-23 en `badge`).
Cubre la ranura de tinta (`fg` vs `color`) y la posición del modificador, pero
no que una clave POR TALLA se llame `…-height-{k}` y no
`…-min-block-size-{k}` (el nombre de la PROPIEDAD). Una clave así pasó el
guard y vivió unas horas; la cazó una lectura del catálogo (24 componentes
dimensionan por `-height-{k}`, uno por `-min-block-size-{k}`) y la tabla del
propio censo, que ya mapea `min-block-size → height`. Extender R-5.3 con esa
tabla —propiedad → ranura— cierra la clase entera.
fix(theming): el guard da por VIVO lo que un tema no alcanza — y dos bloques del ledger se descartaban en silencio Dos defectos del instrumental, los dos medidos, ninguno arreglado en el guard todavía (el arreglo toca a todos los componentes y pide su re-verificación). EL GUARD TIENE FALSOS POSITIVOS: ESCRIBE EL CENTINELA EN EL style INLINE `staticPass` escribe el token en documentElement Y en todos los nodos del componente — y ahí es donde vive el canal inline de los wrappers. Si un wrapper escribe un token PÚBLICO en el `style` del nodo, el guard lo pisa, ve el cambio y lo da por VIVO, aunque un tema (que sólo puede escribir en :root o en una hoja) no lo alcance jamás. Es la cara opuesta de drag-drop.preview-z. Censo: siete wrappers escriben un --{c}-* público inline y DOS están en el contrato — dialog.overlay-opacity y drawer.overlay-opacity. Medido en dialog con el diálogo abierto y las animaciones congeladas: desde :root el fondo del velo NO se mueve (color(srgb 0.1098 0.098 0.0902 / 0.2518) idéntico), desde el inline SÍ. Su token MIENTE y el guard lo bendice. Los otros cinco no están en ningún contrato: son sólo canal. El arreglo propuesto va en §13: escribir en todos los nodos MENOS en los cuyo `style` ya declara ese mismo token, y avisar cuando ocurra — no basta con escribir sólo en :root, porque eso es justo lo que hace medibles los paneles portalados. Con re-verificación del ledger entero, así que no entra aquí. El veredicto de dialog (commiteado hoy) queda corregido con la medición. DOS BLOQUES DEL LEDGER SE DESCARTABAN EN SILENCIO `metrics` y `color-picker` aparecían DOS VECES en el mismo literal de objeto, así que el primero de cada par no existía para el guard — la trampa que el handoff documenta para base.ts, un piso más abajo. Comprobado antes de tocar nada: en los dos casos el bloque que gana es SUPERCONJUNTO del muerto, así que no se perdía ninguna adjudicación; era deuda con riesgo futuro, no un rojo. Retirados los dos bloques muertos: 69 → 67 entradas, cero duplicados, y `metrics` (54/72) y `color-picker` (46/71) siguen en verde. De paso, el fichero baja de 63 a 61 hunks de prettier preexistentes. Lo destapó la sesión de `progress` al leer el fichero para añadir su bloque. Rama con varias sesiones en paralelo: índice construido desde HEAD + sólo mis hunks, verificado con git diff --cached. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **⚠⚠ EL GUARD TIENE FALSOS POSITIVOS: escribe el centinela en el `style` INLINE
de cada nodo** (medido 2026-08-23). `staticPass` escribe el token en
`documentElement` **y en todos los nodos del componente**, y ahí es donde vive
el canal inline de los wrappers: si el wrapper escribe un token PÚBLICO en el
`style` del nodo, el guard lo pisa, ve el cambio y lo da por VIVO — aunque un
tema, que sólo puede escribir en `:root` o en una hoja, **no lo alcance
jamás**. Es la cara opuesta de `drag-drop.preview-z`.
Censo de lo afectado: siete wrappers escriben un `--{c}-*` público inline y
**CUATRO de ellos están en el contrato** — `dialog.overlay-opacity`,
`drawer.overlay-opacity`, `avatar.badge-color-custom` y
`avatar.ring-color-custom`. Medido en `dialog` con el diálogo abierto y las
fix(theming): el guard da por VIVO lo que un tema no alcanza — y dos bloques del ledger se descartaban en silencio Dos defectos del instrumental, los dos medidos, ninguno arreglado en el guard todavía (el arreglo toca a todos los componentes y pide su re-verificación). EL GUARD TIENE FALSOS POSITIVOS: ESCRIBE EL CENTINELA EN EL style INLINE `staticPass` escribe el token en documentElement Y en todos los nodos del componente — y ahí es donde vive el canal inline de los wrappers. Si un wrapper escribe un token PÚBLICO en el `style` del nodo, el guard lo pisa, ve el cambio y lo da por VIVO, aunque un tema (que sólo puede escribir en :root o en una hoja) no lo alcance jamás. Es la cara opuesta de drag-drop.preview-z. Censo: siete wrappers escriben un --{c}-* público inline y DOS están en el contrato — dialog.overlay-opacity y drawer.overlay-opacity. Medido en dialog con el diálogo abierto y las animaciones congeladas: desde :root el fondo del velo NO se mueve (color(srgb 0.1098 0.098 0.0902 / 0.2518) idéntico), desde el inline SÍ. Su token MIENTE y el guard lo bendice. Los otros cinco no están en ningún contrato: son sólo canal. El arreglo propuesto va en §13: escribir en todos los nodos MENOS en los cuyo `style` ya declara ese mismo token, y avisar cuando ocurra — no basta con escribir sólo en :root, porque eso es justo lo que hace medibles los paneles portalados. Con re-verificación del ledger entero, así que no entra aquí. El veredicto de dialog (commiteado hoy) queda corregido con la medición. DOS BLOQUES DEL LEDGER SE DESCARTABAN EN SILENCIO `metrics` y `color-picker` aparecían DOS VECES en el mismo literal de objeto, así que el primero de cada par no existía para el guard — la trampa que el handoff documenta para base.ts, un piso más abajo. Comprobado antes de tocar nada: en los dos casos el bloque que gana es SUPERCONJUNTO del muerto, así que no se perdía ninguna adjudicación; era deuda con riesgo futuro, no un rojo. Retirados los dos bloques muertos: 69 → 67 entradas, cero duplicados, y `metrics` (54/72) y `color-picker` (46/71) siguen en verde. De paso, el fichero baja de 63 a 61 hunks de prettier preexistentes. Lo destapó la sesión de `progress` al leer el fichero para añadir su bloque. Rama con varias sesiones en paralelo: índice construido desde HEAD + sólo mis hunks, verificado con git diff --cached. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
animaciones congeladas: desde `:root` el fondo del velo NO se mueve
(`color(srgb 0.1098 0.098 0.0902 / 0.2518)` idéntico), desde el inline SÍ. **Su
token miente**, y el guard lo da por bueno.
*(Corrección 2026-08-23: la primera redacción decía «dos» y ponía los de
`avatar` entre los sin-contrato — su bloque de `base.ts` es una IIFE que el
censo no sabía leer hasta `f68bac4a6`, así que sus 84 claves eran invisibles.
Re-medidos aquí los dos: desde `:root` ni el fondo del badge ni el
`box-shadow` del anillo se mueven, y no puede existir instancia alcanzable —
la puerta `data-*-custom` que activa la regla y la escritura inline del token
viajan JUNTAS en el envoltorio. Son canal de valor filtrado al contrato; su
hermana `badge-color-custom-contrast` sí vive desde `:root`.)*
Los otros cuatro (`emoji-picker.columns`, `text-focus.border-color` /
`-glow-color`, `text-gradient.duration`) no están en ningún contrato, así que
sólo son canal.
fix(theming): el guard da por VIVO lo que un tema no alcanza — y dos bloques del ledger se descartaban en silencio Dos defectos del instrumental, los dos medidos, ninguno arreglado en el guard todavía (el arreglo toca a todos los componentes y pide su re-verificación). EL GUARD TIENE FALSOS POSITIVOS: ESCRIBE EL CENTINELA EN EL style INLINE `staticPass` escribe el token en documentElement Y en todos los nodos del componente — y ahí es donde vive el canal inline de los wrappers. Si un wrapper escribe un token PÚBLICO en el `style` del nodo, el guard lo pisa, ve el cambio y lo da por VIVO, aunque un tema (que sólo puede escribir en :root o en una hoja) no lo alcance jamás. Es la cara opuesta de drag-drop.preview-z. Censo: siete wrappers escriben un --{c}-* público inline y DOS están en el contrato — dialog.overlay-opacity y drawer.overlay-opacity. Medido en dialog con el diálogo abierto y las animaciones congeladas: desde :root el fondo del velo NO se mueve (color(srgb 0.1098 0.098 0.0902 / 0.2518) idéntico), desde el inline SÍ. Su token MIENTE y el guard lo bendice. Los otros cinco no están en ningún contrato: son sólo canal. El arreglo propuesto va en §13: escribir en todos los nodos MENOS en los cuyo `style` ya declara ese mismo token, y avisar cuando ocurra — no basta con escribir sólo en :root, porque eso es justo lo que hace medibles los paneles portalados. Con re-verificación del ledger entero, así que no entra aquí. El veredicto de dialog (commiteado hoy) queda corregido con la medición. DOS BLOQUES DEL LEDGER SE DESCARTABAN EN SILENCIO `metrics` y `color-picker` aparecían DOS VECES en el mismo literal de objeto, así que el primero de cada par no existía para el guard — la trampa que el handoff documenta para base.ts, un piso más abajo. Comprobado antes de tocar nada: en los dos casos el bloque que gana es SUPERCONJUNTO del muerto, así que no se perdía ninguna adjudicación; era deuda con riesgo futuro, no un rojo. Retirados los dos bloques muertos: 69 → 67 entradas, cero duplicados, y `metrics` (54/72) y `color-picker` (46/71) siguen en verde. De paso, el fichero baja de 63 a 61 hunks de prettier preexistentes. Lo destapó la sesión de `progress` al leer el fichero para añadir su bloque. Rama con varias sesiones en paralelo: índice construido desde HEAD + sólo mis hunks, verificado con git diff --cached. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
El arreglo del guard: escribir el centinela **sólo en `:root` y en el host**,
nunca en el `style` de cada nodo — pero eso rompería la medida de los paneles
PORTALADOS, que es justo por lo que se escribe en todos. La forma correcta es
escribir en todos MENOS en los nodos cuyo `style` inline ya declara ese mismo
token, y avisar cuando eso ocurra. Con re-verificación del ledger entero.
fix(theming): float-panel tenía DOCE rojos sin medir — el guard R-5.4 vuelve a verde El barrido del centinela sobre TODO el ledger (59 componentes) destapó que `float-panel` fallaba: 17/40 con doce tokens públicos sin adjudicar. No es regresión —la bisección de las NUEVE versiones del guard desde `cd6a9f4e3` da siempre 17/40 con los mismos doce— sino un rojo que su propio commit del 22 dejó escrito («lo rojo que queda son 12 tokens anteriores») y que nadie midió. Y la frase del handoff «desde el 23 no queda ningún rojo del bloque viejo» era FALSA: se adjudicaron drawer, metrics y color-picker; éste no. Corregida, con la lección: un componente cuyo commit dice «lo que queda» NO está cerrado, y la única forma de saberlo es correr el guard sobre el ledger ENTERO. LOS DOCE, MEDIDOS UNO A UNO SEIS son el plano de profundidad, y la predicción del 22 era exacta: el contenido lleva data-depth=overlay y [data-depth=overlay] declara background, border, box-shadow, font-family y line-height a la MISMA especificidad (0,1,0) y más tarde en la cascada. Verificado moviendo los tokens del propio plano sobre el nodo (superficie, borde, fuente y leading lo repintan) y enumerando las reglas que casan. border-width cae con border (el plano escribe el shorthand). Misma adjudicación que tooltip, misma decisión abierta (§12.9). ⚠ Y una corrección a mi propio diagnóstico: primero atribuí la sombra a [data-topmost] porque el panel de la demo arranca frontal. Falso — quitando el atributo sigue muerta: es el plano también. Lo que sí depende de topmost es su compañero shadow-topmost, que SÍ alcanza (→ rgb(4,5,6)) porque su regla es (0,2,0) y le gana al plano: la prueba de que la causa es la cascada y no un token roto. TRES son estados o partes que la demo no monta: active-shadow (forzado data-dragging → alcanza), resize-size (lo consumen las asas de ESQUINA se/ne/sw/nw y la demo monta sólo las de borde, e y s) y button-size (lo consumen [data-float-panel-action] y [data-float-panel-stage-trigger], ninguno montado — el único control es el cierre, un <Button> compuesto que se dimensiona solo: el mismo grupo que las tres action-* ya adjudicadas). UNO es el PUENTE DE PALETA: el contenido lleva data-color, así que --_float-panel-accent viene del forward THM-2 (--_float-panel-palette-border → rgb(7,8,9)); sin data-color la clave alcanza (→ rgb(1,2,3)). DOS son la ley del congelado: transition-duration / -ease no pueden moverse bajo un guard que congela `transition`. Pasada aparte: 0.12s → 11.5s y cubic-bezier(0.4,0,0.2,1) → steps(7). DE PASO, DOS ARREGLOS DE LA MISMA REVISIÓN - `background.parallax-travel` estaba MAL CLASIFICADO como estructural («el escenario no tiene scroll timeline»): lo tiene (animation-timeline: view()). Forzando data-parallax + speed 1, el translate sigue al token (14,6 → 60,5 px al mismo scrollY). La causa real es que el guard NO fotografía translate ni transform — registrado en §13 junto al hueco de mask-image, para entrar en PROPS en un pase con re-verificación del ledger entero. - `cropper`: el hueco de composición que el README daba por «anotado» y no lo estaba, ahora es fila de §Gaps con su disposición. Y las tablas de tokens de `cropper` y `waveform` pasan a la sección «Talla y tema», el molde del resto. La nueva constante PLANE_SUPERSEDED se iza sobre el objeto (un `const` usado antes de su declaración reventaba el guard, y el fallo se veía sólo mirando el exit REAL: tras la tubería, `$?` es el del grep). Verificación: R-5.4 float-panel 17/40 con 23 adjudicaciones, cero STALE, exit 0 · docs:check 0 · el ledger vuelve a sus 63 hunks de prettier preexistentes (mis entradas se reformatearon a mano, sin tocar las ajenas). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **El guard no fotografía `translate` ni `transform`** (medido 2026-08-23 en
`background`, y ya latente en `drawer.handle-active-scale`). El paralaje entra
por `translate` sobre la capa; con `data-parallax` forzado y speed 1 el token
SIGUE al scroll (14,6 → 60,5 px al mismo `scrollY`), pero el guard no puede
verlo. Tres tokens vivos leen muertos por esta causa y una adjudicación estuvo
MAL CLASIFICADA un día («estructural, el escenario no tiene scroll timeline» —
falso: lo tiene, `animation-timeline: view()`). Va con el hueco de
`mask-image`: los tres a `PROPS` en el mismo pase, y **re-verificando los ~59
con ledger** (el barrido de `REVIEW-theming-2026-08-23.md` §5 es la base).
uix(background): temable — 57 % → 95 %, y montar MÁS capas hacía medir MENOS Seis claves nuevas (31 → 37), tres literales firmados, un privado adjudicado y 19 tokens preexistentes medidos uno a uno: no había pasado por R-5.4. LAS SEIS CLAVES SON LA MISMA COSTURA, DOS VECES El patrón mesh leía var(--gradient-aurora) y los cinco pasos de escarcha leían var(--blur-*), los dos A PELO: el VALOR es del sistema, el KNOB es del componente. Con scrim-blur-* hay además una razón dura — `blur` es familia MÉTRICA del eje `scaling` (theming §23), así que un literal ahí sería ciego al zoom global. TRES LITERALES FIRMADOS, Y EL GRANO NO SE ACUÑA El 100% de img/video es identidad (una capa de medio ES la caja de su capa). El grano del ruido es un feTurbulence en un data-URI: la textura ES la técnica —el único patrón que el UA no elimina bajo forced-colors— y re-escaparlo para meterlo en el contrato cambiaría el valor serializado sin cambiar la imagen. Su mando sigue siendo pattern-noise-opacity. EL 5 % QUE QUEDA ES CANAL DE VALOR --_background-gradient-image lo escribe background-gradient.svelte EN LÍNEA desde la prop `colors`. Un público encima no lo alcanzaría —el inline gana— y mentiría: el caso de --gp-current-gradient y del preview-z que F2-B retiró. UNA SOSPECHA MÍA, REFUTADA MIDIENDO La línea del mesh hace lo que un apunte da por roto: un gradiente MESH por el longhand background-image (su color base final no es capa de imagen válida → computa `none`). Medido aquí: --gradient-aurora PINTA, porque su serialización empieza por un radial-gradient. No hay defecto. La nota vale para un mesh serializado con color base final, no para este token. EL GUARD: DE 3/37 A 18/37, Y UNA LECCIÓN AL REVÉS Las partes de este componente son CAPAS OPT-IN independientes (velo, foco, pausa…), cada una tras su control, y sólo un patrón se renderiza a la vez: sobre el escenario por defecto casi todo el contrato no tiene nodo que pintar. Nace `prepareWith` — enciende TODOS los controles que se le den, al contrario que `openWith`, que para en el primero — y con el barrido de data-pattern sube a 18. Y la lección: MONTAR MÁS PUEDE MEDIR MENOS. Encender además el foco puntual, la velocidad de paralaje y la profundidad bajó la corrida de 17 a 9: los tres repintan el background-image y el translate de la MISMA capa donde se miden los patrones. prepareWith se dejó en lo que no tapa. LAS 19 ADJUDICACIONES, FORZANDO SOBRE LA CAPA REAL Nunca sobre un nodo fabricado. Las dos del mask (fade-*, que el guard no puede ver porque no fotografía mask-image — hueco de instrumento, §13), las dos del contexto de tinta (on=dark/light), los ocho pasos de las dos escalas (blur 4→33, 8→33, 12→33, 24→33 px; strength alpha 0.08 · 0.13 · 0.4 · 0.7 → 0.9), el gradient-drift-duration (24s → 42s, en una capa gradient que EXCLUYE a la de patrón), los tres del foco puntual (40% → 42px…, y spotlight-fg sólo cuando la capa NO lleva data-color: con él manda --palette-solid, por diseño), el parallax-travel (sólo entra por las keyframes ligadas al scroll) y los dos de la pausa (la demo no monta ese control: cero nodos). AVISO: LOS CHIPS DE ESTA DEMO SE APLICAN CON RETRASO Una comparación por chips capturó cada estado con el patrón ANTERIOR —el estado «mesh» guardaba el glow— y dio 0 diffs por estar desfasada IGUAL en las dos corridas: un gate verde sobre dos medidas equivocadas. La prueba buena fue la de EQUIVALENCIA, determinista: cada token nuevo resuelve al mismo valor que el primitivo que sustituyó (pattern-mesh-image = --gradient-aurora; scrim-blur-{k} = --blur-{k} = 4/8/12/16/24 px) y lo pintado al forzar el atributo es idéntico (el mesh pinta el aurora, el ruido pinta su data-URI intacto). VERIFICACIÓN Sonda estándar antes/después 0 diffs (384 valores · 7 estados) · equivalencia token↔primitivo exacta en las seis · R-5.4 18/37 con las 19 restantes adjudicadas y medidas, cero STALE · component:audit PASS · censo 95 % · eidos-lint 3 morfo-backed / 63 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido). Tras el cambio del guard, command, table, tooltip, navigation-menu y waveform re-verificados sin regresión ni STALE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **El guard no fotografía `mask-image`** (medido 2026-08-23 en `background`).
Sus dos tokens de desvanecido (`fade-size`, `fade-at`) alimentan un
`mask-image` radial y leen muertos aunque están vivos — medidos a mano, el
mask cambia (`at 50% 35%` → `at 11% 50%`). Añadir `maskImage` a la lista de
propiedades del guard es una línea, pero toca el instrumento de TODOS: entra
con su re-verificación de los componentes con ledger.
- **Montar MÁS capas puede medir MENOS** (medido 2026-08-23 en `background`).
Al enseñar al guard a encender las capas opt-in del componente, añadir el foco
puntual, la velocidad de paralaje y la profundidad hizo BAJAR la corrida de 17
a 9 tokens: los tres repintan el `background-image` y el `translate` de la
misma capa donde se miden los patrones. La regla operativa: `prepareWith` sólo
debe encender lo que NO tapa lo ya medido, y eso hay que comprobarlo corriendo
el guard antes y después de cada control que se añada.
- **Los chips de la demo de `background` se aplican con RETRASO.** Una captura
por chips guardó cada estado con el patrón ANTERIOR (el estado «mesh» tenía el
glow) y dio 0 diffs por estar desfasada IGUAL en las dos corridas — un gate
verde sobre dos medidas equivocadas. Donde la demo no coopere, la prueba buena
es la de EQUIVALENCIA (el token nuevo resuelve al mismo valor que el primitivo
que sustituyó) más forzar el atributo en el DOM, las dos deterministas.
uix(navigation-menu): temable — 44 % → 100 %, y el guard cerraba el panel que medía Bloque B1 de D-TH.4. Contrato 26 → 44 claves: 19 nuevas y UNA retirada. Diff de computed 0 sobre 2.752 valores en 8 estados; R-5.4 43/44 con una adjudicación escrita. Global 65 % (al 100 % 25 → 26). LOS NOMBRES LOS DIO EL CATÁLOGO, NO YO Ninguno se inventó. Los gemelos cerrados el mismo día (dropdown-menu 96 %, context-menu 87 %) dan content-{min-width,padding,radius,fg} y chevron-{size,stroke}; accordion da trigger-font-family y disabled-trigger-fg. Las cuatro recetas que declaran un chevron lo dimensionan con un LITERAL (0.42em · 0.5em · 0.625rem): un glifo no es un paso de la escala de espacio. Al revés con el subrayado del indicador: su 2px ES --border-width-medium, y dropdown-menu.separator-thickness dice que un grosor decorativo se nombra por esa familia. LA §4 GENERADA FALLA DE CINCO MANERAS, UNA NUEVA Llamaba trigger-width / trigger-height al CHEVRON — nombrando por el primer selector de la regla ([…-trigger]::after) y colisionando con trigger-height, que YA EXISTE y es la altura del control. Fundía en content-radius dos nodos con dos valores. Cruzaba los nombres: content-padding-* para lo que es de la FILA, y el padding real del panel a «sin nombre mecánico». Llamaba content-width a un min-inline-size. Y la nueva: abría exigiendo «consume la capa compartida menu-indicator» — HOMONIMIA, esa capa es el check de los CheckboxItem de dropdown/context-menu y no toca el subrayado de esta barra (LAYER_VOCABULARY sólo registra calendar-surface). LAS FILAS DEL PANEL NO SON item- NI link- En los gemelos, `item` ES la fila. Aquí `item` es el <li> de la barra (parte del morfo) y el Link de barra viste el cromo del trigger: --…-item-radius o --…-link-radius prometerían a un tema un nodo que no alcanzan. Se acuñan content-link-*, que nombra el nodo por donde vive. Su tinta sigue leyendo trigger-fg, como venía. EL PRIVADO ERA UNA ESCALA, NO UN CONMUTADOR --_navigation-menu-list-gap tenía tres fuentes (0 / --space-1 / --space-2) por data-size: sube al TSC como list-gap-{sm,md,lg} + el resuelto, el molde que el bundle del trigger ya tenía al lado. Los dos bloques [data-size] del CSS se retiran — los emite el TSC, y dejarlos vivos da un diff de 0 por la ruta vieja. indicator-x ERA DECLARACIÓN MUERTA Soma escribe las cuatro coordenadas del rect INLINE sobre el indicador (-x, -y, -w, -h) y la receta lee sólo -w y -h; su propio comentario registra que sumar translateX(var(--…-x)) DUPLICABA el desplazamiento. No la leía nadie. Retirada, 0 diffs — la prueba de que estaba muerta (precedente: el re-point de --command-radius, el trío item-gap* de carousel). -w se queda: vive, y es canal de valor, no superficie de tema. EL PANEL NO ACUÑA TIPOGRAFÍA, A PROPÓSITO Lleva [data-depth='overlay'], y ese plano declara font-family / line-height a la misma especificidad (0,1,0) y más tarde en la cascada: un token ahí leería muerto. Es §12.9, pendiente de firma — la misma que hizo retirar claves en menubar, dropdown-menu, context-menu y tooltip. Sí se acuña la familia de la BARRA (trigger-font-family), que no es overlay y alcanza. TRES COSAS QUE APRENDIÓ EL INSTRUMENTO (registradas en next-features §13) 1. Sonda y centinela eran CIEGOS A LA MITAD DEL COMPONENTE. Las filas del mega-menú son los <a> DESNUDOS del consumidor, estilados por descendencia: el filtro data-{c}-* veía 10 nodos y NINGUNO era el panel ni una fila — 9 de los 19 knobs fuera del diff. Con EXTRA_NODES / extraNodes la sonda pasa a 16 nodos abiertos. Y el paso `open` de la sonda no honraba EXTRA_NODES. 2. EL GUARD CERRABA EL PANEL QUE MEDÍA. Este mega-menú abre con pointerenter y pointerleave programa el cierre; el guard abría con clic y luego aparcaba el puntero en (0,0) — justo el gesto que lo cierra. Dos corridas del mismo código discrepaban. Y daba las DOS caras del error: falso negativo en los tokens de la fila, falso positivo en content-link-padding-inline, que leía «vivo» por ensanchar el panel que sí se medía. Con openBy: 'hover', dos corridas idénticas. 3. `color` está EN TRANSICIÓN en esta parte: forzar data-disabled y leer en el acto devuelve el valor VIEJO — tres diagnósticos falsos («ni el inline lo mueve») hasta congelar. La trampa del cuentagotas del color-picker, en otro componente. Congelado: reposo oklch(0.2435 0 0) → disabled oklch(0.61 0 0). Y una de contabilidad, del instrumento y no del componente: el censo decide el eje `size` grepeando data-size en el CSS, y el protocolo §7.4 ORDENA retirar esos bloques. Todo componente que lo aplique bien aparece «sin eje». Ya le pasa a sidebar y nav-tree. En §13. VERIFICACIÓN Sonda antes/después 0 diffs (2.752 valores · 8 estados · 16 nodos con el panel abierto) · R-5.4 43/44 en dos corridas idénticas, adjudicación única (disabled-trigger-fg: la demo no monta el estado; forzado alcanza en trigger Y link, rgb(1,2,3)) · component:audit PASS · censo --only 100 % · eidos-lint 30 morfo-backed / 3 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido) · check sin error nuevo en los ficheros tocados · Chrome con el panel abierto: panel 8/10px/224px, fila 8/12/6px, trigger 36px/6px/gap 4px, indicador 2px. Tras tocar el guard, command, table y tooltip re-verificados sin regresión ni STALE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Un panel de HOVER se cierra por el propio guard.** `navigation-menu` abre
con `pointerenter` y `pointerleave` programa el cierre; el guard abre con
clic y luego aparca el puntero en (0,0), que es exactamente el gesto que lo
cierra. Producía no-determinismo (dos corridas del mismo código discrepando
en un token) y **las dos caras del error a la vez**: falso negativo en los
tokens de la fila y falso positivo en `content-link-padding-inline`, que leía
«vivo» por ensanchar el panel que sí se medía. Resuelto con `openBy: 'hover'`.
La regla general: **antes de creer un «no effect», comprobar que la superficie
seguía abierta al medirlo**.
uix(select): temable — 83 % → 92 %, y un token que ALCANZA y aun así no pinta Dos claves de COSTURA (PLAN §2-A: el valor es del sistema, el knob es del componente) y tres literales de identidad firmados. Ni un píxel movido. Qué entra en el contrato (101 → 103 claves): - invalid-border — las TRES declaraciones del estado inválido (el borde en reposo, el borde con foco/abierto y el anillo box-shadow que lo dobla) leían --color-risk-border A PELO. Es UNA clave, no dos: la propuesta §4 pedía invalid-trigger-border + open-invalid-trigger-border, y las filas agrupadas por VALOR son una sola. Bare como sus vecinas disabled-* (las ranuras de estado de esta receta son las del trigger) y como las siete invalid-border del catálogo. - group-gap — el hueco entre filas de un grupo iba a var(--space-1) directo. Nombre CONTADO en base.ts antes de acuñarlo: toolbar.group-gap. Tres literales firmados con su razón (recipe-contract §3), fuera del ratio: los dos inline-size: 100% (identidad — el select ES la anchura de su contenedor, el trigger ES la caja del select) y el opacity: 1 del check revelado, que deshace el 0 de la regla base. Censo: 83 % → 92 % (72 knobs, 54 públicos, 3 globales, 3 literales → 69 knobs, 57 públicos, 0 globales, 0 literales, 3 excepciones). Los 5 que quedan fuera son los forwards de paleta THM-2 leídos a pelo: no se acuñan. CERRADO en su 92 %. Global 68 %, no baja. Diff de computed: sonda antes/después, 2.176 valores en 8 estados = 0 diffs. Capturas 2× antes/después (cerrado y abierto): IDÉNTICAS byte a byte. Centinela R-5.4: 69/103, 34 adjudicadas en el ledger, todas MEDIDAS sobre el nodo real con las transiciones congeladas. Verde (exit 0). Lo que enseñó, todo del PORTAL: 1. El guard tiene que mantener el panel ABIERTO para medir los ~30 tokens de dentro, y el estado abierto TAPA los de reposo: trigger-border y trigger-indicator-fg leen muertos abiertos y MUEVEN con el panel cerrado. El instrumento sólo puede estar en un estado a la vez. 2. Tres knobs viven en nodos SIN data-select-*: la z del panel está en el [data-floating-wrapper] que soma porta (el caso popover.content-z) y el ritmo del viewport lo pinta el ScrollArea compuesto. Con extraNodes, 65/101 → 69/103. Nodos: 4 en reposo, 42 con el panel abierto. 3. La cascada de paleta anula sus 24 tonos, aquí en su forma MULTI-PARTE (trigger + content, porque el panel se porta fuera). Medido sobre el trigger abierto: --select-primary-border no mueve nada, --palette-border sobre el mismo nodo repinta. 4. separator-size ALCANZA y aun así no pinta: flex-shrink 1 en la columna del ScrollArea y un panel que desborda (scrollHeight 670 vs clientHeight 310) aplastan su 1px a 0px; con flex-shrink 0 el token sigue (1px → 9px). Un token vivo sobre un nodo aplastado lee igual que uno muerto. Defecto de píxel → next-features §13, no se arregla en este eje. §12.9 respetado: el contenido lleva data-depth=overlay, así que no se acuñó font-family ni line-height para el panel. Guards: censo --only select 92 % · component-audit PASS · eidos-lint 0 invalid · vitest src/uix/eidos sin rojos nuevos (los dos rojos son ajenos: skin-media-player, el conocido, y un fantasma de radio-group de otra sesión) · rtl:check 0 · docs:check 0 · npm run check COMPLETED con 0 errores en ficheros de select · prettier limpio sobre el contenido normalizado a LF. README con su sección «Talla y tema» y pestaña Tokens en la demo (103 claves, verificada en navegador: 0 errores de consola). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **El estado que el guard NECESITA para medir puede TAPAR el de reposo**
(medido 2026-08-24 en `select`). Su panel es portalado, así que el guard lo
mantiene abierto para medir los ~30 tokens de dentro — y
`[data-select-trigger][data-state=open]` re-apunta el borde y la tinta del
chevron, con lo que los dos tokens de reposo del trigger leen muertos. Miden
bien con el panel CERRADO. No hay arreglo dentro de una corrida: el
instrumento sólo puede estar en un estado a la vez; se adjudica.
- **⚠ UN COMPONENTE BARRE DESCENDIENTES Y SE COME EL TOKEN DE SUS HUÉSPEDES**
(medido 2026-08-24 en `chat-composer` dentro de `file-upload`, verificado en
la supervisión). `file-upload` declara
`[data-file-upload] [data-disabled] { opacity: var(--file-upload-disabled-opacity) }`
— un selector de DESCENDIENTE, (0,2,0), el mismo peso que
`[data-chat-composer-send][data-disabled]`, y se emite más tarde. Dentro de
un FileUpload, que es donde la composición **documentada** pone al composer,
el token del composer muere.
Medido desde los dos asientos sobre el mismo nodo deshabilitado: suelto y
envuelto computa `0.4` en los dos casos —los defaults coinciden, así que el
VALOR no delata nada—, pero envuelto `--chat-composer-disabled-opacity`
desde `:root` no lo mueve (0.4) y `--file-upload-disabled-opacity` sí
(0.4 → 0.321). Es invisible hasta que un tema mueva uno de los dos, y
entonces el huésped no responde a su propia clave.
**`file-upload` es HOY el único componente del catálogo con un barrido de
descendientes así** (`grep` sobre las 162 recetas: una sola ocurrencia), o
sea que no es una clase, es una regla concreta que se puede acotar. El
arreglo —restringirla a las partes propias— mueve el píxel de cualquier
huésped deshabilitado que hoy hereda, así que es firma, no commit de
theming. Emparenta con «un componente COMPUESTO gana a la receta que lo
compone» (gradient-picker / form / emoji-picker): misma cascada, dirección
contraria.
- **⚠ LA SONDA Y EL GUARD NO SE PUEDEN LANZAR CON `--import tsx/esm`** (medido
2026-08-24 supervisando `calendar`). Cualquier script del eje que use
`page.evaluate` muere con `ReferenceError: __name is not defined` dentro del
navegador: el transform de esbuild que trae `tsx` envuelve las funciones con
su ayudante `keepNames`, y ese ayudante existe en Node, no en la página — el
cuerpo de la evaluación viaja serializado y llega sin él. Reproducido en
`__theming-probe.ts` y en `theming-sentinel.ts`.
Se lanzan con **`node` a secas** (Node 24 desnuda los tipos de forma nativa),
que es justo lo que hace `npm run theming:sentinel`. Sólo los scripts SIN
`page.evaluate` —el censo, `component-audit`, `eidos-lint`— toleran el
`--import tsx/esm`. Cuesta una sesión entera si se confunde, porque el error
no nombra ni a tsx ni a la sonda.
uix(select): temable — 83 % → 92 %, y un token que ALCANZA y aun así no pinta Dos claves de COSTURA (PLAN §2-A: el valor es del sistema, el knob es del componente) y tres literales de identidad firmados. Ni un píxel movido. Qué entra en el contrato (101 → 103 claves): - invalid-border — las TRES declaraciones del estado inválido (el borde en reposo, el borde con foco/abierto y el anillo box-shadow que lo dobla) leían --color-risk-border A PELO. Es UNA clave, no dos: la propuesta §4 pedía invalid-trigger-border + open-invalid-trigger-border, y las filas agrupadas por VALOR son una sola. Bare como sus vecinas disabled-* (las ranuras de estado de esta receta son las del trigger) y como las siete invalid-border del catálogo. - group-gap — el hueco entre filas de un grupo iba a var(--space-1) directo. Nombre CONTADO en base.ts antes de acuñarlo: toolbar.group-gap. Tres literales firmados con su razón (recipe-contract §3), fuera del ratio: los dos inline-size: 100% (identidad — el select ES la anchura de su contenedor, el trigger ES la caja del select) y el opacity: 1 del check revelado, que deshace el 0 de la regla base. Censo: 83 % → 92 % (72 knobs, 54 públicos, 3 globales, 3 literales → 69 knobs, 57 públicos, 0 globales, 0 literales, 3 excepciones). Los 5 que quedan fuera son los forwards de paleta THM-2 leídos a pelo: no se acuñan. CERRADO en su 92 %. Global 68 %, no baja. Diff de computed: sonda antes/después, 2.176 valores en 8 estados = 0 diffs. Capturas 2× antes/después (cerrado y abierto): IDÉNTICAS byte a byte. Centinela R-5.4: 69/103, 34 adjudicadas en el ledger, todas MEDIDAS sobre el nodo real con las transiciones congeladas. Verde (exit 0). Lo que enseñó, todo del PORTAL: 1. El guard tiene que mantener el panel ABIERTO para medir los ~30 tokens de dentro, y el estado abierto TAPA los de reposo: trigger-border y trigger-indicator-fg leen muertos abiertos y MUEVEN con el panel cerrado. El instrumento sólo puede estar en un estado a la vez. 2. Tres knobs viven en nodos SIN data-select-*: la z del panel está en el [data-floating-wrapper] que soma porta (el caso popover.content-z) y el ritmo del viewport lo pinta el ScrollArea compuesto. Con extraNodes, 65/101 → 69/103. Nodos: 4 en reposo, 42 con el panel abierto. 3. La cascada de paleta anula sus 24 tonos, aquí en su forma MULTI-PARTE (trigger + content, porque el panel se porta fuera). Medido sobre el trigger abierto: --select-primary-border no mueve nada, --palette-border sobre el mismo nodo repinta. 4. separator-size ALCANZA y aun así no pinta: flex-shrink 1 en la columna del ScrollArea y un panel que desborda (scrollHeight 670 vs clientHeight 310) aplastan su 1px a 0px; con flex-shrink 0 el token sigue (1px → 9px). Un token vivo sobre un nodo aplastado lee igual que uno muerto. Defecto de píxel → next-features §13, no se arregla en este eje. §12.9 respetado: el contenido lleva data-depth=overlay, así que no se acuñó font-family ni line-height para el panel. Guards: censo --only select 92 % · component-audit PASS · eidos-lint 0 invalid · vitest src/uix/eidos sin rojos nuevos (los dos rojos son ajenos: skin-media-player, el conocido, y un fantasma de radio-group de otra sesión) · rtl:check 0 · docs:check 0 · npm run check COMPLETED con 0 errores en ficheros de select · prettier limpio sobre el contenido normalizado a LF. README con su sección «Talla y tema» y pestaña Tokens en la demo (103 claves, verificada en navegador: 0 errores de consola). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Un token VIVO sobre un nodo APLASTADO lee igual que uno muerto** (medido
2026-08-24 en `select.separator-size`). El separador vive en la columna flex
del `ScrollArea` con `flex-shrink` 1 y el panel desborda (scrollHeight 670 vs
clientHeight 310): su `block-size: 1px` computa **0px** y ningún valor del
token lo mueve. Con `flex-shrink: 0` en el mismo nodo el token sigue
(1px → 9px). Es además **un defecto de píxel**: el separador de `select` no
pinta nada en un panel que desborde. El arreglo (`flex: 0 0 auto` en
`[data-select-separator]`) mueve píxel → fuera del eje de theming, pendiente.
uix(calendar): temable — 84 % → 89 %, y una entrada de FAMILIA no se juzga en la ruta de UN componente CERO claves acuñadas: el censo no tenía nada que coser. Su columna `global` ya era 0 desde la capa calendar-surface (2026-08-21), así que lo único fuera de alcance eran cuatro literales de IDENTIDAD. Todo el trabajo fue MEDIR, y ahí estaba el hallazgo. Qué entra: - Cuatro literales firmados con su razón (recipe-contract §3), fuera del ratio: el inline-size: max-content del panel y de la tabla (su anchura ES day-size × 7 más el espaciado — precedente progress), el min-inline-size: max-content del panel de mes y el inline-size: 100% de la cabecera. La propuesta §4 pedía acuñar width / month-panel-width / grid-width para ellos: sería dar a un tema la anchura de una caja cuya anchura es su contenido. - Ninguna línea de CSS nueva. Las 74 claves del contrato quedan como estaban. Censo: 84 % → 89 % (71 knobs, 57 públicos, 4 literales → 67 knobs, 57 públicos, 0 literales, 4 excepciones). Los 7 que quedan fuera son los forwards de paleta THM-2: no se acuñan. CERRADO en su 89 %. Global 68 %, no baja. Centinela R-5.4: 45/74 → 68/74, 6 adjudicadas. Verde (exit 0). Lo que enseñó: 1. Una entrada de receta de FAMILIA no se puede juzgar en la ruta de UN componente. Esta entrada es el vocabulario de las cuatro superficies de rejilla de fechas (lo dice lib/calendar-surface.css): range-calendar, month-grid y year-grid consumen --calendar-* y no acuñan nada — de ahí su 0 % HONESTO. Sus partes llevan data-month-grid-* / data-range-calendar-*, invisibles al filtro data-calendar: 23 de sus 29 «tokens muertos» eran eso. Con urls a las tres rutas + extraNodes + barrido de variant, 45/74 → 68/74. Los tres consumidores NO se han tocado y siguen en su 0 % honesto. 2. Y la propia demo tapa tres claves más: su cabecera monta SELECTORES de mes y año donde iría el heading, así que heading-{fg,font-size,font-weight} no tienen nodo aquí; los tiene month-grid. 3. HALLAZGO — el tamaño de letra de los selectores de mes / año es NO DETERMINISTA. La sonda dio 36 diffs contra un cambio que era SÓLO comentarios; corrida dos veces sobre el MISMO código dio 24, en los mismos dos nodos y las mismas dos propiedades. Causa medida: [data-calendar-month-select][data-button] (calendar-select.css, la regla que existe precisamente para fijar la letra del Button a la del calendario) y [data-popover-trigger]:not([data-archetype='field-trigger']) (popover.css) empatan a (0,2,0), así que gana el módulo que Vite inyecte el ÚLTIMO: cuatro cargas dieron 14 / 16 / 14 / 14 px, con el orden de las hojas invertido en la que dio 16. Es la clase del empate por orden de carga de tree-grid (§12); arreglarlo mueve píxel → next-features §13. Regla que confirma: ante un diff inesperado, corre la sonda dos veces sobre el mismo código ANTES de sospechar de tu cambio. 4. calendar.select-min-width no tiene nodo en NINGUNA página del repo: su único consumidor es [data-range-calendar-month-select] / -year-select, y la demo de range-calendar renderiza un heading de texto mientras DateRangePicker importa los selectores del CALENDARIO. Hueco de demo → §13. Diff de computed: sonda antes/después, 22.656 valores en 7 estados. Los 36 diffs son ÍNTEGRAMENTE el no-determinismo del punto 3 (misma corrida contra sí misma: 24) y caen en dos nodos y dos propiedades; fuera de ellos, 0. Capturas 2× antes/después: IDÉNTICAS byte a byte. Guards: censo --only calendar 89 % · component-audit PASS · eidos-lint 0 invalid · vitest src/uix/eidos sin rojos nuevos (los dos son ajenos: skin-media-player, el conocido, y un fantasma de radio-group de otra sesión) · rtl:check 0 · docs:check 0 · npm run check COMPLETED con 0 errores en ficheros de calendar · prettier limpio sobre el contenido normalizado a LF. README «Talla y tema» actualizado y pestaña Tokens en la demo (74 claves, verificada en navegador: 0 errores de consola). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Una entrada de receta de FAMILIA no se puede juzgar en la ruta de UN
componente** (medido 2026-08-24 en `calendar`). Su entrada de
`recipes/base.ts` es la de las cuatro superficies de rejilla de fechas —
`range-calendar`, `month-grid` y `year-grid` consumen `--calendar-*` y no
acuñan nada—, pero sus partes llevan `data-month-grid-*` /
`data-range-calendar-*` y el filtro `data-calendar` no las ve: **23 de sus 29
«tokens muertos» eran eso**. Con `urls` a las tres rutas + `extraNodes` +
barrido de `variant`: 45/74 → 68/74. Vale para toda capa híbrida que conserve
entrada de receta.
- **El tamaño de letra de los selectores de mes / año del calendario es NO
DETERMINISTA** (medido 2026-08-24). `[data-calendar-month-select][data-button]`
—la regla de `calendar-select.css` que existe precisamente para fijar la letra
del Button a la del calendario— y
`[data-popover-trigger]:not([data-archetype='field-trigger'])` de `popover.css`
empatan a **(0,2,0)**, así que gana el módulo que Vite inyecte el ÚLTIMO:
cuatro cargas de la misma página dieron 14px / 16px / 14px / 14px, con el orden
de las hojas invertido en la que dio 16. Consecuencia de medición: la sonda dio
**24 diffs corriendo dos veces el MISMO código**, así que todo diff en esos dos
nodos es ruido hasta que se resuelva. Es la familia del empate por orden de
carga de §12 (tree-grid); arreglarlo mueve píxel.
- **`calendar.select-min-width` no tiene nodo en NINGUNA página del repo**
(medido 2026-08-24): su único consumidor es
`[data-range-calendar-month-select]` / `-year-select`, y la demo de
`range-calendar` renderiza un heading de texto en su lugar, mientras
`DateRangePicker` importa los selectores del CALENDARIO (que llevan
`data-calendar-month-select`). Hueco de DEMO: la parte existe en soma y en
eidos y ninguna superficie la monta.
- **La escala del punto de `radio-group` NUNCA ha pintado** (medido
uix(radio-group): temable — 77 % → 81 %, y su punto NUNCA ha pintado su escala Ocho claves acuñadas, diecisiete bloques `[data-size]` retirados y veinticuatro adjudicaciones medidas. El default no se mueve: diff de computed 0 sobre 25.263 valores en 47 estados (20 combinaciones de variante x estado x talla con su pasada de hover, más las 7 de la sonda estándar) y capturas 2x idénticas. Censo: 77 % → 81 % (knobs 75 → 59, público 56 → 46, global 7 → 1, privado 10 invariante, sistema 2). Contrato 61 → 69 claves. Global no baja (68 %). QUÉ ENTRA (8 claves) item-size · dot-size · font-size — los tres ejes RESUELTOS por talla en el TSC, con `scope: 'size:{k}'` sobre el proveedor, que es quien lleva `data-size`. Con ellos se van los DIECISIETE bloques `[data-size]` del CSS (§7.4-4: dejarlos vivos hace que el diff dé 0 por la ruta vieja) y el privado `--_radio-item-size`, que llevaba el nombre del componente ABREVIADO — theming §6 r5 lo prohíbe, es la clase de `--_mp-*` y `--gp-*` — y por eso el censo contaba el ancho y el alto del ítem como deuda. invalid-indicator-border · invalid-ring · invalid-ring-gap · invalid-item-label-fg — el tinte de validación leía `--color-threat-element` y `--color-threat-text` a pelo. El trío del anillo toma el vocabulario del gemelo `checkbox`, que ya lo tenía; el borde se llama `indicator-*` porque en ESTE componente «indicator» es el círculo exterior y `dot-*` el punto. segmented-segment-padding-block — el hermano que le faltaba a `-padding-inline`. UNA CLAVE ACUÑADA Y RETIRADA EN EL ACTO `row-min-height-touch: var(--touch-target)` para el suelo táctil de la fila. La suite `recipe-css-contract` la rechazó como referencia FANTASMA: nadie emite `--touch-target`, es una constante ergonómica escrita a mano en `archetypes.css` («a hard ergonomic constant — the fingertip, NOT a theme value»). Y recipe-contract §2 ya lista el touch-target entre los sistemas transversales que una receta CONSUME y nunca acuña, así que la lectura cruda es el contrato cumpliéndose. El censo la cuenta `global` sólo porque su lista `system` aún no conoce el token — el pendiente de firma «extender la clase system a las capas compartidas». POR QUÉ 81 % ES EL TECHO Los diez knobs que quedan fuera leen `--_radio-group-palette-*`: el puente THM-2 que la capa compartida alimenta por instancia desde `[data-color]`. Un público encima dejaría que un tema lo fijara y matara el `color=` de cada instancia. No es deuda; es el componente cerrado en su número. TRES DECLARACIONES QUE NO PINTAN (medidas, y las tres mueven píxel → firma) 1. La escala del punto NUNCA ha pintado. `<SvgDot>` escribe `style="width: 1em; height: 1em"` y un inline gana a cualquier regla: el punto computa 13,3281px en las CINCO tallas mientras el token dice 5/6/7/9/11px, el valor centinela no lo mueve, y quitando el estilo inline el MISMO token pinta 1234px. Clase «canal de valor inline» del trío `carousel.item-gap*`, pero con arreglo posible: la forma `!important` que recipe-contract §3 autoriza justo para saltarse un estilo inline. 2. `[data-radio-group-label][data-disabled]` no casa jamás. Con el grupo entero deshabilitado la etiqueta lleva `[id, data-radio-group-label, data-archetype]` y nada más: soma no estampa `data-disabled` en la parte `Label` (el morfo declara `data: []`). Forzado a mano el token alcanza, o sea que falta el ATRIBUTO, no la clave — decisión de MORFO. 3. El fondo de un radio marcado Y deshabilitado no llega: la regla de variante es (0,4,0) y `[data-radio-group-item][data-disabled][data-state= 'checked']` sólo (0,3,0). Medido con el centinela escrito: solid da el acento a plena intensidad, outline y segmented transparente, ghost la mezcla al 18 %. Quitar `data-variant` no ayuda: el gemelo `:not([data-variant])` también es (0,4,0). Y una cuarta, de escala del SISTEMA y por tanto fuera de este eje (D-TH.5): el `:hover` del círculo es (0,4,0) y el tinte inválido (0,3,0), así que pasar el ratón por encima TAPA el borde de error. Las cuatro, en next-features §13. EL INSTRUMENTO MEDÍA 27 DE 61 No veía el punto (es un `<svg data-svg='dot'>` sin atributo del componente), ni la variante `segmented` (doce claves), ni los estados disabled / invalid. El guard lleva ahora `extraNodes` para el punto, el barrido de `data-variant` y DOS URL más — `?perm.disabled=true` y `?perm.invalid=true`, que esta demo honra: once adjudicaciones sustituidas por medición real. CENTINELA 45/69, 24 adjudicadas: 6 del punto (estilo inline), 8 tonos por la cascada de paleta (mismo patrón que su gemelo checkbox, medido aquí: la clave no mueve nada, `--palette-solid` en el nodo repinta), 4 del par de transición congelado, 2 del anillo inválido (necesita `:focus-visible` con modalidad de teclado), `gap-horizontal` (el par de gap, sólo pinta la mitad en vigor), `segmented-pill-shadow` (la píldora sólo la renderiza el envoltorio bajo la variante real), `disabled-item-label-fg` y `checked-disabled-indicator-bg` (las dos declaraciones muertas de arriba). GUARDS censo --only radio-group 81 % · global 68 % (no baja) · component-audit PASS · eidos-lint 0 invalid (13 morfo-backed, 24 eidos-only) · vitest src/uix/eidos sin rojos nuevos (el único es `skin-media-player`, conocido) · rtl:check 0 · docs:check 0 · prettier OK sobre el contenido normalizado a LF · check sin errores en ficheros del componente. README con su sección «Talla y tema» y la demo con su pestaña Tokens (69 claves, verificada en navegador). Índice construido a mano desde HEAD sobre los seis ficheros compartidos (recipes/base.ts, generated/base.css, theming-sentinel.ts, theming-sentinel-exceptions.ts, PLAN-theming.md, next-features.md): otras cuatro sesiones los escriben a la vez. Fuera del commit, docs/audit/theming/ README.md — es un agregado global que mezcla trabajo en vuelo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
2026-08-23). `<SvgDot>` escribe `style="width: 1em; height: 1em"` en el glifo y
un inline gana a cualquier regla: el punto computa **13,3281px en las cinco
tallas** mientras `--radio-group-dot-size-{xs…xl}` dice 5/6/7/9/11px. Quitando
el estilo inline el MISMO token pinta. Es la clase «canal de valor inline» de
`carousel.item-gap*` y `drag-drop.preview-z`, pero con arreglo disponible: la
forma `!important` que recipe-contract §3 autoriza justo para saltarse un
estilo inline (o pasarle la talla a `<SvgDot size={…}>`). **Mueve píxel** ⇒
firma. Seis claves adjudicadas mientras tanto.
- **`radio-group` no tiñe la etiqueta de grupo deshabilitada**: la regla es
`[data-radio-group-label][data-disabled]` y soma **no estampa `data-disabled`
en la parte `Label`** (el morfo declara `data: []`). Medido 2026-08-23 con el
grupo entero deshabilitado: la etiqueta lleva `[id, data-radio-group-label,
data-archetype]` y nada más. El token alcanza forzando el atributo, así que
falta el ATRIBUTO, no la clave — decisión de MORFO (el label gemelo de
`checkbox` sí lo lleva).
- **Un radio marcado Y deshabilitado muestra el acento a plena intensidad**
(medido 2026-08-23): la regla de variante
`[data-radio-group][data-variant=X] [data-radio-group-item][data-state='checked']`
es (0,4,0) y `[data-radio-group-item][data-disabled][data-state='checked']`
sólo (0,3,0) — y el gemelo `:not([data-variant])` también es (0,4,0), así que
no hay instancia que escape. Subir la especificidad **mueve píxel** ⇒ firma.
- **El `:hover` de `radio-group` TAPA su tinte inválido**: hover (0,4,0) contra
invalid (0,3,0), la misma escalera que `textarea` (hover > foco > invalid) ya
registrada aquí. Es una decisión de escala del SISTEMA que mueve píxel en
varios controles de formulario con el mismo patrón; se anota, no se toca en
el eje (D-TH.5).
uix(toggle): temable — 81 % → 100 %, y el guard sólo sabía barrer UN eje Tres knobs fuera de alcance, ninguno por doctrina. - **El radio** (1 knob, 6 fuentes). La receta leía `--radius-default` y la escala global `--radius-*` A PELO, así que la única forma de redondear un toggle era mover el radio de todo el sistema — el agujero que tenía `navigation-menu`, y el único de los CINCO componentes con escala `rounded` que no tenía la suya (button, badge, image y color-swatch la declaran). Entran `radius` (el reposo, default `var(--radius-default)`: sigue siguiendo el default del arquetipo) y `radius-{sm,md,lg,xl,full}`. El desacople de `size` se conserva intacto: son pasos de `rounded`, no de talla. - **El tinte inválido**: `var(--color-threat-element)` a pelo → `invalid-border` con ese valor (la costura, PLAN §2-A). - **Los tres literales quedan FIRMADOS** con su anotación y salen del ratio: el `100%` de `data-block` es identidad y el par `1px` del cuerpo ES la técnica sr-only. Censo: 28 knobs · 22 públicos · 81 % → 25 knobs · 24 públicos · **100 %** (3 excepciones firmadas fuera del ratio). Contrato 119 → 126 claves. Global del catálogo sin bajar (68 %). Diff de computed: **VACÍO**. 416 valores en 7 estados (sonda estándar, 2 nodos) más 1.150 valores en 25 celdas de variante / rounded / on / invalid / disabled / block / icon-only / tono, con `transition` y `animation` congeladas — porque el escenario de la demo monta UN toggle solid, apagado y válido, y la sonda no barre esos ejes. Capturas 2× antes y después, miradas. Centinela R-5.4: **70/126, 56 adjudicadas**, verde. Empezó en 36/126 y la diferencia NO es adjudicación, es instrumento: las claves `{variante}-*` y `{variante}-on-*` son las mismas tres variantes en dos ESTADOS, y forzar el estado con `prepareWith` cambiaba una mitad por la otra (36 → 46, y murieron las de reposo) — «montar más puede medir menos», esta vez dentro de un componente. El guard aprende tres cosas, cada una nacida de una medición: - barrer el **producto cartesiano** de varios ejes (un solo eje da la misma secuencia de siempre, así que nada de lo que ya tenía `sweepAttr` cambia); - **quitar** un atributo con `null`, porque sellar `data-invalid` toda la corrida hacía que el borde inválido ganase a los otros seis bordes; - aplicar el barrido también en el **pase de hover**, que no barría nada — que es literalmente la razón escrita en el ledger de `radio-group.hover-segmented-segment-fg`. Con eso, 58 y luego 70. Los **12 componentes con `sweepAttr` re-verificados uno a uno**: cero regresiones, cero STALE (tres dieron exit=1 falso corriendo doce seguidos contra el mismo dev server; a solas, verdes). De las 56 adjudicadas, 48 son la cascada de paleta POR PATRÓN, medida aquí: con `data-color=primary`, `--toggle-primary-solid` no mueve nada y `--palette-solid` sobre el mismo nodo repinta; quita `data-color` y `--toggle-neutral-solid` pinta otra vez. Las ocho `neutral-*` caen con las demás por una razón que conviene escribir: el propio guard ESTAMPA `data-color=neutral` para probarlas, que es justo lo que enciende el bloque genérico. Las otras 8 están medidas una a una sobre el nodo real (los cinco pasos de `rounded`, que la demo no expone; `disabled-opacity`; y el par de transición, que ES lo que el guard congela). Guards: censo `--only toggle` 100 % · `component-audit --only toggle` PASS · `eidos-lint toggle` 0 invalid · `vitest run src/uix/eidos` sin rojos nuevos (el conocido `skin-media-player` en `lint.test.ts`) · `rtl:check` 0 · `docs:check` 0 · `check` sin errores en ningún fichero tocado · prettier sin desviaciones NUEVAS sobre el árbol indexado. Anotado y FUERA por D-TH.5: `[data-toggle]:hover` y `[data-toggle][data-invalid]` pesan lo mismo (0,2,0) y el hover se declara antes, así que un toggle inválido bajo el puntero pierde su tinte. Arreglarlo mueve píxel y es una decisión de escala del sistema — los cuatro controles de formulario comparten el patrón. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **El centinela no fotografía `transform`** (medido 2026-08-23 en
`rating-group`): `item-hover-offset` mueve un `translateY` en hover y es el
único knob de esa receta que lo hace, así que leía muerto con el token
perfectamente vivo (`matrix(1,0,0,1,0,-1)` → `matrix(1,0,0,1,0,-40)` al
escribirlo). Misma familia que el hueco de `mask-image` de `background`.
Añadirlo a `PROPS` obliga a re-verificar los componentes con ledger, así que
queda anotado.
uix(radio-group): temable — 77 % → 81 %, y su punto NUNCA ha pintado su escala Ocho claves acuñadas, diecisiete bloques `[data-size]` retirados y veinticuatro adjudicaciones medidas. El default no se mueve: diff de computed 0 sobre 25.263 valores en 47 estados (20 combinaciones de variante x estado x talla con su pasada de hover, más las 7 de la sonda estándar) y capturas 2x idénticas. Censo: 77 % → 81 % (knobs 75 → 59, público 56 → 46, global 7 → 1, privado 10 invariante, sistema 2). Contrato 61 → 69 claves. Global no baja (68 %). QUÉ ENTRA (8 claves) item-size · dot-size · font-size — los tres ejes RESUELTOS por talla en el TSC, con `scope: 'size:{k}'` sobre el proveedor, que es quien lleva `data-size`. Con ellos se van los DIECISIETE bloques `[data-size]` del CSS (§7.4-4: dejarlos vivos hace que el diff dé 0 por la ruta vieja) y el privado `--_radio-item-size`, que llevaba el nombre del componente ABREVIADO — theming §6 r5 lo prohíbe, es la clase de `--_mp-*` y `--gp-*` — y por eso el censo contaba el ancho y el alto del ítem como deuda. invalid-indicator-border · invalid-ring · invalid-ring-gap · invalid-item-label-fg — el tinte de validación leía `--color-threat-element` y `--color-threat-text` a pelo. El trío del anillo toma el vocabulario del gemelo `checkbox`, que ya lo tenía; el borde se llama `indicator-*` porque en ESTE componente «indicator» es el círculo exterior y `dot-*` el punto. segmented-segment-padding-block — el hermano que le faltaba a `-padding-inline`. UNA CLAVE ACUÑADA Y RETIRADA EN EL ACTO `row-min-height-touch: var(--touch-target)` para el suelo táctil de la fila. La suite `recipe-css-contract` la rechazó como referencia FANTASMA: nadie emite `--touch-target`, es una constante ergonómica escrita a mano en `archetypes.css` («a hard ergonomic constant — the fingertip, NOT a theme value»). Y recipe-contract §2 ya lista el touch-target entre los sistemas transversales que una receta CONSUME y nunca acuña, así que la lectura cruda es el contrato cumpliéndose. El censo la cuenta `global` sólo porque su lista `system` aún no conoce el token — el pendiente de firma «extender la clase system a las capas compartidas». POR QUÉ 81 % ES EL TECHO Los diez knobs que quedan fuera leen `--_radio-group-palette-*`: el puente THM-2 que la capa compartida alimenta por instancia desde `[data-color]`. Un público encima dejaría que un tema lo fijara y matara el `color=` de cada instancia. No es deuda; es el componente cerrado en su número. TRES DECLARACIONES QUE NO PINTAN (medidas, y las tres mueven píxel → firma) 1. La escala del punto NUNCA ha pintado. `<SvgDot>` escribe `style="width: 1em; height: 1em"` y un inline gana a cualquier regla: el punto computa 13,3281px en las CINCO tallas mientras el token dice 5/6/7/9/11px, el valor centinela no lo mueve, y quitando el estilo inline el MISMO token pinta 1234px. Clase «canal de valor inline» del trío `carousel.item-gap*`, pero con arreglo posible: la forma `!important` que recipe-contract §3 autoriza justo para saltarse un estilo inline. 2. `[data-radio-group-label][data-disabled]` no casa jamás. Con el grupo entero deshabilitado la etiqueta lleva `[id, data-radio-group-label, data-archetype]` y nada más: soma no estampa `data-disabled` en la parte `Label` (el morfo declara `data: []`). Forzado a mano el token alcanza, o sea que falta el ATRIBUTO, no la clave — decisión de MORFO. 3. El fondo de un radio marcado Y deshabilitado no llega: la regla de variante es (0,4,0) y `[data-radio-group-item][data-disabled][data-state= 'checked']` sólo (0,3,0). Medido con el centinela escrito: solid da el acento a plena intensidad, outline y segmented transparente, ghost la mezcla al 18 %. Quitar `data-variant` no ayuda: el gemelo `:not([data-variant])` también es (0,4,0). Y una cuarta, de escala del SISTEMA y por tanto fuera de este eje (D-TH.5): el `:hover` del círculo es (0,4,0) y el tinte inválido (0,3,0), así que pasar el ratón por encima TAPA el borde de error. Las cuatro, en next-features §13. EL INSTRUMENTO MEDÍA 27 DE 61 No veía el punto (es un `<svg data-svg='dot'>` sin atributo del componente), ni la variante `segmented` (doce claves), ni los estados disabled / invalid. El guard lleva ahora `extraNodes` para el punto, el barrido de `data-variant` y DOS URL más — `?perm.disabled=true` y `?perm.invalid=true`, que esta demo honra: once adjudicaciones sustituidas por medición real. CENTINELA 45/69, 24 adjudicadas: 6 del punto (estilo inline), 8 tonos por la cascada de paleta (mismo patrón que su gemelo checkbox, medido aquí: la clave no mueve nada, `--palette-solid` en el nodo repinta), 4 del par de transición congelado, 2 del anillo inválido (necesita `:focus-visible` con modalidad de teclado), `gap-horizontal` (el par de gap, sólo pinta la mitad en vigor), `segmented-pill-shadow` (la píldora sólo la renderiza el envoltorio bajo la variante real), `disabled-item-label-fg` y `checked-disabled-indicator-bg` (las dos declaraciones muertas de arriba). GUARDS censo --only radio-group 81 % · global 68 % (no baja) · component-audit PASS · eidos-lint 0 invalid (13 morfo-backed, 24 eidos-only) · vitest src/uix/eidos sin rojos nuevos (el único es `skin-media-player`, conocido) · rtl:check 0 · docs:check 0 · prettier OK sobre el contenido normalizado a LF · check sin errores en ficheros del componente. README con su sección «Talla y tema» y la demo con su pestaña Tokens (69 claves, verificada en navegador). Índice construido a mano desde HEAD sobre los seis ficheros compartidos (recipes/base.ts, generated/base.css, theming-sentinel.ts, theming-sentinel-exceptions.ts, PLAN-theming.md, next-features.md): otras cuatro sesiones los escriben a la vez. Fuera del commit, docs/audit/theming/ README.md — es un agregado global que mezcla trabajo en vuelo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
docs(next-features): el registro de incidencias del eje theme-reach Encargo del autor: anotar todas las incidencias de esta clase para resolverlas después. Van al registro canónico (`next-features.md`), no a un sitio nuevo, y con su MEDICIÓN — no como impresión. **§12 — El contrato de cascada del velo de estado.** Seis incidencias que resultaron ser la misma: nadie fijó nunca cómo compone el velo de `archetypes.css` con las reglas de una receta. Y la raíz explica por qué se manifiestan de formas tan distintas: el velo se pinta con DOS pesos según el arquetipo — `:where()` (0,0,0) para `trigger`, especificidad plena (0,5,0) para `item`/`option`. 131 declaraciones-atajo en 45 componentes cancelan el velo (66 BASE · 32 ACENTO · 23 HOVER · 10 OTRA; cero valores con gradiente, así que el paso a longhand no tiene riesgo técnico; 32 nodos velados que hoy no reaccionan al ratón pasarían a hacerlo) · la prop `hoverable` de table no suprime nada · en tree-grid el empate (0,5,0) lo decide el ORDEN DE CARGA y varía entre recargas · la banda de tree-grid mata el hover en las filas pares · un token de tinta no puede ganar al arquetipo (`selected-row-fg` se retiró por eso) · el thumb de scroll-area no tiene velo al que migrar. Esto explica de paso la adopción 21/135 que midió la auditoría del 2026-07-01: el velo estaba escrito y estructuralmente derrotado. **§13 — Huecos de instrumento y de demo.** Lo que hizo que una medición mintiera o no existiera, que importa porque el eje entero se apoya en ellas: falsos negativos del centinela sobre propiedades transicionadas, pseudo-elementos y componentes compuestos que no ve, la sonda que no pasa el ratón por un `<tr>`, y la lección general —**una sonda sobre una parte que la demo no monta compara CERO valores y pasa**—, con la lista de partes condicionales afectadas. El handoff gana la regla para las sesiones del eje: si una incidencia mueve píxel o toca morfo, se mide, se anota ahí y se sigue; no se arregla dentro del commit del componente. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Deps**: ninguna. Son mejoras del instrumental del eje, ejecutables cuando
estorben.

Powered by TurnKey Linux.