From a74aeec7b6938bcde033d1a40b52e4d0659d5109 Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 26 Aug 2026 00:18:27 +0200 Subject: [PATCH] feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al LEDGER + 0 sin nombre. - Valvula nueva /* private: */ -> clase exception (vocabulario cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de comentario ENCIMA de la declaracion (al final de linea, prettier partiria el var() - un commit de anotacion no reescribe el CSS que viene a firmar); el paseo hacia arriba solo salta lineas que eran solo-comentario. - private es DebtClass: clave `private - {fichero} - {selector} - {propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas, verificado contra el blob de HEAD, no regenerado en bloque). - ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge, surface, switch, timeline, textarea, spinner, skeleton, metrics, float-panel, image, toolbar, button, drawer, listbox) + 25 medidos (proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos). - LEDGER 66: casi todo UN patron - el conmutador de tono A MANO ([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12, date-range-picker 9...) mas las tallas prestadas de field. Se retiran el dia que esa familia adopte la escalera de paleta. - Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el conmutador del icono derivaba entero de publicos - cierto para track/text, falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger. timeline SS5 (pre-B') se respeta y queda señalado para reabrir. Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo -> newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde; ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final: 1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado por strip programatico contra el blob de HEAD), prettier 0 regresiones. Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3. Co-Authored-By: Claude Fable 5 --- scripts/theming-census-debt.ts | 119 +++++++++++++-- scripts/theming-census.ts | 137 ++++++++++++++++-- .../eidos/components/accordion/accordion.css | 4 + src/uix/eidos/components/avatar/avatar.css | 24 +++ src/uix/eidos/components/badge/badge.css | 11 ++ src/uix/eidos/components/button/button.css | 7 + src/uix/eidos/components/card/card.css | 6 + .../components/color-picker/color-picker.css | 6 + src/uix/eidos/components/drawer/drawer.css | 6 + .../components/float-panel/float-panel.css | 4 + src/uix/eidos/components/form/form.css | 9 ++ src/uix/eidos/components/image/image.css | 5 + src/uix/eidos/components/listbox/listbox.css | 12 ++ src/uix/eidos/components/metrics/metrics.css | 5 + src/uix/eidos/components/popover/popover.css | 6 + .../proof-of-human/proof-of-human.css | 12 ++ .../eidos/components/skeleton/skeleton.css | 8 + src/uix/eidos/components/spinner/spinner.css | 7 + src/uix/eidos/components/surface/surface.css | 10 ++ src/uix/eidos/components/switch/switch.css | 6 + .../eidos/components/textarea/textarea.css | 4 + .../eidos/components/timeline/timeline.css | 11 ++ src/uix/eidos/components/toolbar/toolbar.css | 6 + src/uix/eidos/components/tooltip/tooltip.css | 4 + 24 files changed, 399 insertions(+), 30 deletions(-) diff --git a/scripts/theming-census-debt.ts b/scripts/theming-census-debt.ts index 5c5a806a7..bc06e7661 100644 --- a/scripts/theming-census-debt.ts +++ b/scripts/theming-census-debt.ts @@ -79,13 +79,34 @@ * are frozen AS THE CENSUS MEASURES THEM, which is all a ledger ever * claims — never that the measurement has been adjudicated. * - * WHAT IS NOT HERE. Only `global` and `literal` knobs are registered — the - * two classes the signature of 2026-08-25 named. The census's third - * unreached class, a `private` that does NOT derive from a public (318 - * knobs), is debt by the letter of R-5.1 in `PLAN-theming.md` §3 and has NO - * per-key ratchet: it stays under the coarse `reachPct` floor until it is - * signed. Naming it here is the point — it is a hole in the ratchet, not an - * oversight of it. + * WHAT IS NOT HERE — AND THE HOLE THAT CLOSED. The first baseline registered + * only `global` and `literal`; the census's third unreached class, a `private` + * that does NOT derive from a public (318 knobs), had no per-key ratchet and + * this header named that as a hole rather than hiding it. **It is closed.** + * The 318 were adjudicated key by key in two moves, and both are mechanical + * from here on: + * + * 1. the census learned to READ the two shapes the doctrine had already + * adjudicated — the THM-2 palette `bridge` (132) and the per-instance + * value `channel` (36), decided off the GENERATOR'S OUTPUT and not off a + * name (see `PALETTE_BRIDGE` in `theming-census.ts`); + * 2. the remaining RESIDUE of 150 was split by measurement: 84 knobs whose + * shape the doctrine blesses ONE LEVEL BELOW the knob carry a written + * `/* private: *​/` on the declaration that establishes the + * mechanism — class `exception`, the canon's existing signed-deviation + * class, not a new tier — and the other 66 are registered below like any + * other debt. + * + * So `private` is now a `DebtClass` beside `global` and `literal`: a knob + * reading a private that derives from nothing is new debt, named, and has the + * same three exits as the other two (tokenize it, sign it, register it) plus + * the same STALE detector on the way out. A `private · …` entry that gains its + * annotation, or whose private starts deriving from a public, reports STALE + * and its line goes in the commit that earned it. + * + * What remains outside the ledger is only what is outside the RATIO by + * design: `system`, `structural`, `bridge`, `channel` and `exception` — four + * measured classes and one written act, each documented where it is decided. * * Generated: `node --import tsx/esm scripts/theming-census.ts --debt --write` * Checked: `node --import tsx/esm scripts/theming-census.ts --debt` @@ -353,7 +374,21 @@ export const CENSUS_DEBT: Record = { 'literal · chronos.css · .chronos-when [data-time-range-field-input] · inline-size', 'literal · chronos.css · [data-chronos-day-cell]:hover [data-chronos-day-add], [data-chronos-day-add]:focus-visible · opacity', 'literal · chronos.css · [data-chronos-event-chip][data-draft] · opacity', - 'literal · chronos.css · [data-chronos] · inline-size' + 'literal · chronos.css · [data-chronos] · inline-size', + 'private · chronos.css · .chronos-peek-hour · block-size', + 'private · chronos.css · .chronos-peek-slot · block-size', + 'private · chronos.css · .chronos-tg-dayhead[data-today] .chronos-tg-daynum · background', + 'private · chronos.css · .chronos-tg-hour · block-size', + 'private · chronos.css · .chronos-tg-now · border-block-start', + 'private · chronos.css · .chronos-tg-now::before · background', + 'private · chronos.css · .chronos-tg-slot · block-size', + 'private · chronos.css · .chronos-tg-slot[data-dragover], .chronos-tg-alldaycol[data-dragover] · background', + 'private · chronos.css · .chronos-tg-slot[data-dragover], .chronos-tg-alldaycol[data-dragover] · box-shadow', + 'private · chronos.css · [data-chronos-day-cell][data-dragover] · background', + 'private · chronos.css · [data-chronos-day-cell][data-dragover] · box-shadow', + 'private · chronos.css · [data-chronos-day-cell][data-today] [data-chronos-day-number] · background', + 'private · chronos.css · [data-chronos-event-chip][data-continues-after] · padding-inline-end', + 'private · chronos.css · [data-chronos-event-chip][data-continues-before] · padding-inline-start' ], clipboard: ['global · clipboard.css · [data-clipboard-indicator] · font-family'], 'code-block': [ @@ -377,7 +412,10 @@ export const CENSUS_DEBT: Record = { 'literal · color-picker.css · [data-color-picker-swatch-group] · inline-size', 'literal · color-picker.css · [data-color-picker-swatch-indicator] · block-size', 'literal · color-picker.css · [data-color-picker-swatch-indicator] · inline-size', - 'literal · color-picker.css · [data-color-picker] · inline-size' + 'literal · color-picker.css · [data-color-picker] · inline-size', + 'private · color-picker.css · [data-color-picker-label] · font-size', + 'private · color-picker.css · [data-color-picker-trigger] · font-size', + 'private · color-picker.css · [data-color-picker-value-text] · font-size' ], combobox: [ 'global · combobox.css · [data-combobox-selected-tag-remove]:hover · background', @@ -419,7 +457,16 @@ export const CENSUS_DEBT: Record = { 'global · date-range-picker.css · [data-date-range-picker-year-view-prev], [data-date-range-picker-year-view-next], [data-date-range-picker-month-view-prev], [data-date-range-picker-month-view-next] · border', 'global · date-range-picker.css · [data-date-range-picker][data-invalid] [data-field-control] · border-color', 'literal · date-range-picker.css · [data-date-range-picker-calendar][data-range-calendar] · inline-size', - 'literal · date-range-picker.css · [data-date-range-picker] · inline-size' + 'literal · date-range-picker.css · [data-date-range-picker] · inline-size', + 'private · date-range-picker.css · [data-date-range-picker-calendar][data-range-calendar] · font-size', + 'private · date-range-picker.css · [data-date-range-picker-trigger] · block-size', + 'private · date-range-picker.css · [data-date-range-picker-trigger] · inline-size', + 'private · date-range-picker.css · [data-date-range-picker-trigger]:focus-visible · border-color', + 'private · date-range-picker.css · [data-date-range-picker-trigger]:hover · background', + 'private · date-range-picker.css · [data-date-range-picker-trigger]:hover · border-color', + 'private · date-range-picker.css · [data-date-range-picker-trigger]:hover · color', + 'private · date-range-picker.css · [data-date-range-picker] · font-size', + 'private · date-range-picker.css · [data-date-range-picker] · gap' ], drawer: [ "global · drawer.css · [data-drawer-content][data-side='bottom'] · border-top", @@ -497,6 +544,11 @@ export const CENSUS_DEBT: Record = { 'literal · float-panel.css · [data-float-panel-resize-grip]:focus-visible · opacity', 'literal · float-panel.css · [data-float-panel-resize-grip]:hover, [data-float-panel-resize-grip][data-grabbed] · opacity' ], + form: [ + 'private · form.css · [data-form-auto-fields-array-add], [data-form-auto-fields-array-remove] · background', + 'private · form.css · [data-form-auto-fields-array-add], [data-form-auto-fields-array-remove] · color', + 'private · form.css · [data-form-auto-fields-array-add]:not(:disabled):hover, [data-form-auto-fields-array-remove]:not(:disabled):hover · background' + ], 'gradient-builder': [ 'global · gradient-builder.css · [data-gradient-builder-stop]:focus-visible · box-shadow', 'literal · gradient-builder.css · [data-gradient-builder-preset] · inline-size', @@ -559,7 +611,9 @@ export const CENSUS_DEBT: Record = { 'literal · metrics.css · [data-metrics-actions] · inline-size', 'literal · metrics.css · [data-metrics-chart] · inline-size', 'literal · metrics.css · [data-metrics-gauge-value] · line-height', - 'literal · metrics.css · [data-metrics-progress] · inline-size' + 'literal · metrics.css · [data-metrics-progress] · inline-size', + "private · metrics.css · [data-metrics-icon][data-variant='solid'] · background", + "private · metrics.css · [data-metrics-icon][data-variant='solid'] · color" ], mockup: [ 'global · mockup.css · [data-box][data-mockup] [data-mockup-bar] · background', @@ -977,7 +1031,8 @@ export const CENSUS_DEBT: Record = { "literal · palabras.css · [data-palabras-doc] figure[data-palabras-block='image'][data-palabras-image-fit] :where(img, [data-palabras-image-tile]) · inline-size", "literal · palabras.css · [data-palabras-doc] figure[data-palabras-block='image'][data-palabras-image-fit] · inline-size", "literal · palabras.css · [data-palabras-doc] figure[data-palabras-block='image'][data-palabras-image-full-width] img · inline-size", - "literal · palabras.css · [data-palabras-doc] figure[data-palabras-block='image'][data-palabras-image-full-width] · inline-size" + "literal · palabras.css · [data-palabras-doc] figure[data-palabras-block='image'][data-palabras-image-full-width] · inline-size", + 'private · palabras-chrome.css · [data-palabras-col-resize]::before · inline-size' ], 'password-field': [ 'global · password-field.css · [data-password-field-strength-meter-label] · font-size', @@ -990,7 +1045,9 @@ export const CENSUS_DEBT: Record = { 'literal · password-field.css · [data-password-field-caps-lock-indicator] · padding-inline', 'literal · password-field.css · [data-password-field-strength-meter] · gap', 'literal · password-field.css · [data-password-field-visibility-trigger] svg · block-size', - 'literal · password-field.css · [data-password-field-visibility-trigger] svg · inline-size' + 'literal · password-field.css · [data-password-field-visibility-trigger] svg · inline-size', + 'private · password-field.css · [data-password-field-visibility-trigger]:hover:not([data-disabled]) · background', + 'private · password-field.css · [data-password-field-visibility-trigger]:hover:not([data-disabled]) · color' ], 'picker-shell': ['global · picker-time-row.css · .picker-time-label · font-size'], popover: [ @@ -1229,7 +1286,19 @@ export const CENSUS_DEBT: Record = { 'literal · time-picker.css · .time-picker-slider · inline-size', 'literal · time-picker.css · .time-picker-slider-label · line-height', 'literal · time-picker.css · [data-time-picker-clock] [data-slider] · inline-size', - 'literal · time-picker.css · [data-time-picker] · inline-size' + 'literal · time-picker.css · [data-time-picker] · inline-size', + 'private · time-picker.css · .time-picker-slider-label · font-size', + 'private · time-picker.css · [data-time-field-input] [data-time-picker-trigger]:hover · background', + 'private · time-picker.css · [data-time-field-input] [data-time-picker-trigger]:hover · color', + 'private · time-picker.css · [data-time-picker-clock] [data-slider-range] · background', + 'private · time-picker.css · [data-time-picker-clock] [data-slider-thumb] · border-color', + 'private · time-picker.css · [data-time-picker-trigger] · block-size', + 'private · time-picker.css · [data-time-picker-trigger] · inline-size', + 'private · time-picker.css · [data-time-picker-trigger]:focus-visible · border-color', + 'private · time-picker.css · [data-time-picker-trigger]:hover · background', + 'private · time-picker.css · [data-time-picker-trigger]:hover · border-color', + 'private · time-picker.css · [data-time-picker-trigger]:hover · color', + 'private · time-picker.css · [data-time-picker] · font-size' ], 'time-range-picker': [ 'global · time-range-picker.css · .time-range-picker-endpoint · gap', @@ -1279,10 +1348,30 @@ export const CENSUS_DEBT: Record = { "literal · time-range-picker.css · [data-time-range-picker-day-period-item] input[type='radio'] · block-size", "literal · time-range-picker.css · [data-time-range-picker-day-period-item] input[type='radio'] · inline-size", 'literal · time-range-picker.css · [data-time-range-picker-day-period-toggle] · padding', - 'literal · time-range-picker.css · [data-time-range-picker] · inline-size' + 'literal · time-range-picker.css · [data-time-range-picker] · inline-size', + 'private · time-range-picker.css · .time-range-picker-endpoint-label · font-size', + 'private · time-range-picker.css · .time-range-picker-slider-label · font-size', + 'private · time-range-picker.css · [data-time-range-field-input] [data-time-range-picker-trigger] · block-size', + 'private · time-range-picker.css · [data-time-range-field-input] [data-time-range-picker-trigger] · inline-size', + 'private · time-range-picker.css · [data-time-range-field-input] [data-time-range-picker-trigger]:hover · background', + 'private · time-range-picker.css · [data-time-range-field-input] [data-time-range-picker-trigger]:hover · color', + "private · time-range-picker.css · [data-time-range-picker-clock] [data-endpoint='end'] [data-slider-range] · background", + 'private · time-range-picker.css · [data-time-range-picker-clock] [data-slider-range] · background', + 'private · time-range-picker.css · [data-time-range-picker-clock] [data-slider-thumb] · border-color', + 'private · time-range-picker.css · [data-time-range-picker-day-period-item][data-checked] · background', + 'private · time-range-picker.css · [data-time-range-picker-day-period-item][data-checked] · color', + 'private · time-range-picker.css · [data-time-range-picker-trigger] · block-size', + 'private · time-range-picker.css · [data-time-range-picker-trigger] · inline-size', + 'private · time-range-picker.css · [data-time-range-picker-trigger]:focus-visible · border-color', + 'private · time-range-picker.css · [data-time-range-picker-trigger]:hover · background', + 'private · time-range-picker.css · [data-time-range-picker-trigger]:hover · border-color', + 'private · time-range-picker.css · [data-time-range-picker-trigger]:hover · color', + 'private · time-range-picker.css · [data-time-range-picker] · font-size', + 'private · time-range-picker.css · [data-time-range-picker] · gap' ], timeline: ['literal · timeline.css · [data-timeline-marker] · line-height'], 'toggle-group': ['global · toggle-group.css · [data-toggle-group][data-disabled] · opacity'], + tooltip: ['private · tooltip.css · [data-tooltip-content] · background'], 'tree-grid': ['literal · tree-grid.css · [data-tree-grid-root][data-block] · inline-size'], 'tree-view': ['literal · tree-view.css · [data-tree-view-root][data-block] · inline-size'], 'virtual-grid': [ diff --git a/scripts/theming-census.ts b/scripts/theming-census.ts index 0484a6158..5faa03411 100644 --- a/scripts/theming-census.ts +++ b/scripts/theming-census.ts @@ -34,9 +34,12 @@ * global — any other `var(--…)`: a raw primitive (`--space-*`, * `--radius-*`, `--color-*`, `--font-size-*`, `--size-*`…) * literal — no `var(` at all (`8px`, `1.25`, `#fff`) - * exception — a literal ANNOTATED `/* literal: *​/` on its own - * declaration: recipe-contract §3's exception valve, the same one - * `component-audit` honours. A signed deviation is not debt. + * exception — a SIGNED deviation, in either of the canon's two written + * forms: a literal annotated `/* literal: *​/` + * (recipe-contract §3's valve, the same one `component-audit` + * honours) or a residue private annotated `/* private: *​/` + * (the same valve one class over, 2026-08-25 — see + * `ANNOTATED_PRIVATE`). A signed deviation is not debt. * structural — EVERY knob of a component whose 0 % is its NATURE and not its * debt, because it has no contract to write: the signed list is * `STRUCTURAL_COMPONENTS` below. @@ -111,6 +114,46 @@ const INERT = new Set([ /** recipe-contract §3's exception valve, as written in the canon. */ const ANNOTATED = new RegExp('[/][*][ ]*literal:'); +/** + * The SAME valve, one class over: `/* private: *​/` (signature of + * 2026-08-25, F2 of the 318-privates adjudication). + * + * `literal:` signs a value that is not a token. `private:` signs a knob whose + * value routes through a private the doctrine of this axis already blesses but + * that no MECHANICAL test can see, because the blessed shape sits ONE LEVEL + * BELOW the knob: a variant / role CONMUTADOR whose branches are the THM-2 + * palette bridge (`card`, `avatar`, `badge`, `surface`, `switch`, `timeline`), + * a per-instance value CHANNEL read through an alias (`drawer`, `popover`, + * `avatar`'s custom ring), a shared LAYER adopted through a private + * (`listbox` → `list-surface`), or the identity of a variant (`toolbar`'s + * ghost bar, `accordion`'s shadowless outline). + * + * Read in the same TWO places `literal:` is: on the private's own declaration + * (the economical shape — one note adjudicates every knob that reads it) and + * on the knob declaration itself. A knob is signed when its own declaration + * carries the note OR when EVERY private it reads carries it: `every`, not + * `some`, for the same reason the bridge test uses it — the unsigned half is + * exactly what still needs adjudicating. A private is signed ONCE, on the + * declaration that establishes the mechanism: a conmutador is ONE decision, + * not one per branch, and eight identical notes down a per-role cascade would + * be noise pretending to be eight acts. + * + * PLACEMENT differs from `literal:` in one way, and it is a prettier + * constraint, not a doctrinal one: the note may also sit in the comment block + * immediately ABOVE the declaration. These reasons cite a sheet and a date, so + * a trailing comment pushes the line past the 100-column print width and + * prettier answers by breaking the `var(…)` across lines — which would make an + * annotation commit rewrite the CSS it only meant to sign. The upward walk + * stops at the first line that carries actual CSS (tested on the STRIPPED + * body, where a comment-only line is blank), so a note can never leak onto the + * declaration that follows the one it was written for. + * + * Resulting class: `exception`. The vocabulary stays CLOSED — this is the + * canon's existing «signed deviation» class, not a new tier — and, like every + * exception, it leaves the ratio's denominator without leaving the listing. + */ +const ANNOTATED_PRIVATE = new RegExp('[/][*][ ]*private:'); + export type KnobClass = | 'public' | 'private' @@ -308,8 +351,12 @@ const STRUCTURAL_COMPONENTS = new Map([ * * Both leave the ratio's DENOMINATOR, exactly like `system` and `structural`; * their knobs are still counted and still listed. A private that is NEITHER - * stays `private`: the RESIDUE this signature deliberately leaves to be - * adjudicated key by key (`--residue`). + * stays `private`: the RESIDUE that signature deliberately left to be + * adjudicated key by key (`--residue`). That adjudication landed on + * 2026-08-25: of its 150 knobs, 84 carry a written `/* private: *​/` + * (class `exception`) because the shape the doctrine blesses sits one level + * BELOW the knob, and the other 66 are registered in the debt ledger. `private` + * is a `DebtClass` from that day, so a new one arrives NAMED. */ const GENERATED_CSS = readFileSync(resolve('src/uix/eidos/generated/base.css'), 'utf8').replace( /\r\n/g, @@ -432,6 +479,10 @@ export function scanComponent(dir: string): Scan | null { const knobs: Knob[] = []; const privates: PrivateDecl[] = []; const usedPrivates = new Set(); + /** `--_{c}-x` declarations carrying `/* private: … *​/` — the valve, one level down. */ + const signedPrivates = new Set(); + /** Knobs carrying the note on their OWN declaration — the valve, at the knob. */ + const signedKnobs = new Set(); for (const file of files) { // The stripped body is what gets CLASSIFIED (a comment must not read as a @@ -441,6 +492,21 @@ export function scanComponent(dir: string): Scan | null { const raw = readFileSync(join(d, file), 'utf8').replace(/\r\n/g, '\n'); const rawLines = raw.split('\n'); const body = strip(raw); + const bodyLines = body.split('\n'); + /** + * The `private:` note on the declaration at `line`, or in the comment + * block immediately above it. The walk climbs only over lines that are + * BLANK once comments are stripped — i.e. lines that were nothing but a + * comment — so it stops dead at the previous declaration, at the rule's + * `{`, and at the selector. + */ + const signedAt = (line: number, endLine: number): boolean => { + for (let ln = line; ln <= endLine; ln++) + if (ANNOTATED_PRIVATE.test(rawLines[ln - 1] ?? '')) return true; + for (let ln = line - 1; ln >= 1 && (bodyLines[ln - 1] ?? '').trim() === ''; ln--) + if (ANNOTATED_PRIVATE.test(rawLines[ln - 1] ?? '')) return true; + return false; + }; if (body.includes('data-size')) row.hasSize = true; const lineStarts: number[] = [0]; for (let i = 0; i < body.length; i++) if (body.charCodeAt(i) === 10) lineStarts.push(i + 1); @@ -485,6 +551,12 @@ export function scanComponent(dir: string): Scan | null { let source = classify(val, pubNeedle, privNeedle, dir); if (source === 'literal' && ANNOTATED.test(rawLines[line - 1] ?? '')) source = 'exception'; + // The `private:` valve. It does NOT touch `source`: a signed private + // must not read as «derives from a public», which would score the + // knob `public` and inflate the reach. It signs the knobs that READ + // it, in the fourth pass below. + if (signedAt(line, lineAt(innerStart + (m.index ?? 0) + m[0].length - 1))) + signedPrivates.add(prop); privates.push({ name: prop, file, line, selector, value: val, source }); } continue; @@ -510,7 +582,17 @@ export function scanComponent(dir: string): Scan | null { } } row[klass]++; - knobs.push({ file, line, selector, prop, value: val, klass }); + const knob: Knob = { file, line, selector, prop, value: val, klass }; + // Same span as the `literal:` note above: a value broken over several + // lines carries its note at the end. Recorded now and applied in the + // fourth pass, because a knob can still be reclassified `public`, + // `bridge` or `channel` first — and where a mechanical test decides, + // a written note has nothing left to sign. + if (klass === 'private') { + const endLine = lineAt(innerStart + (m.index ?? 0) + m[0].length - 1); + if (signedAt(line, endLine)) signedKnobs.add(knob); + } + knobs.push(knob); } } } @@ -583,6 +665,22 @@ export function scanComponent(dir: string): Scan | null { k.klass = klass; } + // ── Fourth pass: the WRITTEN valve for what no mechanical test can see ── + // LAST on purpose: the three passes above decide by MEASUREMENT, and a + // signature over a knob the machine already adjudicated would be an act with + // nothing left to sign. What survives to here is the residue — and a residue + // knob is `exception` when its own declaration carries `/* private: … *​/`, + // or when EVERY private it reads does (see `ANNOTATED_PRIVATE`). + for (const k of knobs) { + if (k.klass !== 'private') continue; + const refs = [...new Set([...k.value.matchAll(/var\(\s*(--_[a-z0-9-]+)/g)].map((m) => m[1]))]; + if (!signedKnobs.has(k) && !(refs.length > 0 && refs.every((r) => signedPrivates.has(r)))) + continue; + row.private--; + row.exception++; + k.klass = 'exception'; + } + const themeable = row.public + row.private + row.global + row.literal; row.knobs = themeable + row.system + row.structural + row.bridge + row.channel; row.reach = themeable === 0 ? 1 : row.public / themeable; @@ -600,13 +698,20 @@ export function census(only?: string): CensusRow[] { // ─── The debt ledger — R-5.1's per-key ratchet ─────────────────────────────── /** - * The two unreached classes the signature of 2026-08-25 registers key by key. - * `private` is the third and is deliberately NOT here — see the header of - * `theming-census-debt.ts`, which names the hole instead of hiding it. + * The THREE unreached classes the ratchet registers key by key. + * + * `private` joined `global` and `literal` on 2026-08-25 (F2 of the + * 318-privates adjudication), and with it the hole the ledger's header used to + * name is closed: a knob reading a private that derives from nothing — not a + * public, not the palette bridge, not a value channel, and carrying no + * `/* private: … *​/` signature — is now debt like any other. It has the same + * three exits (tokenize it, sign it, or register it) and the same STALE + * detector on the way out. */ -export type DebtClass = Extract; +export type DebtClass = Extract; -const isDebt = (k: Knob): boolean => k.klass === 'global' || k.klass === 'literal'; +const isDebt = (k: Knob): boolean => + k.klass === 'global' || k.klass === 'literal' || k.klass === 'private'; /** * The canonical identity of one debt knob: `{class} · {file} · {selector} · @@ -766,7 +871,7 @@ function reportDebt(only?: string, asJson = false) { `theming-census --debt — ${debt.registered} key(s) registered · ${debt.newDebt.length} new · ${debt.stale.length} stale` ); if (debt.newDebt.length > 0) { - console.log('\n— NEW DEBT (a literal or a raw global outside the ledger) —'); + console.log('\n— NEW DEBT (a literal, a raw global or a bare private outside the ledger) —'); for (const f of debt.newDebt) console.log(` ${f.component} ${f.key}`); } if (debt.stale.length > 0) { @@ -790,9 +895,11 @@ function reportDebt(only?: string, asJson = false) { * private it reads is declared, with the class of each declaration, because * that is the whole material the adjudication needs. * - * It is a REPORT, not a gate: the per-key ratchet for this class is the piece - * this file does not yet have (the hole named in the header of - * `theming-census-debt.ts` and in recipe-contract §4). + * It was a REPORT and not a gate until 2026-08-25; since F2 of that signature + * the residue is gated like every other unreached class (`DebtClass` above), + * so this listing is now the WORKING VIEW of the adjudication — it shows what + * is left to decide, with the material each decision needs, while the ledger + * and the `private:` valve hold the answer already given. */ export interface ResidueRow { component: string; diff --git a/src/uix/eidos/components/accordion/accordion.css b/src/uix/eidos/components/accordion/accordion.css index 5a9ca528e..6d85f7411 100644 --- a/src/uix/eidos/components/accordion/accordion.css +++ b/src/uix/eidos/components/accordion/accordion.css @@ -1,6 +1,10 @@ [data-accordion] { --_accordion-provider-bg: var(--accordion-outline-provider-bg); --_accordion-provider-border: var(--accordion-outline-provider-border); + /* private: CONMUTADOR-IDENTITY by variant — `surface` reads the public + `--accordion-surface-provider-shadow`, and `outline` / `ghost` ARE + shadowless: that `none` is the identity of the variant, not a knob. Same + class `toolbar` §5 signed; measured branch by branch 2026-08-25. */ --_accordion-provider-shadow: none; --_accordion-item-border: var(--accordion-outline-item-border); --_accordion-content-bg: var(--accordion-outline-content-bg); diff --git a/src/uix/eidos/components/avatar/avatar.css b/src/uix/eidos/components/avatar/avatar.css index 99f5c2a24..8ae0ee6db 100644 --- a/src/uix/eidos/components/avatar/avatar.css +++ b/src/uix/eidos/components/avatar/avatar.css @@ -35,7 +35,16 @@ box-sizing: border-box; inline-size: var(--_avatar-size); block-size: var(--_avatar-size); + /* private: `--_avatar-bg` / `--_avatar-fg` are a CONMUTADOR the GENERATOR + declares (contract `_bg` / `_fg`, 24 composite scopes from the IIFE): they + change source with the variant and their value comes from the THM-2 palette + forward the colour layer feeds per instance. A public on top would let a + theme pin them and kill every avatar's `color=`; flattening them would + duplicate every rule per tone, 24 combinations (avatar.md §5, signed + 2026-08-23). */ background: var(--_avatar-bg); + /* private: the ink half of that same generator-declared CONMUTADOR + (avatar.md §5, signed 2026-08-23). */ color: var(--_avatar-fg); border: var(--avatar-border-width) solid var(--_avatar-border); border-radius: var(--_avatar-radius); @@ -153,9 +162,17 @@ } [data-avatar][data-ring='solid'] { + /* private: the width is a public per `data-ring-width`; the colour is a + CONMUTADOR over the component's own role tokens whose last branch is the + custom VALUE CHANNEL (`--_avatar-ring-color-custom`, written inline by the + wrapper). The contract key was RETIRED on 2026-08-25 for that reason: the + gate and the inline write come from one expression, so a public here could + never be reached from a theme (avatar.md §5, firma A-c). */ box-shadow: 0 0 0 var(--_avatar-ring-width) var(--_avatar-ring-color); } [data-avatar][data-ring='soft'] { + /* private: the same ring conmutador + custom value channel (avatar.md §5, + firma A-c 2026-08-25). */ box-shadow: 0 0 0 var(--_avatar-ring-width) color-mix(in srgb, var(--_avatar-ring-color) 30%, transparent), @@ -224,7 +241,14 @@ min-inline-size: var(--_avatar-badge-size); block-size: var(--_avatar-badge-size); padding-inline: calc(var(--_avatar-badge-size) * 0.3); + /* private: `--_avatar-badge-bg` is the third CONMUTADOR of this recipe — the + generator declares its role branches over the component's own public tones + and the CSS below adds the custom VALUE CHANNEL + (`--_avatar-badge-color-custom`, contract key retired 2026-08-25 with the + ring's) — avatar.md §5, signed. */ background: var(--_avatar-badge-bg); + /* private: the ink half of the badge conmutador — the generator's public tone + branches plus the custom value channel (avatar.md §5, signed). */ color: var(--_avatar-badge-fg); border: var(--avatar-badge-border-width) solid var(--_avatar-badge-border); border-radius: var(--avatar-radius-full); diff --git a/src/uix/eidos/components/badge/badge.css b/src/uix/eidos/components/badge/badge.css index 86c7fd420..ac4314a66 100644 --- a/src/uix/eidos/components/badge/badge.css +++ b/src/uix/eidos/components/badge/badge.css @@ -36,7 +36,13 @@ */ /* Default variant = soft; overridden by data-variant below. */ + /* private: CONMUTADOR by variant — `soft` / `solid` read the THM-2 palette + bridge, `outline` / `ghost` are the identity of the variant + (`transparent`). Flattening it would duplicate every rule per variant + (badge.md §5, signed: «las tres clases de F2-B en un solo componente»). */ --_badge-bg: var(--_badge-palette-track); + /* private: same CONMUTADOR — every branch is the bridge, with the named + finish's authored ink first in `solid` (badge.md §5, signed). */ --_badge-fg: var(--_badge-palette-text); --_badge-border-color: transparent; @@ -117,6 +123,11 @@ (deep end falls back to `solid`: badges have no hover swap), scaled by the theme dial (--gradient-finish-lift). Solid-fill treatment only. */ [data-badge][data-gradient][data-variant='solid'] { + /* private: the GENERATOR's gradient-finish ramp — `renderRecipeGradientFinish` + derives `--_badge-fill-finish` from this instance's palette slots, scaled by + the theme dial `--gradient-finish-lift`. A theme reaches it through the + palette and through that dial, never by naming it (gradient-as-color axis, + CLOSED; badge.md §5, signed). */ background-image: var(--_badge-fill-finish); } diff --git a/src/uix/eidos/components/button/button.css b/src/uix/eidos/components/button/button.css index 8fbdae886..83099ea32 100644 --- a/src/uix/eidos/components/button/button.css +++ b/src/uix/eidos/components/button/button.css @@ -97,9 +97,16 @@ re-assert is required because the hover rule above sets the `background:` SHORTHAND, which resets the background-image longhand. */ [data-button][data-gradient][data-variant='solid'] { + /* private: the GENERATOR's gradient-finish ramp — `renderRecipeGradientFinish` + derives `--_button-fill-finish` from this instance's palette slots, scaled + by the theme dial `--gradient-finish-lift`. A theme reaches it through the + palette and through that dial, never by naming it (gradient-as-color axis, + CLOSED; button.md §5: «el techo restante es canal de valor»). */ background-image: var(--_button-fill-finish); } [data-button][data-gradient][data-variant='solid']:hover:not([data-disabled]):not([data-loading]) { + /* private: the same generator ramp, re-asserted because the hover rule sets + the `background:` SHORTHAND (button.md §5). */ background-image: var(--_button-fill-finish); } diff --git a/src/uix/eidos/components/card/card.css b/src/uix/eidos/components/card/card.css index dd4abc5d9..0845f4dd4 100644 --- a/src/uix/eidos/components/card/card.css +++ b/src/uix/eidos/components/card/card.css @@ -46,7 +46,13 @@ * the 33 scales. */ /* Default variant = soft; overridden by data-variant below. */ + /* private: CONMUTADOR by variant, 4 sources — three are the THM-2 palette + bridge and `ghost` is the identity of the variant. A public on top would + let a theme PIN it and kill every instance's `color=` in silence + (card.md §5, signed 2026-08-23; §3.pre of the handoff). */ --_card-bg: var(--_card-palette-track); + /* private: same CONMUTADOR, and here BOTH sources are the THM-2 bridge — + read at the root, the title and the body (card.md §5, signed 2026-08-23). */ --_card-fg: var(--_card-palette-text); --_card-border-color: transparent; diff --git a/src/uix/eidos/components/color-picker/color-picker.css b/src/uix/eidos/components/color-picker/color-picker.css index 39b8f043e..240aced35 100644 --- a/src/uix/eidos/components/color-picker/color-picker.css +++ b/src/uix/eidos/components/color-picker/color-picker.css @@ -94,6 +94,12 @@ linear-gradient(-45deg, transparent 75%, var(--color-picker-transparency-cell) 75%); /* The accent re-derives from the palette forward ON THIS scope — the content stamps its own `data-color`, so custom/scales resolve here. */ + /* private: the THM-2 palette BRIDGE one level below the knob — its ONLY + source is `--_color-picker-palette-border`, which the forward emits as + `var(--palette-border, var(--color-primary-border))`; the alias exists so + the accent re-derives on THIS scope (the content stamps its own + `data-color`). A public on top would pin it and kill the instance's + `color=` — measured 2026-08-25, single declaration. */ --_color-picker-accent: var(--_color-picker-palette-border); --_color-picker-swatch-size: var(--color-picker-swatch-size-md); diff --git a/src/uix/eidos/components/drawer/drawer.css b/src/uix/eidos/components/drawer/drawer.css index 507c40c5a..e05750fce 100644 --- a/src/uix/eidos/components/drawer/drawer.css +++ b/src/uix/eidos/components/drawer/drawer.css @@ -69,7 +69,13 @@ [data-drawer-content] { --_drawer-size-width: var(--drawer-content-width-md); --_drawer-size-height: var(--drawer-content-height-md); + /* private: the per-instance VALUE CHANNEL one level below the knob — soma + writes `--_drawer-content-width-override` per instance and the fallback is + the public size coordinate. A theme must not reach the override: pinning it + breaks the behaviour (drawer.md §5, signed: «los ocho privados son canales + de valor»). */ --_drawer-panel-width: var(--_drawer-content-width-override, var(--_drawer-size-width)); + /* private: the block-axis half of that same value channel (drawer.md §5). */ --_drawer-panel-height: var(--_drawer-content-height-override, var(--_drawer-size-height)); --_drawer-padding: var(--drawer-content-padding-md); diff --git a/src/uix/eidos/components/float-panel/float-panel.css b/src/uix/eidos/components/float-panel/float-panel.css index ac13d2ebe..e08a46606 100644 --- a/src/uix/eidos/components/float-panel/float-panel.css +++ b/src/uix/eidos/components/float-panel/float-panel.css @@ -3,6 +3,10 @@ topmost/behind. Eidos owns the surface chrome and the open/close animation. */ [data-float-panel-content] { + /* private: CONMUTADOR — the no-colour default reads the public + `--float-panel-accent`, a stamped colour the THM-2 palette forward + `--_float-panel-palette-border`, which admits no public on top + (float-panel.md §5, signed: «uno es el PUENTE DE PALETA, no es deuda»). */ --_float-panel-accent: var(--float-panel-accent); /* Open/close = motion-system preset (momento `--state`). The eidos wrapper writes `data-animation-style` (default `scale-fade`); the generated motion diff --git a/src/uix/eidos/components/form/form.css b/src/uix/eidos/components/form/form.css index 0485262ca..fab2fb5bb 100644 --- a/src/uix/eidos/components/form/form.css +++ b/src/uix/eidos/components/form/form.css @@ -1,8 +1,14 @@ [data-form] { --_form-gap: var(--form-gap-md); + /* private: CONMUTADOR-IDENTITY by variant — `panel` reads the public + `--form-panel-padding`, and the base `0` IS the identity of a bare form + (a form without a panel has no box). Same class `toolbar` §5 signed; + measured branch by branch 2026-08-25. */ --_form-padding: 0; --_form-border: transparent; + /* private: same CONMUTADOR-IDENTITY — a bare form has no surface. */ --_form-bg: transparent; + /* private: same CONMUTADOR-IDENTITY — a bare form has no elevation. */ --_form-shadow: none; --_form-action-height: var(--form-action-height-md); --_form-action-padding-inline: var(--form-action-padding-inline-md); @@ -101,6 +107,9 @@ --_form-action-bg-hover: var(--color-neutral-hover); --_form-action-border: var(--color-neutral-border); --_form-action-color: var(--color-neutral-text); + /* private: its ONE source is `none` — these array actions ARE flat, which is + identity, not a knob. Written inline the census would skip it as INERT; it + only reads as debt because the private exists to keep the rule single. */ --_form-action-shadow: none; display: inline-flex; diff --git a/src/uix/eidos/components/image/image.css b/src/uix/eidos/components/image/image.css index 2adec2455..71b7bc69e 100644 --- a/src/uix/eidos/components/image/image.css +++ b/src/uix/eidos/components/image/image.css @@ -176,6 +176,11 @@ * so the fallback rule below applies. */ [data-image][data-color], [data-image][data-color-custom] { + /* private: CONMUTADOR whose stamped branch IS the THM-2 palette bridge and + whose default is the neutral track `'neutral'` never stamps. A public on + top would let a theme pin it and kill the instance's `color=` + (image.md §5, signed 2026-08-23: «la fila que queda fuera es el puente de + paleta del placeholder de color, que no es deuda»). */ --_image-placeholder-color: var(--_image-palette-track); } diff --git a/src/uix/eidos/components/listbox/listbox.css b/src/uix/eidos/components/listbox/listbox.css index 19b243bbc..e19774078 100644 --- a/src/uix/eidos/components/listbox/listbox.css +++ b/src/uix/eidos/components/listbox/listbox.css @@ -30,10 +30,22 @@ * `--listbox-item-height` here would be the parallel vocabulary the layer's * own rules forbid, and it would outrank the layer for all of them. A theme * moves this rhythm through `--list-*`. */ + /* private: these five ADOPT the `list-surface` shared layer — the row rhythm + it resolves per size for every list surface. Minting `--listbox-item-*` + here would be the parallel vocabulary the layer forbids AND a token that + LIES: measured in the open menu, a theme writing the sibling's bridge or + `--list-item-height` in `:root` moves nothing (the layer declares on + `[data-list-surface][data-size]`, which always beats `:root`). A theme + moves this rhythm through the layer (listbox.md §5, signed 2026-08-22; + the layer's own recipe entry is next-features §13, ten components at once). */ --_listbox-item-height: var(--list-item-height); + /* private: same layer adoption (listbox.md §5). */ --_listbox-item-padding-inline: var(--list-item-padding-inline); + /* private: same layer adoption (listbox.md §5). */ --_listbox-item-padding-block: var(--list-item-padding-block); + /* private: same layer adoption (listbox.md §5). */ --_listbox-item-gap: var(--list-item-gap); + /* private: same layer adoption (listbox.md §5). */ --_listbox-item-font-size: var(--list-font-size); --_listbox-max-height: var(--listbox-max-block-size); diff --git a/src/uix/eidos/components/metrics/metrics.css b/src/uix/eidos/components/metrics/metrics.css index 8a33668bb..3b4bdbfa9 100644 --- a/src/uix/eidos/components/metrics/metrics.css +++ b/src/uix/eidos/components/metrics/metrics.css @@ -264,8 +264,13 @@ flex: none; color: inherit; /* Defaults for the no-data-color case; per-colour overrides below. */ + /* private: CONMUTADOR by variant — the no-colour default reads the public + `--metrics-icon-bg`, the stamped one the THM-2 palette forward + (metrics.md §5, signed: «ya derivaba de públicos y del puente de paleta»). */ --_metrics-icon-track: var(--metrics-icon-bg); --_metrics-icon-solid: var(--color-neutral-solid); + /* private: same CONMUTADOR — public `--metrics-icon-fg` by default, palette + forward when a colour is stamped (metrics.md §5, signed). */ --_metrics-icon-text: var(--metrics-icon-fg); --_metrics-icon-contrast: var(--color-neutral-contrast); --_metrics-icon-border: var(--color-neutral-border); diff --git a/src/uix/eidos/components/popover/popover.css b/src/uix/eidos/components/popover/popover.css index 09b245205..ed8e6d32c 100644 --- a/src/uix/eidos/components/popover/popover.css +++ b/src/uix/eidos/components/popover/popover.css @@ -133,6 +133,12 @@ ); box-sizing: border-box; + /* private: both terms resolve through per-instance VALUE CHANNELS one level + below the knob — `--_popover-content-width-override` and + `--_popover-match-anchor-width` are written per instance (soma / the + wrapper) and the last fallback of each chain is a public. A theme pinning + an override would break `matchAnchorWidth` and the `width` prop. Same class + `drawer` §5 signed for its eight `*-override` privates. */ inline-size: min(var(--_popover-width), var(--_popover-max-width), calc(100vw - var(--space-4))); min-inline-size: var(--_popover-content-min-width-override, auto); max-inline-size: min(var(--_popover-max-width), calc(100vw - var(--space-4))); diff --git a/src/uix/eidos/components/proof-of-human/proof-of-human.css b/src/uix/eidos/components/proof-of-human/proof-of-human.css index de0818fe8..38bed5aad 100644 --- a/src/uix/eidos/components/proof-of-human/proof-of-human.css +++ b/src/uix/eidos/components/proof-of-human/proof-of-human.css @@ -22,7 +22,19 @@ */ [data-proof-of-human-root] { + /* private: the THM-2 palette BRIDGE one level below the knob. The rule below + re-points this alias at `--_proof-of-human-palette-solid` under + `[data-color]` / `[data-color-custom]`, and the base here is VERBATIM what + the forward itself declares as that slot's base + (`:where([data-proof-of-human-root]) { --_proof-of-human-palette-solid: + var(--color-primary-solid) }`, generated/base.css) — so reading the bridge + directly would compute the same value and class `bridge`. Measured twice on + 2026-08-25 (source read + independent parse of both files). The alias + exists so the seventeen scene rules across three files name ONE accent; a + public on top would pin it and kill the instance's `color=`. */ --_proof-of-human-accent: var(--color-primary-solid); + /* private: the soft half of the same bridge alias, measured the same way + (forward base `var(--color-primary-element)`, identical). */ --_proof-of-human-accent-soft: var(--color-primary-element); --_proof-of-human-min-h: var(--proof-of-human-min-height); diff --git a/src/uix/eidos/components/skeleton/skeleton.css b/src/uix/eidos/components/skeleton/skeleton.css index eae717da8..f0930de25 100644 --- a/src/uix/eidos/components/skeleton/skeleton.css +++ b/src/uix/eidos/components/skeleton/skeleton.css @@ -12,8 +12,16 @@ /* Mix toward `neutral-solid` (which inverts luminance across modes) so the * placeholder stays a visible subtle grey in BOTH light and dark — the bare * `neutral-track` is ~white in light mode and vanishes on a light surface. */ + /* private: CONMUTADOR — two sources depending on `data-color`: this cross-mode + neutral mix, or the THM-2 palette forward. Flattening it would duplicate + every rule per colour (skeleton.md §5 pt.1, signed: «dos de esos privados + NO son deuda: son conmutadores»). */ --_skeleton-bg: color-mix(in oklab, var(--color-neutral-track), var(--color-neutral-solid) 16%); + /* private: the same CONMUTADOR for the shimmer highlight (skeleton.md §5 pt.1). */ --_skeleton-highlight: color-mix(in oklab, var(--color-neutral-track), var(--color-neutral-solid) 7%); + /* private: CONMUTADOR by SHAPE, not by size — `rect` reads the public + `--skeleton-radius`, `circle` is `50%`, which is shape IDENTITY (a circle + is round), so the only knob is the rectangle's (skeleton.md §5 pt.3). */ --_skeleton-radius: var(--skeleton-radius); display: block; diff --git a/src/uix/eidos/components/spinner/spinner.css b/src/uix/eidos/components/spinner/spinner.css index 76c9ea532..83ad9e4e9 100644 --- a/src/uix/eidos/components/spinner/spinner.css +++ b/src/uix/eidos/components/spinner/spinner.css @@ -15,7 +15,14 @@ `--spinner-size` and stays here — that is the shape of each variant. The ink and the track stay private: one variable, two sources, switched by `data-color='inherit'` below. */ + /* private: CONMUTADOR — one variable read in three places, two sources: the + THM-2 palette forward, or `currentColor` under `data-color='inherit'`, + which IS the identity of that value (take the surrounding ink). Same + pattern as `--_textarea-border-focus` (spinner.md §5 pt.3, signed: + «se queda privada»). */ --_spinner-color: var(--_spinner-palette-text); + /* private: same CONMUTADOR, with a different `color-mix` per branch (60 % on + the forward, 25 % on `inherit`) — spinner.md §5 pt.3, signed. */ --_spinner-track: color-mix(in srgb, var(--_spinner-palette-track) 60%, transparent); display: inline-flex; diff --git a/src/uix/eidos/components/surface/surface.css b/src/uix/eidos/components/surface/surface.css index b346de8d8..161b21ae2 100644 --- a/src/uix/eidos/components/surface/surface.css +++ b/src/uix/eidos/components/surface/surface.css @@ -15,7 +15,13 @@ [data-box][data-surface] { /* Variant slice = soft (default): the discreet tint. Content ink stays the global content color — the track tint is quiet enough (step 1). */ + /* private: CONMUTADOR by variant — both branches are the THM-2 palette bridge + (`track` on soft, `solid` on solid). A public on top would let a theme pin + it and kill the instance's `color=` (surface.md §5, signed). */ --_surface-bg: var(--_surface-palette-track); + /* private: same CONMUTADOR — `inherit` IS the soft identity (plain content + ink stays the global content color) and `solid` flips to the bridge's + contrast, or to a NAMED finish's authored ink (surface.md §5, signed). */ --_surface-fg: inherit; background: var(--_surface-bg); @@ -35,6 +41,10 @@ /* Gradient finish (§39) — solid canvas only, like every finish consumer. */ [data-box][data-surface][data-gradient][data-variant='solid'] { + /* private: the GENERATOR's gradient-finish ramp — `renderRecipeGradientFinish` + derives `--_surface-fill-finish` from this instance's palette slots, scaled + by the theme dial `--gradient-finish-lift` (gradient-as-color axis, CLOSED; + surface.md §5: «canal de valor del acabado de degradado»). */ background-image: var(--_surface-fill-finish); } diff --git a/src/uix/eidos/components/switch/switch.css b/src/uix/eidos/components/switch/switch.css index fef6d2d58..2ea2cec7c 100644 --- a/src/uix/eidos/components/switch/switch.css +++ b/src/uix/eidos/components/switch/switch.css @@ -6,8 +6,14 @@ /* `--_switch-palette-*` are private TSC tokens emitted by the * generator from `lib/recipes/base.ts > switch._palette-*`. * Per-color cascade is config-owned via `declarations[]`. */ + /* private: CONMUTADOR by state — `off` reads the public + `--switch-track-bg-off`, `checked` the THM-2 palette bridge + `--_switch-palette-solid`. One variable, two sources (switch.md §5, + signed: «los cuatro privados de §1.2 se quedan»). */ --_switch-track-bg: var(--switch-track-bg-off); --_switch-track-border: var(--switch-track-border-off); + /* private: same CONMUTADOR on the hover border — public off, bridge on + (switch.md §5, signed). */ --_switch-track-border-hover: var(--switch-hover-track-border-off); --_switch-thumb-offset: 0px; diff --git a/src/uix/eidos/components/textarea/textarea.css b/src/uix/eidos/components/textarea/textarea.css index 626cdfffe..a15f757af 100644 --- a/src/uix/eidos/components/textarea/textarea.css +++ b/src/uix/eidos/components/textarea/textarea.css @@ -7,6 +7,10 @@ would shadow the new tokens with the old values. `--_textarea-border-focus` stays private because it carries the per-color swap below: one variable, two sources (base token + palette forward). */ + /* private: CONMUTADOR — one variable, two sources: the public + `--textarea-focus-input-border` with no `data-color`, the THM-2 palette + forward with one. Flattening it would duplicate the focus rules per colour + (textarea.md §5, signed: «el swap es el forward THM-2 y se queda privado»). */ --_textarea-border-focus: var(--textarea-focus-input-border); /* CSS `resize` for the input. Driven by the morfo's `data-resize` enum (see overrides below); inherits down to `[data-textarea-input]`. NOT a diff --git a/src/uix/eidos/components/timeline/timeline.css b/src/uix/eidos/components/timeline/timeline.css index 799d0c32f..a71d3e586 100644 --- a/src/uix/eidos/components/timeline/timeline.css +++ b/src/uix/eidos/components/timeline/timeline.css @@ -68,10 +68,21 @@ /* ── Item: owns the hue (accent by default, intent overrides) ───────────── */ [data-timeline-item] { + /* private: the five `--_timeline-hue-*` are ONE CONMUTADOR — each `data-intent` + re-points them at the role slots and the default intent at the THM-2 palette + bridge, which admits no public without killing the instance's `color=`. + Flattening it would duplicate every consuming rule per intent, which is + exactly what the conmutador avoids (timeline.md §5, signed 2026-08-22: + «su 77 % es el TECHO … no hay contrato que escribir»; doctrine F2-B with + five precedents: textarea, spinner, skeleton, code, label). */ --_timeline-hue-solid: var(--_timeline-palette-solid); + /* private: same CONMUTADOR (timeline.md §5). */ --_timeline-hue-track: var(--_timeline-palette-track); + /* private: same CONMUTADOR (timeline.md §5). */ --_timeline-hue-border: var(--_timeline-palette-border); + /* private: same CONMUTADOR (timeline.md §5). */ --_timeline-hue-contrast: var(--_timeline-palette-contrast); + /* private: same CONMUTADOR (timeline.md §5). */ --_timeline-hue-text: var(--_timeline-palette-text); position: relative; min-inline-size: 0; diff --git a/src/uix/eidos/components/toolbar/toolbar.css b/src/uix/eidos/components/toolbar/toolbar.css index 07e94a537..b414c7a6a 100644 --- a/src/uix/eidos/components/toolbar/toolbar.css +++ b/src/uix/eidos/components/toolbar/toolbar.css @@ -6,8 +6,14 @@ --_toolbar-control-padding-inline: var(--toolbar-control-padding-inline-md); --_toolbar-control-gap: var(--toolbar-control-gap-md); --_toolbar-font-size: var(--toolbar-font-size-md); + /* private: CONMUTADOR-IDENTITY by variant — the base branch reads the public, + and the second value IS the identity of the variant, not a knob: a `ghost` + toolbar is the one WITHOUT a fill (toolbar.md §5, signed: «la clase + conmutador de F2-B, quinto caso»). */ --_toolbar-bg: var(--toolbar-bg); --_toolbar-border: var(--toolbar-border); + /* private: same CONMUTADOR-IDENTITY — `outline` and `ghost` ARE shadowless + (toolbar.md §5, signed). */ --_toolbar-shadow: var(--toolbar-shadow); display: inline-flex; diff --git a/src/uix/eidos/components/tooltip/tooltip.css b/src/uix/eidos/components/tooltip/tooltip.css index 0169eb566..30fad9649 100644 --- a/src/uix/eidos/components/tooltip/tooltip.css +++ b/src/uix/eidos/components/tooltip/tooltip.css @@ -11,6 +11,10 @@ --_tooltip-bg: var(--tooltip-bg); --_tooltip-border: var(--tooltip-border); --_tooltip-shadow: var(--tooltip-shadow); + /* private: per-instance VALUE CHANNEL — `--_tooltip-content-width-override` + and `--_tooltip-match-anchor-width` are written per instance and nobody + declares them; `auto` is the identity when neither is set. A theme pinning + one would break `matchAnchorWidth`. Same class `drawer` §5 signed. */ --_tooltip-width: var(--_tooltip-content-width-override, var(--_tooltip-match-anchor-width, auto)); --_tooltip-max-width: var(--_tooltip-content-max-width-override, var(--tooltip-max-width)); --_tooltip-max-height: var(