synthesisui 0.16.132 → 0.16.133

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,7 @@ import { crosswalk, floorSize, isLibrary, observedRules, useLiveCatalogue, } fro
16
16
  import { keyframeOffsets, moduleImports, readModuleCss, readModuleUsage, transcribeModule, } from "../doctor/css-modules.js";
17
17
  import { dataContract } from "../doctor/data-contract.js";
18
18
  import { reconcile, scanDefinitions, } from "../doctor/definitions-scan.js";
19
+ import { fragmentsOfSource, fragmentsOfStylesheet, judgeFragments, } from "../doctor/fragments.js";
19
20
  import { describeConvention, describeRemainder, detectConventions, } from "../doctor/idiom.js";
20
21
  import { pageLaws } from "../doctor/page-laws.js";
21
22
  import { callSiteNode, placeLayers, } from "../doctor/place-layers.js";
@@ -25,6 +26,7 @@ import { parseSchemeBlocks } from "../doctor/scheme-blocks.js";
25
26
  import { describeSignals, emptySignals, finishSignals, readSignalsInto, } from "../doctor/signals.js";
26
27
  import { sketchOf } from "../doctor/sketch.js";
27
28
  import { describeForeign, readStyleIslands, } from "../doctor/style-island.js";
29
+ import { buildLedger, describeLedger, } from "../doctor/style-ledger.js";
28
30
  import { buildTable } from "../doctor/tokens.js";
29
31
  import { definitionSpan, readInlineStyle, rootClasses, rootTag, transcribe, } from "../doctor/transcribe.js";
30
32
  import { transcribeVariants } from "../doctor/variant-read.js";
@@ -126,12 +128,28 @@ async function siblingProjects(root) {
126
128
  * this" - could not be said, and we ended up rediscovering by colour distance
127
129
  * what their stylesheet had spelled out all along.
128
130
  */
129
- async function harvestOwnCss(root) {
131
+ async function harvestOwnCss(root,
132
+ /**
133
+ * O LEDGER TAMBÉM COME AQUI, e por um motivo de promessa: este é o único laço que vê TODO
134
+ * arquivo de estilo do escopo, inclusive o module de um componente que o portão não admite.
135
+ *
136
+ * O laço de componente lê o module do componente aceito, então sem isto 9 663 declarações
137
+ * continuariam invisíveis - e a promessa desta fatia é que nada é descartado em silêncio
138
+ * (dono, 04/08). O veredito de cada uma sai depois, em `judgeFragments`: pertencer a componente
139
+ * não admitido é um motivo, não um desaparecimento.
140
+ */
141
+ into) {
130
142
  let css = "";
131
143
  for await (const file of walkAll([root])) {
132
144
  if (!/\.(css|scss|sass|less)$/i.test(file))
133
145
  continue;
134
- css += `\n${await readFile(file, "utf8").catch(() => "")}`;
146
+ const body = await readFile(file, "utf8").catch(() => "");
147
+ css += `\n${body}`;
148
+ if (into && body)
149
+ into.push({
150
+ file: relative(root, file),
151
+ fragments: fragmentsOfStylesheet(relative(root, file), body, /\.module\./.test(file)),
152
+ });
135
153
  }
136
154
  return css;
137
155
  }
@@ -255,7 +273,9 @@ export async function takeCensus(root, opts) {
255
273
  */
256
274
  const fetched = await fetchCatalogue(opts?.registry);
257
275
  useLiveCatalogue(fetched.ok ? asCatalogueTable(fetched.index) : null);
258
- const css = await harvestOwnCss(root);
276
+ /** Todo arquivo de estilo do escopo, para o ledger - ver o parâmetro `into`. */
277
+ const styleFiles = [];
278
+ const css = await harvestOwnCss(root, styleFiles);
259
279
  const table = buildTable({ css, source: "yours" });
260
280
  const schemes = parseSchemeBlocks(css);
261
281
  const reports = [];
@@ -268,6 +288,10 @@ export async function takeCensus(root, opts) {
268
288
  const kindOf = new Map();
269
289
  /** A raiz e a composição de cada página, para as leis - ver `pageLaws`. */
270
290
  const pages = [];
291
+ /** Todo fragmento de estilo visto, para o ledger - ver `buildLedger`. */
292
+ const fragments = [];
293
+ /** Arquivos de estilo que pertencem a componente admitido - o veredito do `css` sai daqui. */
294
+ const admittedStyles = new Set();
271
295
  /** What the gate turned away, with the reason. Never dropped in silence. */
272
296
  const skips = [];
273
297
  // Kept for the reference check below, which needs every file at once: a name
@@ -313,9 +337,6 @@ export async function takeCensus(root, opts) {
313
337
  const projectsOf = new Map();
314
338
  /** Component → the axes its own `cva` declares. See `variant-read.ts`. */
315
339
  const variantAxes = new Map();
316
- /** How much came out of CSS Modules, for the coverage report. */
317
- let moduleFiles = 0;
318
- let moduleDeclarations = 0;
319
340
  /**
320
341
  * THE KEYFRAMES THE READING NEEDS, by name - and this was collected and dropped.
321
342
  *
@@ -455,6 +476,11 @@ export async function takeCensus(root, opts) {
455
476
  * numa lista feita para biblioteca. O look já nasce aqui, com o arquivo e a fonte na mão,
456
477
  * e é aqui que a leitura tem que se pendurar.
457
478
  */
479
+ /**
480
+ * TODO FRAGMENTO DE ESTILO DESTE ARQUIVO, cego ao portão - ver `fragmentsOfSource`. É o que
481
+ * permite o relatório dizer o que existe e não foi lido, em vez de só o que foi.
482
+ */
483
+ fragments.push(fragmentsOfSource(rel, src));
458
484
  for (const d of found) {
459
485
  sourceOf.set(d.name, { file: rel, source: src });
460
486
  /** O que a peça É, do portão - ver `GateVerdict.kind` e `CensusLook.kind`. */
@@ -544,12 +570,16 @@ export async function takeCensus(root, opts) {
544
570
  const usage = readModuleUsage(src, mod.local);
545
571
  if (usage.length === 0)
546
572
  continue;
573
+ /**
574
+ * ESTE MODULE PERTENCE A UM COMPONENTE ADMITIDO - o único fato que este laço tem e o
575
+ * ledger precisa. Os fragmentos dele vêm do `harvestOwnCss`, que vê todos os arquivos
576
+ * de estilo e não só os de componente aceito.
577
+ */
578
+ admittedStyles.add(relative(root, cssPath));
547
579
  const readModule = transcribeModule(readModuleCss(moduleCss), usage, declaredValues);
548
580
  if (readModule.read === 0)
549
581
  continue;
550
582
  fromModule = readModule;
551
- moduleFiles += 1;
552
- moduleDeclarations += readModule.read;
553
583
  for (const [name, frames] of Object.entries(readModule.keyframes)) {
554
584
  const offsets = keyframeOffsets(frames);
555
585
  if (!keyframes[name] && Object.keys(offsets).length > 0)
@@ -1152,6 +1182,13 @@ export async function takeCensus(root, opts) {
1152
1182
  Object.keys(l.parts ?? {}).length === 0 &&
1153
1183
  (l.layers ?? []).length === 0);
1154
1184
  }).length;
1185
+ /**
1186
+ * O LEDGER, com o veredito dado pelos mesmos leitores da esteira - ver `judgeFragments`.
1187
+ *
1188
+ * Montado aqui porque é o primeiro momento em que as três coisas existem juntas: os fragmentos
1189
+ * vistos, os tokens declarados, e quais arquivos de estilo pertencem a componente admitido.
1190
+ */
1191
+ const ledger = buildLedger(opts?.cli ?? "unknown", judgeFragments([...fragments.flat(), ...styleFiles.flatMap((s) => s.fragments)], declaredValues, admittedStyles));
1155
1192
  const coverageLines = describeCoverage(coverage, composition);
1156
1193
  if (coverageLines.length > 0) {
1157
1194
  console.log("");
@@ -1175,6 +1212,13 @@ export async function takeCensus(root, opts) {
1175
1212
  * AS LEIS DAS PÁGINAS, ditas onde a arquitetura já é dita - governam o sistema, não uma peça,
1176
1213
  * então `applies` é vazio e elas chegam a todo prompt. Ver `pageLaws`.
1177
1214
  */
1215
+ const ledgerLines = describeLedger(ledger);
1216
+ if (ledgerLines.length > 0) {
1217
+ console.log("");
1218
+ console.log(section("Every style fragment, accounted for"));
1219
+ for (const line of ledgerLines.slice(0, 24))
1220
+ console.log(body(line));
1221
+ }
1178
1222
  const pageRules = pageLaws(pages);
1179
1223
  if (pageRules.length > 0) {
1180
1224
  console.log("");
@@ -1416,6 +1460,14 @@ export async function takeCensus(root, opts) {
1416
1460
  observed: distinctValues(d),
1417
1461
  ...(components.length > 0 ? { components } : {}),
1418
1462
  ...(defined.length > 0 ? { defined } : {}),
1463
+ /**
1464
+ * TODO FRAGMENTO DE ESTILO, CONTABILIZADO - ver `StyleLedger`.
1465
+ *
1466
+ * Viaja com o censo porque é a plataforma que mostra o número ao cliente, e é a partir daqui
1467
+ * que a skill deste repositório descobre qual forma ainda não é lida e onde ela mora, sem
1468
+ * ninguém ter que reportar (dono, 04/08).
1469
+ */
1470
+ ledger,
1419
1471
  /**
1420
1472
  * THE SHAPE, AND THE RULE IT BECOMES.
1421
1473
  *
@@ -2476,6 +2528,7 @@ export async function runImport(opts) {
2476
2528
  census = await takeCensus(readFrom, {
2477
2529
  ...(usage.length > 0 ? { usage } : {}),
2478
2530
  ...(scope ? { scopeLabel: scope } : {}),
2531
+ ...(opts.cli ? { cli: opts.cli } : {}),
2479
2532
  });
2480
2533
  if (scope)
2481
2534
  census.scope = scope;
@@ -0,0 +1,219 @@
1
+ /**
2
+ * TODO FRAGMENTO QUE CARREGA ESTILO, com arquivo e linha.
3
+ *
4
+ * Esta é a metade que faltava para o ledger: a esteira lia estilo em cinco lugares diferentes e
5
+ * nenhum deles registrava o que tinha visto e não conseguido usar. O resultado é que "o que falta"
6
+ * era uma pergunta que só se respondia abrindo o repo do cliente à mão (dono, 04/08).
7
+ *
8
+ * A varredura aqui é DELIBERADAMENTE cega ao portão: ela vê o estilo de todo arquivo, inclusive de
9
+ * componente que não vai virar receita. O portão é uma decisão sobre a VITRINE, e usá-lo para
10
+ * decidir o que se mede é o que fez 9 663 declarações de module ficarem invisíveis.
11
+ *
12
+ * O VEREDITO NÃO É DADO AQUI. Esta função diz "vi este fragmento, desta forma, aqui"; quem sabe
13
+ * se ele foi lido é a interpretação, que roda depois. Juntar as duas coisas numa função seria
14
+ * repetir a mistura que a tabela de cobertura já paga: forma e veredito são fatos diferentes.
15
+ */
16
+ import { readInlineStyle, transcribe } from "./transcribe.js";
17
+ /** `className="…"` e `className={"…"}` - o literal puro. */
18
+ const CLASS_LITERAL = /className=(?:"([^"]{0,400})"|\{\s*"([^"]{0,400})"\s*\})/g;
19
+ /** `className={`… ${x} …`}` - metade literal, metade runtime. */
20
+ const CLASS_TEMPLATE = /className=\{\s*`([^`]{0,400})`\s*\}/g;
21
+ /** `className={algo}` sem literal nenhum: não existe na fonte. */
22
+ const CLASS_RUNTIME = /className=\{\s*([A-Za-z_$][\w$.[\]]{0,60})\s*\}/g;
23
+ /** Um literal dentro de `cn("…", …)` e amigos. */
24
+ const CALL_LITERAL = /\b(?:cn|clsx|classNames|cx|twMerge|twJoin|classcat)\(([^)]{0,600})\)/g;
25
+ const STRING_IN_CALL = /"([^"]{0,300})"|'([^']{0,300})'/g;
26
+ /** `style={{ … }}`. */
27
+ const INLINE = /style=\{\{([^}]{0,400})\}\}/g;
28
+ /** `sx={{ … }}`. */
29
+ const SX = /sx=\{\{([^}]{0,400})\}\}/g;
30
+ /** `styled.div\`…\`` e `styled(X)\`…\``. */
31
+ const STYLED = /styled[.(][^`]{0,60}`([^`]{0,400})`/g;
32
+ /**
33
+ * Um pedaço de linha de estilo e o que o termina: `{` faz do pedaço um seletor, `;` e `}` fazem
34
+ * dele uma declaração. Ler a linha inteira de uma vez perde `.card { padding: 8px }` numa linha só,
35
+ * e um coletor que promete não deixar nada invisível não pode escolher a formatação do arquivo.
36
+ */
37
+ const PIECES = /([^{};]*)([{};]|$)/g;
38
+ /** Uma declaração `prop: value`, já sem o terminador. */
39
+ const DECLARATION = /^(-{0,2}[a-zA-Z][\w-]*)\s*:\s*([^;{}]{1,200})$/;
40
+ /** Uma classe que só um literal pode ter: começa com letra minúscula. */
41
+ const LOOKS_LIKE_CLASS = /^[a-z[]/;
42
+ /**
43
+ * `${styles.card}` dentro de um template: uma classe de CSS module, cujo valor mora no arquivo de
44
+ * estilo - que a esteira lê. É a mesma regra que `CLASS_RUNTIME` já aplica a `className={styles.x}`.
45
+ */
46
+ const MODULE_REF = /\$\{\s*[A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*\s*\}/g;
47
+ /**
48
+ * Os fragmentos de um arquivo `.tsx`/`.jsx`, com a linha de cada um.
49
+ *
50
+ * `read` sai FALSO em todos: quem preenche o veredito é quem tem a interpretação em mão. As formas
51
+ * que nenhum leitor cobre (`sx`, `styled`, `runtime`) já saem com o motivo, porque nesses casos o
52
+ * motivo é a própria forma e não depende de leitura.
53
+ */
54
+ export function fragmentsOfSource(file, source) {
55
+ const out = [];
56
+ const at = (index) => source.slice(0, index).split("\n").length;
57
+ const push = (shape, index, text, reason) => {
58
+ const trimmed = text.trim();
59
+ if (!trimmed)
60
+ return;
61
+ out.push({
62
+ shape,
63
+ file,
64
+ line: at(index),
65
+ text: trimmed,
66
+ read: false,
67
+ ...(reason ? { reason } : {}),
68
+ });
69
+ };
70
+ for (const m of source.matchAll(CLASS_LITERAL))
71
+ push("class", m.index ?? 0, m[1] ?? m[2] ?? "");
72
+ for (const m of source.matchAll(CLASS_TEMPLATE))
73
+ push("template", m.index ?? 0, m[1] ?? "");
74
+ for (const m of source.matchAll(CLASS_RUNTIME)) {
75
+ /**
76
+ * `className={styles.content}` é uma classe de CSS module, que a esteira LÊ - o valor está no
77
+ * arquivo de estilo, não aqui. Só um identificador solto é genuinamente runtime.
78
+ */
79
+ const value = m[1] ?? "";
80
+ if (/^[A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*$/.test(value))
81
+ continue;
82
+ push("runtime", m.index ?? 0, `className={${value}}`, "computed");
83
+ }
84
+ /**
85
+ * OS LITERAIS DENTRO DE UMA CHAMADA. `cn("flex p-4", cond && "gap-2")` tem dois, e os dois são
86
+ * classe escrita à mão - a forma mais comum do repo dele (113 arquivos).
87
+ */
88
+ for (const call of source.matchAll(CALL_LITERAL)) {
89
+ const body = call[1] ?? "";
90
+ const base = (call.index ?? 0) + (call[0].length - body.length - 1);
91
+ for (const s of body.matchAll(STRING_IN_CALL)) {
92
+ const text = s[1] ?? s[2] ?? "";
93
+ if (!LOOKS_LIKE_CLASS.test(text))
94
+ continue;
95
+ push("class", base + (s.index ?? 0), text);
96
+ }
97
+ }
98
+ for (const m of source.matchAll(INLINE))
99
+ push("inline", m.index ?? 0, m[1] ?? "");
100
+ for (const m of source.matchAll(SX))
101
+ push("sx", m.index ?? 0, m[1] ?? "", "shape-not-read");
102
+ for (const m of source.matchAll(STYLED))
103
+ push("styled", m.index ?? 0, m[1] ?? "", "shape-not-read");
104
+ return out;
105
+ }
106
+ /**
107
+ * Os fragmentos de um arquivo de estilo: uma declaração é um fragmento.
108
+ *
109
+ * `scoped` diz se aquele arquivo é um CSS module. A distinção importa para o veredito e não para a
110
+ * contagem: um global com regra de CLASSE é forma que nenhum leitor cobre hoje - 399 declarações
111
+ * no repo dele, e a única lacuna que estava calada em vez de declarada (04/08).
112
+ */
113
+ export function fragmentsOfStylesheet(file, css, scoped) {
114
+ const out = [];
115
+ let selector = "";
116
+ for (const [i, raw] of css.split("\n").entries()) {
117
+ const line = raw.replace(/\/\*.*?\*\//g, "");
118
+ for (const piece of line.matchAll(PIECES)) {
119
+ const text = (piece[1] ?? "").trim();
120
+ if (piece[2] === "{") {
121
+ selector = text;
122
+ continue;
123
+ }
124
+ /**
125
+ * Sem terminador não há declaração: `padding: 8px` no fim de uma linha pode ser a primeira
126
+ * metade de um valor que continua na próxima.
127
+ */
128
+ if (!piece[2] || !text)
129
+ continue;
130
+ const m = DECLARATION.exec(text);
131
+ if (!m)
132
+ continue;
133
+ /**
134
+ * UM TOKEN NÃO É UM FRAGMENTO DE COMPONENTE. `--color-ocean-500: #…` é a paleta, e ela tem
135
+ * o seu próprio caminho e o seu próprio relatório; contá-la aqui inflaria o total com o que
136
+ * já está coberto em outro lugar.
137
+ */
138
+ if (m[1].startsWith("--"))
139
+ continue;
140
+ out.push({
141
+ shape: "css",
142
+ file,
143
+ line: i + 1,
144
+ text: `${selector ? `${selector} ` : ""}{ ${m[1]}: ${m[2].trim()} }`,
145
+ read: false,
146
+ ...(scoped ? {} : { reason: "shape-not-read" }),
147
+ });
148
+ }
149
+ }
150
+ return out;
151
+ }
152
+ /**
153
+ * O VEREDITO DE CADA FRAGMENTO, dado pelos MESMOS leitores que a esteira usa.
154
+ *
155
+ * Não é aproximação: para uma class string, "foi lida" é literalmente `transcribe` devolver ao
156
+ * menos uma declaração - a mesma chamada que produz o look. Um segundo julgamento, escrito à parte,
157
+ * seria a maneira mais rápida de o relatório e a receita começarem a discordar.
158
+ *
159
+ * `admitted` é o conjunto de arquivos de estilo que pertencem a componente que o escopo admite. É
160
+ * o que separa "esta forma não é lida" de "esta forma é lida e este componente não entra" - dois
161
+ * fatos diferentes que, somados num número só, fizeram 36% parecer defeito de leitura quando era
162
+ * o portão funcionando (dono, 04/08).
163
+ */
164
+ export function judgeFragments(seen, declared, admitted) {
165
+ return seen.map((f) => {
166
+ if (f.reason)
167
+ return f;
168
+ switch (f.shape) {
169
+ case "class":
170
+ case "template": {
171
+ /**
172
+ * UMA INTERPOLAÇÃO DE MODULE NÃO É UMA LACUNA. `` `${styles.card} ${styles.open}` `` é
173
+ * classe de CSS module, e o valor dela está no arquivo de estilo - que a esteira lê. São
174
+ * 262 dos 277 templates não lidos do dashboard dele (95%), e reportá-los como valor não
175
+ * lido inventa uma lacuna onde a captura está completa por outra forma. Um relatório com
176
+ * lacuna falsa custa o mesmo que um com lacuna escondida: manda alguém consertar o que
177
+ * não está quebrado (medido, 04/08).
178
+ */
179
+ const modules = f.text.match(MODULE_REF) ?? [];
180
+ const literal = f.text
181
+ .replace(MODULE_REF, " ")
182
+ .replace(/\$\{[^}]*\}/g, " ");
183
+ /**
184
+ * Sem literal nenhum: ou o module responde por tudo, ou o que sobrou é `${className}` -
185
+ * um valor que só existe em tempo de execução, e o motivo disso é `computed`, nunca
186
+ * "valor não lido". A esteira não viu valor algum para não ler.
187
+ */
188
+ if (f.shape === "template" && !/[a-z]/i.test(literal))
189
+ return modules.length > 0
190
+ ? { ...f, read: true }
191
+ : { ...f, read: false, reason: "computed" };
192
+ const t = transcribe(literal.split(/\s+/).filter(Boolean), declared);
193
+ const read = Object.keys(t.base).length +
194
+ Object.keys(t.dark).length +
195
+ Object.values(t.states ?? {}).reduce((n, b) => n + Object.keys(b).length, 0) >
196
+ 0;
197
+ return read
198
+ ? { ...f, read: true }
199
+ : { ...f, read: false, reason: "value-not-read" };
200
+ }
201
+ case "inline": {
202
+ const read = Object.keys(readInlineStyle(`style={{${f.text}}}`)).length > 0;
203
+ return read
204
+ ? { ...f, read: true }
205
+ : { ...f, read: false, reason: "computed" };
206
+ }
207
+ case "css":
208
+ return admitted.has(f.file)
209
+ ? { ...f, read: true }
210
+ : {
211
+ ...f,
212
+ read: false,
213
+ reason: "component-not-admitted",
214
+ };
215
+ default:
216
+ return { ...f, read: false, reason: "shape-not-read" };
217
+ }
218
+ });
219
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * O LEDGER DE ESTILO: todo fragmento que carrega aparência, contabilizado.
3
+ *
4
+ * A esteira sempre soube dizer o que ela LÊ. O que ela nunca soube dizer é o que ela viu e não
5
+ * leu, onde aquilo mora, e quanto disso existe - e sem isso não existe ciclo de conserto: alguém
6
+ * precisa reportar, e um cliente que importou uma vez e foi embora nunca reporta (dono, 04/08).
7
+ *
8
+ * Medido no dashboard dele: 20 541 fragmentos que carregam estilo, 3,0 MB se guardados verbatim -
9
+ * do mesmo tamanho da fonte inteira. Guardar tudo seria dobrar o censo para reter o que a
10
+ * interpretação já extraiu.
11
+ *
12
+ * ENTÃO A CONTABILIDADE É ESTA, e ela é o que "sem perda" significa aqui:
13
+ *
14
+ * contado todo fragmento entra num total, por forma. Nada é invisível.
15
+ * interpretado o valor dele já vive no look, que é de onde a receita lê
16
+ * não lido o TEXTO vem verbatim, com arquivo e linha, agrupado por forma e motivo
17
+ *
18
+ * Um fragmento interpretado não precisa do texto original, porque a gente tem a interpretação; um
19
+ * não lido só serve com o texto e o lugar. O resultado pesa quilobytes, é acionável linha por
20
+ * linha, e nunca guarda o arquivo inteiro de ninguém - que é a resposta que a gente quer poder
21
+ * dar, olhando o schema, quando um cliente perguntar se guardamos o código dele.
22
+ *
23
+ * O QUE ELE NÃO PROMETE: 100% de interpretação. `className={cx(getVariant(props))}` monta a
24
+ * classe em tempo de execução e ela não existe na fonte, em nenhum nível. A promessa é 100%
25
+ * CONTABILIZADO - todo fragmento ou interpretado ou listado com motivo, arquivo e linha - com a
26
+ * interpretação como um número que se move. Dizer 100% de cobertura existindo uma classe
27
+ * calculada em runtime seria exatamente a mentira que esta esteira existe para não contar.
28
+ */
29
+ const SHAPES = [
30
+ "class",
31
+ "template",
32
+ "inline",
33
+ "css",
34
+ "sx",
35
+ "styled",
36
+ "runtime",
37
+ ];
38
+ const BECAUSE = {
39
+ "shape-not-read": "no reader in this version understands this shape, so the styles it holds reach no recipe",
40
+ "value-not-read": "the shape is read and this value is not - a utility or property outside the scales this project declares",
41
+ computed: "the value is assembled at render time, so it is not in the source at any level and there is nothing to read",
42
+ "component-not-admitted": "read, and it belongs to a component this scope does not admit into the system - the styles exist and no recipe claims them",
43
+ };
44
+ /**
45
+ * O ledger de uma varredura, a partir dos fragmentos vistos.
46
+ *
47
+ * Puro: quem chama junta os fragmentos onde já abre os arquivos, e isto só conta e agrupa. O
48
+ * texto de um fragmento LIDO é descartado aqui de propósito - a interpretação dele já vive no
49
+ * look, e reter as duas cópias é o que dobraria o censo.
50
+ */
51
+ export function buildLedger(cli, seen) {
52
+ const counted = blank();
53
+ const interpreted = blank();
54
+ const groups = new Map();
55
+ for (const f of seen) {
56
+ counted[f.shape] += 1;
57
+ if (f.read) {
58
+ interpreted[f.shape] += 1;
59
+ continue;
60
+ }
61
+ const reason = f.reason ?? "shape-not-read";
62
+ const key = `${f.shape}:${reason}`;
63
+ const group = groups.get(key) ?? {
64
+ shape: f.shape,
65
+ reason,
66
+ uses: 0,
67
+ files: 0,
68
+ because: BECAUSE[reason],
69
+ examples: [],
70
+ seenFiles: new Set(),
71
+ };
72
+ group.uses += 1;
73
+ group.seenFiles.add(f.file);
74
+ if (group.examples.length < 3 &&
75
+ !group.examples.some((e) => e.file === f.file))
76
+ group.examples.push({
77
+ file: f.file,
78
+ line: f.line,
79
+ text: f.text.replace(/\s+/g, " ").trim().slice(0, 160),
80
+ });
81
+ groups.set(key, group);
82
+ }
83
+ return {
84
+ cli,
85
+ counted,
86
+ interpreted,
87
+ unread: [...groups.values()]
88
+ .map(({ seenFiles, ...group }) => ({ ...group, files: seenFiles.size }))
89
+ .sort((a, b) => b.uses - a.uses),
90
+ };
91
+ }
92
+ function blank() {
93
+ return Object.fromEntries(SHAPES.map((s) => [s, 0]));
94
+ }
95
+ /**
96
+ * O relatório, na ordem em que uma pessoa decide se aquilo a incomoda.
97
+ *
98
+ * Lidera com o que foi CONTABILIZADO, porque é a única promessa que a esteira pode cumprir, e
99
+ * nomeia cada lacuna com o número do repo dela. Um percentual sem a contagem atrás é número de
100
+ * marketing, e um zero sem motivo lê como falha nossa.
101
+ */
102
+ export function describeLedger(l) {
103
+ const total = sum(l.counted);
104
+ if (total === 0)
105
+ return [];
106
+ const read = sum(l.interpreted);
107
+ const lines = [
108
+ `${total} style fragments in your files, and every one is accounted for: ${read} interpreted (${Math.round((read / total) * 100)}%), ${total - read} listed below with the file and line where each lives.`,
109
+ ];
110
+ for (const g of l.unread) {
111
+ lines.push(` ${g.uses} ${g.shape}${g.uses === 1 ? "" : "s"} in ${g.files} file${g.files === 1 ? "" : "s"} - ${g.because}`);
112
+ for (const e of g.examples)
113
+ lines.push(` ${e.file}:${e.line} ${e.text.slice(0, 80)}`);
114
+ }
115
+ return lines;
116
+ }
117
+ function sum(counts) {
118
+ return Object.values(counts).reduce((n, k) => n + k, 0);
119
+ }
package/dist/index.js CHANGED
@@ -180,6 +180,8 @@ async function main() {
180
180
  // The census is arithmetic over their files; --dry keeps it on disk.
181
181
  await runImport({
182
182
  root: dir,
183
+ /** A versão que mediu, para o ledger - ver `StyleLedger.cli`. */
184
+ cli: CLI_VERSION,
183
185
  dry: flags.dry === true,
184
186
  census: typeof flags.census === "string" ? flags.census : undefined,
185
187
  name: typeof flags.name === "string" ? flags.name : undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.132",
3
+ "version": "0.16.133",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {