synthesisui 0.16.252 → 0.16.253

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.
@@ -35,6 +35,7 @@ import { definitionSpan, parseClass, readInlineStyle, rootClasses, rootTag, tran
35
35
  import { transcribeVariants } from "../doctor/variant-read.js";
36
36
  import { frontierKind, packageRoot } from "../frontier-kind.js";
37
37
  import { withLibraryStructure } from "../library-structure.js";
38
+ import { claimName } from "../name-claim.js";
38
39
  import { body, paint, section } from "../output.js";
39
40
  import { phase, startProgress } from "../progress.js";
40
41
  import { detectStack, resolveDeps, stackVersions } from "../stack.js";
@@ -394,6 +395,16 @@ export async function takeCensus(root, opts) {
394
395
  const internal = await internalSpecifiers(root);
395
396
  const defined = [];
396
397
  /** Nome → arquivo e fonte, para toda leitura por componente - ver o preenchimento abaixo. */
398
+ /**
399
+ * QUEM DETÉM CADA NOME - e o registro de quem pediu o mesmo.
400
+ *
401
+ * Os oito mapas deste laço são indexados por nome de componente, então dois
402
+ * `Card.tsx` disputam a mesma chave e o último do `walk` sobrescrevia os
403
+ * outros em silêncio. Medido no repo do dono: 20 nomes colidem no repo
404
+ * inteiro, 14 dentro de um app, e um dos quatro `Card` é o do design system
405
+ * dele. Ver `name-claim.ts` para o desempate e por que ele é por estrutura.
406
+ */
407
+ const claims = { by: new Map(), collisions: [] };
397
408
  const sourceOf = new Map();
398
409
  /** Nome → o que a peça é, quando o portão soube dizer. Ver `CensusLook.kind`. */
399
410
  const kindOf = new Map();
@@ -592,6 +603,21 @@ export async function takeCensus(root, opts) {
592
603
  because: verdict.because,
593
604
  });
594
605
  }
606
+ /**
607
+ * O NOME É DISPUTADO AQUI, ANTES DE QUALQUER ESCRITA.
608
+ *
609
+ * Filtrar `found` fecha a porta de uma vez: os oito mapas deste laço
610
+ * derivam dele, então um componente que não detém o nome simplesmente não
611
+ * chega a nenhum deles - em vez de oito guardas que alguém esquece de
612
+ * repetir na nona escrita.
613
+ *
614
+ * O descarte NÃO é silencioso: `claims.collisions` guarda os dois caminhos
615
+ * e o censo os carrega. Trazer as duas receitas exige o contexto viajando
616
+ * na chave, que é a fatia dos grupos.
617
+ */
618
+ const claimed = found.filter((d) => claimName(claims, d.name, rel));
619
+ found.length = 0;
620
+ found.push(...claimed);
595
621
  for (const d of found) {
596
622
  const props = readDefinitionProps(d.name, src);
597
623
  propsOf.set(d.name, props);
@@ -1880,6 +1906,14 @@ export async function takeCensus(root, opts) {
1880
1906
  ...(Object.keys(keyframes).length > 0 ? { keyframes } : {}),
1881
1907
  ...(animations.size > 0 ? { animations: [...animations].sort() } : {}),
1882
1908
  ...(brokenRefs.length > 0 ? { brokenRefs } : {}),
1909
+ /**
1910
+ * OS NOMES DISPUTADOS - ver `name-claim.ts`.
1911
+ *
1912
+ * Aditivo e omitido quando vazio, que é o caso de toda biblioteca sã: no repo
1913
+ * do dono, `packages/ui` produz zero e o repo inteiro produz 20. Um campo que
1914
+ * só aparece quando há algo a dizer não muda nada para quem já lê o censo.
1915
+ */
1916
+ ...(claims.collisions.length > 0 ? { collisions: claims.collisions } : {}),
1883
1917
  ...(conventions.length > 0 ? { conventions } : {}),
1884
1918
  classStyle,
1885
1919
  ...(Object.keys(looks).length > 0 ? { looks } : {}),
@@ -2055,8 +2089,37 @@ async function summarize(c, root, scope) {
2055
2089
  }
2056
2090
  }
2057
2091
  sayConventions(c);
2092
+ sayCollisions(c);
2058
2093
  await sayBrokenRefs(c, root, scope);
2059
2094
  }
2095
+ /**
2096
+ * DOIS ARQUIVOS, UM NOME - dito, com os dois caminhos.
2097
+ *
2098
+ * O silêncio aqui era o pior tipo: a pessoa recebe 36 componentes onde o repo
2099
+ * tem 39, e nada no relatório sugere que três existiram. Medido no monorepo do
2100
+ * dono: 20 nomes no repo inteiro, e um dos quatro `Card.tsx` é o do design system
2101
+ * dele competindo com dois que vivem sob rotas.
2102
+ *
2103
+ * A frase diz o que fazer, e são duas coisas diferentes: um escopo mais estreito
2104
+ * quando os homônimos são de outro projeto, ou um rename quando são do mesmo.
2105
+ */
2106
+ function sayCollisions(c) {
2107
+ const all = c.collisions ?? [];
2108
+ if (all.length === 0)
2109
+ return;
2110
+ const names = new Set(all.map((x) => x.name));
2111
+ console.log("");
2112
+ console.log(section("Two files, one name"));
2113
+ console.log(body(`${names.size} name${names.size === 1 ? "" : "s"} ${names.size === 1 ? "is" : "are"} defined more than once in what was measured, so ${all.length} definition${all.length === 1 ? "" : "s"} did not make it into this system. The one closest to the root of the scope kept the name - never the order the disk happened to return.`));
2114
+ console.log("");
2115
+ for (const x of all.slice(0, 6)) {
2116
+ console.log(body(`${paint.strong(x.name)} ${paint.faint("kept")} ${x.kept}\n ${paint.faint("dropped")} ${x.dropped}`));
2117
+ }
2118
+ if (all.length > 6)
2119
+ console.log(body(paint.faint(`(${all.length - 6} more)`)));
2120
+ console.log("");
2121
+ console.log(body(paint.faint("If the homonyms belong to different projects, a narrower --scope is the answer and nothing is lost. If they belong to the same one, renaming in your code is - two components with one name is one name your agent cannot resolve either.")));
2122
+ }
2060
2123
  /**
2061
2124
  * THEIR VOCABULARY, MEASURED. This section exists so every later finding can be
2062
2125
  * phrased as "you already do X, and here you did not" instead of "adopt ours".
@@ -0,0 +1,71 @@
1
+ /**
2
+ * UM NOME, UM COMPONENTE - e o que fazer quando dois pedem o mesmo.
3
+ *
4
+ * O laço de leitura do import escreve em oito mapas indexados por NOME de
5
+ * componente (`looks`, `sourceOf`, `kindOf`, `propsOf`, `runtimeOf`,
6
+ * `variantAxes`, `islands`, `keyframes`). Dois `Card.tsx` em pastas diferentes
7
+ * disputam a mesma chave, e até aqui o último que o `walk` visitou sobrescrevia
8
+ * os outros - em silêncio, e por uma ordem que é do sistema de arquivos e não
9
+ * uma decisão de ninguém.
10
+ *
11
+ * Medido no monorepo do dono (19/08): 20 nomes colidem no repo inteiro, 14 dentro
12
+ * de `apps/web-dashboard/src`, e quatro arquivos se chamam `Card.tsx` - um deles
13
+ * é o `Card` do design system dele, e dois estão sob `_legacy`. `packages/ui`
14
+ * sozinho não tem colisão nenhuma: o problema mora em escopo largo, que é
15
+ * exatamente o default que a flexibilidade pede.
16
+ *
17
+ * ─────────────────────────────────────────────────────────────────────────
18
+ * O DESEMPATE É POR ESTRUTURA, E DETERMINÍSTICO
19
+ *
20
+ * A intuição primeira era "quem o pacote EXPORTA fica com o nome", e ela não
21
+ * responde: `publicApi().exports` é um conjunto de NOMES, e os dois candidatos
22
+ * têm o mesmo nome. Escolher pelo que o pacote realmente publica exige o grafo a
23
+ * partir da entrada do manifesto, e essa é a fatia que também entrega os grupos.
24
+ *
25
+ * Então: menos segmentos de caminho vence, e empate resolve alfabeticamente. É
26
+ * pouco, e é honesto - a diferença que importa é que a resposta deixa de mudar
27
+ * conforme a ordem em que o disco devolve os arquivos.
28
+ *
29
+ * E NUNCA POR NOME DE PASTA. Descartar `_legacy` por se chamar `_legacy` é a lei
30
+ * 14 sendo furada: no repo dele `_legacy` tem 178 arquivos e 106 estão VIVOS,
31
+ * montados por rota (medido 07/08). Classificação é por estrutura, nunca por nome.
32
+ *
33
+ * ─────────────────────────────────────────────────────────────────────────
34
+ * O PERDEDOR NÃO ENTRA, E ISSO É DITO
35
+ *
36
+ * Esta fatia não traz as duas receitas - trazer os dois `Card` exige o contexto
37
+ * viajando na chave, que é a fatia dos GRUPOS. O que ela conserta é a perda
38
+ * calada: cada descarte vira uma linha do censo com os dois caminhos, e o
39
+ * relatório os imprime. Uma lacuna declarada é confiança; calada é bug.
40
+ */
41
+ /** Profundidade como o caminho a declara - o número de pastas até o arquivo. */
42
+ const depth = (file) => file.split("/").length;
43
+ /**
44
+ * ESTE ARQUIVO FICA COM ESTE NOME?
45
+ *
46
+ * `true` quando ele detém o nome depois desta chamada - e aí quem chamou pode
47
+ * escrever nos mapas. `false` quando outro arquivo o detém, e a disputa foi
48
+ * registrada em `collisions`.
49
+ *
50
+ * Idempotente para o mesmo arquivo: o laço lê um arquivo uma vez, mas um
51
+ * componente pode ser reivindicado por mais de um caminho de código, e cobrar
52
+ * uma colisão de um arquivo contra si mesmo seria inventar um conflito.
53
+ */
54
+ export function claimName(claims, name, file) {
55
+ const held = claims.by.get(name);
56
+ if (held === undefined) {
57
+ claims.by.set(name, file);
58
+ return true;
59
+ }
60
+ if (held === file)
61
+ return true;
62
+ const incumbentWins = depth(held) < depth(file) ||
63
+ (depth(held) === depth(file) && held.localeCompare(file) <= 0);
64
+ if (incumbentWins) {
65
+ claims.collisions.push({ name, kept: held, dropped: file });
66
+ return false;
67
+ }
68
+ claims.by.set(name, file);
69
+ claims.collisions.push({ name, kept: file, dropped: held });
70
+ return true;
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.252",
3
+ "version": "0.16.253",
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": {