synthesisui 0.16.272 → 0.16.274

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.
@@ -608,8 +608,12 @@ versionOf) {
608
608
  * one is a hole somebody has to explain.
609
609
  */
610
610
  const expects = EXPECTS[lower];
611
+ /**
612
+ * E O MESMO ÍNDICE NO SLOT DE UM NÓ INTERNO, pela mesma razão: `measuredAt` só alcança um nó que
613
+ * saiba de onde veio. Aqui o `at` é o do PRÓPRIO nó, porque é o conteúdo dele que o slot ocupa.
614
+ */
611
615
  const withSlot = node.slot
612
- ? [...inside, { as: "slot", ...(expects ? { expects } : {}) }]
616
+ ? [...inside, { as: "slot", at: index, ...(expects ? { expects } : {}) }]
613
617
  : inside;
614
618
  if (withSlot.length > 0) {
615
619
  built.children = withSlot;
@@ -655,7 +659,31 @@ versionOf) {
655
659
  const rootNode = sketch[top];
656
660
  if (rootNode?.slot) {
657
661
  const only = EXPECTS[String(rootNode.tag ?? "").toLowerCase()];
658
- read.push({ as: "slot", ...(only ? { expects: only } : {}) });
662
+ /**
663
+ * O ÍNDICE DA RAIZ VIAJA COM O SLOT - e sem ele TODO fato de call-site da raiz sumia.
664
+ *
665
+ * `measuredAt` (anatomy-read.ts) começa com `if (typeof node.at !== "number") return out` e só
666
+ * depois lê `passes`, `contentFrom`, `behaviourProps`, `repeats` e `text`. Um nó sintetizado sem
667
+ * `at` não tem como recuperar nada disso.
668
+ *
669
+ * Medido em 21/08 no repositório do dono, e o que se perdia era ACESSIBILIDADE QUE ELE ESCREVEU:
670
+ *
671
+ * Toast <div role="status" aria-live="polite">
672
+ * TooltipContent <span role="tooltip">
673
+ * ThemeToggle <button type="button" role="switch" aria-label=…>
674
+ * Card <Tag onMouseMove=… onMouseLeave=…> (o tilt inteiro)
675
+ *
676
+ * A lei 10 diz que acessibilidade SUPOSTA é pior que nenhuma. Isto era o inverso e é pior ainda:
677
+ * ele escreveu, o censo colheu, e a receita entregava uma div muda ao agente dele.
678
+ *
679
+ * A raiz continua consumida - ela É o componente. O que muda é que o fato dela deixa de morrer com
680
+ * ela: viaja no slot que representa o conteúdo dela, que é o nó que sobra da raiz.
681
+ */
682
+ read.push({
683
+ as: "slot",
684
+ at: top,
685
+ ...(only ? { expects: only } : {}),
686
+ });
659
687
  }
660
688
  return { read, notes, libraries };
661
689
  }
@@ -42,7 +42,9 @@ import { mergeCensus } from "../merge-census.js";
42
42
  import { claimName } from "../name-claim.js";
43
43
  import { namingQueue } from "../naming-queue.js";
44
44
  import { body, paint, section } from "../output.js";
45
+ import { outsideScope } from "../outside-scope.js";
45
46
  import { phase, startProgress } from "../progress.js";
47
+ import { repoStateOf } from "../repo-state.js";
46
48
  import { detectStack, resolveDeps, stackVersions } from "../stack.js";
47
49
  import { placeInWorkspace } from "../workspace-place.js";
48
50
  import { walk, walkAll } from "./doctor.js";
@@ -380,6 +382,17 @@ export async function takeCensus(root, opts) {
380
382
  */
381
383
  const say = opts?.quiet ? () => { } : (line) => console.log(line);
382
384
  const scopeLabel = opts?.scopeLabel;
385
+ /**
386
+ * O QUE O ESCOPO DEIXOU DE FORA, medido no sistema de arquivos - ver `outside-scope.ts`.
387
+ *
388
+ * Os caminhos que o leitor REALMENTE olhou são o escopo mais os de uso: `--scope packages/ui
389
+ * --usage apps/web` lê os dois, então nenhum dos dois é "fora". Sem `scopeLabel` a função devolve
390
+ * `null` por conta, porque aí ele apontou para a raiz.
391
+ */
392
+ const outside = await outsideScope(root, [
393
+ ...(scopeLabel ? [scopeLabel] : []),
394
+ ...(opts?.usage ?? []).map((u) => u.label),
395
+ ]).catch(() => null);
383
396
  /**
384
397
  * THE LIVE CATALOGUE, once, before anything is matched.
385
398
  *
@@ -2150,6 +2163,15 @@ export async function takeCensus(root, opts) {
2150
2163
  * acima o tempo todo, gravado por este mesmo objeto - os dois chegam juntos e só um sobrevivia.
2151
2164
  */
2152
2165
  ...(scopeLabel ? { scope: scopeLabel } : {}),
2166
+ /**
2167
+ * O DENOMINADOR DO FORA - ver `outside-scope.ts`.
2168
+ *
2169
+ * `reachability` e `coverage` contam o dentro. Esta é a única linha que diz o que o escopo
2170
+ * EXCLUIU, e ela existe porque a garantia honesta sobre o que ninguém abriu não é "leu tudo" - é
2171
+ * "sabe exatamente o que não abriu". `null` quando não há escopo: aí o leitor olhou a raiz e a
2172
+ * categoria não existe.
2173
+ */
2174
+ ...(outside ? { outsideScope: outside } : {}),
2153
2175
  ...((opts?.usage ?? []).length > 0
2154
2176
  ? { usage: (opts?.usage ?? []).map((u) => u.label) }
2155
2177
  : {}),
@@ -3667,6 +3689,10 @@ export async function runImport(opts) {
3667
3689
  console.log("");
3668
3690
  return;
3669
3691
  }
3692
+ /** O que a máquina dele fez até agora - ver o corpo do POST abaixo. */
3693
+ const repoAtImport = opts.cli
3694
+ ? await repoStateOf(root, "", opts.cli).catch(() => null)
3695
+ : null;
3670
3696
  phase(3, 3, "Sending v1 and the v2 proposal");
3671
3697
  const res = await fetch(`${base}/api/onboarding/import`, {
3672
3698
  method: "POST",
@@ -3674,10 +3700,26 @@ export async function runImport(opts) {
3674
3700
  "content-type": "application/json",
3675
3701
  Authorization: `Bearer ${token}`,
3676
3702
  },
3703
+ /**
3704
+ * O ESTADO DO REPO VAI NO IMPORT TAMBÉM - e não só no `sync`.
3705
+ *
3706
+ * Medido em 21/08: `repo_state` era NULL no sistema que acabou de nascer, porque este corpo tinha
3707
+ * três campos e o do `sync` tem seis. A consequência é que TRÊS classes de causa ficam cegas num
3708
+ * import - `command-failed`, `command-flooded` e `command-skipped-check` leem `repo.runs`, e sem
3709
+ * ele o cliente roda, algo quebra no comando, e a plataforma não sabe.
3710
+ *
3711
+ * O import É a primeira sincronização: quando ele roda, o `ledger.jsonl` já tem as execuções
3712
+ * anteriores no disco - 16 delas, no caso medido, e zero enviadas.
3713
+ *
3714
+ * O SLUG AINDA NÃO EXISTE aqui, e é por isso que ele vai vazio: `repoStateOf` usa o slug só para
3715
+ * ler `_synthesisui/ds/<slug>/.lock`, que num import não existe de qualquer forma. Todo campo do
3716
+ * payload é opcional, então o que falta simplesmente não viaja - em vez de o conjunto todo faltar.
3717
+ */
3677
3718
  body: JSON.stringify({
3678
3719
  census,
3679
3720
  name: chosen,
3680
3721
  ...(opts.group ? { group: opts.group } : {}),
3722
+ ...(repoAtImport ? { repo: repoAtImport } : {}),
3681
3723
  }),
3682
3724
  }).catch(() => null);
3683
3725
  if (!res || !res.ok) {
package/dist/index.js CHANGED
@@ -640,7 +640,15 @@ function scrub(message) {
640
640
  .replace(/(?:\.{0,2}\/)[^\s'"`)]{2,}/g, "<path>")
641
641
  .trim();
642
642
  }
643
- async function recordRun(command, started, ok, error, bytes) {
643
+ async function recordRun(command, started, ok, error,
644
+ /**
645
+ * O QUE A SAÍDA MEDIU: quantos bytes, e a LINHA MAIS LARGA.
646
+ *
647
+ * A largura é o eixo novo. Em 21/08 a tabela do import saiu ilegível na tela do dono com 27 947
648
+ * bytes - abaixo de qualquer teto de inundação - porque o problema nunca foi tamanho, foi uma linha
649
+ * que não cabe.
650
+ */
651
+ written) {
644
652
  try {
645
653
  const root = resolve(typeof parseFlags(process.argv.slice(2)).flags.dir === "string"
646
654
  ? parseFlags(process.argv.slice(2)).flags.dir
@@ -681,7 +689,22 @@ async function recordRun(command, started, ok, error, bytes) {
681
689
  ms: Date.now() - started,
682
690
  /** Só a primeira linha, cortada: uma stack carrega caminhos do projeto de alguém. */
683
691
  ...(message ? { error: message.split("\n")[0].slice(0, 200) } : {}),
684
- ...(typeof bytes === "number" && bytes > 0 ? { bytes } : {}),
692
+ ...(written && written.bytes > 0 ? { bytes: written.bytes } : {}),
693
+ /**
694
+ * A LARGURA VIAJA COM A DO TERMINAL, e não sozinha.
695
+ *
696
+ * `widest: 180` não diz nada; `widest: 180` num terminal de 120 diz que sessenta colunas foram
697
+ * redesenhadas em cima de si mesmas. Sem `columns` a plataforma teria que supor uma largura, e
698
+ * supor aqui é o que faz um alarme falso em toda máquina com janela grande.
699
+ */
700
+ ...(written && written.widest > 0
701
+ ? {
702
+ widest: written.widest,
703
+ ...(process.stdout.columns
704
+ ? { columns: process.stdout.columns }
705
+ : {}),
706
+ }
707
+ : {}),
685
708
  ...(frame ? { where: frame } : {}),
686
709
  });
687
710
  }
@@ -706,23 +729,44 @@ async function recordRun(command, started, ok, error, bytes) {
706
729
  function countingStdout() {
707
730
  const original = process.stdout.write.bind(process.stdout);
708
731
  let bytes = 0;
732
+ let widest = 0;
733
+ /**
734
+ * E A LINHA MAIS LARGA, medida junto - o eixo que nunca foi medido.
735
+ *
736
+ * Em 21/08 a tabela de fechamento do import saiu ILEGÍVEL na tela do dono: uma célula sobrescrevendo
737
+ * a outra, palavras cortadas no meio (`"19 com os eixos declaas partes aninhadas"`). A saída tinha
738
+ * 27 947 bytes - bem abaixo do teto de inundação de 64 KB -, então nenhuma medição nossa viu nada.
739
+ *
740
+ * Bytes não é a pergunta. A pergunta é se a linha CABE. Comparar a mais larga com
741
+ * `process.stdout.columns` transforma "a tabela quebrou" num número que a plataforma recebe, e é a
742
+ * primeira coisa que a esteira mede sobre a PRÓPRIA saída em vez de sobre o repositório dele.
743
+ *
744
+ * Códigos ANSI são retirados antes de contar: cor não ocupa coluna, e contá-la acusaria toda linha
745
+ * pintada.
746
+ */
709
747
  process.stdout.write = ((chunk, ...rest) => {
710
- bytes +=
711
- typeof chunk === "string"
712
- ? Buffer.byteLength(chunk)
713
- : Buffer.isBuffer(chunk)
714
- ? chunk.length
715
- : 0;
748
+ const text = typeof chunk === "string"
749
+ ? chunk
750
+ : Buffer.isBuffer(chunk)
751
+ ? chunk.toString("utf8")
752
+ : "";
753
+ bytes += Buffer.byteLength(text);
754
+ for (const line of text.split("\n")) {
755
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI é exatamente o que se retira
756
+ const visible = line.replace(/\u001b\[[0-9;]*m/g, "");
757
+ if (visible.length > widest)
758
+ widest = visible.length;
759
+ }
716
760
  return original(chunk, ...rest);
717
761
  });
718
- return () => bytes;
762
+ return () => ({ bytes, widest });
719
763
  }
720
764
  async function guarded() {
721
765
  const started = Date.now();
722
766
  const command = parseFlags(process.argv.slice(2)).positionals[0] ?? "";
723
767
  /** `help` e a invocação vazia não são trabalho sobre um projeto, e registrá-las seria só ruído. */
724
768
  const worth = command && command !== "help";
725
- const written = worth ? countingStdout() : () => 0;
769
+ const written = worth ? countingStdout() : () => ({ bytes: 0, widest: 0 });
726
770
  try {
727
771
  await main();
728
772
  if (worth)
@@ -253,7 +253,7 @@ export const CHECKER_SINCE = "0.16.250";
253
253
  * publicado antes deste código existir, e uma marca nele calaria o aviso para quem o instalou. Mesma
254
254
  * lição de algumas horas antes, na mesma sessão.
255
255
  */
256
- export const READER_SINCE = "0.16.271";
256
+ export const READER_SINCE = "0.16.274";
257
257
  /**
258
258
  * O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
259
259
  *
@@ -0,0 +1,103 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { join, relative, sep } from "node:path";
3
+ /**
4
+ * O QUE O ESCOPO EXCLUIU - o único denominador que a esteira não tinha.
5
+ *
6
+ * O QUÊ. `census.reachability` diz *"22 de 22 arquivos alcançados"* e `census.coverage` diz *"60
7
+ * componentes, 59 lidos"*. As duas contas são honestas e as duas são sobre o DENTRO: quando alguém
8
+ * roda `--scope packages/ui`, tudo fora dali é invisível por construção, e nada conta o que ficou
9
+ * fora.
10
+ *
11
+ * POR QUÊ. O dono perguntou, com razão, se a gente consegue garantir que nenhuma informação dele se
12
+ * perde. Para tudo o que o leitor TOCA a resposta agora é sim, com portão. Para o que ele nunca abriu,
13
+ * a resposta honesta não é "sim" - é *"a gente sabe exatamente o que não abriu, e diz"*. É a única
14
+ * garantia que a física permite: não se conta o que não se olhou, mas se conta o que se deixou de
15
+ * olhar.
16
+ *
17
+ * COMO. Contagem de sistema de arquivos, determinística e barata. Nenhum parse: só quantos arquivos
18
+ * com a extensão do leitor existem fora do escopo, e em que diretórios de topo.
19
+ *
20
+ * O QUE ISTO NÃO PROMETE: dizer o que existe DENTRO daqueles arquivos. Isso exigiria lê-los, que é
21
+ * exatamente o que o escopo pediu para não fazer. A linha diz o tamanho da porta fechada, não o que
22
+ * há atrás dela - e é por isso que ela é uma oferta ("aponte para cá também"), nunca um alarme.
23
+ */
24
+ /** As extensões que os leitores da esteira realmente abrem. */
25
+ const READ = new Set([".tsx", ".jsx", ".ts", ".js", ".css", ".vue", ".svelte"]);
26
+ /** Pastas que nenhum cliente quer contadas - e a lista é sobre RUÍDO, não sobre leitura. */
27
+ const SKIP = new Set([
28
+ "node_modules",
29
+ "dist",
30
+ "build",
31
+ "out",
32
+ ".next",
33
+ ".turbo",
34
+ ".git",
35
+ "coverage",
36
+ "_synthesisui",
37
+ "storybook-static",
38
+ ]);
39
+ async function countIn(dir, root, inside, tally, depth = 0) {
40
+ /** Um monorepo fundo não vale um passeio infinito; oito níveis cobrem qualquer layout real. */
41
+ if (depth > 8)
42
+ return;
43
+ /**
44
+ * O TIPO ESCRITO À MÃO, e não inferido de `readdir`: a sobrecarga que o TS escolhe sem argumento
45
+ * devolve `Dirent<Buffer>`, e aí `entry.name` deixa de ser string. Inferir de uma sobrecarga é
46
+ * pedir que o compilador adivinhe qual das cinco eu quis.
47
+ */
48
+ let entries;
49
+ try {
50
+ entries = await readdir(dir, { withFileTypes: true });
51
+ }
52
+ catch {
53
+ return;
54
+ }
55
+ for (const entry of entries) {
56
+ if (entry.name.startsWith(".") || SKIP.has(entry.name))
57
+ continue;
58
+ const path = join(dir, entry.name);
59
+ if (entry.isDirectory()) {
60
+ await countIn(path, root, inside, tally, depth + 1);
61
+ continue;
62
+ }
63
+ const dot = entry.name.lastIndexOf(".");
64
+ if (dot < 0 || !READ.has(entry.name.slice(dot)))
65
+ continue;
66
+ const rel = relative(root, path);
67
+ if (inside(rel))
68
+ continue;
69
+ /** O diretório de topo é a unidade que uma pessoa reconhece: `apps/web`, não a pasta folha. */
70
+ const top = rel.split(sep).slice(0, 2).join("/");
71
+ tally.set(top, (tally.get(top) ?? 0) + 1);
72
+ }
73
+ }
74
+ /**
75
+ * MEDE O FORA. `scopes` são os caminhos que o import realmente leu, relativos à raiz.
76
+ *
77
+ * Devolve `null` quando não há escopo declarado: sem `--scope` o leitor olhou a raiz, então "fora do
78
+ * escopo" não é uma categoria que existe - e emitir uma linha de zero ensinaria o cliente a ignorar a
79
+ * seção quando ela tiver algo.
80
+ */
81
+ export async function outsideScope(root, scopes) {
82
+ const declared = scopes.map((s) => s.replace(/^\.\//, "").replace(/\/$/, ""));
83
+ if (declared.length === 0)
84
+ return null;
85
+ const inside = (rel) => declared.some((s) => rel === s || rel.startsWith(`${s}/`));
86
+ const tally = new Map();
87
+ await countIn(root, root, inside, tally);
88
+ const places = [...tally]
89
+ .map(([path, files]) => ({ path, files }))
90
+ .sort((a, b) => b.files - a.files);
91
+ const files = places.reduce((n, p) => n + p.files, 0);
92
+ if (files === 0)
93
+ return null;
94
+ const named = places
95
+ .slice(0, 3)
96
+ .map((p) => `${p.path} (${p.files})`)
97
+ .join(", ");
98
+ return {
99
+ files,
100
+ places,
101
+ said: `${files} more readable file${files === 1 ? "" : "s"} live outside what you pointed at - ${named}${places.length > 3 ? `, and ${places.length - 3} more place${places.length - 3 === 1 ? "" : "s"}` : ""}. Nothing was read there, so nothing about them is in this measurement: point at them too if their design belongs in this system.`,
102
+ };
103
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.272",
3
+ "version": "0.16.274",
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": {