synthesisui 0.16.309 → 0.16.311

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.
@@ -9,7 +9,7 @@ import { body, section, snippet } from "../output.js";
9
9
  import { findCollision, reactMajorOf, readInstalledConvention, readInstalledScheme, } from "../project-facts.js";
10
10
  import { fetchComponent, RegistryError } from "../registry.js";
11
11
  import { flavourResolver } from "../styles-flavour.js";
12
- import { inTheirTongue, tongueOf } from "../their-tongue.js";
12
+ import { inTheirTongue, projectTongue } from "../their-tongue.js";
13
13
  import { readCensus, unreadComment, unreadForComponent, } from "../unread-for-component.js";
14
14
  /**
15
15
  * Writes the shared `cn.ts` next to the components, built from THIS project's
@@ -98,7 +98,7 @@ export async function component(slug, name, opts) {
98
98
  * apagaria a cor. Um projeto de destino chega sem mapa no `.lock`, nada é traduzido, e a folha
99
99
  * continua sendo o caminho - o comando DIZ qual dos dois aconteceu.
100
100
  */
101
- const tongue = await tongueFor(root, slug);
101
+ const tongue = await projectTongue(root, slug);
102
102
  const spoken = tongue ? inTheirTongue(res.css, tongue) : null;
103
103
  const css = spoken ? spoken.css : res.css;
104
104
  /**
@@ -325,35 +325,3 @@ export async function component(slug, name, opts) {
325
325
  }
326
326
  console.log("");
327
327
  }
328
- /**
329
- * O VOCABULÁRIO DESTE REPOSITÓRIO, do que o `add` já deixou na pasta - ver `tongueOf`.
330
- *
331
- * `null` quando não há mapa: é a resposta de um projeto de destino, de um repositório que nunca
332
- * buildou, ou de um install feito por um CLI anterior a 0.16.290. Nos três casos nada é traduzido e
333
- * a folha instalada continua sendo o caminho, que é o comportamento de sempre.
334
- */
335
- async function tongueFor(root, slug) {
336
- const dir = join(root, "_synthesisui", "ds", slug);
337
- const lock = await readFile(join(dir, ".lock"), "utf8").catch(() => null);
338
- if (!lock)
339
- return null;
340
- let map = [];
341
- let version = 0;
342
- try {
343
- const parsed = JSON.parse(lock);
344
- map = parsed.tokenMap ?? [];
345
- version = parsed.version ?? 0;
346
- }
347
- catch {
348
- return null;
349
- }
350
- if (map.length === 0)
351
- return null;
352
- /**
353
- * O VALOR COMPILADO MORA NA PASTA DA VERSÃO - a folha da raiz é um re-export de uma linha, escrito
354
- * assim de propósito para que o `@import` dele nunca mude entre updates (ver `add.ts`). É de lá
355
- * que sai o literal para as variáveis que não têm nome dele.
356
- */
357
- const installed = await readFile(join(dir, `v${version}`, "tokens.css"), "utf8").catch(() => "");
358
- return tongueOf(map, installed);
359
- }
@@ -26,8 +26,12 @@ export async function gaps(opts) {
26
26
  return;
27
27
  }
28
28
  let ledger;
29
+ /** A régua única - ver `describeValueRuler`. Ausente num censo medido antes de 0.16.308. */
30
+ let values;
29
31
  try {
30
- ledger = JSON.parse(raw).ledger;
32
+ const census = JSON.parse(raw);
33
+ ledger = census.ledger;
34
+ values = census.values?.classes ?? undefined;
31
35
  }
32
36
  catch {
33
37
  console.log(body(`${path} is not readable JSON.`));
@@ -42,7 +46,7 @@ export async function gaps(opts) {
42
46
  console.log(body(`This measurement was written by a tool version that did not count style fragments yet, so the numbers do not exist rather than being zero. Run \`synthesisui import --dry\` again with ${opts.cli} to measure.`));
43
47
  return;
44
48
  }
45
- for (const line of describeTriage(triageLedger(ledger, opts.cli)))
49
+ for (const line of describeTriage(triageLedger(ledger, opts.cli), values))
46
50
  console.log(body(line));
47
51
  /**
48
52
  * E O QUE A CAMADA GLOBAL JÁ LEVOU - o desconto, sem o qual este comando repete a mentira que ele
@@ -1849,7 +1849,29 @@ export async function takeCensus(root, opts) {
1849
1849
  * AS LEIS DAS PÁGINAS, ditas onde a arquitetura já é dita - governam o sistema, não uma peça,
1850
1850
  * então `applies` é vazio e elas chegam a todo prompt. Ver `pageLaws`.
1851
1851
  */
1852
- const ledgerLines = describeLedger(ledger);
1852
+ /**
1853
+ * A CONTABILIDADE POR VALOR, contada AQUI porque o material é daqui: os tokens de classe que
1854
+ * vestem cada componente admitido (os nós do sketch e as camadas condicionais cruas), contra
1855
+ * os tokens que o CSS dele declara. Ver `Census.values` e `doctor/value-ledger.ts` - é a
1856
+ * reconciliação do item 11, e a soma fecha com `seen` por construção.
1857
+ *
1858
+ * ANTES do relatório do ledger de propósito: "quanto vocês entenderam?" tem UMA resposta (dono,
1859
+ * 26/08), e é esta régua - a mesma que a tela usa. A frase do ledger passa a liderar com ela.
1860
+ */
1861
+ const valueByComponent = {};
1862
+ for (const [lookName, look] of Object.entries(looks)) {
1863
+ const tokens = [];
1864
+ for (const node of look.sketch ?? []) {
1865
+ tokens.push(...(node.classes ?? "").split(/\s+/).filter(Boolean));
1866
+ }
1867
+ for (const layer of look.rawLayers ?? [])
1868
+ tokens.push(...layer.classes);
1869
+ if (tokens.length > 0) {
1870
+ valueByComponent[lookName] = accountClasses(tokens, declaredValues);
1871
+ }
1872
+ }
1873
+ const valuesTotal = sumAccounts(Object.values(valueByComponent));
1874
+ const ledgerLines = describeLedger(ledger, valuesTotal);
1853
1875
  if (ledgerLines.length > 0) {
1854
1876
  say("");
1855
1877
  say(section("Every style fragment, accounted for"));
@@ -2138,25 +2160,6 @@ export async function takeCensus(root, opts) {
2138
2160
  const target = c?.canonical ?? (c?.bucket === "exclusive" ? c.name : null);
2139
2161
  return target ? safePartName(target) : null;
2140
2162
  }, (pkg) => versions[pkg]);
2141
- /**
2142
- * A CONTABILIDADE POR VALOR, contada AQUI porque o material é daqui: os tokens de classe que
2143
- * vestem cada componente admitido (os nós do sketch e as camadas condicionais cruas), contra
2144
- * os tokens que o CSS dele declara. Ver `Census.values` e `doctor/value-ledger.ts` - é a
2145
- * reconciliação do item 11, e a soma fecha com `seen` por construção.
2146
- */
2147
- const valueByComponent = {};
2148
- for (const [lookName, look] of Object.entries(looks)) {
2149
- const tokens = [];
2150
- for (const node of look.sketch ?? []) {
2151
- tokens.push(...(node.classes ?? "").split(/\s+/).filter(Boolean));
2152
- }
2153
- for (const layer of look.rawLayers ?? [])
2154
- tokens.push(...layer.classes);
2155
- if (tokens.length > 0) {
2156
- valueByComponent[lookName] = accountClasses(tokens, declaredValues);
2157
- }
2158
- }
2159
- const valuesTotal = sumAccounts(Object.values(valueByComponent));
2160
2163
  return {
2161
2164
  census: 1,
2162
2165
  project: {
@@ -2,6 +2,7 @@ import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import { readProjectConfig, resolveRegistry } from "../config.js";
4
4
  import { fetchTemplate } from "../registry.js";
5
+ import { inTheirTongue, projectTongue } from "../their-tongue.js";
5
6
  /**
6
7
  * Materializes a whole page from a DS template into the project (hybrid
7
8
  * codegen-first): the server codegens deterministic files, we write them, and
@@ -27,14 +28,46 @@ export async function template(slug, name, opts) {
27
28
  const defaultDir = join("templates", asName ?? name);
28
29
  const pageRel = opts.out ?? join(defaultDir, pageFile.filename);
29
30
  const pageDir = dirname(join(root, pageRel));
31
+ /**
32
+ * NENHUMA MATERIALIZAÇÃO VAZA VOCABULÁRIO INTERNO (INV-VOLTA-02) - a mesma porta do
33
+ * `component`. Uma página inteira saía com `var(--ds-*)` cru enquanto um componente avulso
34
+ * falava a língua dele; a promessa é uma só. Sem mapa no `.lock`, nada é traduzido e a folha
35
+ * instalada continua sendo o caminho - e a saída diz qual dos dois aconteceu.
36
+ */
37
+ const tongue = await projectTongue(root, slug);
38
+ const speak = (code) => (tongue ? inTheirTongue(code, tongue) : null);
39
+ let named = 0;
40
+ let inlined = 0;
41
+ const still = new Set();
30
42
  await mkdir(pageDir, { recursive: true });
31
- await writeFile(join(root, pageRel), pageFile.code, "utf8");
43
+ const spokenPage = speak(pageFile.code);
44
+ if (spokenPage) {
45
+ named += spokenPage.named;
46
+ inlined += spokenPage.inlined;
47
+ for (const l of spokenPage.left)
48
+ still.add(l);
49
+ }
50
+ await writeFile(join(root, pageRel), spokenPage ? spokenPage.css : pageFile.code, "utf8");
32
51
  console.log(`✓ wrote ${pageRel} (${slug} v${generated.version})`);
33
52
  for (const f of siblings) {
34
53
  const rel = join(dirname(pageRel), f.filename);
35
- await writeFile(join(root, rel), f.code, "utf8");
54
+ const spoken = speak(f.code);
55
+ if (spoken) {
56
+ named += spoken.named;
57
+ inlined += spoken.inlined;
58
+ for (const l of spoken.left)
59
+ still.add(l);
60
+ }
61
+ await writeFile(join(root, rel), spoken ? spoken.css : f.code, "utf8");
36
62
  console.log(`✓ wrote ${rel}`);
37
63
  }
64
+ if (named > 0 || inlined > 0) {
65
+ console.log(` ${named} reference${named === 1 ? "" : "s"} now speak${named === 1 ? "s" : ""} the name YOUR code gives the value${inlined > 0 ? `, and ${inlined} carr${inlined === 1 ? "ies" : "y"} the value because your code names no token for it` : ""}.`);
66
+ if (still.size > 0) {
67
+ const sample = [...still].sort().slice(0, 3).join(", ");
68
+ console.log(` ${still.size} still point${still.size === 1 ? "s" : ""} at our stylesheet (${sample}${still.size > 3 ? ", …" : ""}), so tokens.css is still needed here.`);
69
+ }
70
+ }
38
71
  console.log("");
39
72
  console.log("Next steps:");
40
73
  console.log(` • use it in a route, e.g. ${join(config.pagesDir, "page.tsx")}:`);
@@ -20,6 +20,7 @@
20
20
  * decisão de escrever é de quem lê - a esteira opinando sobre o nosso backlog no terminal do
21
21
  * cliente é ela falando de um assunto que não é dela (dono, 05/08).
22
22
  */
23
+ import { describeValueRuler } from "./style-ledger.js";
23
24
  /**
24
25
  * QUEM É DONO DE CADA FORMA, hoje.
25
26
  *
@@ -151,8 +152,10 @@ export function triageLedger(ledger, triagedBy) {
151
152
  };
152
153
  }
153
154
  /** `0.16.133` é mais velha que `0.16.134`. Compara número por número, e o que não é número não
154
- * desempata nada - uma pré-release não é motivo para dizer que a medição está velha. */
155
- function isOlder(measured, current) {
155
+ * desempata nada - uma pré-release não é motivo para dizer que a medição está velha.
156
+ * Exportada porque é a TERCEIRA cópia desta pergunta (cli-version.ts e reinterpret.ts na web têm
157
+ * as outras) e a auditoria de 26/08 a encontrou fora do twin-drift - agora ele a amarra. */
158
+ export function isOlder(measured, current) {
156
159
  const parts = (v) => v.split(".").map((p) => Number.parseInt(p, 10));
157
160
  const a = parts(measured);
158
161
  const b = parts(current);
@@ -173,11 +176,23 @@ function isOlder(measured, current) {
173
176
  * do não interpretado ser o portão funcionando é a informação mais importante da tela, e ela vem
174
177
  * antes de qualquer lista de tarefa.
175
178
  */
176
- export function describeTriage(t) {
179
+ export function describeTriage(t,
180
+ /**
181
+ * A RÉGUA ÚNICA (ver `describeValueRuler`): quando o censo carrega o ledger de valores, o
182
+ * percentual que abre esta tela é o MESMO da tela do sistema e do fim do `import`. Era aqui
183
+ * que nascia a segunda resposta - "348 fragmentos, 36%" contra os 91% reais do codelevel
184
+ * (26/08) - e o agente do dono repassou a errada ao cliente.
185
+ */
186
+ values) {
177
187
  const unread = t.counted - t.interpreted;
178
- const lines = [
179
- `${t.counted} style fragments accounted for, ${t.interpreted} interpreted (${t.percent}%). The ${unread} below are sorted by what to do about them.`,
180
- ];
188
+ const lines = values
189
+ ? [
190
+ ...describeValueRuler(values),
191
+ `The ${unread} style fragments not interpreted are sorted below by what to do about them.`,
192
+ ]
193
+ : [
194
+ `${t.counted} style fragments accounted for, ${t.interpreted} interpreted (${t.percent}%). The ${unread} below are sorted by what to do about them.`,
195
+ ];
181
196
  if (t.stale) {
182
197
  lines.push("", `MEASURED BY CLI ${t.measuredBy}, TRIAGED BY ${t.triagedBy}. Re-run \`import\` before concluding anything from the numbers below - a gap here may already be closed, and the work already done.`);
183
198
  }
@@ -278,14 +278,27 @@ function blank() {
278
278
  * nomeia cada lacuna com o número do repo dela. Um percentual sem a contagem atrás é número de
279
279
  * marketing, e um zero sem motivo lê como falha nossa.
280
280
  */
281
- export function describeLedger(l) {
281
+ export function describeLedger(l,
282
+ /**
283
+ * A RÉGUA ÚNICA - o ledger de valores por declaração (ver `Census.values.classes`). Quando ela
284
+ * existe, "quanto vocês entenderam?" tem UMA resposta, e é esta: a mesma que a tela usa. O
285
+ * total de fragmentos continua dito - é a promessa de contabilidade -, mas sem um segundo
286
+ * percentual competindo (o agente do dono liderou com "36%" quando a leitura real era 91%,
287
+ * medido no codelevel em 26/08).
288
+ */
289
+ values) {
282
290
  const total = sum(l.counted);
283
291
  if (total === 0)
284
292
  return [];
285
293
  const read = sum(l.interpreted);
286
- const lines = [
287
- `${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.`,
288
- ];
294
+ const lines = values
295
+ ? [
296
+ ...describeValueRuler(values),
297
+ `And every one of the ${total} style fragments in your files is accounted for - the ${total - read} not interpreted are listed below with the file and line where each lives.`,
298
+ ]
299
+ : [
300
+ `${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.`,
301
+ ];
289
302
  /**
290
303
  * POR FORMA, ANTES DOS GRUPOS. O total responde "quanto"; esta tabela responde "de que jeito o
291
304
  * projeto escreve estilo", que é a pergunta que decide qual leitor vale construir. Sem ela, 56%
@@ -319,3 +332,16 @@ export function describeLedger(l) {
319
332
  function sum(counts) {
320
333
  return Object.values(counts).reduce((n, k) => n + k, 0);
321
334
  }
335
+ /**
336
+ * A frase da régua única, com todo denominador em palavras - `import` e `gaps` a imprimem
337
+ * IDÊNTICA, porque duas variações da mesma frase viram duas respostas na cabeça de quem lê.
338
+ * A conta é a da tela: decisões = vistas - estrutura; fechadas = interpretadas + respondidas.
339
+ */
340
+ export function describeValueRuler(values) {
341
+ const decisions = values.seen - values.structure;
342
+ const closed = values.interpreted + values.answered;
343
+ const percent = decisions > 0 ? Math.round((closed / decisions) * 100) : 0;
344
+ return [
345
+ `Of the ${values.seen} class declarations on your components, ${values.structure} are structure (layout plumbing, not design decisions). Of the ${decisions} design decisions, ${closed} are interpreted${values.answered > 0 ? ` (${values.answered} of them answered by you)` : ""} - ${percent}%.`,
346
+ ];
347
+ }
@@ -45,3 +45,43 @@ export function tongueOf(map, installedCss) {
45
45
  }
46
46
  return { names, values };
47
47
  }
48
+ /**
49
+ * NENHUMA MATERIALIZAÇÃO VAZA VOCABULÁRIO INTERNO - a regra da camada, num lugar só (dono, 26/08).
50
+ *
51
+ * Toda saída que escreve código no repositório dele passa por ESTA porta: carrega o vocabulário do
52
+ * projeto (`projectTongue`) e traduz (`inTheirTongue`). O `template` entregava páginas inteiras com
53
+ * `--ds-*` cru enquanto o `component` traduzia - a mesma promessa, duas implementações, uma delas
54
+ * ausente. Um mecanismo novo de materialização que chamar esta função já nasce coberto; um que não
55
+ * chamar é reprovado pelo gate (ver contracts/camada-6-volta.md, INV-VOLTA-02).
56
+ *
57
+ * `null` quando não há mapa: projeto de destino, repositório que nunca buildou, ou install anterior
58
+ * a 0.16.290. Nos três casos nada é traduzido e a folha instalada continua sendo o caminho - e quem
59
+ * chama DIZ qual dos dois aconteceu.
60
+ */
61
+ export async function projectTongue(root, slug) {
62
+ const { readFile } = await import("node:fs/promises");
63
+ const { join } = await import("node:path");
64
+ const dir = join(root, "_synthesisui", "ds", slug);
65
+ const lock = await readFile(join(dir, ".lock"), "utf8").catch(() => null);
66
+ if (!lock)
67
+ return null;
68
+ let map = [];
69
+ let version = 0;
70
+ try {
71
+ const parsed = JSON.parse(lock);
72
+ map = parsed.tokenMap ?? [];
73
+ version = parsed.version ?? 0;
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ if (map.length === 0)
79
+ return null;
80
+ /**
81
+ * O VALOR COMPILADO MORA NA PASTA DA VERSÃO - a folha da raiz é um re-export de uma linha, escrito
82
+ * assim de propósito para que o `@import` dele nunca mude entre updates (ver `add.ts`). É de lá
83
+ * que sai o literal para as variáveis que não têm nome dele.
84
+ */
85
+ const installed = await readFile(join(dir, `v${version}`, "tokens.css"), "utf8").catch(() => "");
86
+ return tongueOf(map, installed);
87
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.309",
3
+ "version": "0.16.311",
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": {