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

40 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 in theming/reference.md §40 + changelog.md §44. Outcome: text·11 = secondary tier, text-strong = AA-guaranteed; the border vocabulary is Radix-subtle by design (only solid·9 clears 3:1 by construction) with a decorative-vs-load-bearing split; the focus ring is a config axis (primitives.focusRing + color.focus, single outline per §32) whose shipped default is soft — hardening is a config-value choice, kept as-is. Stage 2 CLOSED (2026-07-20) — no solver needed. The 6 decisions were locked (D1 band · D2 measured-pass · D3 flip-only · D4 opt-in · D5 runtime-first · D6 now), then execution disproved the premise: the template morph does not compute text contrast, it inherits it (steps 11/12 copy the donor's L-curve verbatim; L-driven contrast is ~chroma-invariant under gamut-mapping), so any scale generated from a §40-compliant donor library clears the ratified text floors by construction. Verified on three banks — authored base, leave-one-out regeneration, and 45 out-of-distribution seeds — 0 hard-gate failures (min WCAG 9.7:1). The luminance solver would have had nothing to resolve for realistic inputs → dropped as speculative. Delivered instead: the ratified pair table as shared data ($color → CONTRAST_PAIRS), the audit refactored to consume it + a morph-generated regression bank (scripts/contrast-audit.ts), and a CI guard that locks the inheritance (eidos/lib/contrast-invariant.test.ts — fails if a future donor/generator breaks it). With D2, the base stays verbatim ground-truth; no base→seeds migration. Plan + decision + outcome record: process/contrast-stage2-plan-2026-07.md; chronicle changelog.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-height from the size bundle but not letter-spacing; plus ~7 raw letter-spacing literals in component CSS.
  • Tier-A data-depth stamping coverage: overlays that should stamp their plane (dialog / drawer / tooltip / menubar / combobox) per rfcs/rfc-depth.md §5.
  • Calendar family → shared layer/archetype (user design verdict, 2026-07-07): the calendar-grid vocabulary (accent set, day geometry, the holiday/event marks fixed in audit B.2) becomes a FIRST-CLASS shared layer — today the family borrows it informally (measured: range-calendar consumes 79 --calendar-* tokens, month-grid 65, year-grid 65, date-range-picker 38, date-picker 4 — ~250 cross-component borrows), and chronos will draw from it too when its track lands. Materialization to decide at execution: a morfo part-archetype for the day cell (the 26-set grows) and/or a calendar-surface shared recipe à la list-surface (--list-item-* precedent). This supersedes "per-component composition repairs" for the B.2 borrowers: the marks reach the whole family through the shared layer instead.
  • Field family composition — segments under [data-field] (user design verdict, 2026-07-07, audit B.1): segment height derives from the SIZE axis at the FIELD family level — field recipe declares segment-height with size-scoped declarations (calc(var(--field-control-height-{k}) - var(--space-2)); the TSC size: scope axis exists, unused so far), and the per-component copies die (each x-field today re-declares the whole height-{xs..xl} family the field recipe already has). MANDATED by the B.1-(i) verdict: date/time/color-field MUST incorporate the Field wrapper (segments under [data-field][data-size] — probed live: today they don't) as part of the component audit; the three broken per-component segment-height tokens and their consumers were removed (they had computed auto since birth — zero visual change).
  • Whatever F/G registers as "component-level adoption gap" in docs/audit/theming-audit.md when that report lands (deliverable G of the 2026-07 theming audit).

4. architecture/sema.md hold-sync — 2026-07-06

Origin: A.4/A.5 execution (holds single-source). SEMA_HOLDS_BY_INTENT (src/uix/sema/holds.ts) is now THE only hold table (resolver + visual channel consume it; sema-map.ts's per-family hold column died). The chapter architecture/sema.md still narrates per-family holds in ~34 places. Blocked: the file is the author's WIP (uncommitted edits) — sync it when the author's pass lands, not before.

6. THM-2 rollout — per-instance palette to all surfaces — SHIPPED 2026-07-12; scope boundaries REVERTED by the open-color-cage initiative (2026-07-18/19)

Shipped in 05f0031c (media-player Batch-4 + 14 surfaces + vocab + 4 props) and cc7e9a16 (avatar root). Every per-component palette cascade now collapses into the shared --palette-* layer (size win, behavior-preserving for roles); the vocabulary gained the alpha surface/surface-hover + hover slots.

REVERSED (2026-07-18/19) — "abrir la jaula del color". THM-2 originally kept semantic controls (checkbox/radio/stepper/editable/select/file-upload) and Avatar.Badge role-restricted "by design". A later user decision reverses that: color accepts the FULL system (role / intent / 33 scales / raw CSS = ComponentColorProp) on every component, no exceptions — content-ink primitives keep their axis as an additive union. Enforced by two guards in recipe-css-contract.test.ts. Full record + the closed queue (incl. the chart SVG resolver and the Card/Avatar custom-color fix): process/open-color-cage-2026-07.md. Only qr-code stays out (its color is 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):

  • chat-list — the conversation-list pane (rooms, unread badges, typing-in-list). ✅ SHIPPED 2026-07-19 — composes Listbox (single-select + keyboard) with an avatar/title/preview/meta row; unread bold + Badge, typing-in-list composes ChatTyping. Virtualization for very long lists deferred (pairs with the VirtualList bidirectional item).
  • emoji-picker — a real picker component (color-picker scale: categories, search, skin tones). ✅ SHIPPED 2026-07-21 — composes Popover + Command (search + grid keyboard + combobox/listbox a11y, its documented "grid mode") + two ToggleGroups (categories + skin tone). Data is the vendored $libs/emoji (baked from emojibase, MIT — scripts/generate-emoji-data.ts): ~1,900 emoji, 316 tone-capable. The grid double-registers Command's List/Item so it keeps the a11y but wears its own grid-cell styling. Delegated (no own events). Full-grid virtualization + per-emoji tone + image emoji (Twemoji/Noto) deferred. The quick-set Popover in ChatMessage.ReactionAdd stays as the inline tapback bar (different UX).
  • Mention autocomplete (@) — combobox-in-textarea for chat-composer; pairs with the future words/palabras integration.
  • Threads UI — the side-panel composition over the already-shipped Feed.Thread semantics.
  • Jump-to-message highlight — signal-emphasize-target on chat-message (the morfo event was deliberately left out of v1). ✅ SHIPPED 2026-07-19 — emphasized prop → data-emphasized row flash (visual-only, no sema event: reference apps flash silently; the app scrolls via a reply's onJump then toggles emphasized for ~1.4s).
  • Read-by list — "seen by N" avatar row (Teams/Messenger pattern) on chat-message. ✅ SHIPPED 2026-07-19 — ChatMessage.ReadBy part (the app composes AvatarGroup; the sender's onMessageSeen feeds it).
  • VirtualList chat-mode refinements — bidirectional sparse ranges for jump-into-history far from the loaded window.
  • Typing avatars — faces next to the chat-typing dots (Messenger). ✅ SHIPPED 2026-07-19 — ChatTyping.Avatars slot (compose AvatarGroup).
  • 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 QrCode as their own components, sharing the display-primitive shape.

Deps: none — the encoder's BarcodeResult (module lattice + guards + HRI groups in module space) already generalises; the 2D entries would instead reuse the $libs/qr shape.

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 a ScrollProgress fallback, attach='fixed', the pointer depth axis. The infrastructure landed with the component ($adom's ScrollProgress and prefersReducedData); the layers that consume it did not. It is the one follow-up with a mechanism already chosen and measured against browser support, so it is also the one most likely to move first.
  • The scrim strength scale is not ordered by weight — measured: subtle veils MORE than overlay, and overlay and muted resolve to the same number. The names come from the semantic opacity scale, which says how opaque an ELEMENT is; borrowing them for a VEIL imports an ordering that means something else. Renaming or renumbering is an API change, not a fix in passing.
  • A bright photograph is illegible at every scrim weight — white copy over the heaviest veil measures below AA. Either the scale gains a step above --opacity-subtle, or the doctrine becomes "the answer is a graded scrim", stated once and demonstrated.
  • A shared <video> state port — the framework has a load-state contract for <img> (ImageProvider in soma/layers) and none for <video>: ScrollFrames listens by hand and so does Background.Video. Two consumers is the threshold this codebase uses to extract a layer.
  • The Ambient pack honouring the pause — the canon publishes the context (paused, reduced, seen); the pack reads it. A task OF the pack, because the dependency runs pack → framework and never back.

Deps: the parallax entry needs no new service — the two $adom ports it consumes already shipped. The scrim entries are the author's call on the token vocabulary, so they block on a decision, not on code.

12. El contrato de cascada del velo de estado — 2026-08-20

Origen: el eje theme-reach (F2-A, sesión del 2026-08-20). Al tokenizar componente a componente aparecieron, medidas una a una, seis incidencias que son la misma: nadie fijó nunca cómo compone el velo de archetypes.css con las reglas de una receta. No se tocan dentro del eje de theming — mueven píxel y son decisión aparte — pero cada una está medida y localizada.

La raíz: archetypes.css pinta el velo neutro como background-image: linear-gradient(var(--state-hover), var(--state-hover)), y lo hace con DOS pesos distintos según el arquetipo — :where(...) (0,0,0) para trigger, y especificidad PLENA (0,5,0) para item / option, «INTENTIONAL cross-component accent», dice su comentario. Una receta que declare fondo sobre el mismo nodo entra en una relación que nunca se escribió.

Las seis, con su medición

  1. El atajo background: cancela el velo — 131 declaraciones en 45 componentes. El atajo fija background-image: none; sobre un arquetipo velado por :where() eso lo mata para siempre, no sólo en hover. Medido con scripts/__statelayer-analysis.ts, que comprueba contra el morfo compilado que el atajo cae sobre el nodo VELADO y no sobre cualquiera. Por clase de regla: 66 BASE (mecánicas: pasar a background-color no mueve el reposo), 32 ACENTO (selected/checked/open: §38 pide background-color para que el velo COMPONGA encima), 23 HOVER (la migración de la firma 3) y 10 OTRA (disabled, dragging, variantes). Cero valores con gradiente, así que el único riesgo técnico del paso a longhand no existe en este catálogo. El píxel que se mueve: 32 nodos velados que hoy no reaccionan al ratón pasarían a hacerlo. Esto explica la adopción 21/135 que midió la auditoría del 2026-07-01: el velo estaba escrito y estructuralmente derrotado.

  2. La prop hoverable de table no suprime nada. El velo de item es incondicional y a (0,5,0), así que una fila se ilumina al pasar el ratón aunque hoverable esté apagada — verificado con hover real, y ocurre desde antes de tocar la receta. O el arquetipo decide y la prop sobra, o la fila no es un item para el sistema. Decisión de MORFO. Precisión medida 2026-08-21 (revisión adversarial, elementsFromPoint): en table el arquetipo item está declarado en la fila Y EN LA CELDA (morfo/components/table.ts:132 y :155), y el nodo que recibe el velo bajo el puntero es la CELDA — la decisión de morfo debe contemplar los dos portadores, no sólo la fila.

  3. En tree-grid el empate lo decide el ORDEN DE CARGA. El velo de item y el hover de la receta pesan EXACTAMENTE lo mismo (0,5,0), así que gana la hoja que cargue después — y en el dev server varía entre recargas: seis corridas de la misma configuración dieron cuatro «sin velo» y dos «con velo». Un empate resuelto por orden es la misma fragilidad que el canon ya documenta para las bandas de z-index.

  4. En tree-grid la banda mata el hover. La regla de striped pesa (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.

  5. Un token de receta no puede ganar al arquetipo. La regla de item fija también color en el estado seleccionado, a (0,4,0) contra los (0,3,0) de la receta (la cifra (0,5,0) anotada primero era la de la variante :hover de la misma lista; recontada 2026-08-21): un table.selected-row-fg no podía mover nada nunca. Se retiró con su declaración muerta (un token que no mueve nada es un token que miente). Mientras el arquetipo pinte tinta, ningún componente puede tematizar la suya en ese estado.

  6. scroll-area.thumb-bg-hover no tiene velo al que migrar. Su thumb lleva archetype: 'thumb', que archetypes.css no vela, así que la firma 3 no puede ejecutarse ahí sin cambiar el arquetipo — decisión de MORFO. Es el único knob de los seis de la cola que sigue sin resolver.

  7. En grid-list el hover pinta DOS VECES sobre el MISMO nodo (medido 2026-08-21 al tokenizarlo). A diferencia de table / tree-grid —donde el velo cae en la celda y el plano en la fila, nodos distintos— aquí el plano de la receta (background: atajo, (0,3,0) → background-color) y el velo del arquetipo ((0,5,0) → background-image) caen ambos en [data-grid-list-row]: el atajo fija background-image: none pero pierde, así que se ven los dos. Por eso el commit de grid-list no acuñó hover-row-bg: sería un token que no puede ganar (mismo criterio que listbox.highlighted). Y como en table, fila Y celda llevan archetype: 'item' (morfo/components/grid-list.ts:118 y :141), así que bajo el puntero se apilan velo de celda + velo de fila + plano — tres capas. El par de portadores del punto 2 aplica igual aquí.

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.

  • El centinela da falsos negativos sobre una propiedad transicionada. CAUSA ENCONTRADA Y ARREGLADA 2026-08-21 (revisión adversarial): no era la transición. El paso «abrir lo que se pueda abrir» hacía CLIC en [data-command-input], y un clic de ratón sobre un input de texto SÍ casa :focus-visible — la regla de foco (0,2,0) se quedaba con border-color y el token de reposo leía muerto. El guard (theming-sentinel.ts) ahora hace blur() tras abrir. La revisión midió la tasa completa del instrumento viejo: 22 falsos negativos de 26 «no effect» en F2-A, con tres causas (el clic-foco, escribir sólo en [data-{c}] cuando las partes cuelgan de {c}-root, y no leer ::before/::after). Las tres, arregladas.
  • El centinela no ve ::placeholder. ERA FALSO, y ARREGLADO 2026-08-21 (al tokenizar textarea): getComputedStyle(el, '::placeholder') SÍ devuelve el valor — medido, el color centinela volvió tal cual. La afirmación anterior nunca se comprobó y le costó a command.input-placeholder-fg una entrada de ledger «verificada a mano» que ha salido STALE y se ha borrado. El pseudo entra ahora en el snapshot del guard, junto a ::before/::after.
  • 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 un <tr>. ARREGLADO 2026-08-21: tr entra en el filtro de hover de la sonda, y el guard R-5.4 añade un pase de hover propio para los tokens hover-* (así adjudicaron table.hover-row-bg y gradient-picker.hover-preset-border).
  • La sonda medía una demo aún CARGANDO. El auto load-more de feed mantiene [data-busy] ~3 s y su firma commit-settle anima box-shadow sobre el nodo medido — los «5 diffs entre dos corridas del mismo código» del handoff eran esto, no una animación sin localizar. ARREGLADO 2026-08-21: sonda y guard esperan a que [data-busy] caiga; dos corridas del mismo código dan 0 diffs y 0 nodos ausentes (6.264 valores).
  • Una sonda sobre una demo que no monta la parte compara CERO valores y pasa. Ocurrió con el skin de audio: /uix/components/audio-player da 404 porque la demo es la de media-player con el chip media, y la sonda cargaba en modo vídeo. Resuelto con scripts/__probe-audio-skin.ts, pero la clase de fallo es general: una parte condicional necesita que la sonda la monte. Afecta al menos a captions, buffering y paneles portalados de media-player, al estado vacío y la barra de carga de command, y al overview de date-range-picker.
  • date-range-picker no expone kind='month' / 'year' en su demo (norma N-6), así que la mitad de su API no se ve ni se mide.
  • Un componente COMPUESTO puede tener su cromo entero muerto sin que nada avise. gradient-picker re-declaraba altura, padding, tipografía, color, fondo, borde, radio, hover y foco de su trigger, y NADA de ello pintaba: popover.css gana con [data-popover-trigger]:not([data-archetype='field-trigger']) (0,2,0 contra 0,1,0). Lo cazó el centinela —36 de 45 tokens sin efecto— y retirarlo dio 0 diffs. EL GUARD EXISTE desde 2026-08-21: R-5.4 (npm run theming:sentinel -- <c> <url>, scripts/theming-sentinel.ts + ledger theming-sentinel-exceptions.ts, doctrina en recipe-contract §4). Nació con la segunda ocurrencia de la clase: el item-gap de carousel contra un estilo INLINE de soma, invisible para todo análisis estático. Los ocho componentes de F2-A pasan con su adjudicación escrita.
  • --gp-current-gradient es un nombre ABREVIADO en SOMA. Lo estampa gradient-picker-provider.svelte.ts (no el wrapper de eidos, como decía la ficha), y viola theming §6 r5. No es knob de tema —es un canal de valor— pero el nombre es deuda; renombrarlo toca soma, otra capa.
  • Hooks por CLASE en una receta de eidos. gradient-picker usa .gradient-picker-trigger-swatch y .gradient-picker-trigger-label en vez de data-*; eidos-lint los cuenta (class-hooks: 2) y el centinela no los ve, porque filtra nodos por atributo.
  • El foco del trigger de gradient-picker es opaco (--color-primary-solid) mientras el resto del catálogo lleva la mezcla suave del sistema (--focus-ring-color, 52 %). Quedó sin tocar porque alinearlo mueve píxel — y de hecho quedó sin token, porque la regla entera está muerta bajo popover.
  • El censo PENALIZA consumir una capa compartida correctamente. RESUELTO PARA LA FAMILIA CALENDAR 2026-08-21 y abierto para el resto: el censo tiene ahora LAYER_VOCABULARY (mismo precedente que D-TH.2-b), así que el --calendar-* que leen range-calendar / month-grid / year-grid cuenta como system. list-surface NO está registrado a propósito: sus consumidores puentean por PRIVADOS (--_listbox-item-*: var(--list-item-*)), otra forma, y activarlo mueve diez componentes de golpe — sigue siendo decisión aparte. Y aparece un techo NUEVO debajo: con el vocabulario de la capa fuera del denominador, a los tres sólo les quedan los forwards de paleta THM-2 (--_x-palette-*, que el censo cuenta como private en TODO el catálogo) y literales de layout, así que siguen leyendo 0 %. Reclasificar el forward de paleta es la misma pregunta, un piso más abajo, y afecta a todos.
  • El censo PENALIZA consumir una capa compartida correctamente. listbox adopta los cinco ejes de ritmo que list-surface posee — que es exactamente lo que la doctrina de capas manda — y el censo los cuenta como private, así que su alcance queda en 68 % en vez de ~86 %. Es el mismo defecto de medición que D-TH.2-b arregló para los primitivos tipográficos (--style-* pasó a contar como system): la capa ES la superficie de tema de ese eje. Afecta a los consumidores de list-surface (select · combobox · command · listbox · los menús) y de viewport-placement (affix · fab · menu-dial). Extenderlo es una decisión, no una corrección al paso.
  • listbox pinta el highlighted DOS veces: la receta pone un --color-surface-overlay plano y encima el arquetipo item añade su velo del 8 % — medido. El plano es el duplicado que §38 deprecó; se dejó sin token para no bendecirlo, pero retirarlo mueve píxel.
  • 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 de popover.css que dejó muerto el cromo entero del trigger de gradient-picker — y gana la hoja que cargue después: dos corridas del mismo código dan 16px o 14px en los mismos nodos (24 diffs de instrumento, medidos con la sonda dos veces). El pin del calendario existe justamente para que caja, etiqueta y chevron lean UNA talla, así que perderlo es visible. Arreglarlo es subir el peso del pin de forma determinista — decisión, porque fija el píxel en un lado. Segunda ocurrencia de la clase §12.3 (empate por orden de carga) y segunda víctima de la misma regla de popover.
  • Los tres consumidores de la capa siguen leyendo 0 % de alcance, ahora por los forwards de paleta THM-2 — ver el punto del censo más arriba.

Lo que la revisión adversarial de F2-A añadió (2026-08-21):

  • PENDIENTE DE FIRMA — el striped de table es inerte con RowDetail. FIRMADO Y EJECUTADO 2026-08-21: :nth-child(even of [data-table-row]) cuenta filas de DATOS y la paridad ya no cae en los <tr> de detalle (la demo no bandeaba NADA; ahora las filas 2 y 4 tiñen — 12 diffs medidos, 2 filas × 6 estados, y nada más se movió en 13.195 valores). El selector conserva el (0,6,0) del viejo para que el hover siga ganando su empate (no se importa el defecto §12.4 de tree-grid) y un :where(:not([data-selected])) de peso cero deja el acento seleccionado al mando — verificado los tres. La excepción del ledger se retiró; striped-row-bg adjudica VIVO (42/45).
  • El gap entre diapositivas de carousel es un canal de valor de soma. Soma lo estampa INLINE desde la prop gap (necesita el número para el flex-basis), así que ninguna declaración de receta puede ganarle — el trío item-gap* se retiró con 0 diffs. Si algún día debe ser temable, es soma quien tendría que leer un token; decisión de otra capa.
  • Dos header-z llevan el literal '2' (table, tree-grid) mientras los otros catorce *-z del catálogo leen la escalera --z-index-*. Alinearlos es decidir qué peldaño significa «cabecera pegajosa dentro de un scroller».
  • El spinner de media-player conserva 720ms a pelo mientras el spinner idéntico de feed ganó dos tokens públicos de duración (sentinel-spinner-duration / -reduced-duration). El mismo trato o una anotación functional:, pero no las dos varas.

Lo que grid-list añadió (2026-08-21):

  • ⚠ PENDIENTE DE FIRMA — la selección de fila de grid-list nunca ha pintado. La receta seleccionaba [data-grid-list-row][data-selected], un atributo que ni el morfo declara ni soma estampa: soma estampa data-state='selected' + aria-selected='true'. La regla no casaba jamás, así que su acento (--_grid-list-palette-element) no llegó nunca al píxel y una fila seleccionada muestra sólo el velo NEUTRO del arquetipo — visualmente indistinguible de una fila con el ratón encima (capturado). La declaración muerta y su forward huérfano se retiraron en el commit de tokenización con 0 diffs de computed, que es la prueba de que estaban muertos. Repararlo mueve píxel (re-apuntar a [data-state='selected'] haría aparecer un acento que el componente nunca tuvo), y choca de frente con §12.5: el arquetipo item fija también color a (0,4,0) sobre el estado seleccionado. Es la misma decisión del velo, no una corrección al paso. El acento del SelectionCheckbox (_palette-solid) sí vive y se conservó.
  • SHARED_LAYERS declara a grid-list consumidor de menu-indicator, y no lo es. theming-census.ts:432 lo lista, pero lib/menu-indicator.css no tiene un solo selector que apunte a grid-list (sus 134 líneas targetean [data-context-menu-*] / [data-dropdown-menu-*]), la receta no importa la capa y soma no renderiza indicator: el checkbox espeja ese visual pintándolo por su cuenta. La entrada hace que el informe generado escriba «Consume la capa compartida menu-indicator» en la §4 de la ficha, que es falso y dirige mal a quien la lea. Revisar las seis entradas de esa lista contra los selectores reales de cada capa (listbox, menubar y navigation-menu están en la misma entrada y tampoco aparecen en el fichero).

Lo que textarea añadió (2026-08-21):

  • ⚠ El borde de foco de textarea no se ve al hacer CLIC — sólo con Tab. Medido: la regla de hover [data-textarea-input]:hover:not([data-disabled]):not([data-readonly]) pesa (0,4,0) y la de foco [data-textarea][data-focused] [data-textarea-input] (0,3,0), así que con foco y puntero a la vez gana el hover — y hacer clic deja el puntero encima por definición. Verificado con las dos reglas apuntando a colores distintos: sale el del hover. Debajo, [data-invalid] pesa (0,2,0): un campo inválido pierde su borde rojo tanto al enfocarlo como al pasar el ratón. El anillo de foco del SISTEMA (outline) sí se ve siempre, así que no es un fallo de accesibilidad, pero el orden de las tres reglas de border-color está invertido respecto a lo que significan. Arreglarlo mueve píxel: no entra en el commit de tokenización.
  • El guard mide con el PUNTERO encima del componente. ARREGLADO 2026-08-21: reopen() corre antes de CADA token y hace clic en [data-{c}-input] cuando el componente no tiene parte content — el blur() que añadió la revisión adversarial quita el foco pero no el puntero, así que :hover seguía casando durante toda la corrida y toda regla de hover pisaba a sus vecinas de reposo. Es la mitad que aquel arreglo dejó atrás. En textarea costaba tres falsos negativos (input-border, focus-input-border, invalid-input-border); con page.mouse.move(0, 0) tras cada blur, input-border revive solo. Corridos los diez componentes con ledger: ningún otro cambia, un único STALE (command.input-placeholder-fg, por el arreglo del ::placeholder).

Deps: ninguna. Son mejoras del instrumental del eje, ejecutables cuando estorben.

Powered by TurnKey Linux.