feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto

Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):

- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
  verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
  var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
  forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
  residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
  publico encima dejaria a un tema PINearlo y matar el color= por instancia
  (los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
  porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
  uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
  GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
  con su razon medida - adjudicar una clase "finish" seria inventarla sin
  firma).

Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).

Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).

Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alpha-0.1-background
dev 1 month ago
parent 3f9f9be6be
commit e812d3ffd3

@ -7,6 +7,8 @@
* node --import tsx/esm scripts/theming-census.ts --only tabs
* node --import tsx/esm scripts/theming-census.ts --report # docs/audit/theming/
* node --import tsx/esm scripts/theming-census.ts --names # naming grammar (D-TH.6)
* node --import tsx/esm scripts/theming-census.ts --residue [out.tsv]
* # privates still to adjudicate
*
* WHY. The recipe contract already says every recipe declares its knobs in
* `lib/recipes/base.ts` (recipe-contract §1, theming §6), and the R-4.x guards
@ -38,11 +40,19 @@
* 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.
* Reach = public / (public + private + global + literal). `system`, `exception`
* and `structural` are reported but excluded from the ratio on purpose: the
* first is themeable at the system level by design (recipe-contract §2), the
* second is a deviation the canon already accepted in writing (§3), and the
* third has nothing a theme could name in the first place.
* bridge — `var(--_{c}-palette-{slot})`: the private the THM-2 palette
* forward WRITES. The recipe reads the mechanism of the
* per-instance palette, not a name of its own.
* channel — `var(--_{c}-x, fallback)` where NOBODY declares `--_{c}-x`:
* soma or the wrapper writes it per INSTANCE (a %, a measured
* rect, the size a prop asks for). A theme must not reach it.
* Reach = public / (public + private + global + literal). `system`,
* `exception`, `structural`, `bridge` and `channel` are reported but excluded
* from the ratio on purpose: the first is themeable at the system level by
* design (recipe-contract §2), the second is a deviation the canon already
* accepted in writing (§3), the third has nothing a theme could name in the
* first place, and the last two are the two shapes of private the doctrine of
* this axis already adjudicated as NOT debt (see `PALETTE_BRIDGE` below).
*
* REPORT. `--report` writes the audit under `docs/audit/theming/`: a root
* `README.md` with the whole-catalogue view, and one `{c}.md` per component
@ -108,7 +118,9 @@ export type KnobClass =
| 'global'
| 'literal'
| 'exception'
| 'structural';
| 'structural'
| 'bridge'
| 'channel';
export interface Knob {
file: string;
@ -142,7 +154,11 @@ export interface CensusRow {
exception: number;
/** Knobs of a component whose 0 % is its nature — `STRUCTURAL_COMPONENTS`. */
structural: number;
/** public / (public + private + global + literal) — the other three out. */
/** Knobs reading the private the THM-2 palette forward writes — `PALETTE_BRIDGE`. */
bridge: number;
/** Knobs reading a private nobody declares: soma / the wrapper writes it per instance. */
channel: number;
/** public / (public + private + global + literal) — the other five out. */
reach: number;
contractKeys: number;
hasSize: boolean;
@ -256,6 +272,70 @@ const STRUCTURAL_COMPONENTS = new Map<string, string>([
]
]);
/**
* The two shapes of private this axis' doctrine already adjudicated as NOT
* debt — read off the EMITTER'S OUTPUT, never guessed from the name.
*
* The anchor is `src/uix/eidos/generated/base.css`: `generated-css.test.ts`
* keeps it byte-identical to `renderStaticCss()`, and `recipe-css-contract`
* already reads it to assert that every palette recipe emits its forward. So
* the question asked here is «what does the generator WRITE», not «what does
* this name look like» — and that difference is the whole point: adjudicating
* by name is the blindness this same axis was burned by.
*
* `bridge` — the THM-2 forward (`renderRecipePaletteForward` +
* `isPaletteSlotToken`, render-css.ts) writes `--_{c}-palette-{slot}` under
* the presence guard `[data-{c}]:where([data-color], [data-color-custom])`,
* with the component's OWN host default as the fallback. A recipe reading it
* consumes the MECHANISM of the per-instance palette, and it is reachable
* TWICE: through the shared `--palette-*` layer and through the component's
* own tone tokens (`--card-neutral-track`) — both public. A public on top
* would let a theme PIN it and kill the `color=` of every instance in
* silence; that is written, measured, in the §5 verdicts of `card`,
* `tags-input` and `avatar`.
* The test is the forward LINE, not a name shaped like one: only
* `--_{c}-palette-{slot}: var(--palette-{slot}, …)` counts, so a
* `--_c-palette-shadow` nobody forwards stays residue (same discipline as
* `isPaletteSlotToken`, which matches the slot const and not a loose regex).
*
* `channel` — the value channel: a private the recipe READS and NOBODY
* declares, neither the component's CSS nor the generator. Soma or the
* wrapper writes it per INSTANCE (`--_progress-value-pct`,
* `--_dialog-content-width-override`, `--_chronos-event-accent`). A theme
* must not reach it: pinning it breaks the behaviour, which is why `tabs`
* refused its §4.1 proposal in writing. Measured 2026-08-25: all 29 names in
* this class have a writer in a wrapper (`*.svelte`) or in soma.
*
* 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`).
*/
const GENERATED_CSS = readFileSync(resolve('src/uix/eidos/generated/base.css'), 'utf8').replace(
/\r\n/g,
'\n'
);
/** `--_{c}-palette-{slot}` AS THE FORWARD EMITS IT — the bridge, verbatim. */
const PALETTE_BRIDGE = new Set(
[
...GENERATED_CSS.matchAll(
/(--_[a-z0-9-]+-palette-([a-z-]+))\s*:\s*var\(\s*--palette-\2\s*[,)]/g
)
].map((m) => m[1])
);
/**
* Every private the GENERATOR declares — recipe keys, palette forwards and the
* gradient-finish ramp (`--_{c}-fill-finish`). A private in here is written by
* the build, so it is NOT a runtime channel however undeclared the CSS leaves
* it: without this test the four gradient-finish knobs read as «written by
* soma», which is the wrong reason for the right answer.
*/
const GENERATED_PRIVATES = new Set(
[...GENERATED_CSS.matchAll(/(?<=^\s*|;\s*)(--_[a-z0-9-]+)\s*:/gm)].map((m) => m[1])
);
function classify(
value: string,
pubNeedle: string,
@ -343,6 +423,8 @@ export function scanComponent(dir: string): Scan | null {
literal: 0,
exception: 0,
structural: 0,
bridge: 0,
channel: 0,
reach: 0,
contractKeys: contractKeysFor(dir),
hasSize: false
@ -477,8 +559,32 @@ export function scanComponent(dir: string): Scan | null {
k.klass = 'public';
}
// ── Third pass: the palette BRIDGE and the value CHANNEL ──
// The two shapes of private the doctrine adjudicated as not-debt, decided by
// what the GENERATOR writes (see `PALETTE_BRIDGE`). `every`, not `some`: a
// knob mixing a bridge with anything else stays residue, because the
// anything else is precisely what still needs adjudicating. A FOREIGN
// private (another component's `--_x-…`) can be a bridge — the forward is
// the same mechanism wherever it is emitted — but never a channel: this
// component's CSS is not where it would be declared.
const own = '--_' + dir + '-';
for (const k of knobs) {
if (k.klass !== 'private') continue;
const refs = [...k.value.matchAll(/var\(\s*(--_[a-z0-9-]+)/g)].map((m) => m[1]);
if (refs.length === 0) continue;
const klass: KnobClass | null = refs.every((r) => PALETTE_BRIDGE.has(r))
? 'bridge'
: refs.every((r) => r.startsWith(own) && !declBySource.has(r) && !GENERATED_PRIVATES.has(r))
? 'channel'
: null;
if (klass === null) continue;
row.private--;
row[klass]++;
k.klass = klass;
}
const themeable = row.public + row.private + row.global + row.literal;
row.knobs = themeable + row.system + row.structural;
row.knobs = themeable + row.system + row.structural + row.bridge + row.channel;
row.reach = themeable === 0 ? 1 : row.public / themeable;
const declared = new Set(privates.map((p) => p.name));
const privatesFromContract = [...usedPrivates]
@ -671,6 +777,106 @@ function reportDebt(only?: string, asJson = false) {
console.log(' the ledger matches the CSS exactly.');
}
// ─── `--residue`: the privates that are NEITHER bridge NOR channel ───────────
/**
* What the two mechanical signatures deliberately did NOT decide.
*
* The bridge and the channel are adjudicated by measurement; everything else
* that reads a private without deriving from a public is a per-KEY question —
* a switch whose branches are the identity of a variant (`toolbar`), a
* conmutador the TSC feeds (`avatar`), a private that reads a raw global in
* one branch (`proof-of-human`). Each row carries the knob AND where every
* 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).
*/
export interface ResidueRow {
component: string;
file: string;
line: number;
selector: string;
prop: string;
value: string;
/** One entry per private read: `--_x → file:line class | file:line class`. */
sources: string[];
}
export function residue(only?: string): ResidueRow[] {
const out: ResidueRow[] = [];
for (const dir of [...readdirSync(ROOT)].sort()) {
if (only && dir !== only) continue;
const scan = scanComponent(dir);
if (!scan) continue;
for (const k of scan.knobs) {
if (k.klass !== 'private') continue;
const refs = [...new Set([...k.value.matchAll(/var\(\s*(--_[a-z0-9-]+)/g)].map((m) => m[1]))];
out.push({
component: dir,
file: k.file,
line: k.line,
selector: k.selector,
prop: k.prop,
value: k.value,
sources: refs.map((r) => {
const decls = scan.privates.filter((p) => p.name === r);
if (decls.length > 0)
return r + ' → ' + decls.map((d) => `${d.file}:${d.line} ${d.source}`).join(' | ');
return r + ' → ' + (GENERATED_PRIVATES.has(r) ? 'generated/base.css' : 'undeclared');
})
});
}
}
return out;
}
function reportResidue(only?: string, out?: string, asJson = false) {
const rows = residue(only);
if (asJson) {
process.stdout.write(JSON.stringify(rows, null, '\t') + '\n');
return;
}
const per = new Map<string, number>();
for (const r of rows) per.set(r.component, (per.get(r.component) ?? 0) + 1);
const tsv =
['component', 'file:line', 'selector', 'property', 'value', 'sources'].join('\t') +
'\n' +
rows
.map((r) =>
[
r.component,
`${r.file}:${r.line}`,
r.selector,
r.prop,
r.value,
r.sources.join(' ;; ')
].join('\t')
)
.join('\n') +
'\n';
console.log(
`theming-census --residue — ${rows.length} private knob(s) in ${per.size} component(s): ` +
'neither the palette bridge nor a value channel, so each one is a decision.'
);
console.log(
' worst: ' +
[...per.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, 12)
.map(([c, n]) => `${c}:${n}`)
.join(' ')
);
if (out) {
writeFileSync(resolve(out), tsv, 'utf8');
console.log(` wrote ${out}`);
return;
}
process.stdout.write('\n' + tsv);
}
// ─── The proposal — derived from doctrine, never invented ────────────────────
/**
@ -1007,8 +1213,18 @@ function proposalSection(scan: Scan): string {
const c = scan.row.component;
// `structural` out with `public` and `system`: proposing a token for a knob
// whose §5 verdict says there is no contract to write would be inventing debt.
// `bridge` and `channel` out for the same reason, one floor down: a public
// over the palette forward or over a value channel is a token a theme could
// PIN, and pinning it kills the per-instance `color=` / the behaviour. The
// sheets of `card`, `tags-input` and `tabs` had to refuse exactly that
// proposal by hand — the classifier must not keep making it.
const targets = scan.knobs.filter(
(k) => k.klass !== 'public' && k.klass !== 'system' && k.klass !== 'structural'
(k) =>
k.klass !== 'public' &&
k.klass !== 'system' &&
k.klass !== 'structural' &&
k.klass !== 'bridge' &&
k.klass !== 'channel'
);
if (targets.length === 0)
return STRUCTURAL_COMPONENTS.has(c)
@ -1256,7 +1472,7 @@ function sheet(scan: Scan, today: string, verdict: string): string {
? 'ESTRUCTURAL: sin denominador que medir, y eso es la respuesta'
: `${row.public} de ${themeable} knobs por token público`
}
- **Knobs de apariencia**: ${row.knobs} — público ${row.public} · privado ${row.private} · global ${row.global} · literal ${row.literal} · sistema ${row.system} · excepción ${row.exception} · estructural ${row.structural} _(los tres últimos, fuera del ratio)_
- **Knobs de apariencia**: ${row.knobs} — público ${row.public} · privado ${row.private} · global ${row.global} · literal ${row.literal} · sistema ${row.system} · excepción ${row.exception} · estructural ${row.structural} · puente ${row.bridge} · canal ${row.channel} _(los cinco últimos, fuera del ratio)_
- **Contrato hoy** (\`lib/recipes/base.ts\`): ${contractLine}
- **Eje \`size\`**: ${row.hasSize ? 'sí' : 'no'} · **ficheros**: ${list(scan.files)}
@ -1266,7 +1482,11 @@ function sheet(scan: Scan, today: string, verdict: string): string {
${knobTable(by('global'), c)}
### 1.2 A través de un privado (${by('private').length})
${
row.bridge + row.channel > 0
? '\nSólo el **residuo**: el puente de paleta (§2-ter) y el canal de valor\n(§2-quater) salen aparte, porque no son deuda ni tienen nombre que acuñar.\n'
: ''
}
${knobTable(by('private'), c)}
### 1.3 Literales (${by('literal').length})
@ -1295,6 +1515,33 @@ lean, **no** para acuñarlos.
${knobTable(by('structural'), c)}`
: ''
}${
row.bridge > 0
? `
## 2-ter. Puente de paleta THM-2 (${row.bridge}) — fuera del ratio
La receta lee \`var(--_${c}-palette-{slot})\`, que **lo escribe el forward** de la
cascada de paleta bajo \`[data-${c}]:where([data-color], [data-color-custom])\`
(\`renderRecipePaletteForward\`, firma B′). Es el MECANISMO de la paleta por
instancia, y se alcanza **dos veces**: por la capa compartida \`--palette-*\` y
por los tonos públicos del propio componente. **No se acuña**: un público
encima dejaría que un tema lo fijara y matara en silencio el \`color=\` de cada
instancia (veredictos §5 de \`card\`, \`tags-input\`, \`avatar\`).
${knobTable(by('bridge'), c)}`
: ''
}${
row.channel > 0
? `
## 2-quater. Canal de valor (${row.channel}) — fuera del ratio
La receta lee un privado que **nadie declara** — ni su CSS ni el generador: lo
escribe soma o el envoltorio **por instancia** (un %, un rect medido, la talla
que pide una prop). Un tema no debe alcanzarlo: fijarlo rompe el
comportamiento, y por eso \`tabs\` rechazó por escrito esa misma propuesta.
${knobTable(by('channel'), c)}`
: ''
}
## 3. Privados de la receta — ¿de dónde sale su valor?
@ -1331,6 +1578,8 @@ function rootReadme(scans: Scan[], empties: EmptyScan[], today: string, verdict:
const noContract = rows
.filter((r) => r.contractKeys === 0 && !STRUCTURAL_COMPONENTS.has(r.component))
.map((r) => r.component);
const bridgeRows = rows.filter((r) => r.bridge > 0).sort((a, b) => b.bridge - a.bridge);
const channelRows = rows.filter((r) => r.channel > 0).sort((a, b) => b.channel - a.channel);
const worst = [...rows]
.map((r) => ({ c: r.component, out: r.private + r.global + r.literal, r }))
.sort((a, b) => b.out - a.out)
@ -1349,7 +1598,7 @@ function rootReadme(scans: Scan[], empties: EmptyScan[], today: string, verdict:
- **Medido**: ${today} · **${rows.length} recetas** con CSS + **${empties.length} componentes sin receta** = ${rows.length + empties.length} fichas, el árbol entero de \`eidos/components/\`
- **La pregunta**: ¿cuánto de la apariencia de cada componente puede cambiar un tema **sin tocar el sistema ni la receta**?
- **Alcance global**: **${pct(sum('public'), themeable)}** — ${sum('public')} de ${themeable} knobs pasan por un token público del componente
- **Reparto**: público ${sum('public')} · privado ${sum('private')} · global ${sum('global')} · literal ${sum('literal')} · sistema transversal ${sum('system')} · excepción firmada ${sum('exception')} · estructural ${sum('structural')} _(los tres últimos, fuera del ratio)_
- **Reparto**: público ${sum('public')} · privado ${sum('private')} · global ${sum('global')} · literal ${sum('literal')} · sistema transversal ${sum('system')} · excepción firmada ${sum('exception')} · estructural ${sum('structural')} · puente de paleta ${sum('bridge')} · canal de valor ${sum('channel')} _(los cinco últimos, fuera del ratio)_
- **Sin token público propio**: ${noContract.length} · **alcance < 20 %**: ${rows.filter((r) => r.reach < 0.2).length} · **alcance 100 %**: ${rows.filter((r) => r.reach === 1 && r.public > 0).length} · **con eje \`size\`**: ${rows.filter((r) => r.hasSize).length} · **estructurales**: ${structural.length}
## Cómo se lee
@ -1363,11 +1612,17 @@ function rootReadme(scans: Scan[], empties: EmptyScan[], today: string, verdict:
| \`system\` | sistemas transversales que la receta CONSUME por contrato (capa de estado, anillo de foco, planos de depth, motion, bandas z, opacidad, shape, floating-gap) | sí, **a nivel de sistema**, por diseño (recipe-contract §2) — fuera del ratio |
| \`exception\` | un literal con su anotación \`/* literal: <razón> */\` en la propia declaración | no hace falta: es la válvula de recipe-contract §3, una desviación ya firmada — fuera del ratio |
| \`structural\` | **todos** los knobs de un componente cuyo 0 % es su NATURALEZA y no su deuda: no hay contrato que escribir (lista firmada abajo) | no hace falta: no hay superficie que un tema pueda nombrar — fuera del ratio, y su fila lee \`strct\`, no \`0 %\` |
| \`bridge\` | \`var(--_{c}-palette-{slot})\`: el privado que **escribe el forward** de la cascada de paleta (THM-2) | sí, **por la paleta** — y también por los tonos públicos del componente; acuñar encima mataría el \`color=\` por instancia — fuera del ratio |
| \`channel\` | \`var(--_{c}-x, fallback)\` que **nadie declara**: lo escribe soma o el envoltorio por INSTANCIA (un %, un rect medido, la talla de una prop) | **no debe**: fijarlo desde un tema rompe el comportamiento — fuera del ratio |
**Alcance** = \`public / (public + private + global + literal)\`. \`system\`,
\`exception\` y \`structural\` quedan fuera del denominador: el primero es
tematizable a nivel de sistema por diseño, el segundo es una desviación que el
canon ya aceptó por escrito, y el tercero no tiene nada que un tema pueda nombrar.
\`exception\`, \`structural\`, \`bridge\` y \`channel\` quedan fuera del denominador: el
primero es tematizable a nivel de sistema por diseño, el segundo es una
desviación que el canon ya aceptó por escrito, el tercero no tiene nada que un
tema pueda nombrar, y los dos últimos son las dos formas de privado que la
doctrina de este eje ya adjudicó como NO deuda — el mecanismo de la paleta y el
valor de runtime. Lo que queda en \`private\` es el **residuo**: se adjudica clave
por clave (\`theming-census.ts --residue\`).
**Límites de la medida** (regex sobre el CSS; sobre-reporta, nunca infra-reporta):
una declaración con varios tokens se clasifica por la primera clase que casa
@ -1389,6 +1644,28 @@ sólo salen del **denominador**, igual que \`system\`. La lista firmada vive en
${structural.map((r) => `- [\`${r.component}\`](./${r.component}.md) (${r.structural}): ${STRUCTURAL_COMPONENTS.get(r.component)}`).join('\n')}
## Puente de paleta (${sum('bridge')} knobs) y canal de valor (${sum('channel')}) — privados que NO son deuda
Las dos formas de privado que la doctrina de este eje ya adjudicó, y que ahora
se distinguen **mecánicamente** — medidas contra el emisor, no adivinadas por el
nombre. La fuente es la SALIDA del generador (\`src/uix/eidos/generated/base.css\`,
que \`generated-css.test.ts\` mantiene idéntica a \`renderStaticCss()\`):
- **puente** — el nombre aparece en la CSS generada como la línea del forward
\`--_{c}-palette-{slot}: var(--palette-{slot}, …)\`. Un nombre con forma de
puente que nadie reenvía **no cuenta**.
- **canal** — el privado no lo declara nadie: ni la CSS del componente ni el
generador. Lo escribe soma o el envoltorio por instancia.
**Puente** (${bridgeRows.length}): ${bridgeRows.map((r) => `[\`${r.component}\`](./${r.component}.md) ${r.bridge}`).join(' · ')}
**Canal** (${channelRows.length}): ${channelRows.map((r) => `[\`${r.component}\`](./${r.component}.md) ${r.channel}`).join(' · ')}
Lo que NO cae en ninguna de las dos es el **residuo** (${sum('private')} knobs en ${rows.filter((r) => r.private > 0).length} componentes):
conmutadores por variante, privados que leen un global en una rama, escaleras
por talla sin público detrás. Se adjudican clave a clave — \`theming-census.ts
--residue\` los vuelca con las fuentes de cada declaración.
## Qué NO propone una ficha
La propuesta deriva nombres de la doctrina; **no la contradice**. Por eso una
@ -1438,12 +1715,12 @@ ${noContract.map((c) => `[\`${c}\`](./${c}.md)`).join(' · ')}
La columna «contrato» cuenta las claves **públicas** del bloque del componente.
| componente | alcance | knobs | público | privado | global | literal | sistema | estructural | contrato | size |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | :-: |
| componente | alcance | knobs | público | privado | global | literal | sistema | estructural | puente | canal | contrato | size |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | :-: |
${sorted
.map(
(r) =>
`| [${r.component}](./${r.component}.md) | ${reachOf(r)} | ${r.knobs} | ${r.public} | ${r.private} | ${r.global} | ${r.literal} | ${r.system} | ${r.structural} | ${r.contractKeys} | ${r.hasSize ? 'y' : '–'} |`
`| [${r.component}](./${r.component}.md) | ${reachOf(r)} | ${r.knobs} | ${r.public} | ${r.private} | ${r.global} | ${r.literal} | ${r.system} | ${r.structural} | ${r.bridge} | ${r.channel} | ${r.contractKeys} | ${r.hasSize ? 'y' : '–'} |`
)
.join('\n')}
@ -1892,6 +2169,11 @@ function main() {
else reportDebt(only, args.includes('--json'));
return;
}
if (args.includes('--residue')) {
const next = args[args.indexOf('--residue') + 1];
reportResidue(only, next && !next.startsWith('--') ? next : undefined, args.includes('--json'));
return;
}
if (args.includes('--report')) {
writeReport();
return;
@ -1905,7 +2187,7 @@ function main() {
const themeable = sum('public') + sum('private') + sum('global') + sum('literal');
console.log(`theming-census — ${rows.length} component recipe(s)`);
console.log(
` appearance knobs ${sum('knobs')} · public ${sum('public')} (${pct(sum('public'), themeable)} reach) · private ${sum('private')} · global ${sum('global')} · literal ${sum('literal')} · system ${sum('system')} · exception ${sum('exception')} · structural ${sum('structural')}`
` appearance knobs ${sum('knobs')} · public ${sum('public')} (${pct(sum('public'), themeable)} reach) · private ${sum('private')} · global ${sum('global')} · literal ${sum('literal')} · system ${sum('system')} · exception ${sum('exception')} · structural ${sum('structural')} · bridge ${sum('bridge')} · channel ${sum('channel')}`
);
console.log(
` no contract entry: ${rows.filter((r) => r.contractKeys === 0 && !STRUCTURAL_COMPONENTS.has(r.component)).length} · reach < 20%: ${rows.filter((r) => r.reach < 0.2).length} · reach = 100%: ${rows.filter((r) => r.reach === 1 && r.public > 0).length} · with data-size: ${rows.filter((r) => r.hasSize).length} · structural: ${rows.filter((r) => STRUCTURAL_COMPONENTS.has(r.component)).length}`
@ -1928,6 +2210,8 @@ function main() {
'excep'.padStart(7) +
'system'.padStart(8) +
'strct'.padStart(7) +
'bridge'.padStart(8) +
'chan'.padStart(6) +
'contract'.padStart(10) +
' size'
);
@ -1943,6 +2227,8 @@ function main() {
String(r.exception).padStart(7) +
String(r.system).padStart(8) +
String(r.structural).padStart(7) +
String(r.bridge).padStart(8) +
String(r.channel).padStart(6) +
String(r.contractKeys).padStart(10) +
(r.hasSize ? ' y' : ' -')
);

Loading…
Cancel
Save

Powered by TurnKey Linux.