248 KiB
| title | type | audience | authority | status | created |
|---|---|---|---|---|---|
| Next features — initiative registry | registry | human + agent | 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) | living — items enter by explicit user decision during audits and sessions; remove an item when it ships (and link where it landed) | 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 intheming/reference.md §40+changelog.md §44. Outcome:text·11= secondary tier,text-strong= AA-guaranteed; the border vocabulary is Radix-subtle by design (onlysolid·9clears 3:1 by construction) with a decorative-vs-load-bearing split; the focus ring is a config axis (primitives.focusRing+color.focus, singleoutlineper §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; chroniclechangelog.md §45.
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
§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.
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 §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-heightfrom the size bundle but notletter-spacing; plus ~7 raw letter-spacing literals in component CSS. - Tier-A
data-depthstamping coverage: overlays that should stamp their plane (dialog / drawer / tooltip / menubar / combobox) perrfcs/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 acalendar-surfaceshared 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 —fieldrecipe declaressegment-heightwith size-scoped declarations (calc(var(--field-control-height-{k}) - var(--space-2)); the TSCsize:scope axis exists, unused so far), and the per-component copies die (each x-field today re-declares the wholeheight-{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-componentsegment-heighttokens and their consumers were removed (they had computedautosince birth — zero visual change). - Whatever F/G registers as "component-level adoption gap" in
docs/audit/theming-audit.mdwhen 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 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.Badgerole-restricted "by design". A later user decision reverses that:coloraccepts 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 inrecipe-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. Onlyqr-codestays out (itscoloris the QR module ink, not a scale).
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 D.12 as a
candidate editorial appendix for the book (Diseñando lo que ocurre,
HOMOGENEIZADO edition) — the book gives qualitative regions and this is the
materialization doctrine worth feeding back to the source.
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):
✅ SHIPPED 2026-07-19 — composeschat-list— the conversation-list pane (rooms, unread badges, typing-in-list).Listbox(single-select + keyboard) with an avatar/title/preview/meta row; unread bold +Badge, typing-in-list composesChatTyping. Virtualization for very long lists deferred (pairs with the VirtualList bidirectional item).✅ SHIPPED 2026-07-21 — composesemoji-picker— a real picker component (color-picker scale: categories, search, skin tones).Popover+Command(search + grid keyboard + combobox/listbox a11y, its documented "grid mode") + twoToggleGroups (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 inChatMessage.ReactionAddstays as the inline tapback bar (different UX).- Mention autocomplete (
@) — combobox-in-textarea forchat-composer; pairs with the futurewords/palabrasintegration. - Threads UI — the side-panel composition over the already-shipped
Feed.Threadsemantics. Jump-to-message highlight —✅ SHIPPED 2026-07-19 —signal-emphasize-targetonchat-message(the morfo event was deliberately left out of v1).emphasizedprop →data-emphasizedrow flash (visual-only, no sema event: reference apps flash silently; the app scrolls via a reply'sonJumpthen togglesemphasizedfor ~1.4s).Read-by list — "seen by N" avatar row (Teams/Messenger pattern) on✅ SHIPPED 2026-07-19 —chat-message.ChatMessage.ReadBypart (the app composesAvatarGroup; the sender'sonMessageSeenfeeds it).- VirtualList chat-mode refinements — bidirectional sparse ranges for jump-into-history far from the loaded window.
Typing avatars — faces next to the✅ SHIPPED 2026-07-19 —chat-typingdots (Messenger).ChatTyping.Avatarsslot (composeAvatarGroup).- 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).
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.
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,
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
§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.
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
§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:
- 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, 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. - 2D — DataMatrix · PDF417 · Aztec — explicitly NOT this component: each is a
whole different encoder (own Reed–Solomon, matrix layout). They belong beside
QrCodeas 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.
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;
plan and signed decisions in
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 aScrollProgressfallback,attach='fixed', the pointerdepthaxis. The infrastructure landed with the component ($adom'sScrollProgressandprefersReducedData); 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
strengthscale is not ordered by weight — measured:subtleveils MORE thanoverlay, andoverlayandmutedresolve 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>(ImageProviderinsoma/layers) and none for<video>:ScrollFrameslistens by hand and so doesBackground.Video. Two consumers is the threshold this codebase uses to extract a layer. - The
Ambientpack 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.
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
-
El atajo
background:cancela el velo — 131 declaraciones en 45 componentes. El atajo fijabackground-image: none; sobre un arquetipo velado por:where()eso lo mata para siempre, no sólo en hover. Medido conscripts/__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 abackground-colorno mueve el reposo), 32 ACENTO (selected/checked/open: §38 pidebackground-colorpara 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. -
La prop
hoverabledetableno suprime nada. El velo deitemes incondicional y a (0,5,0), así que una fila se ilumina al pasar el ratón aunquehoverableesté apagada — verificado con hover real, y ocurre desde antes de tocar la receta. O el arquetipo decide y la prop sobra, o la fila no es unitempara el sistema. Decisión de MORFO. Precisión medida 2026-08-21 (revisión adversarial,elementsFromPoint): entableel arquetipoitemestá declarado en la fila Y EN LA CELDA (morfo/components/table.ts:132y: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. -
En
tree-gridel empate lo decide el ORDEN DE CARGA. El velo deitemy 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 dez-index. -
En
tree-gridla banda mata el hover. La regla destripedpesa (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. -
Un token de receta no puede ganar al arquetipo. La regla de
itemfija tambiéncoloren 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:hoverde la misma lista; recontada 2026-08-21): untable.selected-row-fgno podía mover nada nunca. Se retiró 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. -
scroll-area.thumb-bg-hoverno tiene velo al que migrar. Su thumb llevaarchetype: 'thumb', quearchetypes.cssno 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. -
En
grid-listel hover pinta DOS VECES sobre el MISMO nodo (medido 2026-08-21 al tokenizarlo). A diferencia detable/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 fijabackground-image: nonepero pierde, así que se ven los dos. Por eso el commit degrid-listno acuñóhover-row-bg: sería un token que no puede ganar (mismo criterio quelistbox.highlighted). Y como entable, fila Y celda llevanarchetype: 'item'(morfo/components/grid-list.ts:118y: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í. -
En
tree-viewel 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 elbranch-controlY elbranch-contentcon 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:tablelo tiene en fila Y celda (punto 2),grid-listen 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-viewelbranch-controlno lleva archetype, así que ahí el plano de la receta es la única pintura y suhover-row-bgSÍ es un token legítimo — al revés que engrid-list. La misma regla CSS cubre los dos nodos. -
El plano de profundidad
overlayimpone TIPOGRAFÍA, no sólo elevación — medido con CDP el 2026-08-21 al tokenizarlink-preview.[data-depth='overlay'](generated/base.css:7088) declarafont-family: var(--style-label-font-family)yline-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). Enlink-previewel parfont-family/line-heightde la receta 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 detree-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.
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.
Lo que tags-input añadió (2026-08-24):
- ⚠ El morfo sella
aria-selected='true'como LITERAL en CADA etiqueta (morfo/components/tags-input.ts,v.literal('true')sin condición), y eso arrastra el cromo de SELECCIÓN del arquetipo a todas las etiquetas en reposo. Con el atributo puesto, la regla dearchetypes.css[data-archetype='item'][aria-selected='true']:not([data-state='checked']):not([data-state='on'])pesa (0,4,0) contra los (0,1,0) de[data-tags-input-item]y los (0,2,0) de[data-tags-input-item][data-state='active'], así que gana dos veces: fijacolor: var(--color-content-primary)—el token--tags-input-item-fgno ha pintado nunca, y el--_tags-input-palette-textdel estado activo tampoco— y pinta el velobackground-imageen todas. Medido 2026-08-24 sobre la etiqueta real con las transiciones congeladas: escribir el token no mueve nada (oklch(0.2435 0 0), la tinta del arquetipo); poneraria-selected=falsesobre el mismo nodo y la misma escritura alcanza (rgb(1, 2, 3)). Es la tercera ocurrencia de §12.5 (trastable.selected-row-fgy la tinta delistbox) y la segunda del par arquetipo-itemconaria-selected, trasgrid-list. Tiene además una cara de a11y: un lector de pantalla anuncia las siete etiquetas como seleccionadas. Arreglarlo es morfo Y mueve píxel (las etiquetas perderían el velo en reposo) ⇒ no se toca desde el eje de theming; los dos tokens quedan adjudicados en el ledger de R-5.4 con esa razón medida.SUPERADO EL 2026-08-25 (firma B-a2, PLAN §8): el morfo ata ya
aria-selectedav.stateRef('active'), así queitem-fgPINTA y su adjudicación se retiró.CUARTA OCURRENCIA de §12.5 —contando
grid-list, que el párrafo de arriba enumera aparte como par arquetipo-item— y con el mecanismo corregido (medido 2026-08-25 sobre la etiqueta real navegada por teclado, conCSS.getMatchedStylesForNode). De los dos tokens sigue enmascarada la tinta de TONO de la etiqueta activa, y el que gana no es un selector: es el BLOQUEarchetypes.css:245-257— UNA sola declaración con diez selectores, de los que DOS casan la etiqueta activa ([data-archetype='item'][data-highlighted]:not(…):not(…)y[data-archetype='item'][aria-selected='true']:not(…):not(…)), los dos a (0,4,0) —no (0,3,0)— contra los (0,2,0) de la receta: dos peldaños, no uno. «Esdata-highlighted, noaria-selected» es una distinción sin diferencia: retirar cualquiera de los dos no mueve la tinta porque el otro sostiene la MISMA regla. Coste de píxel de cederla: UN nodo, UN estado —oklch(0.2435 0 0)neutro →oklch(0.5168 0.1733 305.88), el tono— y el velo no se pierde al soltarcolor:--state-hoverescolor-mix(in srgb, currentColor 8%, transparent), así que se tiñe con el tono. Pertenece a la capa del SISTEMA (el bloque es acento cross-component, y el hover es del sistema, nunca una invención por componente), así que no es una firma detags-input: su decisión es la que ya está redactada en «Qué habría que decidir» de esta misma §12 (docs/next-features.md:516). La migración al grid del 2026-08-25 la dejó intacta — medido, 0 diffs sobre 2496 valores computados.
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.
-
HUECO DE POMO —
navigation-menu: el indicador no sabe hacer de píldora (2026-08-26, al reescribir la sección## CSS Variablesdel README de soma). Los dos ejemplos viejos enseñaban a leer las medidas privadas; el segundo era un full-trigger highlight (píldora / fondo detrás del trigger). Reescritos al camino de la casa —estilar la PARTE y sus pomos—, el primero se traduce sin pérdida (la receta YA pinta ese subrayado; el consumidor sólo mueveindicator-{thickness,bg,radius}), pero el segundo NO tiene traducción: la receta clavablock-size: var(--navigation-menu-indicator-thickness)y desplaza el subrayado por debajo del trigger contranslateY, así que ningún pomo del contrato hace que la decoración RELLENE la caja del trigger. Haría falta un eje de forma/variante en la parte. NO se acuñó —«compose existing; flag gaps»—: el README lo dice y para. Decisión pendiente: o el indicador gana ese eje (y entonces es diseño de componente, no de theming), o se documenta que elnavigation-menusubraya y quien quiera píldora usatabs, que es la parte que ya hace exactamente eso conlib/sliding-indicator.css. -
Los PARCHES-PIN que la firma §55 vuelve redundantes o semi-redundantes (2026-08-25, NO tocados — cada uno es una decisión aparte). Cuando el sobre de trigger de
popover.cssestaba a (0,2,0), tres componentes se pincharon un selector propio ENCIMA para recuperar su cromo. Con el sobre en el suelo, esos pines ya no defienden de nada — pero siguen pesando, y ahora ganan a reglas que quizá deberían ganarles:— RETIRADO 2026-08-25 (firma «el velo del sistema no muere por un atajo»), y §13 lo había adjudicado MAL. Decía que suchronos.css:417background: var(--_button-bg)le ganaba al velo de hover dearchetypes.cssy que retirarlo era un cambio de píxel. El HECHO es cierto —el+N moreno recibe el velo, su afordancia es elunderlinede su receta— pero la ATRIBUCIÓN era falsa: al velo lo matabutton.css:69([data-button], (0,1,0)) ybutton.css:88(:hover, (0,2,0)), con el pin puesto o quitado; medido, el único autor debackground-imageque casa esarchetypesy pierde en las dos pasadas. El pin era REDUNDANTE-TOTAL: cuatro de sus cinco declaraciones eran copias literales de[data-button]y la quinta (height: auto) es el gemelo lógico de sublock-size: fit-content. 0 diffs en 20 combinacionesvariant×size, reposo y hover, con el sucesor NOMBRADO y control negativo 20/20. No necesitó firma de producto: fue una limpieza. Fichadocs/audit/theming/chronos.mdanotada (censo 202→198, propuesta 50→47: los tresmore-link-*se quedaron sin consumidor, y proponían un público dechronoscon valor privado debutton— lo que su propio §4 prohíbe).calendar— sus selectores de mes/año NO llevan pin propio; el que empataba era[data-calendar-month-select][data-button](0,2,0), que hoy gana solo. Nada que revisar. Medido 2026-08-25 con la declaración desactivada en vivo: retirarla rompería el tipo en 3 de 4 tallas (xs 12→16, sm 14→16, lg 20→16 px), así que ni es un pin ni se toca. El ⚠ HALLAZGO «no determinista» dedocs/audit/theming/calendar.mdque apuntaba a este §13 queda CERRADO allí con nota fechada.chat-message.css:462—[data-chat-message-actions] [data-chat-message-reaction-add](0,2,0) era una trampa armada (empataba con el sobre y ninguna superficie del repo monta ese anidamiento). Hoy gana de forma determinista: medido montando el nodo, con el sobre viejo reinyectado PIERDE (borde 1px, fondo0.9821, 36px) y con el suelo GANA (borde 0, fondo transparente, píldora de 26px). Ya no es deuda; se anota porque documenta que la clase se cierra entera con una sola decisión.
-
Las DOS reglas de estado de
popover.cssque la firma §55 dejó ARRIBA (2026-08-25):[data-popover-trigger]:not(…)[data-state='open']y…:focus-visible, las dos a (0,3,0). No estaban en la firma, así que no se tocaron — pero el coste de dejarlas está medido: bajarlas también sube el centinela denatural-time-pickerde 48/62 a 50/62 (reviventrigger-bgyopen-trigger-border, que hoy pierden contra el repintado de popover cuando el panel está abierto). El paralelo de §12.9 dice que sí deberían bajar — ninguna de las dos es una petición explícita por elemento como elfrost, yarchetypes.css:19-21ya declara que el anillo de foco «must lose to any component rule». Decisión pendiente, con su número. -
El suelo de
popover.cssCOMPARTE peldaño conarchetypes.css— y lo único que queda del empate estransition(2026-08-25, medido). Al bajar a (0,0,0), las dos reglas del sobre empatan con:where([data-archetype='trigger'])y su gemela de hover: las CUATRO a (0,0,0), así que el orden de documento decide. Se disputaban DOS propiedades y la firma «el velo del sistema no muere por un atajo» sacó una del empate:background-image— RESUELTO. El suelo declarababackground:, un ATAJO que expande a los ocho longhands y entre ellosbackground-image: none— que es EXACTAMENTE dondearchetypes.csspinta el velo de estado. Y no era sólo la regla de hover: el:where()deja también la de REPOSO empatada con la de hover del arquetipo, y ésa sola bastaba para matarlo. Partido enbackground-color:(popover.css:60y:75), el velo compone encima gane quien gane el orden — medido idéntico en los DOS órdenes, donde antes dabanone. Coste 0 px sobre el barrido de 14 identidades del catálogo, 68 lecturas × 2 estados, con control negativo (diff 5 y 4). Es lo quefeedback_hover_is_the_systems_never_a_per_component_inventionya mandaba: el acento de estado va enbackground-colorpara que la capa COMPONGA encima.transition— RESIDUO VIVO, con su número. El suelo declarabackground, border-color; el arquetipo,opacity, background-color. Siguen empatados a (0,0,0) y sigue decidiéndolo el orden: hoy gana el del arquetipo, así que el cambio deborder-colordel hover de popover no se anima — salta. Defecto pequeño y sin efecto sobre el velo, pero es la misma clase que §12.9 y §53 se firmaron para prohibir («no rung ties another»): la doctrina no adjudica un ganador del empate, lo PROHÍBE.- Decisión de producto, aparte: que
popover.cssdeje de declarar hover propio y se apoye entera en la capa del sistema — lo doctrinalmente puro, y lo que §13 proponía. NO es de coste cero: hoy el hover de un trigger desnudo muevebackground-color0.9821 → 0.931 y además recibe el velo; sin la regla propia queda sólo el velo. Cambio visible en los triggers desnudos del catálogo — firma aparte, con censo. Cerraría de paso el residuotransition.
-
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 conborder-colory el token de reposo leía muerto. El guard (theming-sentinel.ts) ahora haceblur()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. -
El centinela no veERA FALSO, y ARREGLADO 2026-08-21 (al tokenizar::placeholder.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ó acommand.input-placeholder-fguna 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. -
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:rooty 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-overlapleía VIVO y desde:rootno 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 pasoxxl(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 demetrics, así que se hace con la re-verificación del ledger entero, no en el commit de un componente. -
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 atributos del componente medido. Sigue vivo; adjudicado por token en el ledger de R-5.4. -
La sonda no pasa el ratón por unARREGLADO 2026-08-21:<tr>.trentra en el filtro de hover de la sonda, y el guard R-5.4 añade un pase de hover propio para los tokenshover-*(así adjudicarontable.hover-row-bgygradient-picker.hover-preset-border). -
La sonda medía una demo aún CARGANDO. El auto load-more de
feedmantiene[data-busy]~3 s y su firmacommit-settleanimabox-shadowsobre 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). -
Una sonda sobre una demo que no monta la parte compara CERO valores y pasa. Ocurrió con el skin de audio:
/uix/components/audio-playerda 404 porque la demo es la demedia-playercon el chipmedia, y la sonda cargaba en modo vídeo. Resuelto conscripts/__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 decommand, y al overview dedate-range-picker. -
date-range-pickerno exponekind='month'/'year'en su demo (norma N-6), así que la mitad de su API no se ve ni se mide. -
Un componente COMPUESTO puede tener su cromo entero muerto sin que nada avise.
gradient-pickerre-declaraba altura, padding, tipografía, color, fondo, borde, radio, hover y foco de su trigger, y NADA de ello pintaba:popover.cssgana 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 retirarlo dio 0 diffs. EL GUARD EXISTE desde 2026-08-21: R-5.4 (npm run theming:sentinel -- <c> <url>,scripts/theming-sentinel.ts+ ledgertheming-sentinel-exceptions.ts, doctrina en recipe-contract §4). Nació con la segunda ocurrencia de la clase: elitem-gapdecarouselcontra un estilo INLINE de soma, invisible para todo análisis estático. Los ocho componentes de F2-A pasan con su adjudicación escrita.NOTA 2026-08-25 — la premisa «(0,2,0 contra 0,1,0)» de este punto CADUCÓ: la firma §55 emite el sobre de trigger de
popover.cssdesde:where(...), (0,0,0), así que hoy PIERDE contra la receta del huésped. El hallazgo sigue en pie como historia y la retirada sigue siendo correcta —la receta calla y el suelo pinta, que es su trabajo—, pero si aquellas declaraciones volvieran hoy ganarían. Se anota en vez de reescribirse: la medida de entonces fue correcta con la cascada de entonces. -
--gp-current-gradientes un nombre ABREVIADO en SOMA. Lo estampagradient-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-pickerusa.gradient-picker-trigger-swatchy.gradient-picker-trigger-labelen vez dedata-*;eidos-lintlos cuenta (class-hooks: 2) y el centinela no los ve, porque filtra nodos por atributo. -
El foco del trigger de
gradient-pickeres 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. -
El censo PENALIZA consumir una capa compartida correctamente.RESUELTO PARA LA FAMILIA CALENDAR 2026-08-21 y abierto para el resto: el censo tiene ahoraLAYER_VOCABULARY(mismo precedente que D-TH.2-b), así que el--calendar-*que leenrange-calendar/month-grid/year-gridcuenta comosystem.list-surfaceNO 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 comoprivateen 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. -
El censo PENALIZA consumir una capa compartida correctamente.
listboxadopta los cinco ejes de ritmo quelist-surfaceposee — que es exactamente lo que la doctrina de capas manda — y el censo los cuenta comoprivate, 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 comosystem): la capa ES la superficie de tema de ese eje. Afecta a los consumidores delist-surface(select · combobox · command · listbox · los menús) y deviewport-placement(affix · fab · menu-dial). Extenderlo es una decisión, no una corrección al paso. -
listboxpinta elhighlightedDOS veces: la receta pone un--color-surface-overlayplano y encima el arquetipoitemañ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. -
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.
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 depopover.cssque dejó muerto el cromo entero del trigger degradient-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.CERRADO 2026-08-25 por §55. No hizo falta subir el peso del pin: bajó el del rival. El sobre de
popover.cssse emite desde:where(...), (0,0,0), así que[data-calendar-month-select][data-button](0,2,0) gana de forma DETERMINISTA y la letra deja de depender de qué chunk inyecte Vite el último. Re-medido 2026-08-25 por talla: 12 / 14 / 16 / 20 px con la regla, 16 px en las cuatro sin ella — no es un pin y no se toca. - 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.
Lo que la revisión adversarial de F2-A añadió (2026-08-21):
PENDIENTE DE FIRMA — elFIRMADO Y EJECUTADO 2026-08-21:stripeddetablees inerte con RowDetail.: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-bgadjudica VIVO (42/45).- El gap entre diapositivas de
carouseles un canal de valor de soma. Soma lo estampa INLINE desde la propgap(necesita el número para elflex-basis), así que ninguna declaración de receta puede ganarle — el tríoitem-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-zllevan el literal'2'(table,tree-grid) mientras los otros catorce*-zdel catálogo leen la escalera--z-index-*. Alinearlos es decidir qué peldaño significa «cabecera pegajosa dentro de un scroller». - El spinner de
media-playerconserva720msa pelo mientras el spinner idéntico defeedganó dos tokens públicos de duración (sentinel-spinner-duration/-reduced-duration). El mismo trato o una anotaciónfunctional:, pero no las dos varas.
Lo que grid-list añadió (2026-08-21):
- ⚠ PENDIENTE DE FIRMA — la selección de fila de
grid-listnunca ha pintado. La receta seleccionaba[data-grid-list-row][data-selected], un atributo que ni el morfo declara ni soma estampa: soma estampadata-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 arquetipoitemfija tambiéncolora (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_LAYERSdeclara agrid-listconsumidor demenu-indicator, y no lo es.theming-census.ts:432lo lista, perolib/menu-indicator.cssno 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 compartidamenu-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,menubarynavigation-menuestán en la misma entrada y tampoco aparecen en el fichero).
Lo que textarea añadió (2026-08-21):
-
⚠ El borde de foco de
textareano 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 deborder-colorestá 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 partecontent— elblur()que añadió la revisión adversarial quita el foco pero no el puntero, así que:hoverseguí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. Entextareacostaba tres falsos negativos (input-border,focus-input-border,invalid-input-border); conpage.mouse.move(0, 0)tras cada blur,input-borderrevive solo. Corridos los diez componentes con ledger: ningún otro cambia, un único STALE (command.input-placeholder-fg, por el arreglo del::placeholder). -
Hueco de demo:
card-groupno 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 quedate-range-pickerconkind='month'|'year'(norma N-6). -
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) 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 haytheming-census.test.tsni equivalente bajosrc/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-widthsignificaba 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 enarc-width+pointer-insetcon 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. -
⚠
mockupno tiene ruta de demo (/uix/components/mockupda 404), como le pasaba apicker-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 «enwarn» y F3 las quiere enerror, así que F3 no puede cerrarse sin escribirlas primero. Cuando lleguen, los componentes con alcance parcial necesitan su marcaR-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:audity son PREEXISTENTES (ninguno tiene un commit de F2-B en su código):badge,gradient-picker,mockupymotion. Anotado para que nadie los atribuya al eje de theming al leer el informe. -
⚠ Un worktree de comparación NO hidrata si su
node_moduleses 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 conserver: { fs: { allow: [...] } }en elvite.config.tsdel worktree, y la regla que generaliza es comprobar que un componente monta ANTES de comparar. (Memoriaworktree-devserver-fsallow; vuelve a morder cada vez.) -
⚠
--radius-xsNO EXISTE, y una receta lo referenciaba. La escala de radios del sistema essm | md | lg | xl | xxl(+none,full,default), sinxs.text-focusescribíaborder-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 a3pxverbatim, 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 buscandovar(--…, …)cuyo primer nombre no esté en el contrato derivado es un pase mecánico que se paga solo. -
⚠
filterybackdrop-filterfaltaban en la lista de propiedades del guard — arreglado 2026-08-22.text-focus.glow-colorvive dentro de undrop-shadow()y leía «no effect» estando vivo;KNOB_PROPSdel 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. -
⚠ 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. -
⚠ El valor centinela GRANDE no prueba nada contra una propiedad capada.
skin-media-playercalcula su ancho comomin(100%, base × scale): el1234pxdel 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» (9999pxcontra un--radius-fullque 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.
Lo que la REVISIÓN ADVERSARIAL de F2-B destapó (2026-08-22):
-
drag-drop.preview-zera 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— llevaz-index: 9999INLINE (drag-drop-provider.svelte.ts:663,671), así que ninguna cascada puede ganarle: el token resolvía a 4321 y el nodo computaba 9999. Retirado con 0 diffs, que es la prueba. Misma clase que elitem-gap*decarouselque 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) ypicker-shell(pie real) coinciden con lo adjudicado y no llevan inline;skeleton-linelleva inline pero deinline-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 delink-previewva ANTES del planooverlay, 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_moduleses 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 conserver: { fs: { allow: [...] } }en elvite.config.tsdel worktree. Es la memoriaworktree-devserver-fsallow, y vuelve a morder cada vez.
Lo que la cola pequeña añadió (2026-08-22):
-
⚠ El censo no distingue «0 % por deuda» de «0 % POR NATURALEZA», y ya son cinco componentes. Medidos uno a uno el 2026-08-22,
aspect-ratio,text-blur,cascade,motionydate-pickertienen alcance 0 % y ninguno tiene contrato que escribir: el knob deaspect-ratioestá PRESTADO debox(la ficha ya lo marca ⤴) porque es una faceta suya, no un componente; los detext-blurson el1pxde la técnica sr-only; los decascadeymotionson elopacitydel gate antiparpadeo, mecánica del canal de motion cuyo valor tematizable vive enEidosConfig.motion; y los dedate-pickerson la correcciónmax-contentque 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 asystem— o una marca en la ficha que los saque del denominador, comoLAYER_VOCABULARYhizo con la familia calendar. Es la misma pregunta que ya está abierta para las capas compartidas, un piso más abajo. EJECUTADA 2026-08-25 (cierre del eje,2c61946fb): clasestructuralen el censo, por COMPONENTE, con la razón AL LADO (formaLAYER_VOCABULARY) y la regla escrita de que no es un cajón para «éste es difícil» — cada entrada exige el §5 firmado. Los cinco salen del denominador (reach 68 → 69 %, no-contract 23 → 18); sus knobs se siguen LISTANDO en §2-bis de su ficha. Candidatos NO incluidos, con su dato:field-langses deuda REAL (31 globales crudos, hoy en el ledger de deuda) yrange-calendar/month-grid/year-gridson la pregunta de las capas compartidas. ⚠ Deuda que dejó el cierre: las fichas dedocs/audit/theming/NO se regeneraron (--reportpisa prosa y exige supervisión por ficha) —aspect-ratio.mdsigue diciendo «0 %» y la tabla del README del corpus no tiene la columnastrct; y el desglose «Knobs de apariencia» de la ficha no suma (listaexcepciónentre los sumandos yrow.knobsla excluye — preexistente). -
⚠ DOS FIRMAS PENDIENTES que el cierre del eje dejó NOMBRADAS (2026-08-25, cabecera de
scripts/theming-census-debt.tsy recipe-contract §4): (1)los 318 privados no-derivados— EJECUTADA 2026-08-26 en tres piezas (e812d3ffdF1 ·a74aeec7bF2 · F3 el suelo y la prosa). Eran deuda por la letra de R-5.1 (PLAN-theming§3: un privado debe derivar de un público) sin ratchet por clave. Ahora un privado no-derivado tiene CINCO salidas, dos mecánicas y tres de acto (recipe-contract §4): 132bridge(lee lo que el forward THM-2 escribe, medido contra la salida real del generador) · 36channel(nadie lo declara; lo escribe soma o el envoltorio por instancia) · 84 anotados con la válvula nueva/* private: <razón> */→ claseexception· 66 al ledger conprivatecomoDebtClassde pleno derecho (newDebty STALE simétricos) · derivar de un público. 132+36+84+66 = 318, sin resto. Ledger 1088 → 1154; reach 69 → 73 % (3074/4228, cayó el denominador, no se movió el numerador); suelo a 73 % / 65 componentes al 100 %. (2)el idioma— EJECUTADA 2026-08-26, firma «la ley del espacio cerrado»: el espacio de nombres público es CERRADO sobre el contrato, unvar(--{c}-x, fallback)sin entrada enbase.ts--{c}-*existe si y sólo sibase.tslo declara, y no hay tercer estado. El fallback es dispositivo de seguridad de VALOR, no escapatoria de contrato; declarada la clave, el fallback inline sobre el nombre propio muere. 11 claves declaradas (mockup×9 — incluida la acuñación de--mockup-phone-shadow, porque--mockup-shadowcargaba DOS defaults bajo un nombre — ytext-scramble×2), defaults verbatim a scoperoot; el guard pasa al «si y sólo si» y muta en tres caras. Neutra en píxel medida dos veces (diff degenerated/= esas 11 líneas y nada más; A/B en navegador con los tokens forzados ainitial, computed idéntico en los tres mockups de/blocks/feature-split, ambos cromos, y entext-scramble). Números:no contract entry18 → 16, todo lo demás quieto (reach 73 %, 65 al 100 %, ledger 1154 · 0 NEW · 0 STALE, audit 162/4,--names0 desviadas) — la ley cerraba un hueco de CONTRATO, no de alcance. Y una re-clasificación abierta, todavía pendiente: los 117 nombres--palabras-*que el censo puntúa comopublicson canal de VALOR delscheme-styledel documento (schemeCssVars), no contrato de tema — el 22 % depalabrasno es alcance parcial. -
⚠ DOS DEUDAS NUEVAS que dejó la ley del espacio cerrado (2026-08-26), cada una de EJE PROPIO — no se ejecutan desde theming. Ambas vivían mientras tanto en el registro
PENDING_PRIVATE_RENAMEdesrc/uix/eidos/recipe-css-contract.test.ts, que sólo puede MENGUAR (entrada que deja de describir un consumo → STALE → rojo hasta borrar la línea). Estado 2026-08-26 (tarde): el registro pasa de 15 entradas a DOS — la (1) se ejecutó salvo un nombre, que queda PARADO con su razón, y la (2) sigue entera: (Anotación al cierre del día: las dos cayeron —el nombre PARADO por firma, la (2) por el cableado destaggerViewport—, el registro llegó a CERO y se DESMONTÓ. Las dos entradas de abajo quedan como historia de su día.)-
El codemod de los 14 CANALES DE VALOR públicos a— EJECUTADO 2026-08-26, 13 de 14; el 14.º PARADO y reportado. Renombrados atómicamente (escritor y TODOS los lectores en la misma edición, localizados con un barrido--_{c}-*.git ls-filesde cada nombre por el árbol trackeado entero):--background-{pointer-x,pointer-y,pointer-px,pointer-py,progress}·--color-swatch-fill·--emoji-picker-columns·--knob-{angle,progress,start-angle,sweep}·--menubar-panel-anchor-width·--text-gradient-duration. 13 entradas del registro RETIRADAS con acta en el propioPENDING_PRIVATE_RENAME. ⚠ La firma MEDIDA que retiró--color-swatch-filldel contrato el 2026-08-23 queda INTACTA: sigue siendo canal de valor, no vuelve al contrato, sólo cambió la ortografía. Neutro medido: sonda estándar antes/después 0 diffs en los 7 componentes (el único diff, unbackground-imagede velo en el trigger deemoji-picker, se reprodujo 2/2 sobre código idéntico — ruido del instrumento, no de la firma) y verificación DINÁMICA de los 7 canales vivos por CDP, que es donde estos viven y la sonda no llega. La trampa del censo NO mordió: sólo UNA clave se reclasificó (color-swatch.background, la única de las 13 en propiedad de apariencia cuya declaración no lee además un público hermano) —public3074 → 3073,channel36 → 37, denominador 4228 → 4227, reach 73 % intacto,atHundred65 intacto, ledger 1154 · 0 NEW · 0 STALE. Las otras doce viven en declaraciones de geometría (translate,rotate,inline-size,grid-template-columns,animation-duration) o que conservan un público hermano (conic-gradientlee--knob-track-bg, el panel demenubarlee--menubar-panel-min-widthcomo fallback), así que el censo no las contaba como knobs de apariencia públicos. El suelo NO se toca.✅ RESUELTO 2026-08-26 (tarde) —
--navigation-menu-indicator-h, con la opción (a) FIRMADA y EJECUTADA. El registro queda en UNA entrada. Lo que estuvo PARADO: el provider escribe CUATRO hermanas en un mismo literal (navigation-menu-provider.svelte.ts:832-835), el README de soma las enseñaba las cuatro bajo## CSS Variablescomo forma controlada por el consumidor con dos ejemplos de CSS de app leyendo-h, y-wera clave DECLARADA del contrato — la única razón por la que el guard veía a-hsola. Lo firmado, (a): las cuatro pasan a--_navigation-menu-indicator-{x,y,w,h}(flip atómico escritor+lectores, barridogit ls-filespor nombre),indicator-wse RETIRA del contrato (44 → 43 filas) y la sección del README se reescribe al camino de la casa — el consumidor estiliza la PARTE ([data-navigation-menu-indicator], con sudata-state) y sus tres pomos de forma (indicator-{thickness,bg,radius}), no las medidas. (b) —las cuatro públicas, declarando-x/-y/-h— se RECHAZÓ: bendecir como superficie de tema cuatro medidas por instancia es exactamente lo que la clasechannelde la firma de los 318 existe para negar. Y se rechazó una especie nueva,output(una tercera clase entre público y privado): abre excepciones sobre una clase barata con frontera de PROSA — el patrón que ya se había rechazado dos veces.-wera un token-que-miente, y está MEDIDO (CDP, transiciones congeladas porqueinline-sizese transiciona en esta parte): con el subrayado pintado, escribirlo en la raíz del componente —lo que hace el centinela— dejainline-sizeen 107px, y en:rootigual; sólo una regla!importantde autor llega. Puntuabareachen la única ventana donde la declaración asoma: el indicador montado SIN rect todavía, donde el subrayado mide 0px y no se ve. Números:public3073 → 3072,channel37 → 38, denominador 4227 → 4226, reach 73 % yatHundred65 intactos,--names4567 → 4566, ledger 1154 · 0 new · 0 stale. Centinela denavigation-menu43/44 → 42/43, misma única adjudicación (disabled-trigger-fg): el total baja porque la clave sale del contrato, no porque nada deje de llegar. Dinámica (la sonda estándar es ciega a estos canales): las privadas llevan la geometría del trigger activo (Products w=107px h=36px · Solutions w=108.56px h=36px),inline-sizeyleftlas siguen (0px → 111px al deslizar) y eltranslateY(36px)se mantiene; los nombres viejos computan VACÍO y fijarlos a 777px no mueve nada. Los tres pomos del contrato siguen llegando (block-size2 → 9px,background→rgb(1,2,3)).Docs: se ANOTARON, no se reescribieron. Las fichas medidas (
audit/theming/{knob,color-swatch,text-gradient}.md), el README decolor-swatch(su adjudicación medida del 23-08),PLAN-background.md(9 menciones, una sola anotación fechada que las cubre) ySTUMBLES.md§7 llevan nota con fecha; la medida de cada una fue correcta con el nombre de su día. Sí se volcó el nombre nuevo donde el texto describe código VIVO: READMEs de componente (eidos y soma) y las dos páginas de demo que citan el mecanismo en prosa. Deuda menor que deja: la fila GENERADAdocs/audit/theming/background.md:72sigue citando--background-progressdentro de la tabla de--report— no se toca a mano; la regenerará el--reportsupervisado que ya estaba registrado como pendiente. Y dos del adversarial del codemod (2026-08-26): (i) la sonda estándar es CIEGA a 12 de los 13 canales —PROPSde__theming-probe.tsno leetranslate,rotate,gridTemplateColumnsnianimationDuration, así que su «0 diffs» mide la apariencia circundante, no la supervivencia del canal (el control positivo debackgrounddio 0 diffs con el canal ROTO a propósito); lo que sostiene estos canales es la verificación dinámica por CDP. (ii) Export muerto con riesgo de colisión futura:getFloatingContentCSSVars(soma/layers/floating/utils.ts:18) construye--${name}-anchor-widthpor plantilla — paraname='menubar-panel'daría el nombre público RETIRADO, y un grep literal nunca lo vería; hoy no lo llama nadie. ✅ MUERTO con §15 (2026-08-26) — retirado deutils.tsy del barrel, cero llamadores verificado por barrido. Hallazgo lateral, AJENO a esta firma: el controlcolumnsde la demo deemoji-pickeres un<Slider>DESNUDO (sin hijos) yslider.svelterenderiza<Slider.Provider>{@render children?.()}</Slider.Provider>— o sea, undiv[data-slider]VACÍO, sin raíl ni pulgar. El control no se puede accionar, así que el canal se verificó por sus DOS mitades (el envoltorio escribe el privado desdeopts.columns; mover el privado a 6 y a 12 mueve el número de tracks a 6 y a 12, y fijar el nombre VIEJO a 3 no mueve nada — control negativo). -
El cableado— ✅ EJECUTADO 2026-08-26 (firma opción 1 del autor; commit del coordinador). No era canal de valor y resultó ser algo peor: el nombre no se declaraba EN NINGUNA PARTE, así que elEidosConfig.motion→ CSS de--motion-stagger-each-defaultvar(--motion-stagger-each-default, 70ms)demotion.css:42resolvía siempre al fallback — un knob de PAPEL (E14 dePLAN-blocks-quality.mdya lo había diagnosticado). Hogar:primitives.motion.staggerViewport: '70ms'(junto astagger: '20ms') → token--motion-stagger-viewport; el sufijo-defaultse descarta porque en esta casa nombra el miembro por defecto de una ESCALA (--radius-default,--ease-default), nunca «el default de otro token». Y la regla[data-stagger] > [data-animation-trigger='viewport']EMIGRA demotion.cssarenderMotionBlocks, junto a las 24 filas de índice del mismo selector:motion.cssdeja de consumir--motion-*, la entrada del registro muere por mecánica (cero guards tocados, cero excepciones) yPENDING_PRIVATE_RENAMEllega a CERO y se DESMONTA — no se deja{}, porque unitsobre un registro vacío no afirma nada. El veredictostructuraldemotionqueda MÁS verdadero: sigue sin clave enbase.ts, el censo no se mueve (strct, 1 knob; catálogo 73 %, 66 al 100 %, ledger 1152 · 0 · 0) ymotion.cssse queda exactamente con los dos gates deopacityque ese veredicto describe. Cero cambio de píxel (70 ms verbatim, medido en Chrome real; el nombre viejo a 333 ms no mueve nada, el token nuevo a 333 ms reescribe la cascada). Acta:docs/theming/changelog.md§57 +docs/canon/recipe-contract.md.
- Deuda menor de la misma ley: el crudo
var(--primitive-neutral-12)demockup(eramockup.css:73) MIGRÓ verbatim abase.tscomo default demockup.phone-bezel. Sigue siendo deuda R-4.6, sólo que en otra dirección — la regla escanea CSS de componente y ya no lo ve, así que la fila demockupperdió ese error sin que nadie lo arreglara. Anotado en la propia clave para que no se pierda; el bisel quiere un slot de rol.
Trabajo futuro que dejó ESCRITO la medida de los 318 (2026-08-26, nada de esto es la firma que se cerró):
- El alias
--_proof-of-human-accent{,-soft}es borrable a coste CERO de píxel: su base es verbatim la que el forward declara para ese slot (medido dos veces), así que leer el puente directo en las diecisiete reglas de escena computaría lo mismo y clasificaríabridgesin anotación. Hoy son DOS notas (una por alias) adjudicando 17 knobs repartidos en tres ficheros de escena, cuyo único motivo es nombrar UN acento. - Los privados del ledger se retiran ADOPTANDO el forward, no
tokenizando: 56 de los 66 son un único patrón — el conmutador de tono A
MANO (
[data-color=risk] { --_c-accent: var(--color-risk-solid) }) entime-range-picker(19) ·chronos(14) ·time-picker(12) ·date-range-picker(9) ·password-field(2). El día que esa familia adopte la escalera de paleta THM-2, esas entradas salen del ledger como STALE, todas juntas. - La fila §5 de
timelinehay que REABRIRLA si la escalera de B′ pesa más que su veredicto: sus 5 anotados se apoyan en un §5 pre-B′ que se respetó tal cual, sin re-medir contra la doctrina nueva. - Los 5 anotados de
listboxson reversibles ante la entrada §13 delist-surface: dicen «adopto la capa», y el día que la capa tenga receta propia (diez componentes de golpe) esa adopción se re-decide.
-
Lo que picker-shell añadió (2026-08-21):
- ⚠
picker-shellno tiene ruta de demo propia (/uix/components/picker-shellda 404) y sus partes se llamandata-picker-header/-body/-footeren vez dedata-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 deldate-picker, captura los seis nodos reales en las cuatrodata-picker-size: 576 valores, 0 diffs) y el guard R-5.4 ganó unCOMPONENT_OVERRIDESpara 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-shellesdata-picker-sizesobre 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.
Lo que code-block añadió (2026-08-21):
-
⚠ La demo de
code-blockNO ENSEÑA elcode-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) yline-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» (precedenteproof-of-human), pero aquí son siete propiedades y le toca justo al componente cuya razón de ser es mostrar código. Por esopre-paddinglee 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.
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] codedeclarafont-family,font-size,paddingyborder-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 suradiuslee muerto — 3px del sitio donde el componente pide 4px. Cualquier decisión sobre la regla del harness cubre los dos componentes. -
⚠ 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») esgrep -oE "^ '?[a-z0-9-]+'?: \{" src/uix/eidos/lib/recipes/base.ts | …, ygrep -Eno interpretacomo tabulador — lo lee como unatliteral, 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)—4xles un paso que la escala canónicaxxs..xxxldelib/primitives/typography.tsNO tiene: idioma Tailwind del import de julio63b371d5c, pinta el fallback) yword-weight(900) ·text-circular.weight(900) yfont-size(1.5rem) ·text-gradient.font-weight(500, subida el 22 en97a6f03d7). 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 unh1ES un h1.Son SIETE claves en CUATRO de los cinco componentes (medido 2026-08-22 al adjudicar
text-scramble): las cinco de arriba mástext-scramble.fonty.size, éstas aún sin declarar enbase.ts. Y la detext-scramblees 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 pintaui-monospacey el componente ignora la mono del tema (Azeret Mono). Medido en navegador.text-blures 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
4xlsin 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-xsya pedía:var(--nombre, …)cuyo nombre no emite ningún generador. -
⚠ 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-xsy el de--font-size-4xl, y ya no hay que suponer cuántas quedan. Método: recoger las 5.793 custom properties declaradas en TODOsrc/uix/eidos(fundación + capas + recetas +generated/) y buscar en las recetas losvar(--x, fallback)cuyo--xsea vocabulario de SISTEMA y no esté declarado — el sistema nunca lo escribe en runtime, así que un nombre ausente es una errata. (Losvar(--x)SIN fallback ya los guardarecipe-css-contract; el hueco eran justo los que llevan fallback, que «funcionan por accidente».)referencia inexistente dónde qué pinta en su lugar --color-content-tertiary×3float-panel.css:227·proof-of-human/clock.css:87·proof-of-human/rotate-align.css:114--color-content-subtle. Es el caso histórico querecipe-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×4text.css:32,55,65,75normal— los estilos con nombre emiten 6 claves y ésta no está, así que el eje variable no llega nunca al primitivoText--font-family-sanspalabras.css:296sans-serifdel sistema. El canónico es--font-family-primary(palabras es track WIP)--font-monotext-scramble.css:11ui-monospace. El canónico es--font-family-monoNinguna 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.
Lo que chart destapó (2026-08-22):
-
El censo penaliza consumir la capa tipográfica— RESUELTO el mismo día, y la nota estaba equivocada. Escribí que las doce declaraciones dechartque pasaban avar(--style-caption|label-font-size)seguían contandoglobalporque 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 esvar(--style-{rol}-*),globalcayó 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 delist-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 unStat: no son encabezados (h2está 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. -
⚠ La clase
privatedel 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 columnaprivatede los componentes con paleta:tag-group14/14,stepper11/11,radio-group10,checkbox8,file-upload8,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 elcolor=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-group65 %,stepper79 %,timeline73 %); los demás la heredan cuando les toque. Si algún día se quiere una cifra comparable, la respuesta es una clasebridgeen el censo, no acuñar los tokens. -
⚠ La capa
list-surfaceno 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: 1234pxen:rootno mueve nada, y uno que escribe--dropdown-menu-item-heighttampoco. 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
listboxno se cosió como sus hermanos: copiar el puente habría subido la cifra sin dar alcance (un token que miente). La salida es la decalendar-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». -
⚠ 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 enpopover(22 reglas),drawer(22) ydialog(9), leyendo--color-{fulfill,threat,neutral}-element. Y ya ha derivado: dialog tiñe sóloborder-top-colormientras 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 enarchetypes.css. Coserlo por componente sería triplicar un vocabulario que debería vivir UNA vez; la salida es una regla transversal sobre el arquetipocontent, con los colores de intent (que son canon) — 53 knobs asystemy 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-bgno lo leía ninguna regla; el único sitio del corpus que lo nombraba era un comentario depopover-arrow.svelte. El testdoes not leave declared public recipe variables orphanedbusca 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. -
⚠
color-pickerarrastra 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 dedata-size) ni que el guard no vea sus nodos (ya se le dieron las dos familias que le faltaban). -
Un falso negativo del centinela sin causa— RESUELTO en la revisión adversarial del bloque (2026-08-22).float-panel.resize-grip-fgmoví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:hovergana 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 tokenhover-*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-fgymedia-player.track), retiradas. Confirma la regla de F2-A: un falso negativo siempre tiene causa. -
⚠⚠ EL VELO DEL
drawerNO PINTA — defecto real, medido y visto (2026-08-22). Su regla esbackground: color-mix(in srgb, var(--drawer-overlay-bg) var(--drawer-overlay-opacity), transparent), y soma escribe--drawer-overlay-opacity: 1INLINE en el nodo del velo (es su progreso de arrastre, un número sin unidad).color-mixexige un PORCENTAJE ahí: con1la función entera es inválida y el fondo cae atransparent. Computado en el velo abierto:rgba(0, 0, 0, 0); escribiendo80%en el propio nodo pinta. En la captura se ve: el fondo detrás del cajón está DESENFOCADO pero no atenuado — elbackdrop-filterfunciona 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
drawerson canal de valor en el eje que manda: soma escribewidth(oheight, según el lado) INLINE en el contenido, así quecontent-width-{sm,md,lg}no puede ganar desde:rootpara un cajón izquierda/derecha —el eje cruzado sí alcanza (height-smforzado: 260px → 976px)—. Misma clase que elitem-gapde carousel y elpreview-zde 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. -
⚠ El eje
sizedelcolor-pickerMUERE 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:--_color-picker-content-widthse 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 pasomd. Un pickersmy unolgabren el MISMO panel de 344 px, ycontent-width-sm/content-width-lgson dos públicos que no mueven nada (elmdsí alcanza, por el override).- 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 elmd.
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 parasmylg, así que es firma. Misma familia que la capalist-surfaceinalcanzable 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-cardsy compañía— y con ellotransition-duration/transition-easeno pueden moverse jamás. Medidos sin congelar encolor-pickeralcanzan (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. -
✅ §12.9 FIRMADA Y EJECUTADA (2026-08-24) — el plano es el SUELO,
:where(), especificidad cero. Todo lo que sigue en este apartado sobre el plano queda RESUELTO; se conserva porque es el expediente de cómo se midió. La doctrina vive endocs/theming/changelog.md§29 y el emisor enrender-css.ts(renderDepthBlocks), con guarda enactive-eidos-config.test.ts.Lo que restauró, medido en la demo real con motion congelada: los seis tonos de
toastvuelven a pintar —4 de 4 distintos donde antes 3 de 3 eran idénticos— y su franja de acento pasa de 0,67 px de gris neutro a 3 px del color de la intención; las tres variantes detooltipvuelven a distinguirse (solidopaco con sombra ·outlinetransparente con borde ·ghosttranslúcido al 70 %); y 17 claves adjudicadas como mudas en cinco componentes salieron STALE solas y se retiraron del ledger.Lo que movió de píxel, dicho entero: de 19 adoptantes, 16 con diff CERO sobre ~49.000 valores computados. Los tres que se mueven son la firma haciendo su trabajo:
toast(35 diffs: 7 fondos + 7 bordes de tono + 21 anchuras por la franja de 3 px),popover(line-height17,5 → 22,4 px, y el panel crece 26 px porque el texto respira) y —sólo visible abriendo la superficie, que la sonda no hace—tooltipylink-preview, mismoline-height. Es EXACTAMENTE la decisión que la receta delink-previewtenía escrita desde el 2026-08-21: «se quedan declaradas A PROPÓSITO; retirarlas arreglaría el empate a favor del plano — una decisión de píxel (1.25 contra el 1.5 que esta receta pide) que pertenece a la firma pendiente de §12.9».font-familyno se movió en ninguno: los tres recetarios pidenvar(--style-label-font-family), el mismo valor que el plano pinta, así que la seguridad tipográfica del portal (Decisión 8) queda intacta.Refutada una hipótesis propia: temía que el atajo
backgrounddel plano estuviera matando el velo de hover del sistema (que va en:where(), cero) y que bajar el plano lo resucitara moviendo píxel.backgroundImageno cambia en ninguno de los 19 — el velo vive en los ítems, no en la superficie.⚠ Al medir el plano, cuenta los nodos: la sonda compartida mide UN nodo en
dialog,drawer,popover,tooltip,context-menu,dropdown-menuylink-preview—nunca abre la superficie, que es justo donde el plano pinta—, así que sus «0 diffs» no probaban nada. La medición buena reproduce el «antes» en runtime, inyectando la regla vieja a(0,1,0)al final del head, en vez de revertir el fichero (scripts/__plane-open.mjs). -
⚠⚠ §12.9 tiene MECANISMO: el plano
overlayno «impone» tipografía donde el componente calla — GANA donde el componente habla (medido enmenubar, 2026-08-23). Acuñadaspanel-font-familyypanel-line-heightsobre 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,coloryfont-size—que el plano no declara— sí alcanzan. Consecuencia para la cola: mientras §12.9 no se firme, un token defont-familyoline-heighten cualquier superficieoverlayMIENTE, 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
menubarmata la capa del sistema — y es del eje navigation-menu, no de éste (medido 2026-08-23).[data-menubar-trigger]:hoverpintabackground: var(--color-surface-overlay): en hover elbackground-imagecomputanonecon--state-hovervivo. Dos razones, las dos ya escritas ennavigation-menu.csscuando retiró esta MISMA regla: el shorthand reseteabackground-image, y la regla del arquetipo va en:where(), especificidad CERO. El eje navigation-menu tienemenubaren su cola por esto; aquí no se acuña unhover-trigger-bgporque fosilizaría la invención (doctrina: el hover es del SISTEMA). -
⚠ Un token de
::placeholderlee MUERTO sobre un input sinplaceholder(medido enfield, 2026-08-23). Sin el atributo no existe la caja del pseudo-elemento, ygetComputedStyle(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. -
⚠ 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 :global(svg), que cree pintar el glifo al 70 % de su caja — computa 14px, el tamaño propio del Icon) ytimeline.css:331([data-timeline-marker] :global(svg)).:global()es un envoltorio de SVELTE: fuera de un<style>de componente el selector es inválido.image-picker.cssya 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-opacityleí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. -
✅
⚠⚠ LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE — 419 claves públicas del catálogo— FIRMADA Y ARREGLADA 2026-08-24 (firma B′). Todo lo que sigue en esta entrada queda RESUELTO; se conserva porque es el expediente de cómo se midió, y porque la resolución NO fue la que la entrada proponía. La doctrina vive entheming/changelog.md§53, el emisor enrender-css.ts(isPaletteSlotToken+renderRecipePaletteForward) y la guarda enactive-eidos-config.test.ts(«palette cascade is a ladder»).Por qué B′ (escalera) y no A (reordenar la emisión), que es lo que el párrafo «Arreglo propuesto» de abajo pedía: reordenar habría dejado el resultado dependiendo de quién se emite antes, que es un contrato invisible — no se lee en el artefacto, no lo verifica un guard sin recorrer la hoja entera, y se rompe con mover una línea. La especificidad, en cambio, es contrato EN el artefacto:
:where([data-{c}])(0,0,0)<[data-{c}]:where([data-color], …)(0,1,0)<[data-{c}][data-color='X'](0,2,0), y cualquiera que lea el CSS ve qué gana. Es además la doctrina que §12.9 acababa de firmar para el plano: una sola ley aplicada dos veces, no dos arreglos parecidos.La enmienda de
switchde abajo se resolvió SIN caso especial. No hizo falta distinguir «componentes cuyo proveedor estampa siempredata-color»: la escalera los cubre igual, y lo que queda de ellos es una sola frase general — un tono por defecto ESTAMPADO resuelve por el forward, y quitar el atributo es hablar en silencio (PALETTE_FLOORen el ledger del centinela).Medido: neutra en píxel sobre ~297.000 valores computados (0 diffs reales, los 17 crudos probados ruido por reproducción sobre código idéntico) y 207 de las 353 claves adjudicadas volvieron a mover un valor computado. Las 146 restantes ya no son supersesión: son el suelo (
PALETTE_FLOOR) o el punto ciego del guard (TONE_UNREACHED), y el ledger pasó de 17 a 28 patrones para no volver a mezclarlas. (Anotación 2026-08-25: la GRANDE enseñó al guard a combinar tono y estado — 130 de las 132TONE_UNREACHEDrevivieron y la constante está RETIRADA; quedaPALETTE_FLOORcomo el único hecho, 15 de los 16 patrones supervivientes.)⚠ Y el silencio del guard NO fue señal:
theming-sentinel.tssólo detecta STALE contra el ledger EXACTO, nunca contraSENTINEL_PATTERN_EXCEPTIONS. Las 207 claves resucitadas no habrían disparado ni un aviso — el estrechamiento se hizo comparando a mano contra las 13 entradas. Vale para cualquier futura adjudicación por patrón.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 condata-colorresuelve por el--palette-*GLOBAL, que[data-color='{tono}']llena desde--color-{tono}-*. Consecuencia medida sobredata-color='risk':--button-risk-solidno mueve nada ni en:rootni en el nodo, mientras--palette-solidy--color-risk-soliden el nodo repintan. Las siete{c}-primary-*son el RESPALDO de la regla genérica y sólo actúan en una instancia SINdata-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 ESvar(--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 estampadata-colorSIEMPRE, el respaldo tampoco existe. El párrafo de arriba dice que las{c}-primary-*salvan a la instancia SINdata-color— pero hay componentes cuyo proveedor estampa el atributo en toda instancia, porque para ellos el color es un valor RESUELTO yneutrales su default, no su ausencia. En esos, el bloque genérico casa siempre y cae también el tono por defecto: medido enswitchen estado checked,--switch-neutral-solidno mueve nada y--palette-solidsobre el nodo repinta. Amplía el alcance de la firma: no es «los tonos no canónicos», es los tonos.Verdades residuales que dejó la firma (2026-08-24): tres, nuevas y medidas. Ninguna es la cascada de paleta; salieron al barrer los 15 componentes con bloques por tono, y ninguna bloquea:
stepper.neutral-textno miente: miente el VALOR CENTINELA.sentinelForsólo elige un color cuando la clave casa/color|bg$|fg$|border$|ring$|…|track$/, yneutral-textno casa ninguna — así que el guard escribe1234px, que es inválido paracolor, y la propiedad cae a la tinta HEREDADA. El tono neutral de texto es prácticamente esa misma tinta, así que el fallback aterriza en el mismo valor computado y nada se mueve. Con un color real sobre el indicadorcurrent, con el tono estampado, la clave repinta. Es una clase, no un caso: afectaría a cualquier{tono}-textcuyo tono quede cerca de la tinta heredada. OsentinelForaprende quetext$es una ranura de color, o cada uno paga su adjudicación escrita.- Las celdas
fontSize/lineHeightdemonth-selectyyear-selectdecalendarno son medibles con la sonda. Medido: el nodo no responde adata-size, y la lectura resultó 99,912 % estable en cinco corridas — es decir, la inestabilidad no explica nada y el nodo simplemente no es el que la sonda cree. Queda registrado como límite del instrumento, no como token muerto: nadie retire esas claves apoyándose en esta lectura. tag-group.hover-item-fglo gana el ARQUETIPO, y es un token que SOBRA.archetypes.csspinta[data-archetype='item']:hover:not([data-disabled]):not([data-state='checked']):not([data-state='on'])a(0,4,0)y la receta escribe[data-tag-group-item]:hover:not([data-disabled])a(0,3,0): la tinta al puntero es la del SISTEMA, nunca la de la receta. Medido con un puntero real sobre una etiqueta real (:hoverconfirmado): el color se queda enoklch(0.2435 0 0)=--color-content-primary, con y sinaria-selected. Misma clase quetags-input.item-fg. Y por doctrina —el hover es del sistema, nunca una invención por componente— esto no es un token que mienta sino uno que no debería existir: queda para su eje, no se toca desde aquí.
-
⚠
combobox.content-font-familyes el TERCER token de tipografía que el planooverlayanula (medido 2026-08-23). Su panel llevadata-depth='overlay', así que la clave no mueve nada, igual que las demenubar,dropdown-menuycontext-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. -
⚠ Ya son CUATRO los componentes canónicos sin ruta de demo:
picker-shell,mockup,surfaceyaudio-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 chipmedia: 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 unradiusque está vivo. El parche es el mismo que se le puso a picker-shell —medirlo donde SÍ se renderiza (urlsen el override)—, pero la solución de fondo es que un componente del canon tenga su demo. -
⚠⚠ EL PLANO
overlayANULA LAS TRES VARIANTES DEtooltip(medido 2026-08-23). No es sólo la tipografía de §12.9:[data-depth='overlay']declarabackground,borderybox-shadowcon 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/-shadowno mueven nada,--depth-overlay-surface/-border/-shadowrepintan.Consecuencia visual:
solid,outlineyghostcomputan el MISMO fondo, el MISMO borde y la MISMA sombra.outlinees indistinguible desolid;ghostsólo aporta unbackdrop-filterinvisible 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.
-
⚠⚠ Y EN
toastEL PLANO SE LLEVA LA TARJETA ENTERA — 21 claves, y esta vez con consecuencia de PRODUCTO (medido 2026-08-24). Misma mecánica quetooltip, un piso más arriba:toast-item.svelteselladata-depth='overlay'y el plano declarabackground, el ATAJOborderybox-shadowa la misma (0,1,0) y más tarde. Medido sobre la tarjeta real con transiciones congeladas (scripts/__dbg-toast-plane.mjs):--toast-{default,affirm,fulfill,risk,threat,loss}-surfacey-border(12),--toast-provider-border-width,--toast-accent-widthy--toast-provider-shadowno mueven nada;--depth-overlay-surface/-border/-shadowy--border-widthen la misma raíz lo repintan todo.Consecuencia visual: un toast
risky unofulfillvisten la misma tarjeta gris. Sólo el disco de estado y la tinta de la acción llevan la intención, porque viven en OTROS nodos. Y la franja de acento —el rasgo que identifica la intención de un vistazo— es unborder-inline-start, un longhand que el atajo posterior pisa en los cuatro lados: no se ve nunca en reposo. Es la primera vez que §12.9 tiene coste de producto y no sólo de contrato.Re-medido en la supervisión (2026-08-24), porque una afirmación así no se hereda: disparados en la demo real un toast
risk, unofulfilly unoaffirmcon transiciones y animaciones congeladas, los TRES computan idénticos —background oklch(0.285 0 0), bordeoklch(0.3485 0 0), franja de acento de 0,67 px del mismo neutro, misma sombra—. La tarjeta llevadata-intent='risk'ydata-depth='overlay': escribir--toast-risk-surface/-borderen:rootno la mueve, y--depth-overlay-borderla repinta. La intención llega al DOM y muere en la cascada.⚠ Ojo al medirlo: el tono viaja en
data-intent, no endata-color. Una primera sonda leyódata-color, lo encontrónully estuvo a punto de diagnosticar «el tono no llega» — que es un defecto DISTINTO y habría mandado la firma por el camino equivocado.Matiz medido, que conviene a la firma: los seis
{tono}-accentSÍ alcanzan bajo[data-toast-item][data-loading], donde la regla del pulso es (0,2,0) y su@keyframesgana a toda declaración normal (100 fotogramas: sin escribir, el color interpolado corre en oklab L 0.5032..0.8514; con el token enrgb(1,2,3), 0.0823..0.5032). O sea: el plano gana en reposo y pierde en animación, así que la misma clave miente y dice la verdad según el estado. Las 21 quedan ADJUDICADAS conPLANE_SUPERSEDED, no retiradas. -
El censo pierde el eje
sizeDE PREMIO por aplicar bien el protocolo (medido 2026-08-23 ennavigation-menu).hasSizese decide grepeandodata-sizeen 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 «ejesize: no» en su ficha y sinyen la tabla del censo. Ya le pasa asidebarynav-tree, y anavigation-menudesde hoy — los tres con su cascada por talla viva engenerated/base.css. La columna es informativa (no entra en el ratio), pero el conteo «con ejesize: 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 unscope: '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 filtrodata-{c}-*veía 10 nodos y ninguno era el panel ni una fila — 9 de 19 knobs fuera del diff. Es la MISMA clase queproseyonion-menu, pero por composición del consumidor, no por HTML crudo ni por hooks de clase. Resuelto para este componente conEXTRA_NODES/extraNodes(y el pasoopende 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. -
…y la TERCERA cara: las partes de un componente COMPUESTO (medido 2026-08-23 en
waveform). Su cromo entero es unSliderembebido que la receta re-tinta —el playhead ES el thumb del slider—, así que esos nodos llevandata-slider*y el filtrodata-waveform*medía 4 nodos sin ninguno de ellos. ConextraNodes, 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)enwaveform.csses un primitivo CRUDO pintando el playhead, y no aparece en ninguna columna: las custom properties no están enKNOB_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 nirecipe-css-contractlo vean. Medir cuántas hay antes de decidir si D-TH.2 se ensancha. -
Dos propiedades que la sonda NO puede medir: la
opacityde un<img>con animación de entrada y elinline-sizedel rango buffered de un audio (medido 2026-08-23 enimageyaudio-player). La sonda congelatransitionpero nuncaanimation—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 enimagey 11 enaudio-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
backgroundcambia 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 deinlineSizeen 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 deinlineSizesobre el provider/capa de background se repite antes de creerla. -
audio-playerno 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 desde83a897b92. -
reopen()mira el CONTENT, y el estado vive en el TRIGGER (medido 2026-08-23 enemoji-picker). Suopen-trigger-fgoscila 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 unopenMarkersobre 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 quereopen()espere a la condición (waitForSelectordel 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. -
✅
⚠⚠ UN COMPONENTE COMPUESTO GANA A LA RECETA QUE LO COMPONE, y ya van TRES— RESPONDIDA 2026-08-25 para la mitaddata-popover-trigger, por la firma «el sobre de trigger de popover es el SUELO» (changelog §55). (medido 2026-08-23). Cuando una receta estiliza un nodo que también llevadata-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-picker36 claves de 45 (F2-A, se retiraron),emoji-picker3 (retiradas hoy) yform23 (adjudicadas, no retiradas: son demasiadas para decidirlo sin firma). La pregunta de fondo es la misma de §12.9 con el planooverlay: 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 ~62 claves públicas que retirar en tres componentes. La respuesta firmada es «LA RECETA», y no se ejecutó subiendo la especificidad de cada víctima sino BAJANDO la del baseline a:where(), que es la misma herramienta de §12.9 y §53. Nótese que el enunciado de arriba se quedaba corto en un punto medido: el sobre depopover.cssno estaba a (0,1,0) sino a (0,2,0) —y su:hovera (0,5,0)—, así que no era un empate por orden de carga, era una derrota limpia; el empate por orden sí existía entre el sobre y[data-calendar-*-select][data-button], los dos a (0,2,0) (reproducido: 2 de 6 cargas limpias en la cara equivocada). Lo que queda abierto es la otra mitad,data-buttoncontra la receta que lo compone (form23 claves adjudicadas): esa firma no se ha hecho, ybutton.cssNO se tocó aquí. -
R-5.3 no guarda el vocabulario DIMENSIONAL (medido 2026-08-23 en
badge). Cubre la ranura de tinta (fgvscolor) 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 mapeamin-block-size → height. Extender R-5.3 con esa tabla —propiedad → ranura— cierra la clase entera. -
✅
— RESUELTO 2026-08-24 por la firma «una palabra, un significado» (changelog §54): de las dos salidas que la entrada dejaba abiertas se tomó la primera —labeles el ÚNICO primitivo de tinta sin el paso'on-solid'labelGANA el paso, y la familia quedó pareja de verdad: los seisCONTENT_INKson ahora idénticos,{subtle, muted, disabled, on-solid}, con guarda estructural que lo exige (probada por mutación). Se conserva el expediente porque documenta cómo se detectó: lo omitían a la vez SU TIPO (label/types.ts:ComponentColorProp | 'muted' | 'disabled') y su runtime (label.svelte), así que era auto-consistente —no un bug— y sólo se veía comparando los seis entre sí; y tampoco lo salvaba el «pues compila», que era el brazo de CSS crudo del tipo abierto, idéntico en los tres. -
⚠⚠ EL GUARD TIENE FALSOS POSITIVOS: escribe el centinela en el
styleINLINE de cada nodo (medido 2026-08-23).staticPassescribe el token endocumentElementy 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 elstyledel nodo, el guard lo pisa, ve el cambio y lo da por VIVO — aunque un tema, que sólo puede escribir en:rooto en una hoja, no lo alcance jamás. Es la cara opuesta dedrag-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-customyavatar.ring-color-custom. Medido endialogcon el diálogo abierto y las animaciones congeladas: desde:rootel 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
avatarentre los sin-contrato — su bloque debase.tses una IIFE que el censo no sabía leer hastaf68bac4a6, así que sus 84 claves eran invisibles. Re-medidos aquí los dos: desde:rootni el fondo del badge ni elbox-shadowdel anillo se mueven, y no puede existir instancia alcanzable — la puertadata-_-customque activa la regla y la escritura inline del token viajan JUNTAS en el envoltorio. Son canal de valor filtrado al contrato; su hermanabadge-color-custom-contrastsí vive desde:root.)*(Actualización 2026-08-25 — los dos de
avatarya NO están en el contrato, así que en el contrato quedan DOS, los dosoverlay-opacity. La firma A-c retiróring-color-customybadge-color-customa privadas--_avatar-{ring,badge}-color-custom: el canal sigue funcionando igual y el falso positivo del guard deja de contar como público que miente, porque ya no hay público. No arregla el guard —el mecanismo sigue intacto ydialog/drawerlo siguen sufriendo—, sólo saca aavatarde la lista. Detalle en PLAN-theming §8.)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.El arreglo del guard: escribir el centinela sólo en
:rooty en el host, nunca en elstylede 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 cuyostyleinline ya declara ese mismo token, y avisar cuando eso ocurra. Con re-verificación del ledger entero. -
El guard no fotografía
translatenitransform(medido 2026-08-23 enbackground, y ya latente endrawer.handle-active-scale). El paralaje entra portranslatesobre la capa; condata-parallaxforzado y speed 1 el token SIGUE al scroll (14,6 → 60,5 px al mismoscrollY), 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 demask-image: los tres aPROPSen el mismo pase, y re-verificando los ~59 con ledger (el barrido deREVIEW-theming-2026-08-23.md§5 es la base). -
El guard no fotografía
mask-image(medido 2026-08-23 enbackground). Sus dos tokens de desvanecido (fade-size,fade-at) alimentan unmask-imageradial y leen muertos aunque están vivos — medidos a mano, el mask cambia (at 50% 35%→at 11% 50%). AñadirmaskImagea 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. -
El detector STALE del centinela es CIEGO a las adjudicaciones por PATRÓN (verificado 2026-08-24 sobre el código, tras B′).
theming-sentinel.ts:1140-1141filtrank in ledger, es decir sólo las excepciones EXACTAS. Las cubiertas porSENTINEL_PATTERN_EXCEPTIONSsí entran enreasonFor(:1129) para adjudicar un muerto, pero nunca enstale: una clave de patrón que pasa a viva no dispara ni un aviso. El coste ya está pagado: en B′ revivieron 207 claves y el guard no dijo nada — el estrechamiento del ledger hubo que hacerlo comparando a mano contra las 13 entradas. Arreglo mínimo: filtrar porreasonFor(k) !== nullen vez dek in ledger, e imprimir QUÉ patrón la cubría (si no, el aviso no dice dónde ir). Es de la clase re-verificar-ledger —tocar el guard obliga a re-pasar el ledger entero—, así que firma pequeña, como las otras del instrumento. EJECUTADA 2026-08-25 (S1 de la GRANDE,6392d6003), con el mensaje partido en dos: exacta → «retira la entrada», patrón → «ESTRECHA el<source>» (nunca retirarlo: cubre una familia). El primer censo completo destapó su primera deuda: los dos patronesPLANE_SUPERSEDEDdetoast(18 claves), dejados rancios por §12.9 la misma noche que se escribieron — 18/18 vivas, 3/3 reproducido, RETIRADOS. Y en el mismo commit, la ceguera 6 (hallazgo del expediente VA): elcatchdelprepareWithGRITA siempre —:796ya filtró la ausencia, así que lo que cae ahí es un control que estaba y falló, la fuente de las muertes fantasma dechat-message(59/81 ↔ 77/81 sobre código idéntico). -
El guard no sabe medir TONO y ESTADO a la vez (verificado 2026-08-24; es la raíz de
TONE_UNREACHED, la razón nueva del ledger). El estampado del tono vive sólo en el paso estático (theming-sentinel.ts:913,TONES.find(...)→setAttribute('data-color', tone)); el paso de hover (:999) barre los ejes desweepAttrpero no estampa tono. YsweepAttrpuede forzar un estado, pero de los 48 componentes deCOMPONENT_OVERRIDESsólotoggledeclara un eje de estado (:635,data-state: off|on) —switchybuttonno tienen entrada. Medido: la ficha deswitchmonta 1 nodouncheckedy todas sus ranuras de paleta cuelgan de[data-state='checked']; la debuttonmonta 1 nodo sólido. Consecuencia: toda clave de tono cuya ranura esté detrás de un estado o de un hover se mide A MANO, y por eso son 146 las que siguen adjudicadas tras B′ y no cero. Arreglo: estampar el tono también en el paso de hover, y dar eje de estado a los componentes cuyas ranuras lo exigen. EJECUTADA 2026-08-25 (S3 de la GRANDE,b9cef9a43), en cuatro piezas: tono en el pase de hover (restaurado al salir) · tono MEDIAL con(^|-){tono}-(censo: 4556 claves, 556 nombran tono, 16 mediales — las 16 detag-group, cero falsos positivos y cero dobles) · el pase de hover corre para toda clave de TONO muerta aunque no diga «hover» · seis ejes de estado como DATO enCOMPONENT_OVERRIDES, cada valor sacado del morfo/receta/DOM. Barrido: 91/91, monótono, exactamente los 9 previstos, +140 claves (catálogo 2778 → 2918). 130 de las 132TONE_UNREACHEDreviven; las 2 que no son el tono HOST por defecto (test de dos caras →PALETTE_FLOOR). La constanteTONE_UNREACHEDRETIRADA como huérfana; tier de patrones de la firma entera 28 → 16, 15 de los 16 son ya el único hechoPALETTE_FLOOR— y el adversarial verificó que los 16 cubren SOLO claves muertas (cero zombis). Dos razones corregidas por medida al pasar: las-textdetags-inputsí reviven (el arquetipo sólo tapa el ítem RESALTADO y la escena no tiene ninguno) ytag-group.hover-item-fgla callaba sudata-state='selected'nativo, no el arquetipo. -
Deuda de instrumento que dejó la verificación adversarial de la GRANDE (2026-08-25, ninguna bloquea): (i) el pase estático estampa
data-coloren TODOS los nodos y producción sólo en el proveedor (theming-sentinel.ts:1215-1224y:1346-1352vscolorAttrs.dataColoren los.svelte) — medido que NO es la causa de la revivida detags-input(el privado hereda del proveedor: en forma de producción el token mueve 6 nodos igual), pero la asimetría merece expediente propio. (ii) La restauración de atributos barridos usa un ternario FALSY (sweptWas[i2][j] ? set : remove,:1268-1272y:1387-1391, nace en007c2af52): un atributo con valor CADENA VACÍA se borra en vez de restaurarse — hoy inocuo (todo afectado es a la vez eje barrido; corridas dobles idénticas), pero es una mina. (iii) El recorte del pase de hover a claves de tono deja un residuo genuino,button.palette-element, con su límite escrito en su propia razón del ledger. -
Tres huecos de la SONDA (
scripts/__theming-probe.ts, verificados 2026-08-24), independientes entre sí:- El pase
opendeeditableno muere por el localizador: muere por el CLIC. El localizador (:324-326) SÍ casa[data-editable-input](3 nodos), pero esos inputs estánhidden/ 0×0, así quetrigger.click({ timeout: 2000 })(:330) expira dentro delcatchvacío (:359) y el pase se pierde EN SILENCIO. La puerta real es[data-editable-preview]+ foco, que el CENTINELA ya declara (theming-sentinel.ts:214-221:openBy: 'focus',openWith) y la sonda no. Dos arreglos, y conviene hacer los dos: copiar la puerta, y que elcatchavise en vez de tragar (un pase perdido en silencio es un 0/N que nadie investiga). EJECUTADO 2026-08-25 («la mitad gratis»): la sonda importaCOMPONENT_OVERRIDESdel centinela (exportada con guarda de entrypoint — conrealpathSync, o el guard muere a través de un junction) y habla su vocabulario entero (prepareWith/openBy/openWith/openMarker); elcatchAVISA por stderr distinguiendo «no hay trigger» de «el gesto falló».editableregistra ya bloqueopen(25 filas, 6 condata-editing; dos corridas sobre código idéntico = 0 diffs) ycardsigue a 0 diffs en sus 7 bloques pre-firma. - La sonda no tiene análogo del barrido de variantes.
DEMO_VARIANTS(:139-202, 10 claves: avatar, audio-player, text-gradient, badge, image-picker, search-field, chat-composer, chat-message, chat-typing, toast) TRUECA de demo, no barre: mide un estado en lugar de otro. Por esobutton, que monta un solo botón sólido, no tiene sondeadas sus otras variantes. Es el hermano exacto del hueco del guard de arriba. - Dos alias de escala hacen sondas DÉBILES.
affirm: 'teal'(lib/config-types.ts:95ylib/themes/base.ts:40): estampartealcomo escala «cruda» computa idéntico a estampar el rolaffirmen los 10 slots, así que una sonda que crea estar probando la vía de escala puede estar midiendo la de rol. Y su hermana, medida:risk: 'orange'(themes/base.ts:42), de donderisk ≡ orangeyrisk ≠ amber— ojo, que la convención canónica del libro nombraambermientras el tema base mapeaorange. No son dos casos: son NUEVE de las 33 escalas, las que el tema base ata a un rol —purple(primary),slate(secondary),indigo(tertiary),gray(neutral),teal(affirm),green(fulfill),orange(risk),red(threat) yplum(loss). Para barridos futuros: usar una escala sonda de las 24 libres (fuchsia,steel,sand… ) y NOplum, que a primera vista parece neutra y es el rolloss. EJECUTADO 2026-08-25:PROBE_ROLE_BOUND_SCALES(las 9 → su rol) +PROBE_SAFE_SCALES(las 24) +assertProbeSafeScale()en la sonda, con la discrepanciarisk: amber(config-types) vsorange(tema base) documentada en el comentario. Guardia sin usos inventados: hoy nada en la sonda estampa una escala; las 33 verificadas contraPALETTE_SCALES(lib/types.ts:232-270), 0 sobras, 0 faltas.
- El pase
-
Lo que «la mitad gratis» añadió a la sonda, y lo que abrió (2026-08-25). Ganó el pase de FOCO que la cabecera prometía desde siempre sin que nada llamara a
.focus()(recarga propia — el bucle de hover deja el puntero aparcado y una regla:hoverpesa más; mide foco PROGRAMÁTICO,:focus, nunca mezclado con gestos), un pasedisabledcomo bloque APARTE y ÚLTIMO de la corrida (la trampa «montar más mide menos»: la regla de file-upload apagapointer-eventsdel subárbol), yUIX_DEV_URLen sonda y centinela (adiós el:5173clavado; el argumento explícito sigue ganando). Deudas nuevas, medidas por la verificación adversarial de la firma: (i)search-fieldpierde[data-search-field-clear-trigger]en el paseopen— la guarda «prepareWithsólo corre sin fila enDEMO_VARIANTS» lo salta (su fila sólo clica el chip de loading) y el centinela documenta que el ORDEN importa (el clear ha de venir ANTES); cobertura, no regresión — antes la sonda no corríaprepareWithen absoluto. (ii) En los seis conprepareWithy sin fila (editable,rating-group,toolbar,form,progress,background) el bloqueopenmonta capas querest/size/hover/focus/disabledno ven — comparar bloques entre sí no es comparar lo mismo. (iii) La sonda no tiene el cortocircuitoalreadyOpendel centinela y, tras unopenMarkerfallido, prueba el siguiente opener sobre página ya ensuciada (no se manifestó en lo corrido). Y un dato para la GRANDE: el no-determinismo dechat-messagequedó ACOTADO al bloquerestde la sonda (13 vs 16 nodos — los chips deDEMO_VARIANTSsin cuajar al medir;size/open/hover/focus/ disableddan 16/24/11/11/16 con 0 diffs), coherente con elprepareWithmudo del guard que la ceguera 6 arregla. -
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 elbackground-imagey eltranslatede la misma capa donde se miden los patrones. La regla operativa:prepareWithsó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
backgroundse 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. -
Un panel de HOVER se cierra por el propio guard.
navigation-menuabre conpointerenterypointerleaveprograma 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 encontent-link-padding-inline, que leía «vivo» por ensanchar el panel que sí se medía. Resuelto conopenBy: 'hover'. La regla general: antes de creer un «no effect», comprobar que la superficie seguía abierta al medirlo. -
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. -
⚠
eidos-lintno conoce las exenciones que el test de vitest SÍ tiene (medido 2026-08-24 revisando la tanda).src/uix/eidos/lint.test.tsmantiene cuatro conjuntos de exención declarados y razonados —CSS_ONLY_LAYERS(spin-field: una capa CSS compartida que consumen css-field y number-field por identidad estructural, sin morfo POR DISEÑO),WIP_TRACKS,KNOWN_MISSING_MORFOy las raíces competidoras—, peroscripts/eidos-lint.tsno los conoce: sobrespin-fieldmuere con «No morfo file» en vez de eximirlo. El guard de la suite y el script de línea de comandos discrepan sobre la misma ley, y el que se corre a mano es el que miente. Arreglo: que el script importe los mismos conjuntos, o que vivan en un módulo que los dos lean. Preexistente, no de esta tanda. -
⚠ La columna
sizedel censo se apaga cuando el eje de talla SUBE al contrato (medido 2026-08-24 enradio-groupyrating-group). La columna se calcula leyendo bloques[data-size]en el CSS; cuando una escala privada se promueve al TSC conscope: 'size:{k}'—que es la forma CANÓNICA— esos bloques desaparecen de la receta y el censo deja de marcar el eje: el total bajó de 57 a 55 justo al hacerlo mejor. El instrumento premia la forma vieja. No afecta al ratio de alcance, pero sí a la tabla §9 y a cualquiera que la lea para decidir si un componente tiene talla. Arreglo: leer también losscope: 'size:*'del contrato, no sólo el CSS. -
⚠ LA SONDA NO CONGELABA NADA, Y EL GUARD SÓLO CONGELA
transition(medido y ARREGLADO a medias 2026-08-24).scripts/__theming-probe.ts—cuyo gate es «el default NO se movió»— no inyectaba ninguna congelación: una propiedad en transición devolvía el valor VIEJO y una bajo animación en BUCLE devolvía donde el bucle estuviera. Ruido puro sobre el único artefacto que prueba que un commit no mueve píxel.Destapado por
chat-typing, cuyos tres puntos montan el buclepulsedel sistema: 18 diffs deopacitycorriendo la sonda dos veces sobre código idéntico. Con la congelación (transitionyanimation, antes de cada instantánea): 0 diffs sobre 1.568 valores. Arreglado en la sonda.Lo que queda:
scripts/theming-sentinel.tscongelatransitionpero NOanimation, así que un token bajo animación sigue leyéndose donde pille. Es la misma clase que «el guard no fotografíatransform» y tiene el mismo bloqueo: cambiarlo obliga a re-verificar el ledger entero, porque los tokens que SON la animación pasarían a leer muertos y necesitarían la adjudicación «medido sin congelar» que ya usan lostransition-*. -
⚠ EL GENERADOR DE FICHAS PISA LA PROSA ESCRITA A MANO (medido 2026-08-24 sobre 164 fichas regeneradas de golpe). El
--reportdel censo reescribedocs/audit/theming/*.mdentero, y las secciones 1.1–1.4 vuelven a su plantilla: donde un humano había escrito «Ninguno — los cuatro que había (elpadding-blockdel divisor, …) están cosidos», queda «Ninguno.». El veredicto §5 sobrevive (va entre marcadoresveredicto:end), el resto no.Del barrido: 155 de 164 sólo cambiaban la FECHA (inocuo), 7 traían cifras nuevas y correctas o retiraban propuestas ya cumplidas (mejora), y 2 —las dos fichas de
chat-*— perdían prosa medida. O sea que la regeneración es mayoritariamente buena y ocasionalmente destructiva, que es el peor reparto posible: nadie la mira.Arreglo: extender los marcadores tipo
veredicto:enda cualquier bloque que un humano haya ampliado, o que el generador respete una línea que no empiece por su propia plantilla. Mientras tanto, regenerar fichas y commitear sin leer el diff es una pérdida de datos silenciosa. -
⚠ SNAPSHOT-Y-RESTAURA SOBRE UN DIRECTORIO COMPARTIDO PISA A QUIEN ESCRIBA EN MEDIO (reportado y medido por una sesión de la tanda, 2026-08-24). Dos sesiones hicieron
cp -r docs/audit/themingantes de regenerar y restauraron después; la segunda restauró desde un snapshot ANTERIOR al trabajo de la primera, y le devolvió sus dos veredictos a «pendiente» a media faena. Es la hermana del índice compartido, un piso más arriba: el árbol de trabajo también es compartido, y una copia de seguridad tomada antes de que otro escriba es una máquina del tiempo para su trabajo. Respalda y restaura por FICHERO tocado, nunca por directorio. -
⚠⚠ EL ÍNDICE DE GIT ES COMPARTIDO: VERIFICAR EL ÁRBOL INDEXADO NO BASTA (medido 2026-08-24, con ocho sesiones sobre el mismo worktree). Hay UN solo
.git/index. La técnica que esta rama daba por segura —construir el índice desde HEAD con sólo tus hunks y comprobarlo congit diff --cached— protege del árbol de trabajo ajeno, no de la ventana entre mirar y confirmar: si otra sesión hace sureset+adden ese hueco, tugit commitfotografía SU índice. Ocurrido dos veces en una tarde:edc019515(toggle) salió con tres fragmentos derating-groupdentro, y sin la mitad de los suyos; hizo falta007c2af52para repararlo.- La reparación cosió el bloque de
rating-groupcon el detoggley dejó},,en el literal del ledger: cuatro commits de HEAD contheming-sentinel-exceptions.tsque no parsea. El guard R-5.4 no queda en rojo, queda MUERTO, y sólo se ve al hacer checkout limpio (arreglado en88a208644).
La regla que sustituye a la vieja: verificación y confirmación tienen que ir en la MISMA invocación de shell (
git reset && … && git commitencadenado), nunca en dos turnos. Y al cerrar una tanda, comprobar HEAD —no el árbol— con un parseo del fichero compartido.Corolario del mismo día: un árbol de trabajo con restos de una sesión ya terminada hace MENTIR al instrumento sin tocar ningún commit. El guard de
carddaba0/98—la firma del instrumento ciego— por untheming-sentinel.tssucio con el bloque dechat-logDUPLICADO; desde una copia limpia de HEAD,49/98y verde. Antes de creer un0/N, comprobar que el instrumento coincide con HEAD. -
⚠ UN COMPONENTE BARRE DESCENDIENTES Y SE COME EL TOKEN DE SUS HUÉSPEDES (medido 2026-08-24 en
chat-composerdentro defile-upload, verificado en la supervisión).file-uploaddeclara[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.4en los dos casos —los defaults coinciden, así que el VALOR no delata nada—, pero envuelto--chat-composer-disabled-opacitydesde:rootno lo mueve (0.4) y--file-upload-disabled-opacitysí (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-uploades HOY el único componente del catálogo con un barrido de descendientes así (grepsobre 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 supervisandocalendar). Cualquier script del eje que usepage.evaluatemuere conReferenceError: __name is not defineddentro del navegador: el transform de esbuild que traetsxenvuelve las funciones con su ayudantekeepNames, 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.tsy entheming-sentinel.ts.Se lanzan con
nodea secas (Node 24 desnuda los tipos de forma nativa), que es justo lo que hacenpm run theming:sentinel. Sólo los scripts SINpage.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. -
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 delScrollAreaconflex-shrink1 y el panel desborda (scrollHeight 670 vs clientHeight 310): sublock-size: 1pxcomputa 0px y ningún valor del token lo mueve. Conflex-shrink: 0en el mismo nodo el token sigue (1px → 9px). Es además un defecto de píxel: el separador deselectno pinta nada en un panel que desborde. El arreglo (flex: 0 0 autoen[data-select-separator]) mueve píxel → fuera del eje de theming, pendiente. -
Una entrada de receta de FAMILIA no se puede juzgar en la ruta de UN componente (medido 2026-08-24 en
calendar). Su entrada derecipes/base.tses la de las cuatro superficies de rejilla de fechas —range-calendar,month-gridyyear-gridconsumen--calendar-*y no acuñan nada—, pero sus partes llevandata-month-grid-*/data-range-calendar-*y el filtrodata-calendarno las ve: 23 de sus 29 «tokens muertos» eran eso. Conurlsa las tres rutas +extraNodes+ barrido devariant: 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 decalendar-select.cssque existe precisamente para fijar la letra del Button a la del calendario— y[data-popover-trigger]:not([data-archetype='field-trigger'])depopover.cssempatan 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-widthno 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 derange-calendarrenderiza un heading de texto en su lugar, mientrasDateRangePickerimporta los selectores del CALENDARIO (que llevandata-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-groupNUNCA ha pintado (medido 2026-08-23).<SvgDot>escribestyle="width: 1em; height: 1em"en el glifo y un inline gana a cualquier regla NORMAL —sólo un!importantde autor lo pisa, que es justo el arreglo de abajo—: 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» decarousel.item-gap*ydrag-drop.preview-z, pero con arreglo disponible: la forma!importantque 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-groupno tiñe la etiqueta de grupo deshabilitada: la regla es[data-radio-group-label][data-disabled]y soma no estampadata-disableden la parteLabel(el morfo declaradata: []). 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 decheckboxsí 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
:hoverderadio-groupTAPA su tinte inválido: hover (0,4,0) contra invalid (0,3,0), la misma escalera quetextarea(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). -
El centinela no fotografía
transform(medido 2026-08-23 enrating-group):item-hover-offsetmueve untranslateYen 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 demask-imagedebackground. Añadirlo aPROPSobliga a re-verificar los componentes con ledger, así que queda anotado. -
Los dos botones del array de
Form.AutoFieldsdeberían COMPONER elButtoncanónico (destapado por la firma deformdel 2026-08-24, que los dejó como únicos dueños del bloque de acción de la receta).soma/components/form/components/form-auto-fields.svelte:562-568y:583-591los renderiza como<button>PELADOS, sin snippetchildque ofrecer, así que hoy su cromo entero lo escribeform.cssa mano: 23 claves públicas duplicando lo quebutton.cssya sabe pintar (medidas vivas, 23 de 23 en las cinco tallas — no es código muerto, es cromo re-implementado). Y son además perceptualmente MUDOS: el morfo deformdeclara tres eventos y los tres apuntan alprovider, así que ninguno de los dos emite nada. Toca soma (componer elButton) y morfo (darles evento) — es la clase A-112, el precedenteToolbar.Button. Al cerrarlo, las 23 claves se retiran: el censo deformbajaría 64 → 41. Ceder sin componer no es opción — medido, caen al<button>del UA (89,52×36 → 68,61×21 px, bordeoutset, Arial 13,33 px, eje de talla colapsado a 21 px). -
El eje— EJECUTADO 2026-08-25. El diagnóstico era exacto: unsizedeformNO LLEGA a sus acciones<Form size="xs">renderizabaForm.Submitcondata-size="md"—36 px de alto y 16 px de letra en las cinco tallas— porqueform-submit.svelte:20yform-reset.svelte:15declarabansize = 'md'y nada les pasaba la talla del formulario;form.svelte:16-19estampabadata-sizeen el provider y no había contexto. Del eje sólo sobrevivía elrow-gap(12/16/20/24/28 px). ⚠ Quitar el default no arreglaba nada: elButtontiene el suyo — hacía falta el puente. Firmado el puente de densidad calcado 1:1 detoolbar/context.ts: nuevoeidos/components/form/context.ts(Symbol('form-eidos-ctx')+ getter de propiedad para la reactividad),form.sveltepublica elresolvedSizeque ya calculaba, y las dos acciones pierden su default y leensize={size ?? form?.size}— la prop explícita siempre gana, y sin<Form>ancestro se cae en elmddel propioButton, sin re-declararlo. Sólo eidos: cinco ficheros, cero morfo / soma / sema; el contrato de 64 claves y el ledger, intactos. Medido con el chipsizeREAL de la demo: alto −10 / −6 / 0 / +8 / +16 px y letra −4 / −2 / 0 / +4 / +12 px (los dos últimos son elclamp()fluido de--font-size-lg/-xlsaturado a 1280 px de ancho; a 900 px dan +3,05 / +10,1),mdsin mover un valor, y la sonda estándar 0 diffs sobre 832 valores en 7 estados. Doctrina adjudicada: VERBATIM, no la norma tapada enmd— ver §«Anchura vs densidad» al final de esta sección. -
alert-dialogtiene el MISMO agujero, un peldaño (registrado por el barrido de la clase, no medido).alert-dialog-action.svelte:25yalert-dialog-cancel.svelte:24llevansize = 'md'sobre un<Button>;alert-dialog-content.svelte:19/:34/:78resuelve y estampa la talla y no publica contexto (no haycontext.tsen el directorio). Un<AlertDialog size="sm">deja sus botones enmd— justo lo queDialog.Closesí resuelve. ⚠ SusizeesDialogContentProps['size'](types.ts:112reexporta el del diálogo) ⇒ eje de ANCHURA, así que le toca la norma TAPADA, NO el verbatim deform. Verificado además quealert-dialog-content.svelte:75envuelve la SOMAAlertDialog.Content, no la eidosDialog.Content: tampoco hereda el puente del diálogo por la puerta de atrás. -
Form.ErrorSummaryestampa undata-sizeque NADIE lee — tercer defecto de talla del mismo componente, y el único totalmente inerte.form-error-summary.svelte:7declarasize = 'md',:14lo resuelve y:18/:24lo estampan. Verificado por grep: las ÚNICAS cuatro reglas condata-sizede todoform.cssson[data-form][data-size='xs'|'sm'|'lg'|'xl'](:25,:32,:39,:46;mdes la base), y el bloque del resumen (:154-178) no tiene variante de talla. El instrumento lo corrobora —el nodo no mueve un valor en las cinco— con el matiz de que en modoautoel resumen estádata-hidden, así que el peso de la prueba lo lleva el grep. Decidirlo es elegir entre darle escalera o retirar el prop: decisión de producto. Quedó FUERA de la firma del 2026-08-25 a propósito. -
La escalera de fuente de la acción de
formviola el 1:1 universal.lib/recipes/base.ts:1405-1409mapeaaction-font-size-{xs,sm}→--size-xs-font-size,{md,lg}→--size-sm-font-sizeyxl→--size-md-font-size: cada peldaño una coordenada por debajo de su propio nombre, y sólo tres valores distintos estirados sobre cinco. Medido: 12·12·14·14·16, ni el 1:1 dereference.md§«The size→font mapping» (12·14·16·18→20·24→28) ni la escalera de etiqueta — una TERCERA.formno está en las excepciones legítimas. Hoy ese bloque sólo pinta los dos<button>pelados del array, así que la corrige E1 de paso al componer elButton— por eso el +2 px de fuente que E1 mide no es regresión, es el peldaño que manda el canon. -
dialog/context.ts:9cita una sección que NO EXISTE. Remite aTHEMING.md §5 "Subset por componente";src/uix/eidos/THEMING.mdes hoy un stub de redirección y su §5 se llama «The size canon». La sección que la norma necesita vive endocs/theming/reference.md§«Per-component subset» + §«Container→part derivation». El único fichero del repo con el literalSubset por componenteesdocs/decisions/guia-semantica-historica.md:206, marcado como NO autoritativo. Una línea. ⚠ Por esto elcontext.tsdeformcita por título de sección, nunca por número de línea.
Lo que dejó ABIERTO la firma aria-selected de tags-input (2026-08-25) —
CERRADO el 2026-08-25 por la firma «el árbol ARIA de tags-input habla UN
patrón» (estructura B):
- Los dos primeros puntos —el
apg: gridimplementado comolistbox/optiony los doslistboxanidados— no eran dos hallazgos: eran las dos primeras caras de F-1, ya adjudicado como UNA decisión el 2026-07-07 (docs/audit/components/tags-input.md:18:31, «elegir UNO … eliminar el rol duplicado Provider/Control»). Se cerraron JUNTOS con la tercera cara que aquel registro no recogía —elcomboboxsin popup— porque cerrar dos deja el árbol mezclado igual. Resultado: el Control es elgridde UNA fila, la etiqueta esgridcell(que admitearia-selected, así que la firma anterior sobrevive intacta), el Provider se queda sin role y el campo es untextboxen su propia celda. Píxel medido: 0 diffs sobre 2496 valores computados. - El punto del foco que no salía del input era la condición para poder
retirar el
aria-activedescendant(su destino debe ser descendiente del nodo que lo lleva, y una etiqueta nunca lo fue), así que se cerró con él: el resalte mueve ahora foco DOM real sobre la celda — el modelo del propio grid. - El punto de la tinta de tono no era una firma de
tags-input: es de la capa del sistema y se movió a §12 como cuarta ocurrencia de §12.5, con la cifra corregida a (0,4,0) y la atribución al BLOQUE, que es donde vive su decisión.
Lo que ABRE la firma «el árbol ARIA de tags-input habla UN patrón» (2026-08-25):
-
⚠
tag-groupimplementa los NOMBRES del patrón grid, no su ESTRUCTURA. Declara el MISMOapg(morfo/components/tag-group.ts:9) y sus roles songrid(:64) yrow(:137el item,:163el link), pero no declara NINGUNA parte conrole: 'gridcell'y el DOM renderizado lo confirma (medido en/uix/components/tag-group): filas con unbuttoncolgando directamente, y undata-tag-group-labelque es hijo delgridsin serrow. WAI-ARIA:rowexige al menos uncell/gridcell/columnheader/rowheader, ygridno puede poseer nada que no searow/rowgroup. Es el «precedente vivo» que el registro anterior citaba como prueba de que el patrón ya estaba implementado: no lo es. Y ahora los dos hermanos con el mismoapgtienen estructuras distintas, lo cual es una decisión consciente pendiente, no un accidente. -
⚠
search-fieldF-1 sigue abierto y es la MISMA clase que la tercera cara que acaba de cerrarse:apg: comboboxsin popup (docs/audit/components/search-field.md:18:31). El precedente ya existe —uncomboboxsinaria-expanded/aria-controlsno es uncombobox, y el campo desnudo es la respuesta— pero su cita APG sigue apuntando a un patrón que el componente no implementa. -
⚠ El censo de citas APG erróneas sigue SIN REGLA. La propia ficha lo nombra (
docs/audit/components/tags-input.md:38: «F-1 alimenta el censo de citas APG erróneas (consearch-field)») ydocs/audit/components/_system.md:35deja la pregunta escrita: cómo satisfacer «sin patrón APG oficial» (media-player, drag-drop) — ¿apg: 'none — rationale'? Contags-inputcerrado el censo tiene ya un miembro RESUELTO al que mirar, y ocho componentes sinapgesperando la forma. -
El codemod de nombres NO SABE LEER una IIFE, y aborta sobre
avatar(medido 2026-08-24 por el verificador V3′, re-verificado al ejecutar la firma el 2026-08-25).scripts/__names-codemod.tslocaliza el bloque de un componente conblockRange(), que busca una aperturanombre: {(/^\t'?([a-z0-9-]+)'?: \{/gm); el deavataresavatar: ((): RecipeTokenMap => {, así que no casa y el--dryrespondemuro 1 · colisiones: avatar: SIN BLOQUE→ABORTA: un muro está en rojo. Es exactamente la misma ceguera quef68bac4a6corrigió en el censo y en el centinela («los dos lectores leen ya la IIFE»), pero al codemod nadie se la enseñó — yavatarera el ÚNICO componente que lo necesitaba, porque era el único con desviaciones de nombre. Consecuencia real: la firma A-c del 2026-08-25 se ejecutó A MANO, con verificación por grep. Coste, no bloqueo, y hoy sin urgencia (el censo--namesda 0 desviadas, así que no queda cola para el codemod); pero si vuelve a haber cola, esto se arregla ANTES — son unas líneas, con el mismo dedentado que censo y centinela ya tienen. EJECUTADA 2026-08-25 (firma «la mitad gratis»):blocks()devuelve{name, at, end, depth}con offsets REALES del fichero (dedentar virtualmente rompería el pase A, que edita por rango); la IIFE acota su región alreturn {…};condepth: 3y una guarda que LANZA si no lo encuentra. Los muros 2-3 leen por fin las 97 claves de avatar (86 públicas + 11 privadas — el codemod ve las privadas a propósito) y el pase A renombra a\t{depth}: verificado en laboratorio sobre copias (duplicado inyectado a 3 tabs DETECTADO, renombrado de una clave de avatar = 1 línea, y la frontera-hoverintacta). La vía de carga SE QUEDA en tsx: la premisa «el censo corre con node pelado» era falsa —theming-censusimporta../src/uix/morfo/compileSIN extensión y todosrc/uix/morfoestá escrito así; alinear la vía es un barrido desrc/, no del codemod. Queda vivo el riesgo hermano: TRES lectores independientes de la estructura debase.ts(este codemod,theming-census.ts recipeBlock()y el centinela) — la próxima forma nueva de bloque habrá que enseñarla tres veces. -
El centinela elige el VALOR de sonda por el NOMBRE de la clave, y D-TH.6 fabrica nombres que ese chequeo no reconoce (hallazgo propio del ejecutor E-avatar, 2026-08-25).
sentinelFor()(theming-sentinel.ts:692) es una cadena de regex sobre el nombre; su prueba de tinta es/color|bg$|fg$|…|-bg-|…/— lleva-bg-para elbgMEDIAL pero no lleva-fg-. Así que toda clave confgmedial cae al default1234px, que es inválido paracolor:y no mueve nada: el guard la da por muerta. Son cinco en el catálogo y las cinco nacen de la gramática firmada ({parte}-fg-{calificador}):avatar.badge-fg-custom-contrast,chat-message.bubble-fg-in/-out,background.scrim-fg-over-dark/-over-light. Reproducido bajo las condiciones del propio guard (mismo conjunto de 55 nodos, mismo barrido dedata-variant, mismo forzado de talla) sobre la insignia custom real: con1234pxno mueve en ningún combo; conrgb(1, 2, 3)mueve ensolid(rgb(255,255,255)→rgb(1,2,3)). Es la imagen especular del caso que el propio comentario del chooser ya registra (chart.slice-stroke-width, una DIMENSIÓN que cazaba una palabra de color), y es peor que un falso negativo limpio: el veredicto depende de si el valor de respaldo coincide por casualidad con el original — bajo el barrido deavatarcoincide y lee muerta, enchat-messageno coincide ybubble-fg-in/-outleen VIVAS por accidente. Además deja incompleta la razón escrita descrim-fg-over-*en el ledger, que atribuye la ceguera sólo a la puertaon='dark'. Arreglo: añadir-fg-a la cadena, junto a-bg-. No se hizo aquí porque tocar el chooser obliga a re-verificar el ledger entero (la misma condición que el cambio de la congelación); mientras tanto,avatar.badge-fg-custom-contrastva adjudicada en el ledger con la razón medida. EJECUTADA 2026-08-25 (S2 de la GRANDE,b4dd5a906) — y no con-fg-sino con el arreglo que cierra la CLASE: el chooser tipa la sonda por el VALOR por defecto que el guard ya leía debase.ts(literales por sintaxis,var()por vocabulario del sistema con resolución transitiva; la escalera de nombre queda de respaldo). 357 claves cambian de sonda (320 de la clase A). Barrido completo MONÓTONO contra la base S1: sóloavatar+1 ystepper+1 — las dos víctimas medidas — y sus dos entradas exactas retiradas con acta.scrim-fg-over-*siguen muertas por su puerta real y su entrada sobrevive tal cual. Residuo conocido del expediente de easing:checkbox.stroke-ease(uncubic-bezierliteral que la escalera caza por el substringstroke→ sonda de color; preexistente, adjudicada, única entre las 51 de easing).
Anchura vs densidad — cuándo la parte TAPA en md y cuándo hereda VERBATIM
Adjudicado el 2026-08-25 al firmar el puente de talla de form, porque la
doctrina bifurca y una firma tiene que elegir bando.
El parque tiene DOS mecanismos para que una parte reciba la talla de su contenedor, y no son alternativas de gusto:
- TAPADO en
md—docs/theming/reference.md§«Container→part derivation» (norma 2026-06-19). Primer consumidor:Dialog.Close. Se aplica cuando el ejesizedel contenedor es ANCHURA: un diálogolg/xl/fulles más ANCHO, no más denso, y engordar sus controles sería leer mal el eje. - VERBATIM —
toolbar/context.ts, y los puentes debutton-group,card-group,split-buttonytoggle-group. Se aplica cuando el eje es DENSIDAD: el contenedor declara la escala del control y la parte la hereda entera.
La prueba no es la opinión: es la propia receta. Si la receta del contenedor
YA escribió peldaños DISTINTOS para su control en las cinco tallas, el eje es
densidad y la derivación va verbatim. form es el caso canónico — types.ts
documenta su size como «Visual density for form spacing and action
controls» y form.css:25-51 declara los cinco valores distintos de
--_form-action-height / -padding-inline / -font-size. Una receta que ya
escaló su acción en cinco peldaños no está tapando en md.
Corolario para el hermano: alert-dialog hereda su size del diálogo
(DialogContentProps['size']) ⇒ eje de anchura ⇒ le toca la norma TAPADA,
aunque hoy no aplique ninguna de las dos.
⚠ La razón MECÁNICA de que haga falta un contexto (y no baste un selector
descendente) es la misma en los dos bandos, y la escribe toolbar/context.ts:
la receta del Button lee data-size SOBRE el elemento, así que el hijo
tiene que llevar el atributo — el contexto sólo le deja heredar el valor.
Deps: ninguna. Son mejoras del instrumental del eje, ejecutables cuando estorben.
14. Unificar tabs bajo la adjudicación del canal de valor — 2026-08-26
✅ EJECUTADA 2026-08-26 — firma «MeasuredIndicator con nombre poseído», la
tercera aplicación de la doctrina del canal (navigation-menu fue la
segunda). El acta, con los números medidos, cierra esta sección; lo redactado
abajo se conserva porque el diagnóstico fue correcto entero.
Lo abre la firma «(a) completada» de
navigation-menu (§13 arriba y el punto 1 del bloque del registro): un
indicador de tablist es canal de valor, no superficie de tema, y tabs
citó a navigation-menu como precedente cuando firmó eso por escrito. Ahora el
precedente está ejecutado y tabs es el que queda fuera de su propia doctrina.
El hecho. La capa COMPARTIDA MeasuredIndicator
(src/uix/soma/layers/measured-indicator.svelte.ts:80-83) escribe cuatro
medidas por instancia con nombres SIN prefijo de componente:
--indicator-{x,y,w,h}. Sus consumidores son tabs-provider.svelte.ts:454 y
radio-group-provider.svelte.ts:353. Los lectores son
src/uix/eidos/components/tabs/tabs.css:312-314 y la capa
src/uix/eidos/lib/sliding-indicator.css:32-34.
Por qué NO lo caza ningún guard hoy, y es lo interesante. La ley del espacio
cerrado (recipe-css-contract.test.ts) sólo mira el prefijo PROPIO del
componente (--{c}-*); un nombre sin prefijo es vocabulario ajeno y se le
escapa. El censo, por la misma razón, los puntúa global: hoy son 2
entradas del ledger de deuda (global · tabs.css · [data-tabs-indicator] · block-size y · inline-size, scripts/theming-census-debt.ts:1242-1243). O
sea: navigation-menu fue visible porque llevaba prefijo, y tabs es invisible
porque NO lo lleva — el guard premió al que nombraba mejor.
Lo que habría que decidir (la firma, no el codemod):
- ¿A qué nombre van? Un canal de una capa compartida no puede ser
--_{c}-*sin más: lo escribe la capa, y lo leen DOS componentes más la propia capa. Candidatos:--_indicator-*(privado de capa, un solo nombre para los tres) o--_{c}-indicator-*por componente, que obligaría a la capa a saber su anfitrión. - Qué pasa con
--indicator-duration/--indicator-ease(sliding-indicator.css:36-39): NO son canal — nadie los escribe en runtime, son pomos de forma sin declarar. Van por la puerta del contrato, no por ésta. - Las 2 entradas
globaldel ledger salen o cambian de clase con el renombrado; hay que medir si el suelo se mueve (ennavigation-menuno se movió: 1 knob depublicachannel, reach yatHundredintactos).
Dependencias / trampas ya medidas. El barrido git ls-files por nombre es
obligatorio y no basta con el literal: en navigation-menu la forma
--…-indicator-{x,y,w,h} (llaves) escondía tres menciones más que el grep del
nombre suelto no veía. rtl-lint.test.ts:81-92 lleva estos nombres en un
fixture. Y la verificación que vale es DINÁMICA: inline-size va transicionada
en estas partes, así que una lectura inmediata devuelve el valor VIEJO — hay que
congelar transiciones o el probe informa «no se movió» de todo.
Deps: ninguna. Ejecutable cuando se quiera cerrar la clase.
14-bis. Acta de ejecución — 2026-08-26
Lo decidido en el punto 1: --_{c}-indicator-{x,y,w,h}, no
--_indicator-* de capa. Porque el rect es un hecho de CADA INSTANCIA del
anfitrión: la capa sostiene la pluma, no es la dueña. Un solo nombre de capa
para dos anfitriones habría vuelto a poner una medida por instancia en un
espacio que nadie posee — el mismo defecto, un piso más abajo.
El paso del kebab, mínimo: MeasuredIndicatorOpts gana un campo,
component: string, y los dos hosts pasan tabsMorfo.kebab /
radioGroupMorfo.kebab — nunca un literal. El nombre del canal queda atado
a la declaración de morfo que lo posee, así que un renombrado de kebab viaja
solo. Nada más se movió en la capa.
El cableado del CSS COMPARTIDO (lib/sliding-indicator.css, que no puede
deletrear el nombre del anfitrión): cada receta declara su alias local explícito
—--_indicator-x: var(--_radio-group-indicator-x)— y el fichero compartido lee
el alias. Es la forma del puente THM-2 y la que calendar-surface.css ya usa
para --_calendar-surface-accent: contrato EN el artefacto, greppable en un
paso. tabs no consume la capa (tiene su bloque propio), así que lee el nombre
poseído directo.
Hosts, barridos: exactamente DOS (git grep "new MeasuredIndicator"), los
dos ya redactados arriba. El barrido de retirada confirmó la trampa de las
llaves otra vez: el grep del nombre LITERAL veía 8 menciones, y el de la forma
--indicator-{…} más el genérico --indicator- destapó tres comentarios de
código vivo más (radio-group.svelte:21, morfo/components/radio-group.ts:140,
tabs-provider.svelte.ts:423). Consumos vivos de los cuatro nombres viejos tras
el flip: 0.
El punto 2 se respetó: --indicator-duration / --indicator-ease siguen
bare y sin tocar — son ranuras de FORMA de la capa (un tema las mueve), no
canal. El _ es lo que separa las dos especies dentro del mismo fichero, y así
queda escrito en su cabecera. Su puerta sigue siendo la del contrato.
_(Nota 2026-08-26, item 2 53810cb5e: superado — «un tema las mueve» resultó
CONTRAFACTUAL (el pin en :root nunca alcanzó, medido) y la cabecera viva
dice hoy lo contrario: ambas especies llevan _ y lo que las separa es su
RÉGIMEN. Acta en el hueco flagged, más abajo.)_
El punto 3, medido: el suelo sube. tabs 97 % → 100 % (global
2 → 0, canal 0 → 2, 77 knobs y 79 claves de contrato intactos: no se acuñó ni se
retiró una sola). Catálogo: atHundred 65 → 66, global 763 → 761, canal
38 → 41, reach global 73 % intacto, --names 4566 · 0 desviadas
(intacto: base.ts no se tocó). Ledger de deuda: las 2 entradas de tabs
RETIRADAS —la clave entera desaparece— con acta en la cabecera de
scripts/theming-census-debt.ts; 1154 → 1152 · 0 NEW · 0 STALE.
La trampa de clasificación, prevista y resuelta — con la causa CORREGIDA
por el adversarial el mismo día: la premisa «la línea-alias esquiva la regla y
cae a residuo» era CONTRAFACTUAL — el alias de §14 (--_indicator-*) no lleva
prefijo propio y el censo lo descarta antes de coleccionar privados: un knob que
lo lea clasifica global, rojo con la razón CORRECTA. El caso que JUSTIFICA la
extensión es tooltip (alias con prefijo propio sobre canal propio: reclasificó
exception → channel, lo que su comentario firmado siempre afirmó). El tercer
paso de scanComponent resuelve el canal TRANSITIVAMENTE (molde de derivesFromPublic:
memoizado, a prueba de ciclos, every en todo el camino). La mutación de
rigor que lo delimita: un alias hacia un privado que SÍ está declarado NO
clasifica canal — resuelve al valor de esa declaración, cuyas refs son vacías (un
literal) o no son canal; refs.length > 0 es la línea que lo impone. Y el orden
de pasos es carga estructural: el segundo (deriva de un público) corre ANTES, así
que --_tooltip-max-width, que lee var(--_tooltip-content-max-width-override, var(--tooltip-max-width)), sigue siendo público.
Un efecto colateral, medido y adjudicado con acta: la extensión reclasificó
un knob ajeno, tooltip.css:27 (inline-size: var(--_tooltip-width)), de
exception a channel. No es regresión, es validación independiente: el
comentario firmado de esa declaración ya decía «per-instance VALUE CHANNEL» con
esas palabras, y --_tooltip-width resuelve a dos nombres que nadie declara.
Faltaba el mecanismo, no el criterio — que es exactamente lo que el cuarto paso
del censo dice de sí mismo («una firma sobre un knob que la máquina ya adjudicó
es un acta sin nada que firmar»). tooltip no mueve su alcance (94 %: las dos
clases están fuera del ratio). Acta en docs/audit/theming/tooltip.md §2-quater.
Verificación. Dinámica por CDP con transiciones CONGELADAS (el aviso de esta
misma sección: inline-size va transicionada y una lectura inmediata devuelve el
valor VIEJO), en los dos hosts. tabs: el estilo inline lleva sólo los
nombres poseídos, los cuatro viejos computan cadena vacía, y al cambiar de
pestaña translate 0px → 183.719px e inline-size 99.906px → 71.359px — el
indicador cae EXACTO sobre el trigger activo (x 784 vs 784, w 71 vs 71). Control
negativo: escribir los cuatro nombres viejos en :root no mueve nada.
Control positivo: --_tabs-indicator-w en el elemento → 333px. radio-group
manifiesta su indicador sólo en la variante segmented (la píldora
[data-radio-group-selection-indicator], que el envoltorio auto-monta ahí y en
ningún otro sitio): el alias computa los mismos cuatro valores que el nombre
poseído, la píldora desliza (translate 4px 32px → 4px 60px) y cubre exacto el
item marcado (x 793 vs 793, w 102 vs 102); mismo control negativo a cero y el
positivo probando la CADENA del alias (101.703px → 222px a través del CSS
compartido).
Estática: sonda estándar de tabs antes/después, 2528 valores computados en 10
estados, 0 diffs y 0 nodos perdidos. ⚠ Y hay que decirlo: esa sonda es CIEGA
al canal — lee valores computados, no nombres de var, así que un flip de nombre
correcto le sale idéntico por construcción. Su 0 no prueba el flip; prueba lo
CIRCUNDANTE, que es para lo que se corre. Quien prueba el flip es la dinámica.
Verde lo demás: recipe-css-contract + rtl-lint + theming-reach-floor +
component-visual-attrs + generated-css + las suites de los tocados (10
ficheros, 154 tests), centinela de tabs 70/79 idéntico, component:audit
162 PASS / 4 NEEDS-WORK / 0 BROKEN idéntico, docs:check 0/0 en 816
docs, npm run check sin errores atribuibles.
Lo que queda flagged, sin acuñar: —
✅ RESUELTO 2026-08-26 (item 2, firma (a) del autor): la adjudicación no fue
ni contrato de componente ni vocabulario de capa declarado — son el ALIAS de
la capa, la misma especie que --indicator-duration /
--indicator-ease siguen siendo dos públicos de FORMA sin prefijo de dueño--_indicator-x cuatro líneas más arriba, sólo
que opcionales-con-fallback. Flip a --_indicator-{duration,ease} (1 escritor:
radio-group.css:320 alimentándolos desde sus claves de CONTRATO
segmented-transition-*; 1 lector: lib/sliding-indicator.css). El reparto de
propiedad que la firma deja escrito: la CAPA posee QUÉ propiedades deslizan (y
su colapso reduced-motion), el ANFITRIÓN posee A QUÉ VELOCIDAD vía contrato; el
alias es la costura. Contrafáctico MEDIDO antes de tocar: fijar el nombre bare
en :root a 9s no movía el pill NI ANTES del flip (la declaración de la receta
gana la cascada) — la ortografía bare prometía un pomo que la cascada negaba;
no se retiró superficie alguna. Post: píxel idéntico (0.18s ×3 + 0.12s fade,
mismo bezier) · bare INERTE y computando vacío en el pill · la clave de
contrato ALCANZA (7s) · el alias en :root también inerte. Los dos comentarios
que afirmaban «themeable, so bare» reescritos; la prosa del sentinel
actualizada al nombre vivo (clave intacta). Lint 0 invalid, reach-floor 5/5,
censo neutro (sin prefijo reconocido no entra al clasificador, con o sin _).
Fe de erratas 2026-08-27 — el ABSOLUTO DE CASCADA, y por qué el pase adversarial no lo cazó entero
El hecho. El pase adversarial ab54a8821 corrigió el absoluto falso «so no
theme or consumer stylesheet can win them» en los cinco READMEs de §15.
Un barrido posterior (2026-08-27) encontró la misma especie en DOCE sitios
más — contados: los 5 de docs/ que esta acta cierra (dos de ellos por
ANOTACIÓN fechada, no por reescritura: son actas del 26 y una de ellas declara
por escrito que no se reescribe) y los 7 fuera de docs/ de la tabla de
abajo, que siguen sin tocar. El motivo de la fuga es de MANDATO, no de
descuido: el pase estaba acotado a §15 (la capa flotante), y la mitad de los
supervivientes son de §14/(a) (MeasuredIndicator) o de otros mecanismos
inline.
La regla, escrita de una vez para no volver a discutirla: una escritura
inline gana a toda regla NORMAL, y pierde ante un !important de autor.
Eso no es una laguna de la doctrina — docs/canon/recipe-contract.md:174 ya
lista «bypassing a soma inline style» entre las tres formas legítimas de
!important. El canon nunca creyó el absoluto; lo escribió mal la prosa de
componente. Y la consecuencia operativa es la que importa: cuando ese
!important llega a un canal de VALOR, no lo tematiza — lo CONGELA. Medido
en Chrome sobre el indicador de navigation-menu (1440×900, transiciones
congeladas): con el canal pineado a 333px !important, soma sigue midiendo el
rect verdadero (107 → 108,56px) y left sigue deslizando (111 → 0px), así que
el subrayado pinta 333px bajo triggers de 107px. Llega, y al llegar rompe.
Por eso el token-que-miente sigue siendo token-que-miente: su única ventana de
alcance lo desincroniza.
Los siete sitios que quedan por corregir (ninguno se tocó en esta pasada
—están fuera de docs/, que era el alcance de quien escribe esta acta—; la
redacción propuesta está medida y lista, y NINGUNO mueve píxel ni número:
son prosa y cadenas de razón del ledger):
| # | sitio | frase |
|---|---|---|
| A-1 | src/uix/soma/components/navigation-menu/README.md:182 |
«*a stylesheet — yours or a theme's — can never win against them*» — el sexto README de la especie de §15 |
| A-2 | src/uix/eidos/components/navigation-menu/README.md:103-104 |
«ninguna hoja de tema puede ganarles» — se contradice en :112, que ya dice «sólo una regla !important de autor llega» |
| A-3 | scripts/theming-sentinel-exceptions.ts:802 (drawer.content-width-sm) |
«*so no cascade can beat it*» — su propia cabecera :798-801 sí acota, «cannot win from :root» |
| A-4 | scripts/theming-sentinel-exceptions.ts:1231 (radio-group.dot-size) |
«*an inline declaration beats any stylesheet rule*» — y la MISMA entrada propone arreglarlo con un !important |
| A-7 | src/uix/eidos/components/radio-group/README.md:122 |
«un inline gana a cualquier regla» |
| B-1 | src/uix/eidos/components/color-swatch/README.md:76 |
«la declaración inline gana siempre» (medida acotada a :root, razón sin acotar) |
| B-3 | src/uix/eidos/components/background/README.md:333 |
«no lo alcanzaría (el inline gana)» |
Los CINCO de docs/, cerrados — pero de DOS maneras distintas, y la
diferencia es doctrina, no estilo:
- Corregidos EN LÍNEA (prosa viva, sin fecha propia que proteger):
next-features.md:2316·docs/audit/theming/radio-group.md:170·docs/audit/theming/background.md:128. - ANOTADOS, NO reescritos — son actas FECHADAS, y a un acta fechada se le
AÑADE una entrada con su fecha que dice qué medida posterior la invalida,
mientras el texto original se queda:
docs/process/CONTINUE-theming.md:412— §«Las leyes nuevas del día» del handoff del 26, primer punto, con su anotación justo debajo — ·docs/audit/theming/color-swatch.md:89, cuyo párrafo declara por escrito dos líneas más arriba que «*la medida de abajo fue correcta con el nombre de su día y no se reescribe*». ⚠ La primera pasada del 27 reescribió los dos en línea, de modo que dos actas pasaron a decir algo que su día no dijo y sin citar el original; se revirtieron a su texto de origen (613215fe7para la ley) y la corrección bajó a una anotación fechada. Queda como precedente: una fecha en la cabecera es una promesa de que el texto de abajo es el de ese día.
Lo que NO hay que tocar, porque estaba bien ANTES del pase adversarial y
sigue estándolo: src/uix/eidos/lib/recipes/base.ts:8179-8181 (el acta
load-bearing de la firma, ya nombra el !important) · recipe-contract.md:311-315
(acotado por medida) · sliding-indicator.css:30-33 y radio-group.css:313-322
(acotados a :root, y ya escriben el argumento de la CONGELACIÓN) ·
tabs.css:293-294 (dice «fijarlo rompe el deslizamiento», que es el argumento
correcto) · y los cuatro cuerpos de commit de2e136a0, 53810cb5e,
6499eb2a4, 8ac23047f, que no necesitan fe de erratas: ninguno afirma el
absoluto.
Ninguna firma queda invalidada. La de de2e136a0 sale REFORZADA (pasa de
una prueba a dos: el nombre llegaba sólo donde pintaba 0px e invisible, y ahora
además llega rompiendo). El item 2 de 53810cb5e está intacto y su prosa
también: su mecanismo es otro (:root vs declaración propia del elemento, es
decir herencia vs declaración) y ya venía acotado por escrito con la palabra
:root; re-medido el 27, su contrafáctico aguanta incluso bajo !important.
15. La capa flotante publica cinco medidas con nombre público sin dueño — 2026-08-26
✅ EJECUTADO 2026-08-26 (firma A del autor — §14 literal). Cuarta
aplicación de la doctrina «la capa es la pluma, no la dueña»:
FloatingContentOpts gana component (kebab del morfo, nunca literal — 11
hilos de creación en 9 anfitriones: combobox · context-menu ×2 · dropdown-menu
×2 · link-preview · menubar · popover · select · sidebar · tooltip), y las
cinco medidas aterrizan como --_{host}-floating-*. El SEXTO nombre que el
expediente no contaba (--floating-native-offset, rama nativa) se adjudicó
canal INTERNO de capa (--_floating-native-offset: lo escribe la capa en su
propio wrapper y lo leen solo sus reglas foundation — nunca hecho del host).
El lector COMPARTIDO (preset scale-fade de motion) lee el alias neutro
--_floating-transform-origin que cada receta anfitriona declara desde su
canal (cableado sliding-indicator, 9 declaraciones). getFloatingContentCSSVars
MUERTO (abajo, punto ii de la firma del codemod). Dos matices que la ejecución
destapó: (a) el content de MENUBAR compone DropdownMenu — su canal es
--_dropdown-menu-floating-* y el alias de menubar se acota al PANEL (el
canal sigue al componente que POSEE la composición flotante; README corregido
en esa dirección); (b) split-button y palabras leen el canal AJENO de su
composición (dropdown-menu / menubar) en el punto de composición, anotado en
cada sitio. Verificación: suites 96+60+78 · eidos-lint ×10 con 0 invalid ·
reach-floor 5/5 (ledger 0 NEW/0 STALE) · component:audit 162 PASS idéntico ·
check 0 atribuibles · dinámica CDP 9/9 anfitriones (canal vivo con px por
instancia; los nombres VIEJOS computan VACÍO en todos; positivo: tooltip
max-block-size 525.033px == canal 525.0326px, combobox ídem (precisión del
adversarial: la igualdad exacta de combobox depende del viewport — en pantallas
altas gana el token 22rem del min() POR DISEÑO; re-medido a 1440×620:
canal 158.34375px → max-block-size 158.344px exacto), panel de menubar
anchor-width 886px = la barra; negativo: fijar los viejos en :root no mueve
nada). ⚠ Trampa del instrumento reconfirmada: el popout de sidebar montado SIN
ANCLAR da undefinedpx — comprobar que la superficie estaba ABIERTA antes de
creer una lectura.
Pase adversarial (2026-08-26, tres Opus con mandato de refutar — A1
data-ready · A2 §15 · A3 item 2; informes en el scratchpad de la sesión,
advf/a{1,2,3}/informe.md): las TRES firmas se sostienen en lo sustantivo
(A1 7/7 con 4 refutaciones intentadas y fracasadas; A2 8/8 con refutación
PARCIAL solo de prosa; A3 8/8 con la fecha refutada). Toda refutación aceptada
fue de PROSA y está corregida en este mismo lote. Cosecha para la cola del
autor, registrada sin ejecutar:
- A1-G1, la GRIETA:
el enlace— ✅ EJECUTADA la firma C (autor, 2026-08-27): LAS DOS MITADES. (1)part.states[]/props↔ fuente del provider va por CADENA sin tiparstates/partsESTRICTOS en ambos niveles (StateNameOf<M>desdepart.states[]con terminador de profundidad; cláusula[Names] extends [never] ? nevermolde dedir— cazó en compilación un QUINTO defecto que ningún censo podía contar:knobregistrandostates: {}sobre un morfo sin estados);propsquedaSourceMapCON ACTA en el propio tipo (v.propRef tira el literal; el arreglo arrastra 3 builders en fichero de otro eje). (2) El CENSOsource-census.test.ts: 2 GATES (huérfana nueva · fuente muerta per-parte) + advisory 78 filas nominales c1a/c1b/c2 para adjudicación del autor + excepciones-vivas + negativos probados en rojo. Cosecha de defectos REALES retirados: buttonstates.loading(error de cubo, vivía de duplicación) · tabs/radio-groupstates.value(huérfanos) · media-player rate-buttonprops.label(fuente muerta + comentario falso) · knobstates:{}. Ciclo completo expediente+C1+C2+adversarial+C1-fix (Opus ultra): el adversarial refutó el descubrimiento del censo (partes por VARIABLE invisibles, ≥4 filas falsas en advisory) y C1-fix lo corrigió con CONVERGENCIA contra el censo independiente (props 602==602; filas opacas estampadas ⚠ UNCERTAIN, no afirmadas). TRES COMPROMISOS de la firma, vigentes: (i) generificación destateRef/propRef/mapRefcomo eje propio al liberarsetypes.ts→ cierraprops(594/729 claves); (ii) flip del censo a gate BIDIRECCIONAL cuando el autor adjudique las 78 filas advisory (cada c1a se cierra registrando la fuente O retirando la declaración que miente — el tooltip deshabilitado mudo está ahí); (iii) wave c1b→bolsa (36 escrituras manuales) bajo la ley de única-tubería de P0. Deudas nombradas: las deps de condiciones dekeyboard[]siguen invisibles aCompiledPart.depsy la población CRECIÓ con el wave keyboard (dato vicen-42: prop-equals ×7 + activationMode) — el censo las une por AST con retirada fechada; secuenciar con el dueño decompile.ts·StateNameOfvive en runtime, su casa natural esmorfo/selectors.ts(colocación) ·MediaPlayerRateButtonProvider.labelhuérfano (anotado en código) · hallazgo lateral del adversarial:MediaPlayer.RateButtonNO SE RENDERIZA en ningún sitio del repo — el demo lo nombra en prosa y no lo monta; por eso su defecto vivió invisible. - A1-G2 + A3-H3:
eidos-lint-allno recorreeidos/lib/*.css(la puerta anti-flash desliding-indicator.css:66no la audita ningún linter) ydata-sliding-indicatorno está enEIDOS_ONLY_ATTRS. Emparentado: un consumidor futuro de la capa que NO declare los alias de forma dejaría entrar un pin de:rootpor el fallback (hoy 0 consumidores así; se cierra declarando defaults de capa sobre[data-sliding-indicator], como ya hacemenu-indicator.css). - A2-H5:
--_{host}-floating-available-widthse ESCRIBE en los 9 anfitriones y no lo LEE ninguna receta — preexistente (el bare tampoco tenía lector), ahora multiplicado por nueve. ¿Canal sin consumo o hueco de receta? - A2-H6: la rama NATIVA no publica
available-*(sin middlewaresize) — degradación documentada de diseño, sin defecto observable; queda dicho. - A3-H4: la regla 2 de capas compartidas (
eidos/components/README.md:322, «un token público de capa + una ranura privada») no la cumple NINGUNA capa viva (sliding-indicator,menu-indicator,list-surface). No es regresión del item 2 (lo acerca a la regla); la contradicción doctrina↔catálogo espera adjudicación.
Expediente original: el gemelo del
defecto que §14 acaba de cerrar, un piso arriba y con más superficie:
src/uix/soma/layers/floating/floating.svelte.ts:300-336 publica por instancia
--floating-{transform-origin, available-width, available-height, anchor-width, anchor-height} — una capa compartida escribiendo medidas de instancia bajo
forma PÚBLICA sin dueño, la especie exacta de los --indicator-* retirados.
Los leen ~8 recetas (popover.css:132,157 · tooltip.css:22,45 ·
select.css:192,205 · combobox.css:178,191 · menubar.css · split-button ·
palabras-chrome.css:161 · generated/base.css:8872). Invisibles a la ley del
espacio cerrado por la misma razón: esa ley audita --{c}-* y un nombre sin
prefijo de componente es vocabulario ajeno.
La diferencia que hace este expediente MÁS grande que §14: aquí el host no es
uno por instancia de capa — el posicionador flotante sirve a MUCHOS componentes
a la vez, así que «el namespace del host» exige que la capa reciba el kebab de
CADA superficie que posiciona (la forma ya existe: §14 le enseñó a
MeasuredIndicator a recibirlo del morfo). Y getFloatingContentCSSVars
(floating/utils.ts:18, export muerto ya registrado en §13) construye
--${name}-anchor-width por plantilla — el mismo expediente decide si muere o
se alinea. Dimensionar antes de firmar: ~8 recetas × N sitios, la cadena de
verificación dinámica de §14 sirve de molde.
16. El guard vive donde NACE el valor — el eje audita MEDIO espacio (A-1) — 2026-08-27
✅ EJECUTADA 2026-08-27 — firma «la capa sostiene la pluma», la quinta aplicación de la doctrina del canal de valor (§14 la tercera, §15 la cuarta). El acta, con los números medidos, cierra esta sección en §16-bis; lo redactado abajo se conserva entero porque el diagnóstico fue correcto — incluidas las dos cifras que el acta corrige, que se corrigen anotándolas.
Anotación 2026-08-27 (cierre) — el párrafo de estado que sigue decía «PENDIENTE — redactado, NO ejecutado» y era cierto la mañana de ese día. Se conserva como el acta que es; el estado de HOY es el de la línea de arriba.
PENDIENTE — redactado, NO ejecutado. Dimensionado y medido el 2026-08-27 con aguja propia; nada de lo de abajo se ha tocado en el código. Va PRIMERO, antes que §17 (C-1); las cuatro razones, al final de esta sección.
El problema, en tres líneas. El censo, el ledger, R-5.1 y la ley del espacio
cerrado leen src/uix/eidos/components y nada más
(scripts/theming-census.ts:86, const ROOT). Quien ESCRIBE la custom property
por instancia vive en src/uix/soma y src/arts, y ningún instrumento del eje
lo ha enumerado jamás. El guard mira el lado que LEE; el valor nace en el lado
que ESCRIBE.
Y eso no es una observación nueva — es la LEY que este repo adoptó el mismo
día. El commit 05dcb4db7, «LEY — web/routes congelado, y la consecuencia:
el guard vive donde NACE el valor», la firma para el eje P1 con este argumento
verbatim: los instrumentos «validan NAVEGANDO a
web/routes/uix/components//+page.svelte, de modo que el 161/161 que este
handoff cita mide un árbol que se va — no es evidencia sobre el contrato».
A-1 es esa frase traducida al eje theming: el 73 % se mide sobre el lado
lector, y el lado escritor nunca entró en el denominador.
El censo MEDIDO — aguja propia, 2.014 ficheros de soma+arts, 4 formas de escritura
| clase | nombres | sitios | qué es |
|---|---|---|---|
| A · token que MIENTE | 1 | 1 | declarado en el contrato y escrito inline |
| B · forma pública sin contrato | 15 | 22 | --{c}-x que base.ts no declara |
C · --_* |
30 | 48 | el destino de la doctrina — nada que hacer |
| D · vocabulario SIN DUEÑO | 3 | 5 | la especie --indicator-* de §14 |
| (ruido) | 3 | 16 | --- de tabla markdown · --state en un comentario · --ty de un fixture del spring |
49 nombres reales · 76 sitios. ⚠ Dos cifras previas corregidas aquí:
- El barrido anterior dio 52 y acertó el total bruto, pero no descontó el ruido — son 49.
- Donde el handoff del 27 escribió «49 escrituras reales» hay que leer «49 nombres»: las escrituras son 76.
Radio de explosión CERRADO: src/uix/blocks y src/packs escriben 0.
soma+arts es el universo completo — no hay una tercera mitad esperando.
Los tres hallazgos que el barrido previo no tenía
1 · Sólo 3 de los 19 nombres (A+B+D) tienen LECTOR. Los 15 de clase B
tienen CERO lectores: ni en eidos, ni en src/, ni en el repo. Verificado con
la aguja y recontado con grep crudo por lo extremo del número — cada
aparición es una escritura, un comentario, una fila de README o una mención en
docs. Los tres que sí se leen: --drawer-overlay-opacity (drawer.css:51) ·
--tree-depth (tree-view.css:99,179) · --gp-current-gradient
(gradient-picker.css:61,73).
Consecuencia: los 15 no son «tokens sin declarar». Son una superficie pública publicada que el framework no consume — sólo un consumidor externo podría leerlos, que es exactamente lo que sus READMEs le invitan a hacer.
2 · Son QUINCE READMEs con ## CSS Variables, no cinco. Cinco publican los
nombres de clase B (command 1 · dialog 2 · drawer 4 · scroll-area 4 ·
toast 4 = los 15 exactos, más 2 de clase A). Y la doctrina correcta ya existe
al lado: popover, tooltip, menubar, context-menu y link-preview
publican --_{host}-floating-* diciendo por escrito que son internos y que soma
los escribe inline por instancia (§15). La misma casa hace las dos cosas, y
docs/architecture/soma-architecture.md:837-841 lista las dos formas en el
mismo bloque: es el acta de una migración a medias.
3 · La clase A no es hipotética — es un fallo de pintura VIVO, y ya está
escrito en el fichero de excepciones del propio eje.
scripts/theming-sentinel-exceptions.ts:815 (entrada drawer.overlay-bg) dice
palabra por palabra que el velo del drawer no pinta nada hoy. Confirmado
estáticamente:
drawer-provider.svelte.ts:372→get overlayOpacity(): numberdevuelve1/0/ una fracción, sin unidaddrawer-provider.svelte.ts:1145→ la escribe INLINE en el overlaydrawer.css:49-51→background: color-mix(in srgb, var(--drawer-overlay-bg) var(--drawer-overlay-opacity), transparent)base.ts:1474→ el contrato dice'overlay-opacity': '62%'— el contrato tiene razón; la escritura inline lo rompe
⚠ Corrección del dimensionado: ese último puntero se midió primero como
base.ts:1201, que es la clave de dialog, no la de drawer. El bloque
drawer de base.ts va de :1457 a :1522 y su overlay-opacity está en
:1474. Verificado al redactar esta sección.
color-mix() exige un <percentage> en esa posición: un 1 pelado invalida la
función entera ⇒ el background cae a transparente. Y aunque la unidad fuera
correcta, el inline gana a toda regla normal: un tema que mueva
--drawer-overlay-opacity no mueve nada. R-5.4 es el guard de esta especie y
su propio mensaje la nombra («a token that moves nothing … is a token that
lies»), pero corre A MANO contra web/routes — el árbol que 05dcb4db7 acaba
de congelar. Aquí el guard no falló: la EXCEPCIÓN se tragó el bug. Está
adjudicado en prosa, no arreglado.
MEDIDO EN NAVEGADOR el 2026-08-27 (sesión del eje, Chrome real sobre
/uix/components/drawer con el cajón abierto, leyendo el computado del
[data-drawer-overlay]; el :root dice 62% y el inline dice 1):
--drawer-overlay-opacity en el nodo |
background-color computado |
|---|---|
1 — lo que soma escribe hoy |
rgba(0, 0, 0, 0) · transparente, el velo NO pinta |
100% — el mismo número, con unidad |
color(srgb 0 0 0 / 0.658824) · pinta |
62% — lo que dice el contrato |
color(srgb 0 0 0 / 0.408471) · pinta |
un tema en :root a 20%, con el inline puesto |
rgba(0, 0, 0, 0) · no llega |
Las dos mitades del defecto quedan probadas por separado y en la misma serie:
la unidad (la fila 2 pinta con el MISMO número, sólo por llevar %) y la
inalcanzabilidad (la fila 4). El control positivo del contrato —fila 3—
es además el valor que el arreglo debe dejar. ⚠ Sin captura: el panel del
navegador no componía frames en esa sesión; la prueba es el computado.
Opciones, con su coste y su precio
| qué | coste | precio | |
|---|---|---|---|
| 0 · No hacer nada | — | 0 | El velo del drawer sigue sin pintar. 15 nombres siguen publicados como API que nadie lee. Y el eje sigue midiendo el 73 % sobre medio espacio, sin que ningún número futuro sepa que le falta la otra mitad. |
| 1 · Sólo la aguja | guard nuevo sobre soma+arts que clasifica contra el contrato (--_* ok · en contrato = miente · resto = rojo) |
~120 líneas + registro de adjudicación. No ~40: la aguja de usar y tirar ya son 121 líneas, y no adjudica nada | Nace en ROJO con 19 filas. Hay que adjudicarlas o taparlas el mismo día. |
| 2 · Aguja + cierre de B y D | lo anterior + el codemod a --_{c}-* |
+5 componentes · 22 sitios de escritura · 5 secciones de README · 1 bloque de soma-architecture.md. Sin lectores que reparar (son 0) ⇒ el rename es libre de riesgo de pintura |
Retira 15 nombres documentados. |
| 3 · Todo + clase A | + arreglar --drawer-overlay-opacity |
+ la unidad (%) y retirar el inline, o un !important por la válvula de recipe-contract §3 |
MUEVE PÍXEL: el velo pasa de transparente a pintado. |
Recomendación
Opción 3, en ese orden — y no es una decisión nueva: es la QUINTA aplicación
de una doctrina ya ejecutada cuatro veces. §14 firmó la forma exacta
(--_{c}-indicator-{x,y,w,h}, con component: string tomado del kebab del
morfo, «la capa sostiene la pluma, no es la dueña») y §15 la aplicó por cuarta
vez a la capa flotante. Clase B y clase D son el mismo animal un piso más
abajo. §14 ya explicó por qué ningún guard los ve —«el guard premió al que
nombraba mejor»—; A-1 completa la frase: el guard premia al que se deja LEER,
y estos no se leen, se ESCRIBEN.
Exige FIRMA del autor por dos cosas, y sólo dos:
- arreglar el velo del drawer MUEVE PÍXEL — pasa de transparente a pintado;
- retirar los 15 nombres publicados rompe una API documentada, aunque nunca haya tenido implementación del lado lector (0 lectores, medido y recontado).
El resto es EJECUTABLE bajo la doctrina vigente: la aguja en sí y el rename de clase D son §14 aplicada donde ya se aplicó dos veces.
Cuál va PRIMERO — A-1, por cuatro razones
Orden propuesto: aguja de A-1 → clase D (ejecutable) → FIRMA del velo del drawer + retirada de los 15 nombres → clase B → y sólo entonces §17 (C-1), cobrando de paso su firma de una línea.
- Es lo único de la cola que mueve un píxel hoy. C-1 es código muerto y trampas latentes; A-1 es un fallo VIVO más un token contratado que ningún tema puede mover.
- Es la aplicación directa de la ley de
05dcb4db7. C-1, en cambio, es afinar un guard del lado que se OBSERVA — la mitad que esa ley acaba de degradar. - C-1 no puede cerrarse bien antes. Su violación más nítida
(
scroll-area: contrato--space-2-5escalado contra un8pxliteral) vive en soma, donde una aguja de corolario 2 escrita hoy no mira. Escribir C-1 primero es construir un guard sobre medio espacio y cobrarlo como verde — la versión suave de «un guard que inspecciona el vacío pasa». - A-1 es más barato de lo que parece y C-1 más caro. A-1 tiene precedente ejecutado dos veces y cero lectores que reparar en las 15 claves de clase B; C-1 arrastra 147 sitios y una decisión de alcance sin resolver.
Criterio de éxito y aviso de método
Éxito = existe una aguja que enumera las escrituras de soma+arts y las clasifica contra el contrato; sus 19 filas rojas están adjudicadas una a una (no tapadas en bloque); el velo del drawer PINTA, medido en Chrome real; y el censo del eje declara sobre qué espacio mide.
A-1 toca docs/architecture/soma-architecture.md y 5 READMEs de soma. Ninguno
está sucio hoy, y los cuatro instrumentos del eje están limpios (medido el 27) —
pero la rama es COMPARTIDA: mide antes de tocar. Si la aguja quiere reusar el
lector de contrato del censo, que nazca como fichero propio.
Ficheros clave: scripts/theming-census.ts:86 (const ROOT, el alcance que
A-1 amplía) · scripts/theming-sentinel-exceptions.ts:815 (el bug del velo,
adjudicado en prosa) ·
src/uix/soma/components/drawer/drawer-provider.svelte.ts:372,1145 ·
src/uix/eidos/components/drawer/drawer.css:49-51 ·
src/uix/eidos/lib/recipes/base.ts:1474 ·
src/uix/soma/components/scroll-area/scroll-area-provider.svelte.ts:186,190
(la intersección A-1 ∩ C-1) · docs/architecture/soma-architecture.md:837-841.
16-bis. Acta de ejecución — 2026-08-27
Anotación 2026-08-27 (cierre de firma), TRES correcciones a esta acta. Sus números son ciertos y el pase crítico los reprodujo al dígito; lo que falló fue el alcance escrito. Veredicto literal: «es LEY sobre la mitad que barre, y sigue siendo PROSA sobre el absoluto que enuncia». Lo corregido el mismo día, sin reescribir una línea de lo de abajo:
- El enunciado universal está ACOTADO en la ley. «Toda escritura por instancia» lo desmentían once escrituras de nombres contratados vivas en
src/uix/eidos, una raíz que la ley no excluía por escrito y la aguja no miraba. R-5.5 nombra ahora sus nueve raíces una a una y declara sus dos fronteras —src/uix/eidosyweb/routes— con su expediente abierto: §18, en este mismo fichero.- Las salidas son CUATRO, no tres. La ley decía «tres, y no hay cuarta» mientras su propia tabla de mutaciones (fila d, aquí abajo) enseñaba una cuarta VERDE: el vocabulario de SISTEMA. Corregido, y en el orden en que el guard las pregunta.
- El registro se QUEDA, con cuatro campos por entrada. Es el patrón de la casa —transitorio, sólo mengua, con STALE que lo vacía solo— y responde por escrito a la objeción de que
PENDING_PRIVATE_RENAMEse desmontó 24 horas antes. Cada entrada gana ahora fecha · razón · destino · por qué NO se movió hoy, y el guard asserta que los cuatro están rellenos: es el cuarto campo el que impide que un registro se vuelva un cajón. Las 8 entradas (7--palabras-scheme-*+--scrollbar-width) están completas.Dos menores del mismo pase, medidos y ejecutados:
classifypreguntaba por el prefijo--_ANTES que por el contrato, así que las 124 claves_-prefijadas debase.tsse podían escribir inline gratis — medido: 0 de 64 escrituras privadas de soma+arts y 0 de 46 de eidos nombran una clave contratada, así que el orden se invirtió y no enrojeció nada. YSILENT_ROOTSprohibía TODA escritura enblocks/packs, con lo que un bloque que mañana estampe un--_{c}-*legítimo nacía rojo: hoy esas raíces —ylibs,svrs,sema,morfo,active-uix, verificadas en cero antes de asertarlas— se barren bajo los mismos dos rojos, nunca contra cero.⚠ Donde el acta dice «
blocksypackssiguen escribiendo 0, y el guard lo asserta», léase: siguen escribiendo 0 (medido), y el guard ya no lo asserta — los barre bajo la ley, que es lo que la ley prohíbe de verdad.
Lo firmado, en una línea. _Toda escritura de custom property POR INSTANCIA
vive en --\_{c}-_. Si el nombre que se escribe está en el contrato, es un TOKEN
QUE MIENTE: un tema no puede ganarle jamás.* Es R-5.5 en
docs/canon/recipe-contract.md §4, hermana de la ley del espacio cerrado —
aquélla audita quien LEE, ésta quien ESCRIBE.
La aguja: src/uix/value-channels.test.ts, fichero PROPIO — ni reusa el
lector de contrato del censo ni toca src/uix/contracts.test.ts. Es un test de
vitest y no un script porque npm run gate termina en npm run test: la suite
es lo que convierte la doctrina en ley; un scripts/*.ts necesita entrada nueva
de gate y un autor que se acuerde. Vive en la raíz de src/uix/ y no bajo
eidos/ porque su SUJETO es soma + arts y su JUEZ es el contrato de eidos: no
es de ninguna capa. Lee el contrato del propio THEME_BASE_RECIPE_TOKENS y
deriva el vocabulario de SISTEMA restando ese contrato a todo lo que emite
renderGeneratedBaseEidosCss() — lista DERIVADA, nunca escrita a mano.
El censo, medido sobre el árbol ya movido (1.729 ficheros de código de soma + arts; los tests se excluyen porque un spec OBSERVA a un escritor, no lo es):
| clase | nombres | qué es |
|---|---|---|
--_{c}-* |
53 | el destino de la doctrina |
| token que MIENTE | 0 | era 1: el velo del drawer |
| forma pública sin dueño | 8 | los 7 --palabras-scheme-* + --scrollbar-width, los dos REGISTRADOS |
| vocabulario de sistema | 0 | nadie en soma ni en arts escribe un nombre de la fundación |
61 nombres · 72 sitios, sobre un contrato de 4.693 nombres y un vocabulario
de sistema de 1.058. blocks y packs siguen escribiendo 0, y el guard lo
asserta.
⚠ Dos correcciones al dimensionado de arriba, por MEDIDA. El censo previo enumeró cuatro formas de escritura; el árbol tiene SEIS, y una de las dos que faltaban era la CANÓNICA:
computed-key—[`--_${component}-floating-anchor-width`]:, la forma concomponent: stringque §14 y §15 firmaron. Un guard ciego a ella es ciego al destino que hace cumplir, y habría dado verde sobre un--{c}-*público escrito así. 8 sitios.member-assign—out['--x'] = vsobre un objeto de estilo. 7 sitios, todos depalabras, y son la razón de que esos siete nombres estén hoy en el registro en vez de invisibles.
Por eso 61 nombres y no 49: el barrido por CAPTURA ve doce nombres que el
barrido por PATRÓN no podía ver. Dos formas quedan irresolubles y se escriben en
el fichero en vez de taparse con un verde: un nombre ENSAMBLADO entre sentencias
(soma/layers/measured-indicator) y uno que llega a setProperty por variable.
⚠ Y una tercera lección: un barrido de literales pelados —probado y REVERTIDO—
marcaba en rojo --floating-anchor-{id}, que es un VALOR de anchor-name, un
dashed-ident idéntico en gramática a un nombre de propiedad y jamás escrito
como tal.
El registro de adjudicaciones abiertas tiene la forma del ledger de deuda, no la del fichero de excepciones: NOMBRE + razón escrita + destino, y sólo puede MENGUAR (una entrada cuyo nombre deja de ser escritura sin dueño sale STALE y sigue roja hasta que se borre la línea). Dos entradas, y ninguna es una desviación firmada — las dos caen FUERA del antecedente de la ley:
--scrollbar-width(arts/adom/body-scroll-lock) — no es por instancia: una sola escritura sobre el único<body>, y su valor es un hecho del VIEWPORT. Su destino es una bifurcación que exige firma (declararla como sistema la vuelve API real que nadie lee; retirarla borra un nombre).- Los siete
--palabras-scheme-*— REGISTRADOS, no eximidos como pista WIP, porque el ledger de deuda ya zanjó esa pregunta al revés («THE WIP TRACKS ARE IN … debt is real»). Un octavo hermano ya no puede colarse sin firma.
Las mutaciones — seis caras, el árbol restaurado byte a byte idéntico en cada
una (SHA verificado antes y después; git diff HEAD vacío):
| # | inyección | veredicto |
|---|---|---|
| a | '--sticky-shadow-strength': '3' en un provider |
ROJO, nombrándolo: --sticky-shadow-strength ← …/sticky-provider.svelte.ts:134 (object-key) |
| b | '--drawer-overlay-opacity': '1' — clave DEL CONTRATO |
ROJO: «A token that LIES…» |
| c | '--_sticky-shadow-strength': '3' |
VERDE |
| d | '--color-surface-raised': 'red' — nombre de SISTEMA |
VERDE — la única rama sin caso vivo en el árbol, así que sólo se prueba por inyección |
| e | una entrada de registro que ya nadie escribe | ROJO STALE |
| f | [`--${this.opts.component}-floating-leak`] en floating |
ROJO como --{}-floating-leak … (computed-key) |
Y tres más del pase de alcance (2026-08-27, cierre), cada una con backup y restauración en la misma invocación y SHA idéntico antes y después. Las dos primeras re-corren caras de arriba para probar que la aguja sigue mordiendo tras los cambios; las tres últimas prueban lo que los cambios añaden:
| # | inyección | veredicto |
|---|---|---|
| b′ | '--drawer-overlay-opacity': '1' (re-corrida) |
ROJO «A token that LIES» — … ← sticky-provider.svelte.ts:134 (object-key) |
| f′ | [`--${this.opts.component}-floating-leak`] (re-corrida) |
ROJO — --{}-floating-leak ← floating.svelte.ts:316 (computed-key) |
| g | '--_avatar-badge-bg': 'red' — clave _ DEL CONTRATO |
ROJO «token that LIES». Era VERDE antes de invertir el orden de classify |
| h | --_hero-private-leak en blocks/hero/hero.svelte |
VERDE. Era ROJO bajo SILENT_ROOTS sin ser violación |
| i | --hero-public-leak en el mismo fichero |
ROJO — … ← src/uix/blocks/hero/hero.svelte:222 (declaration), en una raíz que esa mañana no barría nadie |
Lo que las tres piezas del codemod dejaron (detalle en sus propios informes y en los READMEs que tocaron):
- Clase A — el velo del drawer PINTA.
overlayOpacitypasa a llamarseoverlayProgressy se escribe como--_drawer-overlay-progress; la receta COMPONE:var(--drawer-overlay-opacity) * var(--_drawer-overlay-progress, 1). Medido en Chrome real: reposocolor(srgb 0 0 0 / 0.408471)(antesrgba(0,0,0,0)), y un tema en:roota 20 % / 62 % / 100 % ahora sí llega (0.131765 / 0.408471 / 0.658824). - Clase B — los 15 nombres publicados, retirados en
command·dialog·drawer·scroll-area·toast, con sus READMEs reescritos. ⚠ La frase «los 15 tienen CERO lectores» era falsa para dos:--scroll-area-corner-{width,height}tenían un lector VIVO — unvar()inline dentro de soma (scroll-area-provider:687,688), invisible a un censo de lectores acotado a la capa que lee. El renombrado no era libre de riesgo por construcción; lo cazó barrer por STEM. - Clase D — dos de tres renombrados (
--_tree-view-depth,--_gradient-picker-current-gradient) y--scrollbar-widthadjudicado por medida, sin tocar. Consecuencia cobrada: el censo reclasificó los dos lectores degradient-pickerdeglobalachannely dos líneas del ledger salieron STALE y se retiraron con acta fechada — ledger 1.150 → 1.148 · 0 new · 0 stale. Es el único movimiento del ledger en toda la firma, y no lo produjo la aguja: renombrar privados no toca el ledger.
Verificación final, verbatim: npx vitest run src/uix/value-channels.test.ts
5/5 · npm run docs:check 0 errores / 0 avisos · npx tsc 0 errores
propios · censo --debt 1.148 · 0 new · 0 stale · prettier --check sobre
el fichero nuevo, limpio.
Lo que NO se ejecutó, y por qué (no es recorte silencioso: cada uno tiene dueño ajeno o veto escrito):
src/uix/eidos/lib/recipes/base.ts— tres comentarios (:2530,:3201,:3268) citan--tree-depthy--gp-current-gradienty hoy son FALSOS. Vetado a las piezas por brief; es una palabra en cada uno y debería viajar en el mismo commit.web/routes/**— CONGELADO por ley: 4 sitios (tree-view/+page.svelte×3,gradient-picker/+page.svelte×1).- La cabecera de
scripts/theming-census-debt.tstodavía dice «582 of the 1150 keys»; el total vivo es 1.148. Fichero de otra mano en esta misma firma. (Anotación del cierre: CORREGIDO el mismo día — el fichero estaba sucio sólo por el acta de clase D de esta firma, no por un peer, así que se heredó. Verificado con--debtantes de tocarlo: 1148 · 0 new · 0 stale, y las dos cifras por pista siguen siendo 359 + 223 = 582, que no se mueve.) scripts/theming-sentinel-exceptions.ts:815(drawer.overlay-bg) dice que el velo no pinta nada hoy: ya está arreglado, y es un acta — se anota, no se reescribe. (Anotación del cierre: queda además registrada como fila de §18, porque el fichero es un instrumento del eje y su pase es propio.)types.tsdedrawerydialogdocumentan'0.5'como valor CSS válido del prop de opacidad;color-mix()no admite un número ahí. Misma frase en los dos gemelos: pide una firma conjunta. (Anotación del cierre: es fila de §18 —drawer/types.ts:73ydialog/types.ts:102.)
§17 (C-1) sigue esperando firma y es ahora lo siguiente de la cola. Gana
algo con esto: su violación más nítida (scroll-area, --space-2-5 del
contrato contra un 8px literal) vive en soma, y el espacio que A-1 abrió
es exactamente donde una aguja de corolario 2 tendrá que mirar.
(Anotación del cierre, 2026-08-27: el pase de alcance abrió §18 en este
mismo fichero — el otro lado del muro, src/uix/eidos. También espera firma, y
es la respuesta medida a «¿por dónde vuelve a nacer un nombre público sin
dueño?». No compite con §17: §17 es el corolario 2 del lado que LEE.)
17. El corolario 2 no tiene aguja (C-1) — 2026-08-27
PENDIENTE — redactado, NO ejecutado. Dimensionado y medido el 2026-08-27; nada se ha tocado en el código. Va DESPUÉS de §16 (A-1) — el porqué está en «Cuál va PRIMERO» de esa sección.
El problema, en tres líneas. La ley del espacio cerrado
(docs/canon/recipe-contract.md §4, corolario 2, firmado 2026-08-26) dice que
una vez declarada la clave, el fallback inline sobre el propio nombre público
MUERE — el default vive en UN sitio. Nunca recibió guard. Hoy 147 sitios
siguen leyendo una clave declarada con un default de repuesto escrito al lado.
El censo MEDIDO — y la cifra previa medía OTRO corolario
147 sitios · 115 nombres · 16 componentes. Estable en las cuatro variantes
de alcance que se probaron (con y sin comentarios · components+lib · todo
eidos · todo src): 147 en las cuatro.
⚠ La cifra vieja de 171 · 124 era de otro corolario. Medir sin filtrar el
corolario 3 (var(--x, fb) donde --x NO está declarado, incluyendo
vocabulario ajeno y de sistema) da 173 sitios · 130 nombres — casi
exactamente aquel 171 · 124. El barrido previo midió el corolario 3 creyendo
medir el 2.
Y el corolario 3 está LIMPIO: filtrando al prefijo PROPIO y descontando las
recetas sin contrato quedan 14 sitios, todos en calendar, todos SIN
fallback — la familia prestadora ya adjudicada. Las recetas descontadas son
23, que es exactamente el conjunto sin contrato que R-5.2 documenta: ese
cuadre valida el instrumento contra las propias cifras del canon.
Ruido contra bug — y el hecho que manda
Los 147 fallbacks son código MUERTO. Comprobado token a token en
src/uix/eidos/generated/base.css: cada clave en disputa se emite en :root, y
un var() sólo cae al fallback si la propiedad está sin declarar. C-1 son
CERO PÍXELES hoy.
- 121 sitios · 98 nombres — RUIDO: el fallback repite el default, literalmente.
- 26 sitios · 24 nombres — el fallback DISCREPA, y hay que partirlos:
- 14 (
box.css) — cascada de atajos propia (--box-padding-top→var(--box-padding-block, var(--box-padding, revert-layer))). Termina en el mismo default del contrato: idioma CSS legítimo, no deuda. - 2 (
grid.css) —place-content/place-itemssintetizados de dos longhands. Misma clase, muertos igual. - 4 — lectura AJENA de
--box-*con default propio:stack.css:11(--box-display,flex) ·container.css:12(--box-max-width) ·aspect-ratio.css:22ymockup.css:13(--box-width,100%). Token prestado más su propia opinión del default. (Como--box-widthvalerevert-layer, ese100%no se aplica nunca, aunque su autor lo escribiera creyendo que sí.) - 6 — las discrepancias REALES, abajo.
- 14 (
Las SEIS que justifican la firma — la lista exacta, verificada
| nombre | contrato | fallback inline | ¿difiere de verdad? |
|---|---|---|---|
--context-menu-separator-bg context-menu/context-menu.css:160 |
var(--color-neutral-separator) → primitive-neutral-6 |
var(--color-border-subtle) → primitive-neutral-4/-7 |
Sí — color distinto |
--splitter-disabled-opacity splitter/splitter.css:35 |
var(--opacity-disabled) = 0.4 |
0.6 | Sí — valor distinto |
--calendar-select-padding-inline month-grid/month-grid.css:123 · year-grid/year-grid.css:123 |
var(--space-2) = calc(8px × density × scaling) |
8px |
Sí — ignora densidad y zoom |
--context-menu-content-font-size context-menu/context-menu.css:37 |
var(--size-sm-font-size) |
var(--font-size-sm) |
Iguales HOY por construcción (generated/base.css:420 los alía 1:1); lee la primitiva en vez de la escalera |
--context-menu-heading-font-size context-menu/context-menu.css:151 |
var(--size-xs-font-size) |
var(--font-size-xs) |
ídem |
Las «tres discrepancias reales en context-menu» del barrido previo quedan
CONFIRMADAS, exactamente tres.
⚠ Punteros corregidos al redactar esta sección. El dimensionado del 27 dio
context-menu.css:121/16/112 · splitter.css:19 · month-grid.css:120 ·
year-grid.css:120 · stack.css:5 · container.css:4 · aspect-ratio.css:6 ·
mockup.css:8, y ninguno apunta a su declaración (caen en comentarios,
selectores o cursor: default); los de la tabla se re-localizaron por grep del
nombre y son los buenos. Las CIFRAS del censo no dependen de esos punteros y no
se tocan — pero comprueba cada línea antes de editarla: el instrumento que
las produjo numera distinto que el fichero.
Y la forma correcta ya existe al lado: range-calendar/range-calendar.css:129
lee esa MISMA clave --calendar-select-padding-inline sin fallback. El
corolario 2 no es una regla nueva que haya que enseñar — es una que la casa ya
cumple en un sitio e incumple en los dos de al lado.
⚠ --scroll-area-scrollbar-size: el barrido previo se equivocó, y el error es
revelador. En eidos son 6 sitios, todos var(--space-2-5), idénticos al
contrato — ruido puro. El segundo valor existe, pero no vive en CSS:
scroll-area-provider.svelte.ts:186,190 escribe
'var(--scroll-area-scrollbar-size, 8px)' y el README lo documenta como
«default 8px», mientras el contrato dice --space-2-5 =
calc(10px × density × scaling). El contrato dice una cosa; soma y el README
dicen otra.
Esa línea es la intersección de los dos expedientes: escribe un nombre de clase B de §16 (sin contrato) cuyo valor es una lectura de corolario 2 con fallback discrepante. Los dos guards son ciegos a ella — y es la razón operativa de que §16 vaya primero.
Opciones, con su coste y su precio
| qué | coste | precio | |
|---|---|---|---|
| 0 · No hacer nada | — | 0 | 147 líneas muertas y 6 pistolas cargadas: el día que alguien pode o acote una clave, esos sitios empiezan a pintar otro valor en silencio, y ningún guard chista porque recipe-css-contract sanciona la forma con fallback por escrito. |
| 1 · Sólo los 6 | corregir las discrepancias y dejar el ruido | ~6 ediciones de una línea | Sin guard, vuelven. |
| 2 · Aguja + los 6 | guard de corolario 2 sobre el CSS de eidos + limpieza | ~80 líneas (reusa collectOwnPublicVariables, src/uix/eidos/recipe-css-contract.test.ts:140, que ya extrae exactamente esto) + 26 ediciones |
Hay que decidir qué hace el guard con las 16 de cascada box/grid. |
| 3 · Aguja completa | + retirar los 121 redundantes | + 121 ediciones mecánicas | Diff grande y ruidoso sobre CSS que hoy funciona. |
Recomendación
Opción 2, con una FIRMA previa de una sola línea: ¿la cascada de atajos de
box/grid es DEUDA o es IDIOMA? De eso depende si el rojo del guard son
6, 22 o 26.
Criterio de quien lo midió: NO es deuda. El corolario 2 existe para que dos
sitios no puedan discrepar mientras el default vive en el punto de uso — el caso
--mockup-shadow, dos superficies distintas bajo un nombre. La cascada de box
no discrepa: es una sola resolución longhand→shorthand que termina en el mismo
revert-layer del contrato. Meterla en el rojo obliga a reescribir un mecanismo
sano y enseña al guard a gritar donde no hay daño.
Todo lo demás es EJECUTABLE, precisamente porque son cero píxeles hoy: ni los 6 arreglos ni los 121 redundantes pueden regresionar la pintura. Lo único que exige firma es el ALCANCE del rojo.
Criterio de éxito
Existe un guard de corolario 2 sobre el CSS de eidos, con su alcance FIRMADO
(6/22/26); las 6 discrepancias reales están corregidas; y el guard se ha mutado
por sus dos caras (reintroducir un fallback discrepante ⇒ rojo nombrándolo;
retirarlo ⇒ verde). ⚠ Y queda dicho por escrito que el guard sólo mira el lado
que LEE hasta que §16 amplíe el espacio: la violación de scroll-area seguirá
fuera de su vista.
18. El otro lado del muro — eidos también escribe por instancia — 2026-08-27
PENDIENTE — redactado, NO ejecutado. Medido el 2026-08-27 apuntando la
aguja de §16 sin tocarle una línea a src/uix/eidos; nada de lo de abajo se
ha tocado en el código.
Por qué existe esta sección. Al cerrar §16 se le hizo al pase adversarial
una pregunta concreta: ¿queda alguna vía por la que un nombre público sin dueño
vuelva a nacer sin que nadie se entere? La respuesta, medida por dos manos
independientes, es sí: desde eidos. R-5.5 barre nueve raíces y src/uix/eidos
no es una de ellas — hoy eso está escrito en la ley (§4 de
canon/recipe-contract.md, «THE SCOPE»), que es la
mitad de la reparación. La otra mitad es este expediente.
El censo MEDIDO — 3.034 ficheros de eidos, la misma aguja
| clase | sitios | qué es |
|---|---|---|
| token que MIENTE | 11 | el nombre está en base.ts y se escribe inline |
| forma pública sin dueño | 18 | --{c}-x que ningún contrato declara |
--_{c}-* |
46 | el destino de la doctrina — nada que hacer |
| vocabulario de sistema | 15 | --motion-stagger-*, --shape-*: la fundación hablando su idioma |
90 escrituras. Las 29 rojas por la letra se parten en dos especies que no son la misma, y esa es toda la dificultad de la firma:
Especie 1 · LA FUNDACIÓN generando su propio vocabulario — legítima. 13 de
las 29 viven en eidos/lib/* y no son escrituras por instancia en absoluto:
son los generadores emitiendo la hoja. build-space-scale.ts:93,104 emite
--space-{n}, build-scheme.ts:211,214 emite --primitive-* y
--color-*-contrast, build-type-scale.ts:85 emite --font-size-{n},
build-depth.ts:35, build-gradient.ts:126,135, component-color.ts:57
(--color-custom) y render-css.ts:1173,3054,3058. Un generador que declara
el vocabulario que todo el mundo lee no está publicando una superficie sin
dueño: está siendo la fundación. Lo mismo vale para el único token contratado
del grupo, --box-position (render-css.ts:620), que es una declaración
estática en la hoja generada — el mecanismo propio de Box, documentado en su
sitio y medido en Chrome el 2026-08-17 — y no un inline por instancia. Por la
letra de R-5.5 sería rojo; por su antecedente no lo es. Añadir el root a la
aguja pondría estas 13 en rojo el primer día.
Especie 2 · LAS PROPS ERGONÓMICAS del consumidor — ésta es la que pide
firma. 16 sitios en eidos/components/*, y 10 de ellos escriben un nombre
del CONTRATO:
| nombre contratado | sitio |
|---|---|
--dialog-overlay-opacity |
dialog/dialog-overlay.svelte:27 |
--drawer-overlay-opacity |
drawer/drawer-overlay.svelte:32 |
--scroll-area-scrollbar-size |
scroll-area/scroll-area.svelte:58 |
--scroll-area-thumb-radius |
scroll-area/scroll-area.svelte:59 |
--toast-toaster-gap |
toast/toast-viewport.svelte:17 |
--barcode-fg · --barcode-bg |
barcode/barcode.svelte:173,174 |
--qr-code-fg · --qr-code-bg |
qr-code/qr-code.svelte:90,91 |
--background-pattern-cell |
background/background-pattern.svelte:39 |
Más 6 formas públicas sin dueño al mismo nivel: --cf-swatch-color
(color-field.svelte:34, y abreviada, contra theming §6 r5) ·
--text-focus-border-color y --text-focus-glow-color
(text-focus.svelte:117,118, style: directive) · --pcols
(palabras-element-inspector.svelte:550) · --palabras-scheme-{}-size y
--palabras-scheme-image-filter (palabras-scheme.ts:330,368, hermanos de los
siete ya registrados en la aguja).
El caso que lo hace urgente — el GEMELO del velo
--dialog-overlay-opacity es el gemelo exacto del velo del drawer que §16
acaba de arreglar: mismo mecanismo, mismo serializeOpacity, mismo
color-mix(), y sigue vivo por una razón sola — dialog no tiene canal de
progreso, así que no había nada que componer y la pieza 1 no lo tocó. Los dos
ficheros son la misma línea:
dialog-overlay.svelte:27 composeInlineStyle(style, resolvedOpacity ? `--dialog-overlay-opacity: ${resolvedOpacity};` : undefined)
drawer-overlay.svelte:32 composeInlineStyle(style, resolvedOpacity ? `--drawer-overlay-opacity: ${resolvedOpacity};` : undefined)
Mientras soma ahora COMPONE, eidos sigue PISANDO. El velo del drawer es hoy
var(--drawer-overlay-opacity) * var(--_drawer-overlay-progress, 1) — el knob
público por el canal privado, que es la forma firmada. El envoltorio de eidos, a
un directorio de distancia, sigue escribiendo el knob entero: la especie
vieja, del otro lado del muro. Medido en Chrome por el pase adversarial: con
el prop puesto (y la demo lo pone siempre), un tema en :root a 20 / 62 / 100 %
da 0.408471 en las tres — no llega; sin el prop, las tres llegan.
Dos matices que la firma tiene que respetar, y que la separan de un codemod:
- Las diez son CONDICIONALES (
if (size),resolvedOpacity ? … : undefined): sólo escriben cuando el consumidor pasa el prop. La escritura de soma que §16 retiró era INCONDICIONAL — el tema no ganaba nunca. Aquí el tema gana por defecto y sólo pierde donde alguien pidió explícitamente ese valor, que es para lo que sirve un estilo inline. Es un argumento real de legitimidad, no una excusa. - Y aun así publica el nombre del contrato como su mecanismo. El JSDoc dice
«Drives
--drawer-overlay-opacity», de modo que el prop no es un valor: es una vía documentada para pisar un token contratado sin composición posible.
La fila que la propia firma dejó abierta — el ejemplo del tipo produce el bug
src/uix/eidos/components/drawer/types.ts:73 y dialog/types.ts:102
documentan, con la misma frase en los dos gemelos, que el prop acepta
«'80%', '0.5'». serializeOpacity convierte un número ≤ 1 a porcentaje pero
pasa las cadenas verbatim, y color-mix() no admite un número pelado en esa
posición: overlayOpacity="0.5" cae la mezcla entera y el velo desaparece.
El ejemplo del propio tipo produce exactamente el bug que §16 acabó de
cerrar. Es una firma conjunta de una línea en dos ficheros; no se tocó porque
son los dos gemelos y arreglar uno solo los divergiría.
Actas que quedan como actas (se ANOTAN, no se reescriben)
scripts/theming-sentinel-exceptions.ts:815(entradadrawer.overlay-bg) declara que el velo no pinta nada hoy. Quedó OBSOLETA el 2026-08-27: ya pinta (color(srgb 0 0 0 / 0.408471)en reposo). No se reescribe aquí por dos razones que apuntan al mismo sitio — es un acta fechada, y el fichero es un instrumento del eje. Se anota en su propio pase.
Opciones, con su coste y su precio
| qué | coste | precio | |
|---|---|---|---|
| 0 · No hacer nada | — | 0 | La vía sigue abierta: un prop ergonómico nuevo que escriba --{c}-x inline nace VERDE, y 29 sitios ya viven ahí. El gemelo del velo sigue pisando el knob, y el ejemplo '0.5' de dos types.ts sigue enseñando a romperlo. |
| 1 · Sólo el gemelo | dialog compone como el drawer, o el prop escribe --_dialog-overlay-opacity y la receta lee var(--_…, var(--…)) |
2 ficheros | Cierra el caso vivo y deja la puerta abierta: la aguja sigue sin ver eidos. |
| 2 · Firma de especie + aguja acotada | adjudicar «fundación vs prop del consumidor» y extender R-5.5 a eidos/components/* excluyendo eidos/lib/* por escrito |
la firma + ~1 línea de aguja + adjudicar 16 sitios | Nace en rojo con 16 filas; hay que adjudicarlas o convertirlas el mismo día. |
| 3 · Todo | + la forma canónica en los diez: --_{c}-x escrito, var(--_{c}-x, var(--{c}-x)) leído |
+ 10 recetas y 10 envoltorios | Toca CSS que hoy pinta: exige verificación en navegador componente a componente. |
Recomendación
Opción 2, y la firma va PRIMERO — no es la misma decisión que §16. Allí el sujeto era el framework escribiendo su propio canal, y la doctrina llevaba cuatro aplicaciones firmadas. Aquí el sujeto es un envoltorio ofreciendo un prop a su consumidor, que es la pregunta que §16 dejó explícitamente sin responder al declarar su frontera. Los dos pases adversariales coinciden en lo mismo y conviene copiarlo literal: no es «añadir el root» — distinguir fundación de prop-del-consumidor exige firma, igual que §16 exigió las suyas.
Dentro de la opción 2, el criterio de quien lo midió: las 13 de eidos/lib/*
no son deuda (un generador ES la fundación) y deben quedar excluidas por
escrito en la ley, no por omisión del array — que es precisamente el defecto
que esta corrección acaba de arreglar en R-5.5.
Lo que exige firma del autor, y sólo esto:
- la especie: ¿un prop ergonómico que pisa un token contratado es legítimo por ser condicional, o es la misma mentira con mejores modales?
- el gemelo
dialog, si se arregla, MUEVE PÍXEL en cualquier demo que pase el prop; - el
'0.5'de los dostypes.ts— una frase, dos ficheros, gemelos.
Criterio de éxito
La ley dice qué hace R-5.5 con eidos/components/* y con eidos/lib/*, las
dos por escrito; el gemelo dialog compone o está adjudicado con su razón
medida; los 16 sitios de componente están adjudicados uno a uno (no tapados en
bloque); y la aguja se ha mutado por sus dos caras en la raíz nueva — un prop
que pisa un nombre contratado ⇒ rojo nombrándolo, el mismo valor por --_{c}-*
⇒ verde.
Ficheros clave: src/uix/value-channels.test.ts (LAW_ROOTS y el bloque
«THE BORDER», que es donde vive la frontera) ·
src/uix/eidos/components/{dialog/dialog-overlay,drawer/drawer-overlay}.svelte
· src/uix/eidos/components/{drawer,dialog}/types.ts ·
src/uix/eidos/lib/{render-css,build-scheme,build-space-scale,build-type-scale,build-depth,build-gradient,component-color}.ts
· docs/canon/recipe-contract.md §4 (R-5.5, «THE SCOPE»).