|
|
---
|
|
|
title: Next features — initiative registry
|
|
|
type: registry
|
|
|
audience: human + agent
|
|
|
authority: backlog of user-decided future initiatives — records scope, sequencing and dependencies; it does NOT record verdicts (those live in the RFCs / changelog / book-deviations when executed)
|
|
|
status: living — items enter by explicit user decision during audits and sessions; remove an item when it ships (and link where it landed)
|
|
|
created: 2026-07-06
|
|
|
---
|
|
|
|
|
|
# Next features — initiative registry
|
|
|
|
|
|
Rules of this file: one entry per initiative, dated, with its origin (which
|
|
|
audit/session raised it), its scope in one paragraph, and its dependencies.
|
|
|
Entries are **registered, not started** — starting one is a session decision.
|
|
|
Detail lives in the linked doc, never copied here.
|
|
|
|
|
|
## 1. Tonal-ramp contrast parity (measure, then construct) — 2026-07-06 · Stage 1 DONE 2026-07-19 · Stage 2 CLOSED 2026-07-20
|
|
|
|
|
|
> **Stage 1 SHIPPED (2026-07-19).** Measured with `scripts/contrast-audit.ts`
|
|
|
> (33 scales × 2 modes); drift + user verdicts ratified as doctrine in
|
|
|
> [`theming/reference.md §40`](./theming/reference.md) + [`changelog.md §44`](./theming/changelog.md).
|
|
|
> Outcome: `text·11` = secondary tier, `text-strong` = AA-guaranteed; the border
|
|
|
> vocabulary is Radix-subtle by design (only `solid·9` clears 3:1 by
|
|
|
> construction) with a decorative-vs-load-bearing split; the focus ring is a
|
|
|
> config axis (`primitives.focusRing` + `color.focus`, single `outline` per §32)
|
|
|
> whose shipped default is soft — hardening is a config-value choice, kept as-is.
|
|
|
> **Stage 2 CLOSED (2026-07-20) — no solver needed.** The 6 decisions were
|
|
|
> locked (D1 band · D2 measured-pass · D3 flip-only · D4 opt-in · D5 runtime-first
|
|
|
> · D6 now), then **execution disproved the premise**: the template morph does not
|
|
|
> _compute_ text contrast, it **inherits** it (steps 11/12 copy the donor's L-curve
|
|
|
> verbatim; L-driven contrast is ~chroma-invariant under gamut-mapping), so any
|
|
|
> scale generated from a §40-compliant donor library clears the ratified text
|
|
|
> floors **by construction**. Verified on three banks — authored base, leave-one-out
|
|
|
> regeneration, and 45 out-of-distribution seeds — **0 hard-gate failures**
|
|
|
> (min WCAG 9.7:1). The luminance solver would have had nothing to resolve for
|
|
|
> realistic inputs → dropped as speculative. **Delivered instead:** the ratified
|
|
|
> pair table as shared data (`$color` → `CONTRAST_PAIRS`), the audit refactored to
|
|
|
> consume it + a morph-generated regression bank (`scripts/contrast-audit.ts`), and
|
|
|
> a CI guard that locks the inheritance (`eidos/lib/contrast-invariant.test.ts` —
|
|
|
> fails if a future donor/generator breaks it). With D2, the base stays verbatim
|
|
|
> ground-truth; **no base→seeds migration**.
|
|
|
> **Plan + decision + outcome record:**
|
|
|
> [`process/contrast-stage2-plan-2026-07.md`](./process/contrast-stage2-plan-2026-07.md);
|
|
|
> chronicle [`changelog.md §45`](./theming/changelog.md).
|
|
|
|
|
|
**Origin**: A.7 follow-up (theming audit F). The on-solid pair is now computed
|
|
|
at generation with a single criterion ([`rfcs/rfc-color-engine.md`](./rfcs/rfc-color-engine.md)
|
|
|
§8, `eidos/lib/on-solid.ts`). M3 still guarantees more: contrast **by
|
|
|
construction across its whole tonal ramp** (container/on-container pairs at
|
|
|
fixed tone deltas — ΔT 40 ≈ 3:1, ΔT 50 ≈ 4.5:1). Our equivalent promises
|
|
|
(text-11 on bg-2/element-3, border-7 vs bg-2 at the 3:1 non-text floor, text
|
|
|
over composited soft surfaces a2/a3) rest on Radix-style curation, unmeasured.
|
|
|
|
|
|
**Stage 1 — pair-contract verification** (the A.7 pattern generalized): a
|
|
|
canonical table of slot pairs + floors (a user-decided doctrine session),
|
|
|
a measurement pass over pairs × 33 scales × 2 modes at generation
|
|
|
(`on-solid.ts` generalizes; the compositing math exists in `$color`), and the
|
|
|
resulting drift list goes to user verdicts (retune vs annotated exception).
|
|
|
Infrastructure is small; the fallout is the unknown.
|
|
|
|
|
|
**Stage 2 — by-construction generator (CLOSED 2026-07-20, solver dropped).** The
|
|
|
plan proposed a luminance solver that resolves each step to satisfy Stage 1's
|
|
|
pair table for a seed. Execution disproved its premise: the morph **inherits**
|
|
|
text contrast from the donor (steps 11/12 copy the donor's L-curve verbatim;
|
|
|
L-driven contrast is ~chroma-invariant under gamut-mapping), so every scale from
|
|
|
a §40-compliant donor library clears the ratified floors by construction. Measured
|
|
|
across three banks (authored base, leave-one-out regeneration, 45 out-of-distribution
|
|
|
seeds): **0 hard-gate failures, min WCAG 9.7:1.** The solver had nothing to
|
|
|
resolve for realistic inputs → dropped (speculative). What shipped: the pair
|
|
|
table as shared data (`$color` `CONTRAST_PAIRS`), the audit consuming it + a
|
|
|
morph-generated regression bank, and a CI guard (`eidos/lib/contrast-invariant.test.ts`)
|
|
|
that locks the inheritance. The BASE stays verbatim ground-truth (D2); no
|
|
|
base→seeds migration.
|
|
|
|
|
|
**Sequence**: Stage 1 first (it is the spec for Stage 2). No M3-style `on-*`
|
|
|
vocabulary expansion — our slots already encode the pairs; the table
|
|
|
formalizes them without inflating the namespace.
|
|
|
|
|
|
## 2. Per-theme palette-contrast cascade emission — 2026-07-06
|
|
|
|
|
|
**Origin**: A.7 execution. The per-instance `palette-contrast` cascade lives
|
|
|
in the STATIC foundation — one polarity for all themes. Today polarity is
|
|
|
computed per configured theme and unanimous (33/33 scales agree across
|
|
|
base-light/base-dark); on disagreement the `*-light` theme wins
|
|
|
(`computeLightSolidScales`, documented in
|
|
|
[`rfcs/rfc-color-engine.md`](./rfcs/rfc-color-engine.md) §8). **Trigger**: the
|
|
|
day a real configured theme disagrees on a scale's polarity, emit the cascade
|
|
|
per-theme instead of statically. Not before — no speculative machinery.
|
|
|
|
|
|
## 3. Component audit (post-system) — 2026-07-06
|
|
|
|
|
|
**Origin**: user decision during the theming audit — "de momento no tocamos
|
|
|
los componentes, merecen una auditoría adicional una vez que se haya resuelto
|
|
|
estas". Gate: the system-level audit (F/G) closes first. Registered items so
|
|
|
far, to seed its scope:
|
|
|
|
|
|
- **Named-styles adoption by controls** (from A.3): controls/recipes that
|
|
|
hand-declare typography instead of consuming `--style-*` /
|
|
|
size-bundle typography — likely by error, to be confirmed per component.
|
|
|
- **Letter-spacing bundle adoption**: ~34 recipes consume `font-size` /
|
|
|
`line-height` from the size bundle but not `letter-spacing`; plus ~7 raw
|
|
|
letter-spacing literals in component CSS.
|
|
|
- **Tier-A `data-depth` stamping coverage**: overlays that should stamp their
|
|
|
plane (dialog / drawer / tooltip / menubar / combobox) per
|
|
|
[`rfcs/rfc-depth.md`](./rfcs/rfc-depth.md) §5.
|
|
|
- **Calendar family → shared layer/archetype** (user design verdict,
|
|
|
2026-07-07): the calendar-grid vocabulary (accent set, day geometry, the
|
|
|
holiday/event marks fixed in audit B.2) becomes a FIRST-CLASS shared layer
|
|
|
— today the family borrows it informally (measured: range-calendar
|
|
|
consumes 79 `--calendar-*` tokens, month-grid 65, year-grid 65,
|
|
|
date-range-picker 38, date-picker 4 — ~250 cross-component borrows), and
|
|
|
**chronos will draw from it too** when its track lands. Materialization to
|
|
|
decide at execution: a morfo part-archetype for the day cell (the 26-set
|
|
|
grows) and/or a `calendar-surface` shared recipe à la list-surface
|
|
|
(`--list-item-*` precedent). This supersedes "per-component composition
|
|
|
repairs" for the B.2 borrowers: the marks reach the whole family through
|
|
|
the shared layer instead.
|
|
|
- **Field family composition — segments under `[data-field]`** (user design
|
|
|
verdict, 2026-07-07, audit B.1): segment height derives from the SIZE axis
|
|
|
at the FIELD family level — `field` recipe declares `segment-height` with
|
|
|
size-scoped declarations (`calc(var(--field-control-height-{k}) -
|
|
|
var(--space-2))`; the TSC `size:` scope axis exists, unused so far), and
|
|
|
the per-component copies die (each x-field today re-declares the whole
|
|
|
`height-{xs..xl}` family the field recipe already has). **MANDATED by the
|
|
|
B.1-(i) verdict**: date/time/color-field MUST incorporate the Field
|
|
|
wrapper (segments under `[data-field][data-size]` — probed live: today
|
|
|
they don't) as part of the component audit; the three broken
|
|
|
per-component `segment-height` tokens and their consumers were removed
|
|
|
(they had computed `auto` since birth — zero visual change).
|
|
|
- Whatever F/G registers as "component-level adoption gap" in
|
|
|
`docs/audit/theming-audit.md` when that report lands (deliverable G of the
|
|
|
2026-07 theming audit).
|
|
|
|
|
|
## 4. `architecture/sema.md` hold-sync — 2026-07-06
|
|
|
|
|
|
**Origin**: A.4/A.5 execution (holds single-source). `SEMA_HOLDS_BY_INTENT`
|
|
|
(`src/uix/sema/holds.ts`) is now THE only hold table (resolver + visual
|
|
|
channel consume it; `sema-map.ts`'s per-family hold column died). The chapter
|
|
|
[`architecture/sema.md`](./architecture/sema.md) still narrates per-family
|
|
|
holds in ~34 places. **Blocked**: the file is the author's WIP (uncommitted
|
|
|
edits) — sync it when the author's pass lands, not before.
|
|
|
|
|
|
## 6. THM-2 rollout — per-instance palette to all surfaces — SHIPPED 2026-07-12; scope boundaries REVERTED by the open-color-cage initiative (2026-07-18/19)
|
|
|
|
|
|
Shipped in `05f0031c` (media-player Batch-4 + 14 surfaces + vocab + 4 props)
|
|
|
and `cc7e9a16` (avatar root). Every per-component palette cascade now collapses
|
|
|
into the shared `--palette-*` layer (size win, behavior-preserving for roles);
|
|
|
the vocabulary gained the alpha `surface`/`surface-hover` + `hover` slots.
|
|
|
|
|
|
> **REVERSED (2026-07-18/19) — "abrir la jaula del color".** THM-2 originally
|
|
|
> kept semantic controls (checkbox/radio/stepper/editable/select/file-upload)
|
|
|
> and `Avatar.Badge` role-restricted "by design". A later user decision reverses
|
|
|
> that: `color` accepts the FULL system (role / intent / 33 scales / raw CSS =
|
|
|
> `ComponentColorProp`) on **every** component, no exceptions — content-ink
|
|
|
> primitives keep their axis as an additive union. Enforced by two guards in
|
|
|
> `recipe-css-contract.test.ts`. Full record + the closed queue (incl. the
|
|
|
> chart SVG resolver and the Card/Avatar custom-color fix):
|
|
|
> [`process/open-color-cage-2026-07.md`](./process/open-color-cage-2026-07.md).
|
|
|
> Only `qr-code` stays out (its `color` is the QR module ink, not a scale).
|
|
|
|
|
|
## 5. Book editorial note — "el hold es suelo, no tijera" — 2026-07-06
|
|
|
|
|
|
**Origin**: A.4/A.5 verdict. The mechanism (hold = floor for the expression,
|
|
|
never a scissor; expression waits animation-finish after the hold, capped by
|
|
|
an absolute `MAX_EXPRESSION_WAIT_MS`, never derived from the hold) is recorded
|
|
|
in [`decisions/book-deviations.md`](./decisions/book-deviations.md) D.12 as a
|
|
|
candidate **editorial appendix** for the book (_Diseñando lo que ocurre_,
|
|
|
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 `ToggleGroup`s (categories + skin tone). Data is
|
|
|
the vendored `$libs/emoji` (baked from emojibase, MIT — `scripts/generate-emoji-data.ts`):
|
|
|
~1,900 emoji, 316 tone-capable. The grid double-registers Command's
|
|
|
List/Item so it keeps the a11y but wears its own grid-cell styling. Delegated
|
|
|
(no own events). Full-grid virtualization + per-emoji tone + image emoji
|
|
|
(Twemoji/Noto) deferred. The quick-set Popover in `ChatMessage.ReactionAdd`
|
|
|
stays as the inline tapback bar (different UX).
|
|
|
- **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`](./process/PLAN-blocks.md).
|
|
|
|
|
|
**Status**: D-BLK decisions SIGNED 2026-07-21 (D-BLK.1 amended by the user:
|
|
|
the tier lives at `src/uix/blocks/`, not `src/blocks/`). **F0 SHIPPED
|
|
|
2026-07-21** (`069759604`: doctrine + `$blocks` + `blocks:check` + gallery).
|
|
|
**Reference study SHIPPED 2026-07-21** (`aff42fee0`:
|
|
|
[`process/RESEARCH-blocks-references.md`](./process/RESEARCH-blocks-references.md),
|
|
|
6 research tracks; every phase-0 now checks against it). Amendments E-1…E-5
|
|
|
SIGNED 2026-07-21: F1 = **8** canon components (+`nav-tree`, data-driven docs
|
|
|
navigation tree); parity-floor = v1 scope policy; F2 = **14** site blocks
|
|
|
(+banner · team · contact · content-section); distribution registered as §9;
|
|
|
⌘K stays app-land.
|
|
|
|
|
|
**F3 STARTED 2026-08-19** — `app-shell` shipped with a signed phase 0 (four
|
|
|
decisions: the block wires no services, two scroll models by prop, the top bar
|
|
|
inside the inset, generated bypass links) and a new canon component in front of
|
|
|
it, `SkipLink` (WCAG 2.4.1). It closes ledger row **A-95**, the one thing the
|
|
|
tier could not fix from inside any block: with the shell owning the page's
|
|
|
heights, an in-flow notice and a pinned header overlap by **0px** (measured;
|
|
|
before: 49px, with the hit test landing on the strip).
|
|
|
|
|
|
**Deps**: `scheduler` block gated on `chronos` landing; chat-room block
|
|
|
explicitly NOT here (canon `chat-*` family, §7 above).
|
|
|
|
|
|
## 9. Blocks distribution — registry + llms.txt + MCP — 2026-07-21
|
|
|
|
|
|
**Origin**: the blocks reference study (E-4 signed 2026-07-21; dossier
|
|
|
[`process/RESEARCH-blocks-references.md`](./process/RESEARCH-blocks-references.md)
|
|
|
§P6). The ecosystem's 2026 table stakes for component/block distribution are
|
|
|
agent-enumerable catalogs: a registry protocol (shadcn `registry.json` /
|
|
|
`registry-item.json`), `llms.txt` (+ `llms-full.txt` / `.md` content
|
|
|
negotiation) over the docs corpus, and an MCP server so agents can list and
|
|
|
install blocks (sv-blocks precedent). Scope when started: (a) `llms.txt`
|
|
|
generation from the docs corpus (cheap — the corpus is already Markdown,
|
|
|
pairs with F4 docs blocks); (b) a blocks/components registry manifest; (c)
|
|
|
an MCP server exposing catalog + morfo contracts (pairs with the
|
|
|
contract-introspection superation angle and the palabras F-B agentive
|
|
|
track). Registered, not started.
|
|
|
|
|
|
**Deps**: F2 catalog existing (something to distribute); F4.3 props-table
|
|
|
work shares the morfo-introspection substrate.
|
|
|
|
|
|
## 10. Barcode — deferred symbologies + GS1 profiles — 2026-07-29
|
|
|
|
|
|
**Origin**: the `Barcode` component (shipped 2026-07-29; design record +
|
|
|
dispositions in
|
|
|
[`eidos/components/barcode/README.md`](../src/uix/eidos/components/barcode/README.md)
|
|
|
§Gaps). v1 covers the Retail + Logistics set — Code 128 (A/B/C auto), EAN-13/8,
|
|
|
UPC-A/E, Code 39, ITF/ITF-14 — from their published ISO standards, in the
|
|
|
zero-dependency `$libs/barcode` encoder.
|
|
|
|
|
|
**Shipped the same day, out of this registry: the ISBN input profile.** An ISBN
|
|
|
barcode IS an EAN-13 (ISO 2108, Bookland 978/979), so it entered as a profile
|
|
|
over `ean13` — separators tolerated in the numeric symbologies, ISBN-10 read and
|
|
|
converted after validating its own base-11 check digit — and explicitly NOT as a
|
|
|
ninth `symbology` value, which would make `data-symbology` claim a standard that
|
|
|
does not exist. The doctrine is recorded in the component README §ISBN.
|
|
|
|
|
|
Four dispositions stay **diferir**, each a self-contained follow-up over the
|
|
|
same encoder:
|
|
|
|
|
|
- **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`](../src/uix/eidos/components/background/README.md);
|
|
|
plan and signed decisions in
|
|
|
[`process/PLAN-background.md`](./process/PLAN-background.md)). It ships as the
|
|
|
HOST of a surface's background layers — a child that pins itself behind its
|
|
|
parent's content, with the parent adopted by a foundation rule rather than
|
|
|
wrapped. Patterns, gradients, scrims, images and video are in; so is the WCAG
|
|
|
2.2.2 pause control.
|
|
|
|
|
|
**Scope of this registry entry** — what the component deliberately does NOT own
|
|
|
yet, each a self-contained follow-up:
|
|
|
|
|
|
- **Parallax and the scroll seam** — `animation-timeline: view()` with a
|
|
|
`ScrollProgress` fallback, `attach='fixed'`, the pointer `depth` axis. The
|
|
|
infrastructure landed with the component (`$adom`'s `ScrollProgress` and
|
|
|
`prefersReducedData`); the layers that consume it did not. It is the one
|
|
|
follow-up with a mechanism already chosen and measured against browser
|
|
|
support, so it is also the one most likely to move first.
|
|
|
- **The scrim `strength` scale is not ordered by weight** — measured: `subtle`
|
|
|
veils MORE than `overlay`, and `overlay` and `muted` resolve to the same
|
|
|
number. The names come from the semantic opacity scale, which says how opaque
|
|
|
an ELEMENT is; borrowing them for a VEIL imports an ordering that means
|
|
|
something else. Renaming or renumbering is an API change, not a fix in
|
|
|
passing.
|
|
|
- **A bright photograph is illegible at every scrim weight** — white copy over
|
|
|
the heaviest veil measures below AA. Either the scale gains a step above
|
|
|
`--opacity-subtle`, or the doctrine becomes "the answer is a graded scrim",
|
|
|
stated once and demonstrated.
|
|
|
- **A shared `<video>` state port** — the framework has a load-state contract
|
|
|
for `<img>` (`ImageProvider` in `soma/layers`) and none for `<video>`:
|
|
|
`ScrollFrames` listens by hand and so does `Background.Video`. Two consumers
|
|
|
is the threshold this codebase uses to extract a layer.
|
|
|
- **The `Ambient` pack honouring the pause** — the canon publishes the context
|
|
|
(`paused`, `reduced`, `seen`); the pack reads it. A task OF the pack, because
|
|
|
the dependency runs pack → framework and never back.
|
|
|
|
|
|
**Deps**: the parallax entry needs no new service — the two `$adom` ports it
|
|
|
consumes already shipped. The scrim entries are the author's call on the token
|
|
|
vocabulary, so they block on a decision, not on code.
|
|
|
|
|
|
## 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í.
|
|
|
|
|
|
8. **En `tree-view` el velo DERRAMA sobre el subárbol entero** (medido y
|
|
|
capturado 2026-08-21 al tokenizarlo). `archetype: 'item'` está declarado en
|
|
|
el **`<li>` branch** (`morfo/components/tree-view.ts:103`), que contiene el
|
|
|
`branch-control` Y el `branch-content` con todos los descendientes — así que
|
|
|
al pasar el ratón por la fila de una carpeta abierta se tiñe **la carpeta
|
|
|
entera**: el nodo velado mide **336 px** (la rama completa) frente a los
|
|
|
36 px de la fila que el usuario señala. Un branch anidado acumula además el
|
|
|
velo de sus ancestros, así que sus filas se ven más oscuras que las
|
|
|
hermanas. Es la tercera variante de la misma familia: `table` lo tiene en
|
|
|
fila Y celda (punto 2), `grid-list` en fila Y celda sobre el MISMO nodo
|
|
|
(punto 7), y aquí en un **ANCESTRO** del nodo señalado. Mover el arquetipo
|
|
|
del `<li>` al control es morfo, y mueve píxel en toda la demo.
|
|
|
|
|
|
Ojo al tokenizar la familia: en `tree-view` el `branch-control` **no lleva
|
|
|
archetype**, así que ahí el plano de la receta es la única pintura y su
|
|
|
`hover-row-bg` SÍ es un token legítimo — al revés que en `grid-list`. La
|
|
|
misma regla CSS cubre los dos nodos.
|
|
|
|
|
|
9. **El plano de profundidad `overlay` impone TIPOGRAFÍA, no sólo elevación**
|
|
|
— medido con CDP el 2026-08-21 al tokenizar `link-preview`.
|
|
|
`[data-depth='overlay']` (`generated/base.css:7088`) declara
|
|
|
`font-family: var(--style-label-font-family)` y
|
|
|
`line-height: var(--leading-ui)` junto al fondo, el borde y la sombra. Un
|
|
|
componente que estampe el plano para su elevación —**veinte lo hacen**—
|
|
|
hereda además esa tipografía, y su propia declaración empata a **(0,1,0)**
|
|
|
y **pierde por orden de carga** (la hoja generada va después del chunk de la
|
|
|
receta). En `link-preview` el par `font-family` / `line-height` de la receta
|
|
|
NO pinta: el plano impone 1.25 donde ella pide `--leading-normal` (1.5).
|
|
|
Cuarta ocurrencia de la clase «empate resuelto por orden de carga» (§12.3, y
|
|
|
las dos del calendario y de `tree-grid`).
|
|
|
|
|
|
⚠ **El par se deja DECLARADO a propósito.** Se retiró primero —daba 0 diffs,
|
|
|
porque no pintaba— y se RESTAURÓ el 2026-08-22 al revisar la sesión:
|
|
|
retirarlo no es neutral, **fija el empate a favor del plano** y con él el
|
|
|
1.25, que es justo la decisión que este punto tiene pendiente. Un empate por
|
|
|
orden de carga se registra, no se resuelve dentro de un commit de
|
|
|
tokenización. Los dos tokens quedan adjudicados en el ledger de R-5.4 con esa
|
|
|
razón, y la medición del orden se hizo en DEV (estable en cinco recargas);
|
|
|
en un bundle de producción está sin verificar.
|
|
|
|
|
|
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 de `archetypes.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: fija `color: var(--color-content-primary)` —el token
|
|
|
`--tags-input-item-fg` **no ha pintado nunca**, y el
|
|
|
`--_tags-input-palette-text` del estado activo tampoco— y pinta el velo
|
|
|
`background-image` en 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); poner `aria-selected=false`
|
|
|
sobre el mismo nodo y la misma escritura alcanza (`rgb(1, 2, 3)`). Es la
|
|
|
tercera ocurrencia de §12.5 (tras `table.selected-row-fg` y la tinta de
|
|
|
`listbox`) y la segunda del par arquetipo-`item` con `aria-selected`, tras
|
|
|
`grid-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-selected` a `v.stateRef('active')`, así que `item-fg` PINTA 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, con
|
|
|
> `CSS.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 BLOQUE** `archetypes.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. «Es `data-highlighted`, no `aria-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 soltar `color`: `--state-hover` es
|
|
|
> `color-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 de `tags-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 Variables` del 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 mueve
|
|
|
`indicator-{thickness,bg,radius}`), **pero el segundo NO tiene traducción**:
|
|
|
la receta clava `block-size: var(--navigation-menu-indicator-thickness)` y
|
|
|
desplaza el subrayado por debajo del trigger con `translateY`, 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 el `navigation-menu` subraya y quien quiera píldora usa
|
|
|
`tabs`, que es la parte que ya hace exactamente eso con
|
|
|
`lib/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.css` estaba 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:
|
|
|
- ~~`chronos.css:417`~~ — **RETIRADO 2026-08-25** (firma «el velo del sistema
|
|
|
no muere por un atajo»), y **§13 lo había adjudicado MAL**. Decía que su
|
|
|
`background: var(--_button-bg)` le ganaba al velo de hover de
|
|
|
`archetypes.css` y que retirarlo era un cambio de píxel. El HECHO es cierto
|
|
|
—el `+N more` no recibe el velo, su afordancia es el `underline` de su
|
|
|
receta— pero la ATRIBUCIÓN era falsa: al velo lo mata `button.css:69`
|
|
|
(`[data-button]`, (0,1,0)) y `button.css:88` (`:hover`, (0,2,0)), con el pin
|
|
|
puesto **o quitado**; medido, el único autor de `background-image` que casa
|
|
|
es `archetypes` y 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 su `block-size: fit-content`. **0 diffs en 20 combinaciones
|
|
|
`variant` × `size`**, reposo y hover, con el sucesor NOMBRADO y control
|
|
|
negativo 20/20. No necesitó firma de producto: fue una limpieza. Ficha
|
|
|
`docs/audit/theming/chronos.md` anotada (censo 202→198, propuesta 50→47:
|
|
|
los tres `more-link-*` se quedaron sin consumidor, y proponían un público de
|
|
|
`chronos` con valor privado de `button` — 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» de `docs/audit/theming/calendar.md` que 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, fondo
|
|
|
`0.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.css` que 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 de `natural-time-picker` de **48/62 a 50/62** (reviven `trigger-bg`
|
|
|
y `open-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 el `frost`, y
|
|
|
`archetypes.css:19-21` ya declara que el anillo de foco «must lose to any
|
|
|
component rule». **Decisión pendiente, con su número.**
|
|
|
- **El suelo de `popover.css` COMPARTE peldaño con `archetypes.css`** — y lo
|
|
|
único que queda del empate es `transition` (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 declaraba `background:`, un
|
|
|
ATAJO que expande a los ocho longhands y entre ellos `background-image: none`
|
|
|
— que es EXACTAMENTE donde `archetypes.css` pinta 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 en
|
|
|
`background-color:` (`popover.css:60` y `:75`), el velo **compone encima
|
|
|
gane quien gane el orden** — medido idéntico en los DOS órdenes, donde antes
|
|
|
daba `none`. 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 que `feedback_hover_is_the_systems_never_a_per_component_invention` ya
|
|
|
mandaba: el acento de estado va en `background-color` para que la capa
|
|
|
COMPONGA encima.
|
|
|
- **`transition` — RESIDUO VIVO, con su número.** El suelo declara
|
|
|
`background, 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 de `border-color` del 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.css` deje 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 mueve `background-color` 0.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 residuo
|
|
|
`transition`.
|
|
|
|
|
|
- ~~**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 da FALSOS POSITIVOS cuando la receta re-declara el público en
|
|
|
el ELEMENTO** (medido 2026-08-23 en `avatar`). El guard escribe el token en
|
|
|
`:root` **y sobre cada nodo del componente** —para alcanzar un panel portalado
|
|
|
que no ve la raíz— y el comentario que justifica eso dice que escribir en
|
|
|
todas partes «no puede fingir una victoria a nivel de PROPIEDAD». Cierto para
|
|
|
una propiedad; **falso para una propiedad personalizada** que la receta vuelve
|
|
|
a declarar en un bloque `[data-size]`: ahí el inline del guard gana justo lo
|
|
|
que un tema pierde. `--avatar-group-overlap` leía VIVO y desde `:root` no
|
|
|
movía un píxel. No hay arreglo obvio (quitar la escritura sobre el nodo
|
|
|
resucita el punto ciego del portal); mientras tanto, **un público que la
|
|
|
receta re-declare sobre el elemento se mide a mano desde `:root`**.
|
|
|
- **El barrido de tallas del centinela para en `xl`.** Dos componentes tienen
|
|
|
paso `xxl` (`metrics`, `avatar`) y sus claves leen muertas: ocho excepciones
|
|
|
de ledger escritas por ese motivo. Añadir el paso es una línea, pero deja
|
|
|
STALE las seis de `metrics`, así que se hace con la re-verificación del
|
|
|
ledger entero, no en el commit de un componente.
|
|
|
- **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.
|
|
|
> **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.css` desde
|
|
|
> `: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-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.
|
|
|
> **CERRADO 2026-08-25 por §55.** No hizo falta subir el peso del pin: bajó el
|
|
|
> del rival. El sobre de `popover.css` se 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 — 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`).
|
|
|
|
|
|
- **Hueco de demo: `card-group` no monta su encabezado ESTÁTICO.** La página
|
|
|
renderiza el título sólo en su forma de disclosure (`[data-button]`), así que
|
|
|
la mitad `[data-static]` del cromo del título —tinta, familia, tamaño,
|
|
|
interlineado y el hueco inferior— no se ve ni se mide: cinco tokens
|
|
|
adjudicados por eso. Misma clase que `date-range-picker` con
|
|
|
`kind='month'|'year'` (norma N-6).
|
|
|
|
|
|
- ~~**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 hay
|
|
|
`theming-census.test.ts` ni equivalente bajo `src/uix/eidos/`, así que hoy
|
|
|
**nada impide que el alcance BAJE** entre sesiones: sólo se detectaría
|
|
|
mirando la cifra a mano. Con el censo ya endurecido (ver arriba) es el
|
|
|
momento de escribirlo.
|
|
|
|
|
|
- **⚠ `knob.arc-width` significaba DOS cosas con DOS defaults distintos** —
|
|
|
medido 2026-08-22 al tokenizarlo: el inset de la cara del dial (`--space-3`)
|
|
|
y la separación superior del puntero (`--space-2`). Un tema que lo escribiera
|
|
|
movía las tres declaraciones a la vez, pero SIN tema cada una valía algo
|
|
|
distinto. Separado en `arc-width` + `pointer-inset` con 0 diffs. Queda como
|
|
|
aviso de clase: **un mismo nombre con defaults distintos por sitio es una
|
|
|
colisión aunque el override funcione** — es la definición que el propio
|
|
|
generador usa para marcar `⚠`, y conviene barrer el catálogo buscando más.
|
|
|
|
|
|
- **⚠ `mockup` no tiene ruta de demo** (`/uix/components/mockup` da 404), como
|
|
|
le pasaba a `picker-shell`. Con 29 knobs al 38 % es el mayor de los que
|
|
|
quedan sin tocar de su tramo, y sin demo no hay sonda ni guard: habría que
|
|
|
medirlo prestado o darle página.
|
|
|
|
|
|
- **⚠ R-5.1 y R-5.2 no están implementadas** en `component-audit` (0 disparos
|
|
|
hoy; sólo existe R-5.3 de gramática y R-5.4 de alcance). El plan las da por
|
|
|
hechas en F0 «en `warn`» y F3 las quiere en `error`, así que **F3 no puede
|
|
|
cerrarse sin escribirlas primero**. Cuando lleguen, los componentes con
|
|
|
alcance parcial necesitan su marca `R-5.x exception:` en el README — ya
|
|
|
puestas en los 16 de F2-B que no llegan al 100 %.
|
|
|
|
|
|
- **Cuatro componentes salen NEEDS-WORK en `component:audit`** y son
|
|
|
PREEXISTENTES (ninguno tiene un commit de F2-B en su código): `badge`,
|
|
|
`gradient-picker`, `mockup` y `motion`. Anotado para que nadie los atribuya
|
|
|
al eje de theming al leer el informe.
|
|
|
|
|
|
- **⚠ Un worktree de comparación NO hidrata si su `node_modules` es una
|
|
|
junction fuera del root** — Vite responde **403** a `@fs/…` y SvelteKit no
|
|
|
arranca, así que la página SSR se ve pero ningún componente monta. Mordió en
|
|
|
la revisión adversarial de F2-B, que comparó contra una base muerta sin que
|
|
|
nada lo delatara. Se arregla con `server: { fs: { allow: [...] } }` en el
|
|
|
`vite.config.ts` del worktree, y la regla que generaliza es **comprobar que
|
|
|
un componente monta ANTES de comparar**. (Memoria
|
|
|
`worktree-devserver-fsallow`; vuelve a morder cada vez.)
|
|
|
|
|
|
- **⚠ `--radius-xs` NO EXISTE, y una receta lo referenciaba.** La escala de
|
|
|
radios del sistema es `sm | md | lg | xl | xxl` (+ `none`, `full`,
|
|
|
`default`), sin `xs`. `text-focus` escribía
|
|
|
`border-radius: var(--radius-xs, 3px)`: **pintaba el fallback**, así que
|
|
|
nadie lo notó — hasta que tokenizarlo sin fallback dejó la variable vacía, la
|
|
|
declaración inválida y las cuatro esquinas cuadradas (**48 diffs**, medidos
|
|
|
2026-08-22). Corregido a `3px` verbatim, el valor que de verdad pintaba.
|
|
|
|
|
|
Lo que esto dice del catálogo: **un `var(--fantasma, fallback)` es una
|
|
|
referencia muerta que funciona por accidente**, y el guard G2 que el plan
|
|
|
menciona («referencias-fantasma contra el contrato derivado») o no existe o
|
|
|
no cubre los fallbacks. Barrer el catálogo buscando `var(--…, …)` cuyo primer
|
|
|
nombre no esté en el contrato derivado es un pase mecánico que se paga solo.
|
|
|
|
|
|
- **⚠ `filter` y `backdrop-filter` faltaban en la lista de propiedades del
|
|
|
guard** — arreglado 2026-08-22. `text-focus.glow-color` vive dentro de un
|
|
|
`drop-shadow()` y leía «no effect» estando vivo; `KNOB_PROPS` del censo sí
|
|
|
las tenía, así que el censo contaba el knob y el guard no podía verlo.
|
|
|
Verificados los 30 componentes con ledger tras el cambio: cero regresiones,
|
|
|
cero STALE.
|
|
|
|
|
|
- **⚠ 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-player` calcula su ancho como `min(100%, base × scale)`: el
|
|
|
`1234px` del guard se capa al contenedor y los siete tokens de tamaño leen
|
|
|
«no effect» aunque estén vivos. Con valores PEQUEÑOS (111/122/133px y escalas
|
|
|
0.11/0.22/0.33) alcanzan exactamente. Es primo del caso ya registrado «un
|
|
|
valor centinela igual al real lee como no efecto» (`9999px` contra un
|
|
|
`--radius-full` que ya era 9999px): **el centinela tiene que ser imposible
|
|
|
EN LA DIRECCIÓN en que la propiedad puede moverse**. Un guard que probara
|
|
|
ambos extremos —uno muy grande y uno muy pequeño— cazaría las dos familias.
|
|
|
|
|
|
Lo que la REVISIÓN ADVERSARIAL de F2-B destapó (2026-08-22):
|
|
|
|
|
|
- **`drag-drop.preview-z` era un token que MENTÍA, y lo escondió una
|
|
|
adjudicación hecha sobre un nodo sintético.** El ledger decía «forzado →
|
|
|
alcanza (100 → 4321)», pero ese nodo lo había creado yo con JS. El preview
|
|
|
REAL —el que soma monta durante un arrastre de verdad— lleva
|
|
|
`z-index: 9999` **INLINE** (`drag-drop-provider.svelte.ts:663,671`), así que
|
|
|
ninguna cascada puede ganarle: el token resolvía a 4321 y el nodo computaba 9999. Retirado con 0 diffs, que es la prueba. Misma clase que el `item-gap*`
|
|
|
de `carousel` que retiró la revisión de F2-A: **canal de valor de soma, no
|
|
|
superficie de tema**.
|
|
|
|
|
|
La lección para el protocolo: **una adjudicación «forzado → alcanza» vale lo
|
|
|
que valga el nodo que se forzó**. Si lo creaste tú, no lleva lo que soma le
|
|
|
pone. Verificado el resto de la misma clase: `clipboard` (indicador real tras
|
|
|
un clic real) y `picker-shell` (pie real) coinciden con lo adjudicado y no
|
|
|
llevan inline; `skeleton-line` lleva inline pero de `inline-size`, no del
|
|
|
eje tokenizado.
|
|
|
|
|
|
- **El orden de carga NO es el mismo en dev que en producción.** Medido con
|
|
|
`npm run build` + preview y CDP: en dev la receta de `link-preview` va
|
|
|
ANTES del plano `overlay`, en producción queda EN MEDIO de sus dos reglas.
|
|
|
El ganador coincide por casualidad —hay dos reglas del plano y una queda
|
|
|
siempre al final—, pero la fragilidad de §12.9 es real y ahora está medida en
|
|
|
los dos entornos, no supuesta.
|
|
|
|
|
|
- **⚠ Un worktree de comparación NO hidrata si su `node_modules` es una
|
|
|
junction fuera del root**: Vite responde **403** a `@fs/…` y SvelteKit no
|
|
|
arranca, así que la página SSR se ve pero ningún componente monta. La primera
|
|
|
pasada de esta revisión comparó contra una base muerta y dio «77 casos, 1
|
|
|
diferencia» sin que eso significara nada. Se arregla con
|
|
|
`server: { fs: { allow: [...] } }` en el `vite.config.ts` del worktree. Es la
|
|
|
memoria `worktree-devserver-fsallow`, y vuelve a morder cada vez.
|
|
|
|
|
|
Lo que la cola pequeña añadió (2026-08-22):
|
|
|
|
|
|
- **⚠ El censo no distingue «0 % por deuda» de «0 % POR NATURALEZA», y ya son
|
|
|
cinco componentes.** Medidos uno a uno el 2026-08-22, `aspect-ratio`,
|
|
|
`text-blur`, `cascade`, `motion` y `date-picker` tienen alcance 0 % y
|
|
|
**ninguno tiene contrato que escribir**: el knob de `aspect-ratio` está
|
|
|
PRESTADO de `box` (la ficha ya lo marca ⤴) porque es una faceta suya, no un
|
|
|
componente; los de `text-blur` son el `1px` de la técnica sr-only; los de
|
|
|
`cascade` y `motion` son el `opacity` del gate antiparpadeo, mecánica del
|
|
|
canal de motion cuyo valor tematizable vive en `EidosConfig.motion`; y los de
|
|
|
`date-picker` son la corrección `max-content` que impide que el pie del
|
|
|
popover desborde, con un único valor correcto. Sus veredictos §5 quedan
|
|
|
escritos.
|
|
|
|
|
|
Mientras el censo los cuente igual que a un componente con deuda real, la
|
|
|
cifra global miente por abajo y el gate de F3 («censo 100 %») es inalcanzable
|
|
|
por construcción. Lo que hace falta es una clase más —`structural`, junto a
|
|
|
`system`— o una marca en la ficha que los saque del denominador, como
|
|
|
`LAYER_VOCABULARY` hizo con la familia calendar. Es la misma pregunta que ya
|
|
|
está abierta para las capas compartidas, un piso más abajo.
|
|
|
**EJECUTADA 2026-08-25 (cierre del eje, `2c61946fb`)**: clase `structural`
|
|
|
en el censo, por COMPONENTE, con la razón AL LADO (forma `LAYER_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-langs` es deuda REAL
|
|
|
(31 globales crudos, hoy en el ledger de deuda) y
|
|
|
`range-calendar`/`month-grid`/`year-grid` son la pregunta de las capas
|
|
|
compartidas. ⚠ Deuda que dejó el cierre: las fichas de
|
|
|
`docs/audit/theming/` NO se regeneraron (`--report` pisa prosa y exige
|
|
|
supervisión por ficha) — `aspect-ratio.md` sigue diciendo «0 %» y la tabla
|
|
|
del README del corpus no tiene la columna `strct`; y el desglose «Knobs de
|
|
|
apariencia» de la ficha no suma (lista `excepción` entre los sumandos y
|
|
|
`row.knobs` la excluye — preexistente).
|
|
|
|
|
|
- **⚠ DOS FIRMAS PENDIENTES que el cierre del eje dejó NOMBRADAS (2026-08-25,
|
|
|
cabecera de `scripts/theming-census-debt.ts` y recipe-contract §4)**:
|
|
|
(1) ~~**los 318 privados no-derivados**~~ — **EJECUTADA 2026-08-26** en tres
|
|
|
piezas (`e812d3ffd` F1 · `a74aeec7b` F2 · 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): **132 `bridge`**
|
|
|
(lee lo que el forward THM-2 escribe, medido contra la salida real del
|
|
|
generador) · **36 `channel`** (nadie lo declara; lo escribe soma o el
|
|
|
envoltorio por instancia) · **84 anotados** con la válvula nueva
|
|
|
`/* private: <razón> */` → clase `exception` · **66 al ledger** con `private`
|
|
|
como `DebtClass` de pleno derecho (`newDebt` y 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
|
|
|
`var(--{c}-x, fallback)` sin entrada en `base.ts`**~~ — **EJECUTADA
|
|
|
2026-08-26**, firma «la ley del espacio cerrado»: el espacio de nombres
|
|
|
público es CERRADO sobre el contrato, un `--{c}-*` existe **si y sólo si**
|
|
|
`base.ts` lo 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-shadow` cargaba DOS defaults bajo un nombre — y `text-scramble`
|
|
|
×2), defaults verbatim a scope `root`; el guard pasa al «si y sólo si» y
|
|
|
muta en tres caras. Neutra en píxel medida dos veces (diff de `generated/`
|
|
|
= esas 11 líneas y nada más; A/B en navegador con los tokens forzados a
|
|
|
`initial`, computed idéntico en los tres mockups de `/blocks/feature-split`,
|
|
|
ambos cromos, y en `text-scramble`). Números: `no contract entry` 18 → **16**,
|
|
|
todo lo demás quieto (reach 73 %, 65 al 100 %, ledger 1154 · 0 NEW · 0 STALE,
|
|
|
audit 162/4, `--names` 0 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 como
|
|
|
`public` son canal de VALOR del `scheme-style` del documento
|
|
|
(`schemeCssVars`), no contrato de tema — el 22 % de `palabras` no 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_RENAME` de
|
|
|
`src/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 de `staggerViewport`—, el registro llegó a **CERO** y se
|
|
|
**DESMONTÓ**. Las dos entradas de abajo quedan como historia de su día.)_
|
|
|
1. ~~**El codemod de los 14 CANALES DE VALOR públicos a `--_{c}-*`.**~~ —
|
|
|
**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 `git ls-files` de 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 propio
|
|
|
`PENDING_PRIVATE_RENAME`. ⚠ La firma MEDIDA que retiró
|
|
|
`--color-swatch-fill` del 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, un `background-image` de velo en el trigger de
|
|
|
`emoji-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) — `public`
|
|
|
3074 → **3073**, `channel` 36 → **37**, denominador 4228 → 4227, reach
|
|
|
**73 % intacto**, `atHundred` **65 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-gradient` lee `--knob-track-bg`, el panel de `menubar` lee
|
|
|
`--menubar-panel-min-width` como 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 Variables` como forma controlada
|
|
|
por el consumidor con dos ejemplos de CSS de app leyendo `-h`, y `-w` era
|
|
|
clave DECLARADA del contrato — la única razón por la que el guard veía a
|
|
|
`-h` sola. **Lo firmado, (a)**: las cuatro pasan a
|
|
|
`--_navigation-menu-indicator-{x,y,w,h}` (flip atómico escritor+lectores,
|
|
|
barrido `git ls-files` por nombre), `indicator-w` se 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 su `data-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 clase `channel` de 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.
|
|
|
**`-w` era un token-que-miente, y está MEDIDO** (CDP, transiciones
|
|
|
congeladas porque `inline-size` se transiciona en esta parte): con el
|
|
|
subrayado pintado, escribirlo en la raíz del componente —lo que hace el
|
|
|
centinela— deja `inline-size` en 107px, y en `:root` igual; sólo una regla
|
|
|
`!important` de autor llega. Puntuaba `reach` en 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**: `public` 3073 → **3072**, `channel` 37 → **38**, denominador
|
|
|
4227 → 4226, reach **73 %** y `atHundred` **65** intactos, `--names`
|
|
|
4567 → **4566**, ledger **1154 · 0 new · 0 stale**. Centinela de
|
|
|
`navigation-menu` **43/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-size` y
|
|
|
`left` las siguen (0px → 111px al deslizar) y el `translateY(36px)` se
|
|
|
mantiene; **los nombres viejos computan VACÍO** y fijarlos a 777px no mueve
|
|
|
nada. Los tres pomos del contrato siguen llegando (`block-size` 2 → 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 de
|
|
|
`color-swatch` (su adjudicación medida del 23-08), `PLAN-background.md` (9
|
|
|
menciones, una sola anotación fechada que las cubre) y `STUMBLES.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 GENERADA
|
|
|
`docs/audit/theming/background.md:72` sigue citando `--background-progress`
|
|
|
dentro de la tabla de `--report` — no se toca a mano; la regenerará el
|
|
|
`--report` supervisado 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** — `PROPS` de `__theming-probe.ts` no lee
|
|
|
`translate`, `rotate`, `gridTemplateColumns` ni `animationDuration`, así
|
|
|
que su «0 diffs» mide la apariencia circundante, no la supervivencia del
|
|
|
canal (el control positivo de `background` dio 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-width` por plantilla — para
|
|
|
`name='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 de `utils.ts` y del barrel, cero llamadores
|
|
|
verificado por barrido.
|
|
|
**Hallazgo lateral, AJENO a esta firma**: el control `columns` de la demo
|
|
|
de `emoji-picker` es un `<Slider>` DESNUDO (sin hijos) y `slider.svelte`
|
|
|
renderiza `<Slider.Provider>{@render children?.()}</Slider.Provider>` — o
|
|
|
sea, un `div[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 desde `opts.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).
|
|
|
2. ~~**El cableado `EidosConfig.motion` → CSS de
|
|
|
`--motion-stagger-each-default`**~~ — **✅ 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 el `var(--motion-stagger-each-default, 70ms)` de `motion.css:42`
|
|
|
resolvía siempre al fallback — un knob de PAPEL (E14 de
|
|
|
`PLAN-blocks-quality.md` ya lo había diagnosticado). Hogar:
|
|
|
`primitives.motion.staggerViewport: '70ms'` (junto a `stagger: '20ms'`) →
|
|
|
token `--motion-stagger-viewport`; **el sufijo `-default` se 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**
|
|
|
de `motion.css` a `renderMotionBlocks`, junto a las 24 filas de índice del
|
|
|
mismo selector: `motion.css` deja de consumir `--motion-*`, la entrada del
|
|
|
registro muere **por mecánica** (cero guards tocados, cero excepciones) y
|
|
|
`PENDING_PRIVATE_RENAME` llega a **CERO y se DESMONTA** — no se deja `{}`,
|
|
|
porque un `it` sobre un registro vacío no afirma nada. El veredicto
|
|
|
`structural` de `motion` queda MÁS verdadero: sigue sin clave en `base.ts`,
|
|
|
el censo no se mueve (`strct`, 1 knob; catálogo 73 %, 66 al 100 %, ledger
|
|
|
1152 · 0 · 0) y `motion.css` se queda exactamente con los dos gates de
|
|
|
`opacity` que 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)` de
|
|
|
`mockup` (era `mockup.css:73`) MIGRÓ verbatim a `base.ts` como default de
|
|
|
`mockup.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 de
|
|
|
`mockup` perdió 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ía `bridge` **sin
|
|
|
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) }`) en
|
|
|
`time-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 `timeline` hay 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 `listbox` son reversibles** ante la entrada §13 de
|
|
|
`list-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-shell` no tiene ruta de demo propia** (`/uix/components/picker-shell`
|
|
|
da 404) y sus partes se llaman `data-picker-header` / `-body` / `-footer` en
|
|
|
vez de `data-picker-shell-{parte}` — genéricas **a propósito**, dice su
|
|
|
receta, «so every picker gets the same visual contract for free». La suma de
|
|
|
las dos cosas deja al instrumental ciego: la sonda genérica devolvió **0
|
|
|
nodos**, que es el antipatrón de comparar cero valores y pasar. Se tokenizó
|
|
|
con sonda dirigida (abre el popover del `date-picker`, captura los seis nodos
|
|
|
reales en las cuatro `data-picker-size`: 576 valores, 0 diffs) y el guard
|
|
|
R-5.4 ganó un `COMPONENT_OVERRIDES` para poder abrirlo y filtrarlo — acotado
|
|
|
a este componente, con los otros catorce verificados sin cambio. Lo de fondo
|
|
|
sigue abierto: **una demo propia**, y decidir si el precio de los nombres
|
|
|
genéricos (un contrato visual común para todos los pickers) vale la ceguera
|
|
|
del instrumental.
|
|
|
- **El eje de talla de `picker-shell` es `data-picker-size` sobre un ancestro
|
|
|
PORTALADO**, así que el TSC no puede emitir su cascada (`scope: 'size:xs'`
|
|
|
generaría `[data-picker-shell][data-size='xs']`, que no casa nunca). Es el
|
|
|
único componente del eje cuyas coordenadas por talla quedan como públicos
|
|
|
planos que la receta consume desde sus propios selectores. Si algún día el
|
|
|
TSC gana un eje de scope configurable, éste es su caso de prueba.
|
|
|
|
|
|
Lo que `code-block` añadió (2026-08-21):
|
|
|
|
|
|
- **⚠ La demo de `code-block` NO ENSEÑA el `code-block`: el harness del sitio
|
|
|
de documentación le pinta el `<pre>` por encima.**
|
|
|
`[data-uix-docs] pre` (`web/routes/uix/uix.css:801`) pesa **(0,1,1)** y gana
|
|
|
a `[data-code-block-pre]` **(0,1,0)** — y `[data-uix-docs]` es el shell de
|
|
|
TODAS las páginas, así que la regla alcanza cualquier demo. Medidas **siete
|
|
|
propiedades pisadas**: `padding` (16px 20px del harness contra los 12px de la
|
|
|
receta), `border` (1px sólido contra ninguno), `border-radius` (8px contra
|
|
|
0), `background-color` (gris contra transparente), `font-family`,
|
|
|
`font-size` (13px contra 14px) y `line-height` (18.2px contra 20.3px). El
|
|
|
fondo, el borde y el radio que se ven en esa página son del sitio, no del
|
|
|
componente. Es la clase «la demo puede tapar el componente» (precedente
|
|
|
`proof-of-human`), pero aquí son siete propiedades y le toca justo al
|
|
|
componente cuya razón de ser es mostrar código. Por eso `pre-padding` lee
|
|
|
muerto en el guard y está adjudicado con esa medición.
|
|
|
|
|
|
Acotar la regla del harness es tocar el CSS del SITIO, que sirve a las ~162
|
|
|
páginas y a los bloques de código de las fichas: no entra en el commit de un
|
|
|
componente. Decisión aparte.
|
|
|
|
|
|
**No es un caso aislado — la misma familia de reglas pisa también a `code`**
|
|
|
(medido 2026-08-21 al tokenizarlo, con CDP): `[data-uix-docs] code` declara
|
|
|
`font-family`, `font-size`, `padding` y `border-radius`, y gana (0,1,1)
|
|
|
contra la regla BASE del componente (0,1,0). Sus reglas de VARIANTE (0,2,0)
|
|
|
sí ganan, así que el efecto es selectivo y por eso sólo su `radius` lee
|
|
|
muerto — 3px del sitio donde el componente pide 4px. Cualquier decisión sobre
|
|
|
la regla del harness cubre los dos componentes.
|
|
|
|
|
|
- **⚠ El guard de claves duplicadas del handoff INSPECCIONA EL VACÍO.** El
|
|
|
comando documentado en `CONTINUE-theming.md` («Trampas que costaron un commit
|
|
|
cada una») es
|
|
|
`grep -oE "^ '?[a-z0-9-]+'?: \{" src/uix/eidos/lib/recipes/base.ts | …`,
|
|
|
y **`grep -E` no interpreta ` ` como tabulador** — lo lee como una `t`
|
|
|
literal, así que el patrón no casa NADA: 0 coincidencias sobre 130 bloques
|
|
|
reales. El guard salía siempre «vacío = OK» sin haber mirado nada, que es la
|
|
|
definición del antipatrón que el propio proyecto tiene registrado. Con un tab
|
|
|
real en el patrón encuentra los 130 (comprobado: cero duplicados en el árbol,
|
|
|
así que no hay daño que reparar). El handoff queda corregido con la versión
|
|
|
que sí mira.
|
|
|
|
|
|
Lo que `text-gradient` destapó (2026-08-22) — **PENDIENTE DE FIRMA, apuntado
|
|
|
para resolver después** (autor, 2026-08-22):
|
|
|
|
|
|
- **⚠ La familia text-effects ACUÑA tipografía propia contra el canon.** Cinco
|
|
|
claves del contrato: `text-focus.word-size` (`var(--font-size-4xl, 3rem)` —
|
|
|
`4xl` es un paso que la escala canónica `xxs..xxxl` de
|
|
|
`lib/primitives/typography.ts` NO tiene: idioma Tailwind del import de julio
|
|
|
`63b371d5c`, pinta el fallback) y `word-weight` (900) ·
|
|
|
`text-circular.weight` (900) y `font-size` (1.5rem) ·
|
|
|
`text-gradient.font-weight` (500, subida el 22 en `97a6f03d7`). Son opiniones
|
|
|
del seed (react-bits) que el protocolo «valor verbatim» convirtió en contrato
|
|
|
sin la pregunta previa: ¿un tratamiento de pintura sobre texto real tiene
|
|
|
tipografía propia? La doctrina fijada en F2-B (regla 2, cinco precedentes:
|
|
|
`label`, `code`, `kbd`, `announce`, `code-block`) dice que una receta
|
|
|
CONSUME la capa tipográfica (`--style-*`) o HEREDA (`link` → `inherit`):
|
|
|
un TextGradient dentro de un `h1` ES un h1.
|
|
|
|
|
|
**Son SIETE claves en CUATRO de los cinco componentes** (medido 2026-08-22 al
|
|
|
adjudicar `text-scramble`): las cinco de arriba más `text-scramble.font` y
|
|
|
`.size`, éstas aún sin declarar en `base.ts`. Y la de `text-scramble` es la
|
|
|
peor de todas porque además apunta fuera del canon: `var(--font-mono, …)`,
|
|
|
nombre que **no existe** —el canónico es `--font-family-mono`—, así que pinta
|
|
|
`ui-monospace` y **el componente ignora la mono del tema** (`Azeret Mono`).
|
|
|
Medido en navegador. `text-blur` es el único limpio de la familia: hereda.
|
|
|
|
|
|
**Resolución propuesta**: retirar las cinco claves y heredar del contexto.
|
|
|
Mueve defaults (TextFocus deja de imponer 3rem/900, TextCircular 1.5rem/900,
|
|
|
TextGradient 500) → firma del autor. Con ello muere el fantasma `4xl` sin
|
|
|
tocar el módulo de tipografía, que es canon y está **prohibido modificar**
|
|
|
(autor, 2026-08-22). Refuerza además el barrido mecánico que `--radius-xs`
|
|
|
ya pedía: `var(--nombre, …)` cuyo nombre no emite ningún generador.
|
|
|
|
|
|
- **⚠ El barrido mecánico está HECHO: 9 referencias fantasma con fallback en el
|
|
|
catálogo de componentes** (2026-08-22). Es lo que pedían el hallazgo de
|
|
|
`--radius-xs` y el de `--font-size-4xl`, y ya no hay que suponer cuántas
|
|
|
quedan. Método: recoger las **5.793** custom properties declaradas en TODO
|
|
|
`src/uix/eidos` (fundación + capas + recetas + `generated/`) y buscar en las
|
|
|
recetas los `var(--x, fallback)` cuyo `--x` sea vocabulario de SISTEMA y no
|
|
|
esté declarado — el sistema nunca lo escribe en runtime, así que un nombre
|
|
|
ausente es una errata. (Los `var(--x)` SIN fallback ya los guarda
|
|
|
`recipe-css-contract`; el hueco eran justo los que llevan fallback, que
|
|
|
«funcionan por accidente».)
|
|
|
|
|
|
| referencia inexistente | dónde | qué pinta en su lugar |
|
|
|
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
|
| `--color-content-tertiary` ×3 | `float-panel.css:227` · `proof-of-human/clock.css:87` · `proof-of-human/rotate-align.css:114` | `--color-content-subtle`. **Es el caso histórico** que `recipe-contract` §1 cita como origen de la regla («shipped a month that way») y sigue vivo en tres recetas |
|
|
|
| `--style-{body,prose,label,caption}-font-variation-settings` ×4 | `text.css:32,55,65,75` | `normal` — los estilos con nombre emiten 6 claves y ésta no está, así que **el eje variable no llega nunca al primitivo `Text`** |
|
|
|
| `--font-family-sans` | `palabras.css:296` | `sans-serif` del sistema. El canónico es `--font-family-primary` (palabras es track WIP) |
|
|
|
| `--font-mono` | `text-scramble.css:11` | `ui-monospace`. El canónico es `--font-family-mono` |
|
|
|
|
|
|
Ninguna se arregla aquí: dos son tipografía y esperan la firma 1.bis, y las
|
|
|
otras dos familias son decisión de diseño (¿existe un slot `tertiary`?
|
|
|
¿el eje variable debe emitirse por estilo?). El barrido, en cambio, **debería
|
|
|
ser un guard**: la lógica es la misma que la del test de fantasmas sin
|
|
|
fallback que ya existe, con el filtro de vocabulario de sistema encima.
|
|
|
|
|
|
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 de `chart`
|
|
|
que pasaban a `var(--style-caption|label-font-size)` seguían contando
|
|
|
`global` porque el censo sólo exime `--style-*` a los seis primitivos. El
|
|
|
censo medía BIEN: lo que faltaba era la **costura**. PLAN §2-A pide que todo
|
|
|
knob lea `--{c}-{slot}`, y leer el canon a pelo desde el CSS deja a un tema
|
|
|
sin poder retocar ESE componente sin mover el rol en toda la app. Con seis
|
|
|
claves `--chart-{caption,label}-*` cuyo VALOR es `var(--style-{rol}-*)`,
|
|
|
`global` cayó a cero y el alcance a 89 %. **La lección: cuando la métrica y
|
|
|
la doctrina discrepan, sospecha de la lectura antes que del instrumento.**
|
|
|
El molde canónico ya estaba escrito en la vertebración tipográfica
|
|
|
(`--accordion-trigger-font-family: var(--style-label-font-family)`), y D-TH.2
|
|
|
aclara que sólo los PRIMITIVOS quedan fuera. `listbox` (68 %, ritmo de fila
|
|
|
de `list-surface`) es la misma pregunta un piso más arriba: **una capa
|
|
|
compartida no se lee a pelo, se cose** — pendiente de revisar con este molde.
|
|
|
|
|
|
- **⚠ Un READOUT de dato no tiene estilo con nombre en el canon.** El número del
|
|
|
gauge (`--font-size-xl`), el total de un funnel, el valor de un `Stat`: no
|
|
|
son encabezados (`h2` está en ese tamaño pero significa otra cosa) ni texto de
|
|
|
cuerpo. Su costura toma el bundle de talla, que es correcto pero no dice nada
|
|
|
del ROL. Si aparecen más, la respuesta es un estilo con nombre nuevo en el
|
|
|
módulo de tipografía — firma del autor, y el módulo no se toca sin ella.
|
|
|
|
|
|
- **⚠ La clase `private` del censo ES, en su mayor parte, el PUENTE DE PALETA
|
|
|
THM-2** (medido 2026-08-22). De las 687 declaraciones que leen un privado en
|
|
|
el catálogo, **130 leen un `--_{c}-palette-*`**, y son las que dominan la
|
|
|
columna `private` de los componentes con paleta: `tag-group` 14/14,
|
|
|
`stepper` 11/11, `radio-group` 10, `checkbox` 8, `file-upload` 8,
|
|
|
`calendar`/`editable`/los tres grids 7 cada uno.
|
|
|
|
|
|
**No es deuda y no debe acuñarse**: el puente lo alimenta la capa compartida
|
|
|
por instancia desde `[data-color]`, y un público encima
|
|
|
(`--{c}-selected-bg: var(--_{c}-palette-surface)`) dejaría que un tema lo
|
|
|
fijara y **matara en silencio el `color=` de cada instancia** — la misma clase
|
|
|
de daño que «declarar un público de facto mata una escala» (`knob`). El
|
|
|
componente ya expone sus nueve roles × variante como claves públicas: ésa es
|
|
|
su superficie de color.
|
|
|
|
|
|
Consecuencia para la métrica: **el techo de un componente con paleta no es el
|
|
|
100 %**, y su cifra no compara con la de uno sin ella — el denominador incluye
|
|
|
un puente que por diseño no se acuña. Tres ya adjudicados con esta razón
|
|
|
(`tag-group` 65 %, `stepper` 79 %, `timeline` 73 %); los demás la heredan
|
|
|
cuando les toque. Si algún día se quiere una cifra comparable, la respuesta es
|
|
|
una clase `bridge` en el censo, no acuñar los tokens.
|
|
|
|
|
|
- **⚠ La capa `list-surface` no es alcanzable por un tema — MEDIDO
|
|
|
2026-08-22.** Ni por su propio nombre ni por el puente que acuñan sus
|
|
|
consumidores. Con el menú abierto y la fila a 36 px: un tema que escribe
|
|
|
`--list-item-height: 1234px` en `:root` no mueve nada, y uno que escribe
|
|
|
`--dropdown-menu-item-height` tampoco. Es cascada: la capa declara sobre
|
|
|
`[data-list-surface][data-size]` y el puente sobre `[data-{c}-content]`; las
|
|
|
dos ganan a `:root`.
|
|
|
|
|
|
Por eso `listbox` **no** se cosió como sus hermanos: copiar el puente habría
|
|
|
subido la cifra sin dar alcance (un token que miente). La salida es la de
|
|
|
`calendar-surface`: que el vocabulario de la capa viva en una entrada de
|
|
|
receta y el gancho sólo lo RESUELVA por talla. Toca los diez consumidores de
|
|
|
golpe → eje propio.
|
|
|
|
|
|
Y deja un aviso sobre el instrumento: **el centinela da por vivos los tokens
|
|
|
de esta forma** porque escribe inline en el nodo, que gana a la regla del
|
|
|
gancho. Para un token que un tema sólo puede escribir en `:root`, «mueve un
|
|
|
computed» no equivale a «un tema lo alcanza».
|
|
|
|
|
|
- **⚠ La FIRMA DE SALIDA está escrita a mano tres veces** (medido 2026-08-22).
|
|
|
`data-last-action` × 3 intents × 4 lados tiñe el borde durante la animación
|
|
|
de cierre, y la misma matriz vive en `popover` (22 reglas), `drawer` (22) y
|
|
|
`dialog` (9), leyendo `--color-{fulfill,threat,neutral}-element`. **Y ya ha
|
|
|
derivado**: dialog tiñe sólo `border-top-color` mientras popover y drawer usan
|
|
|
el shorthand con `--border-width-medium`, así que en dos de tres el GROSOR
|
|
|
también cambia al salir.
|
|
|
|
|
|
La tabla morfo→eidos la sitúa como mapeo TRANSVERSAL (« `events[].prewrite` →
|
|
|
tinting the exit anim by cause»), misma fila que los arquetipos y los
|
|
|
selectores `[data-event=…]`: es el canal visual de sema materializado por
|
|
|
eidos, como la capa de estado vive en `archetypes.css`. Coserlo por componente
|
|
|
sería triplicar un vocabulario que debería vivir UNA vez; **la salida es una
|
|
|
regla transversal sobre el arquetipo `content`**, con los colores de intent
|
|
|
(que son canon) — 53 knobs a `system` y un tema que retoca la firma una sola
|
|
|
vez. Lleva una firma dentro: ¿la salida tiñe sólo el color o también el
|
|
|
grosor?
|
|
|
|
|
|
- **⚠ El test de huérfanos cuenta un COMENTARIO como consumo.**
|
|
|
`--popover-bg` no lo leía ninguna regla; el único sitio del corpus que lo
|
|
|
nombraba era un comentario de `popover-arrow.svelte`. El test
|
|
|
`does not leave declared public recipe variables orphaned` busca el nombre
|
|
|
como TEXTO en los ficheros del componente, así que un comentario lo mantiene
|
|
|
«vivo» para siempre. Retirado el token y corregido el comentario
|
|
|
(2026-08-22); el test ganaría con despojar comentarios antes de buscar,
|
|
|
igual que hace el censo.
|
|
|
|
|
|
- **⚠ `color-picker` arrastra 23 tokens sin adjudicar, ANTERIORES al eje**
|
|
|
(2026-08-22). Su costura los dejó en 23 —eran 24 antes—, pero el bloque
|
|
|
viejo necesita su propio pase de medición: las muestras (7 claves), la rueda
|
|
|
de tono (`hue-0..360` + `saturation-floor`), el ancho del panel por talla,
|
|
|
el cuentagotas y las dos de transición. Dos causas ya descartadas: no es que
|
|
|
el panel no abra (abre y sobrevive al blur, al parqueo del ratón y al forzado
|
|
|
de `data-size`) ni que el guard no vea sus nodos (ya se le dieron las dos
|
|
|
familias que le faltaban).
|
|
|
|
|
|
- **~~Un falso negativo del centinela sin causa~~ — RESUELTO en la revisión
|
|
|
adversarial del bloque (2026-08-22).** `float-panel.resize-grip-fg` movía
|
|
|
aislado y leía muerto en una corrida completa. La causa: **la pasada de HOVER
|
|
|
deja el puntero sobre el último nodo que tocó**, y una regla `:hover` gana a
|
|
|
la de reposo con la que comparte nodo — el asa re-apunta su color al acento
|
|
|
al pasar el ratón. Así que CUALQUIER token de reposo probado después de un
|
|
|
token `hover-*` podía leer muerto. Es la misma clase que el envenenamiento
|
|
|
por clic-foco que F2-A arregló, una pasada más tarde. Arreglado aparcando el
|
|
|
puntero tras la pasada de hover; el propio guard delató entonces DOS
|
|
|
excepciones STALE (`float-panel.resize-grip-fg` y `media-player.track`),
|
|
|
retiradas. Confirma la regla de F2-A: **un falso negativo siempre tiene
|
|
|
causa.**
|
|
|
|
|
|
- **⚠⚠ EL VELO DEL `drawer` NO PINTA — defecto real, medido y visto**
|
|
|
(2026-08-22). Su regla es
|
|
|
`background: color-mix(in srgb, var(--drawer-overlay-bg) var(--drawer-overlay-opacity), transparent)`,
|
|
|
y **soma escribe `--drawer-overlay-opacity: 1` INLINE** en el nodo del velo (es
|
|
|
su progreso de arrastre, un número sin unidad). `color-mix` exige un
|
|
|
PORCENTAJE ahí: con `1` la función entera es inválida y el fondo cae a
|
|
|
`transparent`. Computado en el velo abierto: `rgba(0, 0, 0, 0)`; escribiendo
|
|
|
`80%` en el propio nodo pinta. **En la captura se ve**: el fondo detrás del
|
|
|
cajón está DESENFOCADO pero no atenuado — el `backdrop-filter` funciona y el
|
|
|
tinte no existe.
|
|
|
|
|
|
No se arregla aquí porque mueve píxel y toca soma (§7.8). El arreglo puede
|
|
|
ser de un lado o del otro: que soma escriba un porcentaje, o que la receta
|
|
|
no use ese slot como porcentaje. Y la lección general: **un valor que soma
|
|
|
escribe inline y una receta consume tiene que compartir UNIDAD**, o el fallo
|
|
|
es silencioso.
|
|
|
|
|
|
- **⚠ Las tallas del `drawer` son canal de valor en el eje que manda**: soma
|
|
|
escribe `width` (o `height`, según el lado) INLINE en el contenido, así que
|
|
|
`content-width-{sm,md,lg}` no puede ganar desde `:root` para un cajón
|
|
|
izquierda/derecha —el eje cruzado sí alcanza (`height-sm` forzado: 260px →
|
|
|
976px)—. Misma clase que el `item-gap` de carousel y el `preview-z` de
|
|
|
drag-drop, que se RETIRARON; aquí no, porque el token sigue siendo la fuente
|
|
|
del valor que soma resuelve. Decidir si se retira o si soma lo lee es firma.
|
|
|
|
|
|
- **⚠ El eje `size` del `color-picker` MUERE EN EL PORTAL — dos veces**
|
|
|
(2026-08-23). El panel viaja en un portal, fuera de `[data-color-picker]`,
|
|
|
así que ningún privado declarado en la raíz llega hasta él. Consecuencias
|
|
|
medidas:
|
|
|
1. **`--_color-picker-content-width` se declara TRES veces y no lo consume
|
|
|
NADIE.** La anchura del panel la fija
|
|
|
`--_popover-content-width-override: calc(var(--color-picker-content-width-md) + 2rem)`,
|
|
|
cableada al paso `md`. Un picker `sm` y uno `lg` abren el MISMO panel de
|
|
|
344 px, y `content-width-sm` / `content-width-lg` son dos públicos que no
|
|
|
mueven nada (el `md` sí alcanza, por el override).
|
|
|
2. **El tamaño de las muestras dentro del panel está clavado a `md`**: la
|
|
|
regla del contenido redeclara `--_color-picker-swatch-size: var(--color-picker-swatch-size-md)`
|
|
|
sin variantes por talla. Fuera del portal los tres pasos alcanzan
|
|
|
(20 / 24 / 28 px → 1234 px); dentro, sólo el `md`.
|
|
|
|
|
|
El arreglo honesto es una escala por talla EN EL ÁMBITO DEL CONTENIDO (que sí
|
|
|
lleva su `data-size`), no en la raíz. Mueve píxel para `sm` y `lg`, así que
|
|
|
es firma. Misma familia que la capa `list-surface` inalcanzable desde
|
|
|
`:root`: **el portal es una frontera de cascada, y un privado declarado en la
|
|
|
raíz no la cruza.**
|
|
|
|
|
|
- **Punto ciego genérico del centinela: los tokens que SON la transición.** El
|
|
|
guard congela las transiciones antes de medir —el arreglo que hizo medibles
|
|
|
`radio-cards` y compañía— y con ello `transition-duration` / `transition-ease`
|
|
|
no pueden moverse jamás. Medidos sin congelar en `color-picker` alcanzan
|
|
|
(0.12s → 11.5s). Cualquier componente con esos dos públicos los reportará
|
|
|
muertos: o el guard los mide en una pasada sin congelar, o cada uno paga su
|
|
|
adjudicación escrita.
|
|
|
|
|
|
- **✅ §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 en `docs/theming/changelog.md` §29 y el emisor en
|
|
|
`render-css.ts` (`renderDepthBlocks`), con guarda en
|
|
|
`active-eidos-config.test.ts`.
|
|
|
|
|
|
**Lo que restauró, medido en la demo real con motion congelada**: los seis
|
|
|
tonos de `toast` vuelven 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 de `tooltip` vuelven a
|
|
|
distinguirse (`solid` opaco con sombra · `outline` transparente con borde ·
|
|
|
`ghost` translú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-height` 17,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— `tooltip` y `link-preview`, mismo
|
|
|
`line-height`. Es EXACTAMENTE la decisión que la receta de `link-preview`
|
|
|
tení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-family` no se movió en ninguno**: los tres recetarios piden
|
|
|
`var(--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 `background` del plano
|
|
|
estuviera matando el velo de hover del sistema (que va en `:where()`, cero) y
|
|
|
que bajar el plano lo resucitara moviendo píxel. `backgroundImage` no 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-menu` y
|
|
|
`link-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 `overlay` no «impone» tipografía donde
|
|
|
el componente calla — GANA donde el componente habla** (medido en `menubar`,
|
|
|
2026-08-23). Acuñadas `panel-font-family` y `panel-line-height` sobre una
|
|
|
declaración que la receta YA tenía, el centinela las dio muertas:
|
|
|
`[data-depth='overlay']` declara las dos con la MISMA especificidad (0,1,0) y
|
|
|
más tarde en la cascada. Del mismo bloque, `color` y `font-size` —que el
|
|
|
plano no declara— sí alcanzan. Consecuencia para la cola: **mientras §12.9 no
|
|
|
se firme, un token de `font-family` o `line-height` en cualquier superficie
|
|
|
`overlay` MIENTE**, y son veinte componentes. Retiradas del contrato de
|
|
|
menubar y devueltas a su fuente literal, con la razón escrita en el CSS.
|
|
|
|
|
|
- **⚠ El hover de `menubar` mata la capa del sistema — y es del eje
|
|
|
navigation-menu, no de éste** (medido 2026-08-23). `[data-menubar-trigger]:hover`
|
|
|
pinta `background: var(--color-surface-overlay)`: en hover el
|
|
|
`background-image` computa `none` con `--state-hover` vivo. Dos razones, las
|
|
|
dos ya escritas en `navigation-menu.css` cuando retiró esta MISMA regla: el
|
|
|
shorthand resetea `background-image`, y la regla del arquetipo va en
|
|
|
`:where()`, especificidad CERO. El eje navigation-menu tiene `menubar` en su
|
|
|
cola por esto; aquí no se acuña un `hover-trigger-bg` porque fosilizaría la
|
|
|
invención (doctrina: el hover es del SISTEMA).
|
|
|
|
|
|
- **⚠ Un token de `::placeholder` lee MUERTO sobre un input sin `placeholder`**
|
|
|
(medido en `field`, 2026-08-23). Sin el atributo no existe la caja del
|
|
|
pseudo-elemento, y `getComputedStyle(nd, '::placeholder')` devuelve entonces el
|
|
|
estilo del ELEMENTO — que no sigue al token. Poniendo un placeholder, alcanza
|
|
|
(`oklch(0.61 0 0)` → `rgb(1,2,3)`). Es genérico: cualquier componente con un
|
|
|
token de placeholder lo sufrirá si su demo arranca con el input vacío. El guard
|
|
|
podría sembrar un placeholder antes de esa medida.
|
|
|
|
|
|
- **⚠ 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) y `timeline.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.css` ya
|
|
|
documenta la trampa en su propio comentario, así que la lección estaba escrita
|
|
|
y se repitió. Arreglar el selector EMPIEZA A PINTAR lo que la regla dice, así
|
|
|
que mueve píxel en los dos y es firma. Mientras tanto, los literales se quedan
|
|
|
con el defecto escrito encima y sin token (uno acuñado se retiró porque
|
|
|
mentía).
|
|
|
|
|
|
- **⚠ Congelar las transiciones no basta cuando el nodo está ANIMADO** (medido
|
|
|
en `onion-menu`, 2026-08-23). `rim-glow-opacity` leía 0,482956 —el valor que
|
|
|
la animación tenía en ese instante— y el token parecía muerto; con la
|
|
|
animación congelada también, computa 0,45 y sigue al token. El guard no puede
|
|
|
congelar animaciones en general (Presence necesita la suya para montar), así
|
|
|
que es una medida A MANO cuando el nodo pulsa.
|
|
|
|
|
|
- **✅ ~~⚠⚠ 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 en [`theming/changelog.md`](./theming/changelog.md) §53, el
|
|
|
emisor en `render-css.ts` (`isPaletteSlotToken` + `renderRecipePaletteForward`)
|
|
|
y la guarda en `active-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 `switch` de abajo se resolvió SIN caso especial.** No hizo
|
|
|
falta distinguir «componentes cuyo proveedor estampa siempre `data-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_FLOOR` en 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 132 `TONE_UNREACHED` revivieron y la
|
|
|
constante está RETIRADA; queda `PALETTE_FLOOR` como el único hecho, 15 de los
|
|
|
16 patrones supervivientes.)*
|
|
|
|
|
|
⚠ **Y el silencio del guard NO fue señal**: `theming-sentinel.ts` sólo detecta
|
|
|
STALE contra el ledger EXACTO, nunca contra `SENTINEL_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 con `data-color` resuelve por el `--palette-*` GLOBAL, que
|
|
|
`[data-color='{tono}']` llena desde `--color-{tono}-*`. Consecuencia medida
|
|
|
sobre `data-color='risk'`: `--button-risk-solid` no mueve nada ni en `:root`
|
|
|
ni en el nodo, mientras `--palette-solid` y `--color-risk-solid` en el nodo
|
|
|
repintan. Las siete `{c}-primary-*` son el RESPALDO de la regla genérica y
|
|
|
sólo actúan en una instancia SIN `data-color`.
|
|
|
|
|
|
**Tamaño**: 49 recetas emiten el forward genérico y el contrato tiene **419
|
|
|
claves de tono** (`{tono}-{track|element|border|solid|solid-hover|text|contrast}`),
|
|
|
repartidas entre ~13 componentes que además declaran reglas por tono. Son
|
|
|
públicas y no alcanzan: R-5.4 las llamaría mentiras, y no son deuda de nadie
|
|
|
en particular — es el orden de emisión.
|
|
|
|
|
|
**Arreglo propuesto (firma)**: emitir el bloque genérico ANTES de los de tono.
|
|
|
Entonces una instancia con tono lee `--{c}-{tono}-*` —cuyo valor por defecto
|
|
|
ES `var(--color-{tono}-*)`, así que **no movería un píxel**— y las escalas y
|
|
|
el custom siguen cayendo por el genérico. **Contrapartida**: cambia el
|
|
|
contrato de la jaula del color — un ancestro que inyecte `--palette-*` dejaría
|
|
|
de pisar un tono SEMÁNTICO en esos componentes (seguiría pisando los
|
|
|
personalizados). Por eso no se toca aquí.
|
|
|
|
|
|
Mientras tanto el guard lo adjudica con un PATRÓN
|
|
|
(`SENTINEL_PATTERN_EXCEPTIONS`), para que la razón se escriba una vez.
|
|
|
|
|
|
**Enmienda 2026-08-23 (`switch`): donde soma estampa `data-color` SIEMPRE, el
|
|
|
respaldo tampoco existe.** El párrafo de arriba dice que las `{c}-primary-*`
|
|
|
salvan a la instancia SIN `data-color` — pero hay componentes cuyo proveedor
|
|
|
estampa el atributo en toda instancia, porque para ellos el color es un valor
|
|
|
RESUELTO y `neutral` es su default, no su ausencia. En esos, el bloque
|
|
|
genérico casa siempre y **cae también el tono por defecto**: medido en
|
|
|
`switch` en estado checked, `--switch-neutral-solid` no mueve nada y
|
|
|
`--palette-solid` sobre el nodo repinta. Amplía el alcance de la firma: no es
|
|
|
«los tonos no canónicos», es **los tonos**.
|
|
|
|
|
|
**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:
|
|
|
|
|
|
1. **`stepper.neutral-text` no miente: miente el VALOR CENTINELA.** `sentinelFor`
|
|
|
sólo elige un color cuando la clave casa `/color|bg$|fg$|border$|ring$|…|track$/`,
|
|
|
y `neutral-text` no casa ninguna — así que el guard escribe `1234px`, que
|
|
|
es inválido para `color`, 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 indicador `current`, con el tono estampado, la clave repinta.
|
|
|
**Es una clase, no un caso**: afectaría a cualquier `{tono}-text` cuyo tono
|
|
|
quede cerca de la tinta heredada. O `sentinelFor` aprende que `text$` es
|
|
|
una ranura de color, o cada uno paga su adjudicación escrita.
|
|
|
2. **Las celdas `fontSize` / `lineHeight` de `month-select` y `year-select` de
|
|
|
`calendar` no son medibles con la sonda.** Medido: el nodo **no responde a
|
|
|
`data-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.
|
|
|
3. **`tag-group.hover-item-fg` lo gana el ARQUETIPO, y es un token que SOBRA.**
|
|
|
`archetypes.css` pinta `[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 (`:hover` confirmado): el
|
|
|
color se queda en `oklch(0.2435 0 0)` = `--color-content-primary`, con y sin
|
|
|
`aria-selected`. **Misma clase que `tags-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-family` es el TERCER token de tipografía que el
|
|
|
plano `overlay` anula** (medido 2026-08-23). Su panel lleva
|
|
|
`data-depth='overlay'`, así que la clave no mueve nada, igual que las de
|
|
|
`menubar`, `dropdown-menu` y `context-menu` —las tres retiradas ya—. No se
|
|
|
toca aquí porque no es el componente en curso: cuando §12.9 se firme, o se
|
|
|
retira o empieza a pintar, y la decisión es la MISMA para las cuatro.
|
|
|
|
|
|
- **⚠ Ya son CUATRO los componentes canónicos sin ruta de demo**: `picker-shell`,
|
|
|
`mockup`, `surface` y **`audio-player`** (404 los cuatro; los dos últimos
|
|
|
medidos el 2026-08-23). El de audio se mide dentro de `/uix/components/media-player`,
|
|
|
tras el chip `media: audio`, porque es la piel de ese chasis. Lo de abajo vale
|
|
|
igual para él: sin ruta no hay sonda ni guard, y el guard **miente en verde-rojo** (`/uix/components/surface` = 404, medido
|
|
|
2026-08-23). Sin ruta no hay sonda ni guard, y el guard **miente en verde-rojo**:
|
|
|
reportaba 0/25 sobre una página vacía, incluido un `radius` que está vivo. El
|
|
|
parche es el mismo que se le puso a picker-shell —medirlo donde SÍ se renderiza
|
|
|
(`urls` en el override)—, pero la solución de fondo es que un componente del
|
|
|
canon tenga su demo.
|
|
|
|
|
|
- **⚠⚠ EL PLANO `overlay` ANULA LAS TRES VARIANTES DE `tooltip`** (medido
|
|
|
2026-08-23). No es sólo la tipografía de §12.9: `[data-depth='overlay']`
|
|
|
declara `background`, `border` y `box-shadow` con la misma especificidad que
|
|
|
`[data-tooltip-content]` y más tarde en la cascada, así que gana. Medido con el
|
|
|
panel abierto: `--tooltip-bg` / `-border` / `-shadow` no mueven nada,
|
|
|
`--depth-overlay-surface` / `-border` / `-shadow` repintan.
|
|
|
|
|
|
**Consecuencia visual**: `solid`, `outline` y `ghost` computan el MISMO fondo,
|
|
|
el MISMO borde y la MISMA sombra. `outline` es indistinguible de `solid`;
|
|
|
`ghost` sólo aporta un `backdrop-filter` invisible tras una superficie opaca.
|
|
|
La receta tiene una máquina de variantes completa (privados por variante) que
|
|
|
el plano neutraliza entera.
|
|
|
|
|
|
Las cinco claves afectadas quedan ADJUDICADAS, no retiradas: son la fuente de
|
|
|
esa máquina y borrarlas dejaría los privados inválidos sin arreglar el fondo.
|
|
|
La decisión es la de §12.9 —qué manda, el plano o la receta— y aquí ya no es
|
|
|
sólo tipografía: es la superficie.
|
|
|
|
|
|
- **⚠⚠ Y EN `toast` EL PLANO SE LLEVA LA TARJETA ENTERA — 21 claves, y esta vez
|
|
|
con consecuencia de PRODUCTO** (medido 2026-08-24). Misma mecánica que
|
|
|
`tooltip`, un piso más arriba: `toast-item.svelte` sella `data-depth='overlay'`
|
|
|
y el plano declara `background`, el **ATAJO `border`** y `box-shadow` a 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}-surface`
|
|
|
y `-border` (12), `--toast-provider-border-width`, `--toast-accent-width` y
|
|
|
`--toast-provider-shadow` no mueven nada; `--depth-overlay-surface` / `-border`
|
|
|
/ `-shadow` y `--border-width` en la misma raíz lo repintan todo.
|
|
|
|
|
|
**Consecuencia visual**: un toast `risk` y uno `fulfill` visten **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 un `border-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`, uno `fulfill` y uno
|
|
|
`affirm` con transiciones y animaciones congeladas, los TRES computan
|
|
|
idénticos —`background oklch(0.285 0 0)`, borde `oklch(0.3485 0 0)`, franja de
|
|
|
acento de 0,67 px del mismo neutro, misma sombra—. La tarjeta lleva
|
|
|
`data-intent='risk'` **y** `data-depth='overlay'`: escribir
|
|
|
`--toast-risk-surface` / `-border` en `:root` no la mueve, y
|
|
|
`--depth-overlay-border` la repinta. La intención llega al DOM y muere en la
|
|
|
cascada.
|
|
|
|
|
|
⚠ Ojo al medirlo: el tono viaja en **`data-intent`**, no en `data-color`. Una
|
|
|
primera sonda leyó `data-color`, lo encontró `null` y 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}-accent` SÍ alcanzan
|
|
|
bajo `[data-toast-item][data-loading]`, donde la regla del pulso es (0,2,0) y su
|
|
|
`@keyframes` gana a toda declaración normal (100 fotogramas: sin escribir, el
|
|
|
color interpolado corre en oklab L 0.5032..0.8514; con el token en `rgb(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 con `PLANE_SUPERSEDED`, no retiradas.
|
|
|
|
|
|
- **El censo pierde el eje `size` DE PREMIO por aplicar bien el protocolo**
|
|
|
(medido 2026-08-23 en `navigation-menu`). `hasSize` se decide grepeando
|
|
|
`data-size` en el CSS de la receta (`theming-census.ts:287`), y el paso §7.4
|
|
|
del protocolo ORDENA retirar los bloques `[data-size]` del CSS cuando la
|
|
|
cascada la emite el TSC. Resultado: todo componente que haga lo correcto
|
|
|
aparece con «eje `size`: no» en su ficha y sin `y` en la tabla del censo.
|
|
|
Ya le pasa a `sidebar` y `nav-tree`, y a `navigation-menu` desde hoy — los
|
|
|
tres con su cascada por talla viva en `generated/base.css`. La columna es
|
|
|
informativa (no entra en el ratio), pero el conteo «con eje `size`: N» del
|
|
|
encabezado cuenta al revés de lo que quiere medir. El arreglo es leer el
|
|
|
eje del CONTRATO (una clave `-{sm,md,lg}` o un `scope: 'size:{k}'`), no del
|
|
|
CSS.
|
|
|
- **La sonda y el centinela no ven las partes que el CONSUMIDOR compone**
|
|
|
(medido 2026-08-23 en `navigation-menu`). Las filas de su mega-menú son `<a>`
|
|
|
desnudos que la receta estiliza por descendencia
|
|
|
(`[data-navigation-menu-content] :is(a, …)`): sin atributo del componente, el
|
|
|
filtro `data-{c}-*` veía 10 nodos y ninguno era el panel ni una fila — 9 de
|
|
|
19 knobs fuera del diff. Es la MISMA clase que `prose` y `onion-menu`, pero
|
|
|
por composición del consumidor, no por HTML crudo ni por hooks de clase.
|
|
|
Resuelto para este componente con `EXTRA_NODES` / `extraNodes` (y el paso
|
|
|
`open` de la sonda, que no los honraba). **Queda la pregunta general**: una
|
|
|
receta que estiliza nodos sin atributo propio no es medible por defecto, y
|
|
|
nada avisa — el gate sale verde.
|
|
|
- **…y la TERCERA cara: las partes de un componente COMPUESTO** (medido
|
|
|
2026-08-23 en `waveform`). Su cromo entero es un `Slider` embebido que la
|
|
|
receta re-tinta —el playhead ES el thumb del slider—, así que esos nodos
|
|
|
llevan `data-slider*` y el filtro `data-waveform*` medía 4 nodos sin ninguno
|
|
|
de ellos. Con `extraNodes`, 8. Las tres caras del mismo agujero son ya: HTML
|
|
|
crudo (`prose`), partes que compone el CONSUMIDOR (`navigation-menu`) y partes
|
|
|
de un componente COMPUESTO (`waveform`, `picker-shell`). **Lo que falta es que
|
|
|
el instrumento lo DETECTE solo** — hoy hay que sospecharlo y contar los nodos.
|
|
|
- **El censo no ve los knobs que una receta reenvía a un componente compuesto.**
|
|
|
`--slider-thumb-bg: var(--color-content-primary)` en `waveform.css` es un
|
|
|
primitivo CRUDO pintando el playhead, y no aparece en ninguna columna: las
|
|
|
custom properties no están en `KNOB_PROPS` (D-TH.2). El caso se arregló a
|
|
|
mano al tokenizar (`playhead-bg`), pero **el perímetro deja fuera una clase
|
|
|
entera**: toda receta que re-tinta un componente embebido (media-player,
|
|
|
waveform, chronos, los pickers) puede pasarle primitivos crudos sin que el
|
|
|
censo ni `recipe-css-contract` lo vean. Medir cuántas hay antes de decidir si
|
|
|
D-TH.2 se ensancha.
|
|
|
- **Dos propiedades que la sonda NO puede medir: la `opacity` de un `<img>` con
|
|
|
animación de entrada y el `inline-size` del rango *buffered* de un audio**
|
|
|
(medido 2026-08-23 en `image` y `audio-player`). La sonda congela `transition`
|
|
|
pero **nunca `animation`** —congelarla impide que Presence monte los paneles—,
|
|
|
así que la imagen se muestrea a mitad de su fundido y da un valor distinto en
|
|
|
cada corrida; el *buffered* depende de lo que el audio lleve descargado.
|
|
|
Probado: dos corridas sobre el MISMO código dan 1 diff en `image` y **11** en
|
|
|
`audio-player`. Mientras no se resuelva, **un diff en esas dos propiedades se
|
|
|
repite antes de creerlo**, y lo honesto sería excluirlas del snapshot para
|
|
|
esos dos componentes.
|
|
|
- **Un guard de huérfanos que no mira `generated/` ve huérfano casi todo**
|
|
|
(medido 2026-08-23): un script estricto de un solo uso reportó 185 claves «no
|
|
|
leídas» que eran los pasos de escala por talla y los tonos de paleta — los lee
|
|
|
el CSS GENERADO, donde el TSC emite el nombre resuelto y el forward de paleta
|
|
|
resuelve los tonos. El test oficial (`recipe-css-contract.test.ts`) sí los ve.
|
|
|
- **El escenario de la demo de `background` cambia de ANCHO entre corridas**
|
|
|
(medido 2026-08-23, dos veces): 870 px una corrida, 1.350 px la siguiente, con
|
|
|
el mismo código. Produce 2 diffs de `inlineSize` en la sonda —del contenedor,
|
|
|
no del componente— que un lector apresurado leería como regresión. Mientras no
|
|
|
se estabilice, **una corrida de sonda con diffs SÓLO de `inlineSize` sobre el
|
|
|
provider/capa de background se repite antes de creerla**.
|
|
|
- **`audio-player` no tiene README propio** (`src/uix/eidos/components/audio-player/`
|
|
|
tiene css, svelte, index y types y nada más). Es el cuarto canónico sin ruta de
|
|
|
demo y además sin documento: cuando se le escriba, le toca su «Talla y tema»
|
|
|
con las 8 claves que tiene desde `83a897b92`.
|
|
|
- **`reopen()` mira el CONTENT, y el estado vive en el TRIGGER** (medido
|
|
|
2026-08-23 en `emoji-picker`). Su `open-trigger-fg` oscila corrida a corrida
|
|
|
(17/21 · 16/21 · 16/21 con el mismo código): el panel se cierra en algún punto
|
|
|
de la pasada y, durante la animación de SALIDA, el content sigue en el DOM
|
|
|
mientras el trigger ya perdió `data-state='open'` — que es lo que la regla
|
|
|
selecciona. `reopen()` ve el content, cree que está abierto y no reabre.
|
|
|
Probado un `openMarker` sobre el trigger: **no basta**, porque el clic de
|
|
|
reapertura cae dentro de esa misma ventana y sus 250 ms de espera no llegan.
|
|
|
El arreglo es que `reopen()` **espere a la condición** (`waitForSelector` del
|
|
|
marcador tras el clic) en vez de dormir un número fijo. Mientras tanto, su
|
|
|
entrada de ledger sale STALE en las corridas donde alcanza: está escrito en la
|
|
|
propia razón para que nadie la borre por eso.
|
|
|
- **✅ ~~⚠⚠ UN COMPONENTE COMPUESTO GANA A LA RECETA QUE LO COMPONE, y ya van
|
|
|
TRES~~ — RESPONDIDA 2026-08-25 para la mitad `data-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 lleva
|
|
|
`data-button` / `data-popover-trigger`, los dos selectores casan a la MISMA
|
|
|
especificidad (0,1,0) y decide el ORDEN DE CARGA — que gana el componente
|
|
|
canónico. Censo de lo medido: `gradient-picker` 36 claves de 45 (F2-A, se
|
|
|
retiraron), `emoji-picker` 3 (retiradas hoy) y **`form` 23** (adjudicadas, no
|
|
|
retiradas: son demasiadas para decidirlo sin firma). La pregunta de fondo es
|
|
|
la misma de §12.9 con el plano `overlay`: **qué manda, el componente compuesto
|
|
|
o la receta que lo compone**. Si la respuesta es «la receta», el arreglo es
|
|
|
subir la especificidad de esas reglas y no retirar nada; si es «el compuesto»,
|
|
|
hay ~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 de `popover.css` no estaba a
|
|
|
(0,1,0) sino a **(0,2,0)** —y su `:hover` a (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-button` contra la receta que lo compone
|
|
|
(`form` 23 claves adjudicadas): esa firma no se ha hecho, y `button.css` NO se
|
|
|
tocó aquí.
|
|
|
- **R-5.3 no guarda el vocabulario DIMENSIONAL** (medido 2026-08-23 en `badge`).
|
|
|
Cubre la ranura de tinta (`fg` vs `color`) y la posición del modificador, pero
|
|
|
no que una clave POR TALLA se llame `…-height-{k}` y no
|
|
|
`…-min-block-size-{k}` (el nombre de la PROPIEDAD). Una clave así pasó el
|
|
|
guard y vivió unas horas; la cazó una lectura del catálogo (24 componentes
|
|
|
dimensionan por `-height-{k}`, uno por `-min-block-size-{k}`) y la tabla del
|
|
|
propio censo, que ya mapea `min-block-size → height`. Extender R-5.3 con esa
|
|
|
tabla —propiedad → ranura— cierra la clase entera.
|
|
|
- **✅ ~~`label` es el ÚNICO primitivo de tinta sin el paso `'on-solid'`~~ —
|
|
|
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 —
|
|
|
**`label` GANA el paso**, y la familia quedó pareja de verdad: los seis
|
|
|
`CONTENT_INK` son 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 `style` INLINE
|
|
|
de cada nodo** (medido 2026-08-23). `staticPass` escribe el token en
|
|
|
`documentElement` **y en todos los nodos del componente**, y ahí es donde vive
|
|
|
el canal inline de los wrappers: si el wrapper escribe un token PÚBLICO en el
|
|
|
`style` del nodo, el guard lo pisa, ve el cambio y lo da por VIVO — aunque un
|
|
|
tema, que sólo puede escribir en `:root` o en una hoja, **no lo alcance
|
|
|
jamás**. Es la cara opuesta de `drag-drop.preview-z`.
|
|
|
|
|
|
Censo de lo afectado: siete wrappers escriben un `--{c}-*` público inline y
|
|
|
**CUATRO de ellos están en el contrato** — `dialog.overlay-opacity`,
|
|
|
`drawer.overlay-opacity`, `avatar.badge-color-custom` y
|
|
|
`avatar.ring-color-custom`. Medido en `dialog` con el diálogo abierto y las
|
|
|
animaciones congeladas: desde `:root` el fondo del velo NO se mueve
|
|
|
(`color(srgb 0.1098 0.098 0.0902 / 0.2518)` idéntico), desde el inline SÍ. **Su
|
|
|
token miente**, y el guard lo da por bueno.
|
|
|
|
|
|
*(Corrección 2026-08-23: la primera redacción decía «dos» y ponía los de
|
|
|
`avatar` entre los sin-contrato — su bloque de `base.ts` es una IIFE que el
|
|
|
censo no sabía leer hasta `f68bac4a6`, así que sus 84 claves eran invisibles.
|
|
|
Re-medidos aquí los dos: desde `:root` ni el fondo del badge ni el
|
|
|
`box-shadow` del anillo se mueven, y no puede existir instancia alcanzable —
|
|
|
la puerta `data-*-custom` que activa la regla y la escritura inline del token
|
|
|
viajan JUNTAS en el envoltorio. Son canal de valor filtrado al contrato; su
|
|
|
hermana `badge-color-custom-contrast` sí vive desde `:root`.)*
|
|
|
|
|
|
*(Actualización 2026-08-25 — **los dos de `avatar` ya NO están en el
|
|
|
contrato**, así que en el contrato quedan **DOS**, los dos `overlay-opacity`.
|
|
|
La firma A-c retiró `ring-color-custom` y `badge-color-custom` a 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 y `dialog` /
|
|
|
`drawer` lo siguen sufriendo—, sólo saca a `avatar` de 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 `:root` y en el host**,
|
|
|
nunca en el `style` de cada nodo — pero eso rompería la medida de los paneles
|
|
|
PORTALADOS, que es justo por lo que se escribe en todos. La forma correcta es
|
|
|
escribir en todos MENOS en los nodos cuyo `style` inline ya declara ese mismo
|
|
|
token, y avisar cuando eso ocurra. Con re-verificación del ledger entero.
|
|
|
- **El guard no fotografía `translate` ni `transform`** (medido 2026-08-23 en
|
|
|
`background`, y ya latente en `drawer.handle-active-scale`). El paralaje entra
|
|
|
por `translate` sobre la capa; con `data-parallax` forzado y speed 1 el token
|
|
|
SIGUE al scroll (14,6 → 60,5 px al mismo `scrollY`), pero el guard no puede
|
|
|
verlo. Tres tokens vivos leen muertos por esta causa y una adjudicación estuvo
|
|
|
MAL CLASIFICADA un día («estructural, el escenario no tiene scroll timeline» —
|
|
|
falso: lo tiene, `animation-timeline: view()`). Va con el hueco de
|
|
|
`mask-image`: los tres a `PROPS` en el mismo pase, y **re-verificando los ~59
|
|
|
con ledger** (el barrido de `REVIEW-theming-2026-08-23.md` §5 es la base).
|
|
|
- **El guard no fotografía `mask-image`** (medido 2026-08-23 en `background`).
|
|
|
Sus dos tokens de desvanecido (`fade-size`, `fade-at`) alimentan un
|
|
|
`mask-image` radial y leen muertos aunque están vivos — medidos a mano, el
|
|
|
mask cambia (`at 50% 35%` → `at 11% 50%`). Añadir `maskImage` a la lista de
|
|
|
propiedades del guard es una línea, pero toca el instrumento de TODOS: entra
|
|
|
con su re-verificación de los componentes con ledger.
|
|
|
- **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-1141`
|
|
|
filtran `k in ledger`, es decir **sólo las excepciones EXACTAS**. Las cubiertas
|
|
|
por `SENTINEL_PATTERN_EXCEPTIONS` sí entran en `reasonFor` (`:1129`) para
|
|
|
adjudicar un muerto, pero **nunca en `stale`**: 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 por
|
|
|
`reasonFor(k) !== null` en vez de `k 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 patrones
|
|
|
`PLANE_SUPERSEDED` de `toast` (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): el `catch` del
|
|
|
`prepareWith` GRITA siempre — `:796` ya filtró la ausencia, así que lo que
|
|
|
cae ahí es un control que estaba y falló, la fuente de las muertes fantasma
|
|
|
de `chat-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
|
|
|
de `sweepAttr` pero **no estampa tono**. Y `sweepAttr` puede forzar un estado,
|
|
|
pero de los **48** componentes de `COMPONENT_OVERRIDES` sólo `toggle` declara
|
|
|
un eje de estado (`:635`, `data-state: off|on`) — `switch` y `button` **no
|
|
|
tienen entrada**. Medido: la ficha de `switch` monta **1 nodo `unchecked`** y
|
|
|
todas sus ranuras de paleta cuelgan de `[data-state='checked']`; la de `button`
|
|
|
monta **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
|
|
|
de `tag-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 en `COMPONENT_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 132 `TONE_UNREACHED` reviven; las 2 que
|
|
|
no son el tono HOST por defecto** (test de dos caras → `PALETTE_FLOOR`). La
|
|
|
constante `TONE_UNREACHED` RETIRADA como huérfana; tier de patrones de la
|
|
|
firma entera **28 → 16**, 15 de los 16 son ya el único hecho
|
|
|
`PALETTE_FLOOR` — y el adversarial verificó que los 16 cubren SOLO claves
|
|
|
muertas (cero zombis). Dos razones corregidas por medida al pasar: las
|
|
|
`-text` de `tags-input` sí reviven (el arquetipo sólo tapa el ítem RESALTADO
|
|
|
y la escena no tiene ninguno) y `tag-group.hover-item-fg` la callaba su
|
|
|
`data-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-color`
|
|
|
en TODOS los nodos** y producción sólo en el proveedor
|
|
|
(`theming-sentinel.ts:1215-1224` y `:1346-1352` vs `colorAttrs.dataColor` en
|
|
|
los `.svelte`) — medido que NO es la causa de la revivida de `tags-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-1272` y `:1387-1391`, nace en
|
|
|
`007c2af52`): 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í:
|
|
|
1. **El pase `open` de `editable` no muere por el localizador: muere por el
|
|
|
CLIC.** El localizador (`:324-326`) SÍ casa `[data-editable-input]` (3
|
|
|
nodos), pero esos inputs están `hidden` / 0×0, así que
|
|
|
`trigger.click({ timeout: 2000 })` (`:330`) expira **dentro del `catch`
|
|
|
vací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 el
|
|
|
`catch` **avise** 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 importa `COMPONENT_OVERRIDES` del centinela (exportada con guarda
|
|
|
de entrypoint — con `realpathSync`, o el guard muere a través de un
|
|
|
junction) y habla su vocabulario entero (`prepareWith` / `openBy` /
|
|
|
`openWith` / `openMarker`); el `catch` AVISA por stderr distinguiendo «no
|
|
|
hay trigger» de «el gesto falló». `editable` registra ya bloque `open`
|
|
|
(25 filas, 6 con `data-editing`; dos corridas sobre código idéntico = 0
|
|
|
diffs) y `card` sigue a 0 diffs en sus 7 bloques pre-firma.
|
|
|
2. **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 eso
|
|
|
`button`, que monta un solo botón sólido, no tiene sondeadas sus otras
|
|
|
variantes. Es el hermano exacto del hueco del guard de arriba.
|
|
|
3. **Dos alias de escala hacen sondas DÉBILES.** `affirm: 'teal'`
|
|
|
(`lib/config-types.ts:95` y `lib/themes/base.ts:40`): estampar `teal` como
|
|
|
escala «cruda» computa **idéntico** a estampar el rol `affirm` en 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 donde **`risk ≡ orange`** y **`risk ≠ amber`** —
|
|
|
ojo, que la convención canónica del libro nombra `amber` mientras el tema
|
|
|
base mapea `orange`. **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) y `plum` (loss). Para barridos futuros: usar
|
|
|
una escala sonda de las **24 libres** (`fuchsia`, `steel`, `sand`… ) y NO
|
|
|
`plum`, que a primera vista parece neutra y es el rol `loss`.
|
|
|
**EJECUTADO 2026-08-25**: `PROBE_ROLE_BOUND_SCALES` (las 9 → su rol) +
|
|
|
`PROBE_SAFE_SCALES` (las 24) + `assertProbeSafeScale()` en la sonda, con
|
|
|
la discrepancia `risk: amber` (config-types) vs `orange` (tema base)
|
|
|
documentada en el comentario. Guardia sin usos inventados: hoy nada en la
|
|
|
sonda estampa una escala; las 33 verificadas contra `PALETTE_SCALES`
|
|
|
(`lib/types.ts:232-270`), 0 sobras, 0 faltas.
|
|
|
- **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 `:hover` pesa más; mide foco PROGRAMÁTICO, `:focus`,
|
|
|
nunca mezclado con gestos), un pase **`disabled`** como bloque APARTE y
|
|
|
ÚLTIMO de la corrida (la trampa «montar más mide menos»: la regla de
|
|
|
file-upload apaga `pointer-events` del subárbol), y **`UIX_DEV_URL`** en
|
|
|
sonda y centinela (adiós el `:5173` clavado; el argumento explícito sigue
|
|
|
ganando). Deudas nuevas, medidas por la verificación adversarial de la
|
|
|
firma: (i) **`search-field` pierde `[data-search-field-clear-trigger]` en el
|
|
|
pase `open`** — la guarda «`prepareWith` sólo corre sin fila en
|
|
|
`DEMO_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ía `prepareWith` en
|
|
|
absoluto. (ii) En los seis con `prepareWith` y sin fila (`editable`,
|
|
|
`rating-group`, `toolbar`, `form`, `progress`, `background`) el bloque
|
|
|
`open` monta capas que `rest`/`size`/`hover`/`focus`/`disabled` no ven —
|
|
|
comparar bloques entre sí no es comparar lo mismo. (iii) La sonda no tiene
|
|
|
el cortocircuito `alreadyOpen` del centinela y, tras un `openMarker`
|
|
|
fallido, prueba el siguiente opener sobre página ya ensuciada (no se
|
|
|
manifestó en lo corrido). Y un dato para la GRANDE: el no-determinismo de
|
|
|
`chat-message` quedó ACOTADO al bloque `rest` de la sonda (13 vs 16 nodos —
|
|
|
los chips de `DEMO_VARIANTS` sin cuajar al medir; `size/open/hover/focus/
|
|
|
disabled` dan 16/24/11/11/16 con 0 diffs), coherente con el `prepareWith`
|
|
|
mudo 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 el `background-image` y el `translate` de la
|
|
|
misma capa donde se miden los patrones. La regla operativa: `prepareWith` sólo
|
|
|
debe encender lo que NO tapa lo ya medido, y eso hay que comprobarlo corriendo
|
|
|
el guard antes y después de cada control que se añada.
|
|
|
- **Los chips de la demo de `background` se aplican con RETRASO.** Una captura
|
|
|
por chips guardó cada estado con el patrón ANTERIOR (el estado «mesh» tenía el
|
|
|
glow) y dio 0 diffs por estar desfasada IGUAL en las dos corridas — un gate
|
|
|
verde sobre dos medidas equivocadas. Donde la demo no coopere, la prueba buena
|
|
|
es la de EQUIVALENCIA (el token nuevo resuelve al mismo valor que el primitivo
|
|
|
que sustituyó) más forzar el atributo en el DOM, las dos deterministas.
|
|
|
- **Un panel de HOVER se cierra por el propio guard.** `navigation-menu` abre
|
|
|
con `pointerenter` y `pointerleave` programa el cierre; el guard abre con
|
|
|
clic y luego aparca el puntero en (0,0), que es exactamente el gesto que lo
|
|
|
cierra. Producía no-determinismo (dos corridas del mismo código discrepando
|
|
|
en un token) y **las dos caras del error a la vez**: falso negativo en los
|
|
|
tokens de la fila y falso positivo en `content-link-padding-inline`, que leía
|
|
|
«vivo» por ensanchar el panel que sí se medía. Resuelto con `openBy: 'hover'`.
|
|
|
La regla general: **antes de creer un «no effect», comprobar que la superficie
|
|
|
seguía abierta al medirlo**.
|
|
|
|
|
|
- **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-lint` no conoce las exenciones que el test de vitest SÍ tiene**
|
|
|
(medido 2026-08-24 revisando la tanda). `src/uix/eidos/lint.test.ts` mantiene
|
|
|
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_MORFO` y las raíces competidoras—, pero
|
|
|
`scripts/eidos-lint.ts` no los conoce: sobre `spin-field` muere 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 `size` del censo se apaga cuando el eje de talla SUBE al
|
|
|
contrato** (medido 2026-08-24 en `radio-group` y `rating-group`). La columna
|
|
|
se calcula leyendo bloques `[data-size]` en el CSS; cuando una escala privada
|
|
|
se promueve al TSC con `scope: '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
|
|
|
los `scope: '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 bucle `pulse` del
|
|
|
sistema: **18 diffs de `opacity` corriendo la sonda dos veces sobre código
|
|
|
idéntico**. Con la congelación (`transition` **y** `animation`, antes de cada
|
|
|
instantánea): **0 diffs sobre 1.568 valores**. Arreglado en la sonda.
|
|
|
|
|
|
**Lo que queda**: `scripts/theming-sentinel.ts` congela `transition` pero NO
|
|
|
`animation`, así que un token bajo animación sigue leyéndose donde pille. Es
|
|
|
la misma clase que «el guard no fotografía `transform`» 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 los `transition-*`.
|
|
|
|
|
|
- **⚠ EL GENERADOR DE FICHAS PISA LA PROSA ESCRITA A MANO** (medido 2026-08-24
|
|
|
sobre 164 fichas regeneradas de golpe). El `--report` del censo reescribe
|
|
|
`docs/audit/theming/*.md` entero, y las secciones 1.1–1.4 vuelven a su
|
|
|
plantilla: donde un humano había escrito «_Ninguno_ — los cuatro que había
|
|
|
(el `padding-block` del divisor, …) están cosidos», queda «_Ninguno._». El
|
|
|
veredicto §5 sobrevive (va entre marcadores `veredicto: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:end` a 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/theming` antes 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 con `git diff --cached`— protege
|
|
|
del árbol de trabajo ajeno, **no de la ventana entre mirar y confirmar**: si
|
|
|
otra sesión hace su `reset` + `add` en ese hueco, tu `git commit` fotografía
|
|
|
SU índice. Ocurrido dos veces en una tarde:
|
|
|
|
|
|
- `edc019515` (toggle) salió con tres fragmentos de `rating-group` dentro, y
|
|
|
sin la mitad de los suyos; hizo falta `007c2af52` para repararlo.
|
|
|
- La reparación cosió el bloque de `rating-group` con el de `toggle` y dejó
|
|
|
**`},,`** en el literal del ledger: cuatro commits de HEAD con
|
|
|
`theming-sentinel-exceptions.ts` **que no parsea**. El guard R-5.4 no queda
|
|
|
en rojo, queda MUERTO, y sólo se ve al hacer checkout limpio (arreglado en
|
|
|
`88a208644`).
|
|
|
|
|
|
**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 commit` encadenado),
|
|
|
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
|
|
|
`card` daba `0/98` —la firma del instrumento ciego— por un
|
|
|
`theming-sentinel.ts` sucio con el bloque de `chat-log` DUPLICADO; desde una
|
|
|
copia limpia de HEAD, `49/98` y verde. Antes de creer un `0/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-composer` dentro de `file-upload`, verificado en
|
|
|
la supervisión). `file-upload` declara
|
|
|
`[data-file-upload] [data-disabled] { opacity: var(--file-upload-disabled-opacity) }`
|
|
|
— un selector de DESCENDIENTE, (0,2,0), el mismo peso que
|
|
|
`[data-chat-composer-send][data-disabled]`, y se emite más tarde. Dentro de
|
|
|
un FileUpload, que es donde la composición **documentada** pone al composer,
|
|
|
el token del composer muere.
|
|
|
|
|
|
Medido desde los dos asientos sobre el mismo nodo deshabilitado: suelto y
|
|
|
envuelto computa `0.4` en los dos casos —los defaults coinciden, así que el
|
|
|
VALOR no delata nada—, pero envuelto `--chat-composer-disabled-opacity`
|
|
|
desde `:root` no lo mueve (0.4) y `--file-upload-disabled-opacity` sí
|
|
|
(0.4 → 0.321). Es invisible hasta que un tema mueva uno de los dos, y
|
|
|
entonces el huésped no responde a su propia clave.
|
|
|
|
|
|
**`file-upload` es HOY el único componente del catálogo con un barrido de
|
|
|
descendientes así** (`grep` sobre las 162 recetas: una sola ocurrencia), o
|
|
|
sea que no es una clase, es una regla concreta que se puede acotar. El
|
|
|
arreglo —restringirla a las partes propias— mueve el píxel de cualquier
|
|
|
huésped deshabilitado que hoy hereda, así que es firma, no commit de
|
|
|
theming. Emparenta con «un componente COMPUESTO gana a la receta que lo
|
|
|
compone» (gradient-picker / form / emoji-picker): misma cascada, dirección
|
|
|
contraria.
|
|
|
|
|
|
- **⚠ LA SONDA Y EL GUARD NO SE PUEDEN LANZAR CON `--import tsx/esm`** (medido
|
|
|
2026-08-24 supervisando `calendar`). Cualquier script del eje que use
|
|
|
`page.evaluate` muere con `ReferenceError: __name is not defined` dentro del
|
|
|
navegador: el transform de esbuild que trae `tsx` envuelve las funciones con
|
|
|
su ayudante `keepNames`, y ese ayudante existe en Node, no en la página — el
|
|
|
cuerpo de la evaluación viaja serializado y llega sin él. Reproducido en
|
|
|
`__theming-probe.ts` y en `theming-sentinel.ts`.
|
|
|
|
|
|
Se lanzan con **`node` a secas** (Node 24 desnuda los tipos de forma nativa),
|
|
|
que es justo lo que hace `npm run theming:sentinel`. Sólo los scripts SIN
|
|
|
`page.evaluate` —el censo, `component-audit`, `eidos-lint`— toleran el
|
|
|
`--import tsx/esm`. Cuesta una sesión entera si se confunde, porque el error
|
|
|
no nombra ni a tsx ni a la sonda.
|
|
|
|
|
|
- **Un token VIVO sobre un nodo APLASTADO lee igual que uno muerto** (medido
|
|
|
2026-08-24 en `select.separator-size`). El separador vive en la columna flex
|
|
|
del `ScrollArea` con `flex-shrink` 1 y el panel desborda (scrollHeight 670 vs
|
|
|
clientHeight 310): su `block-size: 1px` computa **0px** y ningún valor del
|
|
|
token lo mueve. Con `flex-shrink: 0` en el mismo nodo el token sigue
|
|
|
(1px → 9px). Es además **un defecto de píxel**: el separador de `select` no
|
|
|
pinta nada en un panel que desborde. El arreglo (`flex: 0 0 auto` en
|
|
|
`[data-select-separator]`) mueve píxel → fuera del eje de theming, pendiente.
|
|
|
|
|
|
- **Una entrada de receta de FAMILIA no se puede juzgar en la ruta de UN
|
|
|
componente** (medido 2026-08-24 en `calendar`). Su entrada de
|
|
|
`recipes/base.ts` es la de las cuatro superficies de rejilla de fechas —
|
|
|
`range-calendar`, `month-grid` y `year-grid` consumen `--calendar-*` y no
|
|
|
acuñan nada—, pero sus partes llevan `data-month-grid-*` /
|
|
|
`data-range-calendar-*` y el filtro `data-calendar` no las ve: **23 de sus 29
|
|
|
«tokens muertos» eran eso**. Con `urls` a las tres rutas + `extraNodes` +
|
|
|
barrido de `variant`: 45/74 → 68/74. Vale para toda capa híbrida que conserve
|
|
|
entrada de receta.
|
|
|
- **El tamaño de letra de los selectores de mes / año del calendario es NO
|
|
|
DETERMINISTA** (medido 2026-08-24). `[data-calendar-month-select][data-button]`
|
|
|
—la regla de `calendar-select.css` que existe precisamente para fijar la letra
|
|
|
del Button a la del calendario— y
|
|
|
`[data-popover-trigger]:not([data-archetype='field-trigger'])` de `popover.css`
|
|
|
empatan a **(0,2,0)**, así que gana el módulo que Vite inyecte el ÚLTIMO:
|
|
|
cuatro cargas de la misma página dieron 14px / 16px / 14px / 14px, con el orden
|
|
|
de las hojas invertido en la que dio 16. Consecuencia de medición: la sonda dio
|
|
|
**24 diffs corriendo dos veces el MISMO código**, así que todo diff en esos dos
|
|
|
nodos es ruido hasta que se resuelva. Es la familia del empate por orden de
|
|
|
carga de §12 (tree-grid); arreglarlo mueve píxel.
|
|
|
- **`calendar.select-min-width` no tiene nodo en NINGUNA página del repo**
|
|
|
(medido 2026-08-24): su único consumidor es
|
|
|
`[data-range-calendar-month-select]` / `-year-select`, y la demo de
|
|
|
`range-calendar` renderiza un heading de texto en su lugar, mientras
|
|
|
`DateRangePicker` importa los selectores del CALENDARIO (que llevan
|
|
|
`data-calendar-month-select`). Hueco de DEMO: la parte existe en soma y en
|
|
|
eidos y ninguna superficie la monta.
|
|
|
|
|
|
- **La escala del punto de `radio-group` NUNCA ha pintado** (medido
|
|
|
2026-08-23). `<SvgDot>` escribe `style="width: 1em; height: 1em"` en el glifo y
|
|
|
un inline gana a cualquier regla NORMAL —sólo un `!important` de 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» de
|
|
|
`carousel.item-gap*` y `drag-drop.preview-z`, pero con arreglo disponible: la
|
|
|
forma `!important` que recipe-contract §3 autoriza justo para saltarse un
|
|
|
estilo inline (o pasarle la talla a `<SvgDot size={…}>`). **Mueve píxel** ⇒
|
|
|
firma. Seis claves adjudicadas mientras tanto.
|
|
|
- **`radio-group` no tiñe la etiqueta de grupo deshabilitada**: la regla es
|
|
|
`[data-radio-group-label][data-disabled]` y soma **no estampa `data-disabled`
|
|
|
en la parte `Label`** (el morfo declara `data: []`). Medido 2026-08-23 con el
|
|
|
grupo entero deshabilitado: la etiqueta lleva `[id, data-radio-group-label,
|
|
|
data-archetype]` y nada más. El token alcanza forzando el atributo, así que
|
|
|
falta el ATRIBUTO, no la clave — decisión de MORFO (el label gemelo de
|
|
|
`checkbox` sí lo lleva).
|
|
|
- **Un radio marcado Y deshabilitado muestra el acento a plena intensidad**
|
|
|
(medido 2026-08-23): la regla de variante
|
|
|
`[data-radio-group][data-variant=X] [data-radio-group-item][data-state='checked']`
|
|
|
es (0,4,0) y `[data-radio-group-item][data-disabled][data-state='checked']`
|
|
|
sólo (0,3,0) — y el gemelo `:not([data-variant])` también es (0,4,0), así que
|
|
|
no hay instancia que escape. Subir la especificidad **mueve píxel** ⇒ firma.
|
|
|
- **El `:hover` de `radio-group` TAPA su tinte inválido**: hover (0,4,0) contra
|
|
|
invalid (0,3,0), la misma escalera que `textarea` (hover > foco > invalid) ya
|
|
|
registrada aquí. Es una decisión de escala del SISTEMA que mueve píxel en
|
|
|
varios controles de formulario con el mismo patrón; se anota, no se toca en
|
|
|
el eje (D-TH.5).
|
|
|
- **El centinela no fotografía `transform`** (medido 2026-08-23 en
|
|
|
`rating-group`): `item-hover-offset` mueve un `translateY` en hover y es el
|
|
|
único knob de esa receta que lo hace, así que leía muerto con el token
|
|
|
perfectamente vivo (`matrix(1,0,0,1,0,-1)` → `matrix(1,0,0,1,0,-40)` al
|
|
|
escribirlo). Misma familia que el hueco de `mask-image` de `background`.
|
|
|
Añadirlo a `PROPS` obliga a re-verificar los componentes con ledger, así que
|
|
|
queda anotado.
|
|
|
- **Los dos botones del array de `Form.AutoFields` deberían COMPONER el `Button`
|
|
|
canónico** (destapado por la firma de `form` del 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-568` y `:583-591`
|
|
|
los renderiza como `<button>`
|
|
|
PELADOS, sin snippet `child` que ofrecer, así que hoy su cromo entero lo
|
|
|
escribe `form.css` a mano: 23 claves públicas duplicando lo que `button.css`
|
|
|
ya 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 de `form` declara tres eventos y los tres apuntan al `provider`, así que
|
|
|
ninguno de los dos emite nada. Toca **soma** (componer el `Button`) y **morfo**
|
|
|
(darles evento) — es la clase **A-112**, el precedente `Toolbar.Button`. Al
|
|
|
cerrarlo, las 23 claves se retiran: el censo de `form` bajaría 64 → 41. Ceder
|
|
|
sin componer no es opción — medido, caen al `<button>` del UA (89,52×36 →
|
|
|
68,61×21 px, borde `outset`, Arial 13,33 px, eje de talla colapsado a 21 px).
|
|
|
- ~~**El eje `size` de `form` NO LLEGA a sus acciones**~~ — **EJECUTADO
|
|
|
2026-08-25.** El diagnóstico era exacto: un `<Form size="xs">` renderizaba
|
|
|
`Form.Submit` con `data-size="md"` —36 px de alto y 16 px de letra **en las
|
|
|
cinco tallas**— porque `form-submit.svelte:20` y `form-reset.svelte:15`
|
|
|
declaraban `size = 'md'` y **nada les pasaba la talla del formulario**;
|
|
|
`form.svelte:16-19` estampaba `data-size` en el provider y no había contexto.
|
|
|
Del eje sólo sobrevivía el `row-gap` (12/16/20/24/28 px). ⚠ Quitar el default
|
|
|
no arreglaba nada: el `Button` tiene el suyo — hacía falta el puente.
|
|
|
Firmado el puente de densidad calcado **1:1 de `toolbar/context.ts`**: nuevo
|
|
|
`eidos/components/form/context.ts` (`Symbol('form-eidos-ctx')` + **getter de
|
|
|
propiedad** para la reactividad), `form.svelte` publica el `resolvedSize` que
|
|
|
ya calculaba, y las dos acciones pierden su default y leen
|
|
|
`size={size ?? form?.size}` — la prop explícita siempre gana, y sin `<Form>`
|
|
|
ancestro se cae en el `md` del propio `Button`, sin re-declararlo.
|
|
|
**Sólo eidos**: cinco ficheros, cero morfo / soma / sema; el contrato de 64
|
|
|
claves y el ledger, intactos. Medido con el chip `size` REAL de la demo:
|
|
|
alto **−10 / −6 / 0 / +8 / +16** px y letra **−4 / −2 / 0 / +4 / +12** px
|
|
|
(los dos últimos son el `clamp()` fluido de `--font-size-lg` / `-xl` saturado
|
|
|
a 1280 px de ancho; a 900 px dan +3,05 / +10,1), `md` sin mover un valor, y
|
|
|
la sonda estándar **0 diffs sobre 832 valores en 7 estados**.
|
|
|
**Doctrina adjudicada: VERBATIM, no la norma tapada en `md`** — ver
|
|
|
§«Anchura vs densidad» al final de esta sección.
|
|
|
- **`alert-dialog` tiene el MISMO agujero, un peldaño** (registrado por el
|
|
|
barrido de la clase, **no medido**). `alert-dialog-action.svelte:25` y
|
|
|
`alert-dialog-cancel.svelte:24` llevan `size = 'md'` sobre un `<Button>`;
|
|
|
`alert-dialog-content.svelte:19` / `:34` / `:78` resuelve y estampa la talla y
|
|
|
**no publica contexto** (no hay `context.ts` en el directorio). Un
|
|
|
`<AlertDialog size="sm">` deja sus botones en `md` — justo lo que
|
|
|
`Dialog.Close` sí resuelve. ⚠ Su `size` es `DialogContentProps['size']`
|
|
|
(`types.ts:112` reexporta el del diálogo) ⇒ eje de **ANCHURA**, así que le
|
|
|
toca la norma **TAPADA**, NO el verbatim de `form`. Verificado además que
|
|
|
`alert-dialog-content.svelte:75` envuelve la **SOMA** `AlertDialog.Content`,
|
|
|
no la eidos `Dialog.Content`: tampoco hereda el puente del diálogo por la
|
|
|
puerta de atrás.
|
|
|
- **`Form.ErrorSummary` estampa un `data-size` que NADIE lee** — tercer defecto
|
|
|
de talla del mismo componente, y el único totalmente inerte.
|
|
|
`form-error-summary.svelte:7` declara `size = 'md'`, `:14` lo resuelve y
|
|
|
`:18` / `:24` lo estampan. Verificado por grep: **las ÚNICAS cuatro reglas con
|
|
|
`data-size` de todo `form.css` son `[data-form][data-size='xs'|'sm'|'lg'|'xl']`**
|
|
|
(`:25`, `:32`, `:39`, `:46`; `md` es 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 modo `auto` el 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 `form` viola el 1:1 universal.**
|
|
|
`lib/recipes/base.ts:1405-1409` mapea `action-font-size-{xs,sm}` →
|
|
|
`--size-xs-font-size`, `{md,lg}` → `--size-sm-font-size` y `xl` →
|
|
|
`--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 de `reference.md` §«The size→font mapping»
|
|
|
(12·14·16·18→20·24→28) ni la escalera de etiqueta — una TERCERA. `form` no
|
|
|
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 el
|
|
|
`Button` — 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:9` cita una sección que NO EXISTE.** Remite a
|
|
|
`THEMING.md §5 "Subset por componente"`; `src/uix/eidos/THEMING.md` es hoy un
|
|
|
stub de redirección y su §5 se llama «The size canon». La sección que la norma
|
|
|
necesita vive en `docs/theming/reference.md` §«Per-component subset» +
|
|
|
§«Container→part derivation». El único fichero del repo con el literal
|
|
|
`Subset por componente` es `docs/decisions/guia-semantica-historica.md:206`,
|
|
|
marcado como NO autoritativo. Una línea.
|
|
|
⚠ Por esto el `context.ts` de `form` cita **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: grid` implementado como `listbox`/`option`
|
|
|
y los dos `listbox` anidados— 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 —el `combobox` sin popup— porque cerrar dos deja el
|
|
|
árbol mezclado igual. Resultado: el Control es el `grid` de UNA fila, la
|
|
|
etiqueta es `gridcell` (que admite `aria-selected`, así que la firma anterior
|
|
|
sobrevive intacta), el Provider se queda sin role y el campo es un `textbox`
|
|
|
en 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-group` implementa los NOMBRES del patrón grid, no su ESTRUCTURA.**
|
|
|
Declara el MISMO `apg` (`morfo/components/tag-group.ts:9`) y sus roles son
|
|
|
`grid` (`:64`) y `row` (`:137` el item, `:163` el link), pero **no declara
|
|
|
NINGUNA parte con `role: 'gridcell'`** y el DOM renderizado lo confirma
|
|
|
(medido en `/uix/components/tag-group`): filas con un `button` colgando
|
|
|
directamente, y un `data-tag-group-label` que es hijo del `grid` sin ser
|
|
|
`row`. WAI-ARIA: `row` exige al menos un `cell` / `gridcell` /
|
|
|
`columnheader` / `rowheader`, y `grid` no puede poseer nada que no sea
|
|
|
`row`/`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 mismo `apg` tienen estructuras distintas, lo cual es
|
|
|
una decisión consciente pendiente, no un accidente.
|
|
|
- **⚠ `search-field` F-1 sigue abierto y es la MISMA clase** que la tercera
|
|
|
cara que acaba de cerrarse: `apg: combobox` sin popup
|
|
|
(`docs/audit/components/search-field.md:18` `:31`). El precedente ya existe
|
|
|
—un `combobox` sin `aria-expanded`/`aria-controls` no es un `combobox`, 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 (con `search-field`)») y `docs/audit/components/_system.md:35`
|
|
|
deja la pregunta escrita: cómo satisfacer «sin patrón APG oficial»
|
|
|
(media-player, drag-drop) — ¿`apg: 'none — rationale'`? Con `tags-input`
|
|
|
cerrado el censo tiene ya un miembro RESUELTO al que mirar, y ocho
|
|
|
componentes sin `apg` esperando 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.ts` localiza el bloque de un
|
|
|
componente con `blockRange()`, que busca una apertura `nombre: {`
|
|
|
(`/^\t'?([a-z0-9-]+)'?: \{/gm`); el de `avatar` es
|
|
|
`avatar: ((): RecipeTokenMap => {`, así que no casa y el `--dry` responde
|
|
|
`muro 1 · colisiones: avatar: SIN BLOQUE` → `ABORTA: un muro está en rojo`.
|
|
|
Es **exactamente la misma ceguera** que `f68bac4a6` corrigió en el censo y en
|
|
|
el centinela («los dos lectores leen ya la IIFE»), pero al codemod nadie se la
|
|
|
enseñó — y `avatar` era 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 `--names` da 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 al `return {`…`};` con `depth: 3` y 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 `-hover` intacta). La vía de carga **SE QUEDA en tsx**: la
|
|
|
premisa «el censo corre con node pelado» era falsa — `theming-census`
|
|
|
importa `../src/uix/morfo/compile` SIN extensión y todo `src/uix/morfo` está
|
|
|
escrito así; alinear la vía es un barrido de `src/`, no del codemod. Queda
|
|
|
vivo el riesgo hermano: **TRES lectores independientes** de la estructura de
|
|
|
`base.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 el `bg` MEDIAL pero **no lleva
|
|
|
`-fg-`**. Así que toda clave con `fg` medial cae al default `1234px`, que es
|
|
|
inválido para `color:` 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 de `data-variant`, mismo forzado de
|
|
|
talla) sobre la insignia custom real: con `1234px` no mueve en **ningún**
|
|
|
combo; con `rgb(1, 2, 3)` mueve en `solid` (`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 de `avatar` coincide y lee muerta, en `chat-message` no
|
|
|
coincide y `bubble-fg-in`/`-out` leen VIVAS **por accidente**. Además deja
|
|
|
incompleta la razón escrita de `scrim-fg-over-*` en el ledger, que atribuye la
|
|
|
ceguera sólo a la puerta `on='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-contrast` va 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 de
|
|
|
`base.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ólo `avatar` +1 y `stepper` +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` (un `cubic-bezier` literal que
|
|
|
la escalera caza por el substring `stroke` → 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 eje
|
|
|
`size` del contenedor es **ANCHURA**: un diálogo `lg` / `xl` / `full` es más
|
|
|
ANCHO, no más denso, y engordar sus controles sería leer mal el eje.
|
|
|
- **VERBATIM** — `toolbar/context.ts`, y los puentes de `button-group`,
|
|
|
`card-group`, `split-button` y `toggle-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):
|
|
|
|
|
|
1. ¿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.
|
|
|
2. 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.
|
|
|
3. Las 2 entradas `global` del ledger salen o cambian de clase con el
|
|
|
renombrado; hay que medir si el suelo se mueve (en `navigation-menu` no se
|
|
|
movió: 1 knob de `public` a `channel`, reach y `atHundred` intactos).
|
|
|
|
|
|
**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**: ~~`--indicator-duration` /
|
|
|
`--indicator-ease` siguen siendo dos públicos de FORMA sin prefijo de dueño~~ —
|
|
|
**✅ 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-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 (`613215fe7` para 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 `part.states[]`/`props` ↔ fuente del
|
|
|
provider va por CADENA sin tipar~~ — **✅ EJECUTADA la firma C (autor,
|
|
|
2026-08-27)**: LAS DOS MITADES. (1) `states`/`parts` ESTRICTOS en ambos
|
|
|
niveles (`StateNameOf<M>` desde `part.states[]` con terminador de
|
|
|
profundidad; cláusula `[Names] extends [never] ? never` molde de `dir` —
|
|
|
cazó en compilación un QUINTO defecto que ningún censo podía contar:
|
|
|
`knob` registrando `states: {}` sobre un morfo sin estados); `props` queda
|
|
|
`SourceMap` CON ACTA en el propio tipo (v.propRef tira el literal; el
|
|
|
arreglo arrastra 3 builders en fichero de otro eje). (2) El CENSO
|
|
|
`source-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: button `states.loading` (error de cubo, vivía
|
|
|
de duplicación) · tabs/radio-group `states.value` (huérfanos) ·
|
|
|
media-player rate-button `props.label` (fuente muerta + comentario falso)
|
|
|
· knob `states:{}`. 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 de `stateRef`/`propRef`/`mapRef`
|
|
|
como eje propio al liberarse `types.ts` → cierra `props` (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 de `keyboard[]` siguen
|
|
|
invisibles a `CompiledPart.deps` y 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 de `compile.ts`
|
|
|
· `StateNameOf` vive en runtime, su casa natural es `morfo/selectors.ts`
|
|
|
(colocación) · `MediaPlayerRateButtonProvider.label` huérfano (anotado en
|
|
|
código) · **hallazgo lateral del adversarial: `MediaPlayer.RateButton` NO
|
|
|
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-all` no recorre `eidos/lib/*.css` (la puerta
|
|
|
anti-flash de `sliding-indicator.css:66` no la audita ningún linter) y
|
|
|
`data-sliding-indicator` no está en `EIDOS_ONLY_ATTRS`. Emparentado: un
|
|
|
consumidor futuro de la capa que NO declare los alias de forma dejaría
|
|
|
entrar un pin de `:root` por el fallback (hoy 0 consumidores así; se cierra
|
|
|
declarando defaults de capa sobre `[data-sliding-indicator]`, como ya hace
|
|
|
`menu-indicator.css`).
|
|
|
- **A2-H5**: `--_{host}-floating-available-width` se 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 middleware `size`)
|
|
|
— 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/<kebab>/+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(): number` devuelve
|
|
|
`1` / `0` / una fracción, **sin unidad**
|
|
|
- `drawer-provider.svelte.ts:1145` → la escribe INLINE en el overlay
|
|
|
- `drawer.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:**
|
|
|
|
|
|
1. **arreglar el velo del drawer MUEVE PÍXEL** — pasa de transparente a pintado;
|
|
|
2. **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.
|
|
|
|
|
|
1. **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.
|
|
|
2. **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.
|
|
|
3. **C-1 no puede cerrarse bien antes.** Su violación más nítida
|
|
|
(`scroll-area`: contrato `--space-2-5` escalado contra un `8px` literal) 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».
|
|
|
4. **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:
|
|
|
>
|
|
|
> 1. **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/eidos` y `web/routes` — **con su expediente abierto**:
|
|
|
> **§18**, en este mismo fichero.
|
|
|
> 2. **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.
|
|
|
> 3. **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_RENAME` se 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**: `classify` preguntaba
|
|
|
> por el prefijo `--_` ANTES que por el contrato, así que las **124** claves
|
|
|
> `_`-prefijadas de `base.ts` se 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. Y
|
|
|
> `SILENT_ROOTS` prohibía TODA escritura en `blocks`/`packs`, con lo que un
|
|
|
> bloque que mañana estampe un `--_{c}-*` legítimo nacía rojo: hoy esas raíces
|
|
|
> —y `libs`, `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 «`blocks` y `packs` siguen 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
|
|
|
con `component: string` que §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'] = v` sobre un objeto de estilo. **7 sitios**,
|
|
|
todos de `palabras`, 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:
|
|
|
|
|
|
1. **`--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).
|
|
|
2. **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.** `overlayOpacity` pasa a llamarse
|
|
|
`overlayProgress` y se escribe como `--_drawer-overlay-progress`; la receta
|
|
|
COMPONE: `var(--drawer-overlay-opacity) * var(--_drawer-overlay-progress, 1)`.
|
|
|
Medido en Chrome real: reposo `color(srgb 0 0 0 / 0.408471)` (antes
|
|
|
`rgba(0,0,0,0)`), y un tema en `:root` a 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 — un `var()`
|
|
|
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-width` **adjudicado por
|
|
|
medida, sin tocar**. Consecuencia cobrada: el censo reclasificó los dos
|
|
|
lectores de `gradient-picker` de `global` a `channel` y **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-depth` y `--gp-current-gradient` y 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.ts`** todaví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 `--debt` antes 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.ts` de `drawer` y `dialog`** documentan `'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:73` y `dialog/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-items` sintetizados 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:22` y `mockup.css:13` (`--box-width`, `100%`). Token
|
|
|
prestado más su propia opinión del default. *(Como `--box-width` vale
|
|
|
`revert-layer`, ese `100%` no se aplica nunca, aunque su autor lo
|
|
|
escribiera creyendo que sí.)*
|
|
|
- **6** — las discrepancias REALES, abajo.
|
|
|
|
|
|
### Las SEIS que justifican la firma — la lista exacta, verificada
|
|
|
|
|
|
| nombre | contrato | fallback inline | ¿difiere de verdad? |
|
|
|
|---|---|---|---|
|
|
|
| `--context-menu-separator-bg` <br>`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` <br>`splitter/splitter.css:35` | `var(--opacity-disabled)` = **0.4** | **0.6** | **Sí — valor distinto** |
|
|
|
| `--calendar-select-padding-inline` <br>`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` <br>`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` <br>`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`](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:**
|
|
|
|
|
|
1. **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.**
|
|
|
2. **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`** (entrada `drawer.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:**
|
|
|
|
|
|
1. **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?
|
|
|
2. **el gemelo `dialog`**, si se arregla, **MUEVE PÍXEL** en cualquier demo que
|
|
|
pase el prop;
|
|
|
3. **el `'0.5'` de los dos `types.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»).
|