feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados

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: <razon> */ -> 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 <noreply@anthropic.com>
alpha-0.1-background
dev 1 month ago
parent e812d3ffd3
commit a74aeec7b6

@ -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: <reason> *​/` 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<string, string[]> = {
'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<string, string[]> = {
'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<string, string[]> = {
'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<string, string[]> = {
'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<string, string[]> = {
'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<string, string[]> = {
"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<string, string[]> = {
'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<string, string[]> = {
'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<string, string[]> = {
"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': [

@ -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: <reason> *​/` 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: <reason> *​/`
* (recipe-contract §3's valve, the same one `component-audit`
* honours) or a residue private annotated `/* private: <reason> *​/`
* (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: <reason> *​/` (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<string, string>([
*
* 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: <reason> *​/`
* (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<string>();
/** `--_{c}-x` declarations carrying `/* private: … *​/` — the valve, one level down. */
const signedPrivates = new Set<string>();
/** Knobs carrying the note on their OWN declaration — the valve, at the knob. */
const signedKnobs = new Set<Knob>();
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<KnobClass, 'global' | 'literal'>;
export type DebtClass = Extract<KnobClass, 'global' | 'literal' | 'private'>;
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;

@ -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);

@ -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);

@ -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);
}

@ -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);
}

@ -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;

@ -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);

@ -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);

@ -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

@ -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;

@ -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);
}

@ -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);

@ -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);

@ -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)));

@ -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);

@ -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;

@ -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;

@ -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);
}

@ -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;

@ -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

@ -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;

@ -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;

@ -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(

Loading…
Cancel
Save

Powered by TurnKey Linux.