synthesisui 0.16.344 → 0.16.346

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.
@@ -24,7 +24,7 @@ import { accountExportsInto, emptyExportAccount, exportInvariantHolds, } from ".
24
24
  import { fragmentsOfSource, fragmentsOfStylesheet, judgeFragments, } from "../doctor/fragments.js";
25
25
  import { useFrameworkMajor } from "../doctor/framework-palette.js";
26
26
  import { describeConvention, describeRemainder, detectConventions, } from "../doctor/idiom.js";
27
- import { pageLaws } from "../doctor/page-laws.js";
27
+ import { componentsPagesReachFor, pageLaws } from "../doctor/page-laws.js";
28
28
  import { callSiteNode, placeLayers, } from "../doctor/place-layers.js";
29
29
  import { publicApi, requiredProps } from "../doctor/public-api.js";
30
30
  import { aliasesOf, describeReachability, edgesIn, reachabilityOf, } from "../doctor/reachability.js";
@@ -60,6 +60,25 @@ import { harvestWorkspaceCss } from "../workspace-css.js";
60
60
  import { placeInWorkspace } from "../workspace-place.js";
61
61
  import { add } from "./add.js";
62
62
  import { walk, walkAll } from "./doctor.js";
63
+ /**
64
+ * UMA PÁGINA DELE, como ele a montou - ver `Census.pages`.
65
+ *
66
+ * Não é "uma rota do Next": é qualquer arquivo que o portão de componente recusou por ser tela e
67
+ * não peça (`route` ou `screen`), em qualquer stack. O que a identifica é o veredito do portão,
68
+ * nunca o nome do arquivo - e é isso que a faz valer no projeto de alguém que a gente nunca viu.
69
+ */
70
+ /**
71
+ * QUANTAS PÁGINAS VIAJAM NO CENSO.
72
+ *
73
+ * O teto existe pela mesma razão que o de `skipped`: num monorepo o número estoura e o censo
74
+ * viraria um dump do repositório. Medido em 01/09, o `frontend-hub` tem 145 arquivos de página e
75
+ * o nosso app 65, então 400 cabe as duas populações inteiras e o corte só aparece em repositório
76
+ * bem maior que os que a gente mede.
77
+ *
78
+ * E QUANDO ELE CORTAR, `pagesTotal` diz quantas eram. Uma tela que contasse `pages.length` diria
79
+ * 400 e pareceria completa, que é a forma mais barata de um corte silencioso mentir.
80
+ */
81
+ export const PAGES_MAX = 400;
63
82
  /**
64
83
  * How many distinct values travel, PER KIND.
65
84
  *
@@ -472,7 +491,8 @@ export async function takeCensus(root, opts) {
472
491
  const sourceOf = new Map();
473
492
  /** Nome → o que a peça é, quando o portão soube dizer. Ver `CensusLook.kind`. */
474
493
  const kindOf = new Map();
475
- /** A raiz e a composição de cada página, para as leis - ver `pageLaws`. */
494
+ /** Como ele monta cada página - as leis saem daqui, e o censo carrega o dado. Ver `pageLaws`
495
+ * e `Census.pages`. */
476
496
  const pages = [];
477
497
  /** Todo fragmento de estilo visto, para o ledger - ver `buildLedger`. */
478
498
  const fragments = [];
@@ -753,12 +773,22 @@ export async function takeCensus(root, opts) {
753
773
  const sketch = sketchOf(src, d.name);
754
774
  const root = sketch[0];
755
775
  pages.push({
776
+ file: rel,
756
777
  root: root?.classes
757
778
  ? transcribe(root.classes.split(/\s+/).filter(Boolean), declaredValues).base
758
779
  : {},
780
+ /**
781
+ * `from` VEM DO SKETCH e não de uma segunda leitura do arquivo: o nó já carrega o
782
+ * especificador que o import dele declara, e reabrir o arquivo para descobrir o
783
+ * mesmo fato é a falha de esteira que o dono nomeou em 01/08.
784
+ */
759
785
  composes: sketch
760
786
  .filter((n) => /^[A-Z]/.test(n.tag))
761
- .map((n) => n.tag.split(".")[0]),
787
+ .map((n) => ({
788
+ name: n.tag.split(".")[0],
789
+ ...(n.from ? { from: n.from } : {}),
790
+ depth: n.depth,
791
+ })),
762
792
  });
763
793
  }
764
794
  skips.push({
@@ -2061,7 +2091,15 @@ export async function takeCensus(root, opts) {
2061
2091
  for (const line of ledgerLines.slice(0, 24))
2062
2092
  say(body(line));
2063
2093
  }
2064
- const pageRules = pageLaws(pages);
2094
+ /**
2095
+ * As leis contam NOMES, e o censo carrega a peça inteira. O `map` fica aqui e não em
2096
+ * `page-laws.ts` para o módulo das leis continuar sem saber o que é um censo - ele responde
2097
+ * "no que estas páginas concordam" e nada mais.
2098
+ */
2099
+ const pageRules = pageLaws(pages.map((page) => ({
2100
+ root: page.root ?? {},
2101
+ composes: page.composes.map((c) => c.name),
2102
+ })));
2065
2103
  if (pageRules.length > 0) {
2066
2104
  say("");
2067
2105
  say(section("What your pages agree on"));
@@ -2070,6 +2108,32 @@ export async function takeCensus(root, opts) {
2070
2108
  say(body(paint.faint(` ${law.evidence}`)));
2071
2109
  }
2072
2110
  }
2111
+ /**
2112
+ * AS PEÇAS QUE AS PÁGINAS DELE ALCANÇAM, ditas em voz alta - ver `componentsPagesReachFor`.
2113
+ *
2114
+ * Isto existe porque o campo `pages` do censo nasceria SEM LEITOR: a plataforma vai consumi-lo
2115
+ * num planejador que ainda não existe, e um dado guardado que ninguém mostra é a mesma coisa
2116
+ * que não medir. Aqui ele vira uma frase que já vale sozinha - o desenvolvedor descobre quais
2117
+ * das peças dele são a espinha das telas dele, que é uma pergunta que ninguém respondia.
2118
+ *
2119
+ * O NÚMERO VEM JUNTO SEMPRE, e ele é páginas e não usos - sem o denominador em palavras a lista
2120
+ * lê como ranking de importância em vez de contagem de espalhamento.
2121
+ */
2122
+ const spread = componentsPagesReachFor(pages, (from) => {
2123
+ if (!from)
2124
+ return true;
2125
+ if (internal.some((prefix) => from.startsWith(prefix)))
2126
+ return true;
2127
+ return frontierKind(from) === "own";
2128
+ });
2129
+ if (spread.length > 0) {
2130
+ say("");
2131
+ say(section("The components your pages are built from"));
2132
+ say(body(`${spread.length} of your components hold up ${pages.length} ${pages.length === 1 ? "page" : "pages"} - a new page starts from these.`));
2133
+ for (const piece of spread.slice(0, 8)) {
2134
+ say(body(paint.faint(` ${piece.name} - on ${piece.pages} of your ${pages.length} pages`)));
2135
+ }
2136
+ }
2073
2137
  if (architectures.length > 0) {
2074
2138
  say("");
2075
2139
  say(section("How this project is organised"));
@@ -2397,6 +2461,17 @@ export async function takeCensus(root, opts) {
2397
2461
  * Omitido quando não há nenhuma, que é o caso de todo primeiro import.
2398
2462
  */
2399
2463
  ...(declaredForms.length > 0 ? { declaredForms } : {}),
2464
+ /**
2465
+ * COMO ELE MONTA UMA PÁGINA - ver `Census.pages`. Omitido quando não há página nenhuma, que é
2466
+ * o caso de toda biblioteca: ela não compõe telas dentro de si, e um array vazio aqui leria
2467
+ * como "medimos e ele não monta nada".
2468
+ */
2469
+ ...(pages.length > 0
2470
+ ? {
2471
+ pages: pages.slice(0, PAGES_MAX),
2472
+ ...(pages.length > PAGES_MAX ? { pagesTotal: pages.length } : {}),
2473
+ }
2474
+ : {}),
2400
2475
  /**
2401
2476
  * A FOLHA DELE ENTRA POR ÚLTIMO, e a ordem é a resposta a quem manda.
2402
2477
  *
@@ -458,7 +458,76 @@ function looksLikeTypeArg(source, at) {
458
458
  }
459
459
  export function scanTags(source) {
460
460
  const out = [];
461
+ /**
462
+ * QUANTAS TAGS ESTÃO ABERTAS - o que diz se estamos em CÓDIGO ou em CONTEÚDO.
463
+ *
464
+ * A distinção existe por causa das aspas, e ela é o que separa duas coisas escritas igual:
465
+ * `.replaceAll("<task_complete/>", …)` é código, e a tag ali é texto; `Don't` é conteúdo, e
466
+ * o apóstrofo ali é prosa. Parear aspas nos dois lugares custou marcação REAL na primeira
467
+ * tentativa - o `KeapCRMDialog` deles perdeu quatro `<Skeleton>` legítimos porque
468
+ * `height="70px"` desalinhou o par (medido, 01/09).
469
+ */
470
+ let open = 0;
461
471
  for (let i = 0; i < source.length; i++) {
472
+ /**
473
+ * COMENTÁRIO NÃO É MARCAÇÃO, e vale nos dois lados: `{/* … *\/}` mora no conteúdo e
474
+ * `//<AddIcon />` mora no código.
475
+ *
476
+ * O QUE O CLIENTE PERDE SEM ISTO: uma linha que ele comentou vira um nó da anatomia. No
477
+ * `frontend-hub`, `icon, //<AddIcon iconColor={…} />` faz o `SidebarButton` nascer com um
478
+ * ícone que a marcação não tem - e como o nó fantasma cai em `depth` 0, ele vira a RAIZ e
479
+ * desloca todo `at` abaixo dele, que é o índice pelo qual a anatomia acha o nó medido.
480
+ * Um `{/* … <StudioButtonNextStep …/> … *\/}` entrega um COMPONENTE inteiro que ele apagou.
481
+ *
482
+ * Medido em 01/09: 19 de 378 componentes e 2 de 95 páginas do app dele, 15 de 170
483
+ * componentes e 3 de 56 páginas do nosso. A biblioteca dele dá 0 de 36 - o defeito
484
+ * atravessou porque não aparece onde a esteira é mais olhada.
485
+ */
486
+ if (source[i] === "/" && source[i + 1] === "*") {
487
+ const close = source.indexOf("*/", i + 2);
488
+ i = close === -1 ? source.length : close + 1;
489
+ continue;
490
+ }
491
+ /** `https://…` não abre comentário, e o `://` é o que diz isso sem saber o contexto. */
492
+ if (source[i] === "/" && source[i + 1] === "/" && source[i - 1] !== ":") {
493
+ const nl = source.indexOf("\n", i + 2);
494
+ i = nl === -1 ? source.length : nl;
495
+ continue;
496
+ }
497
+ /**
498
+ * LITERAL DE TEXTO, só em código - com nenhuma tag aberta. Dentro de uma tag as aspas são
499
+ * atributo e o laço abaixo já as consome inteiras; dentro do conteúdo elas são prosa.
500
+ */
501
+ if (open === 0 &&
502
+ (source[i] === '"' || source[i] === "'" || source[i] === "`")) {
503
+ const quote = source[i];
504
+ /**
505
+ * ASPAS NÃO ATRAVESSAM LINHA - é regra da linguagem, e é ela que separa um literal de
506
+ * uma aspa solta dentro de outra coisa.
507
+ *
508
+ * Sem esse limite, o `["']` de uma EXPRESSÃO REGULAR abria uma string que só fechava
509
+ * páginas adiante: `cleanContent.replace(/^["']|["']$/g, "")` apagou os 19 nós do
510
+ * `ToolResultView` deles - o componente inteiro (medido, 01/09). Crase é a exceção,
511
+ * porque template literal atravessa linha de propósito.
512
+ */
513
+ const limit = quote === "`"
514
+ ? source.length
515
+ : source.indexOf("\n", i + 1) + 1 || source.length;
516
+ let j = i + 1;
517
+ while (j < limit) {
518
+ if (source[j] === "\\")
519
+ j += 2;
520
+ else if (source[j] === quote)
521
+ break;
522
+ else
523
+ j += 1;
524
+ }
525
+ /** Sem par até o limite não é literal - é aspa solta, e engoli-la custaria marcação. */
526
+ if (j < limit && source[j] === quote) {
527
+ i = j;
528
+ continue;
529
+ }
530
+ }
462
531
  if (source[i] !== "<")
463
532
  continue;
464
533
  const closing = source[i + 1] === "/";
@@ -498,6 +567,7 @@ export function scanTags(source) {
498
567
  const body = source.slice(nameAt + name.length, end);
499
568
  if (closing) {
500
569
  out.push({ at: i, kind: "close", tag: name, body: "" });
570
+ open = Math.max(0, open - 1);
501
571
  }
502
572
  else {
503
573
  out.push({ at: i, kind: "open", tag: name, body });
@@ -505,6 +575,9 @@ export function scanTags(source) {
505
575
  if (body.trimEnd().endsWith("/")) {
506
576
  out.push({ at: i + 1, kind: "close", tag: name, body: "" });
507
577
  }
578
+ else {
579
+ open += 1;
580
+ }
508
581
  }
509
582
  i = end;
510
583
  }
@@ -104,3 +104,41 @@ export function pageLaws(pages) {
104
104
  function topOf(composed) {
105
105
  return [...composed.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? "";
106
106
  }
107
+ /**
108
+ * OS COMPONENTES QUE AS PÁGINAS DELE MAIS ALCANÇAM - fato contado, e nunca inferência de papel.
109
+ *
110
+ * O QUE ISTO DESTRAVA: a paleta de um planejador de página que abre com os componentes que ELE usa, na ordem
111
+ * em que ele usa, em vez de uma lista que a gente escreveu. Medido em 01/09, uma página do
112
+ * `frontend-hub` monta `ContentWrapper` em 20 delas, `PageHeader` em 14 e `HorizontalMenu` em 8 -
113
+ * e essas três são a gramática de layout daquele projeto, escrita em componentes.
114
+ *
115
+ * O QUE ELA NÃO DIZ, de propósito: que esses componentes SÃO layout. Isso é leitura de papel e ela
116
+ * exigiria provar que o componente envolve os outros; o que está medido aqui é quantas páginas a
117
+ * alcançam, e é só isso que o texto pode afirmar. Chamar frequência de papel seria a plataforma
118
+ * batizando o vocabulário dele, que a decisão 3 proíbe.
119
+ *
120
+ * CONTA PÁGINAS E NÃO USOS: uma página que repete o mesmo `Card` oito vezes não faz dele o
121
+ * esqueleto do projeto - faz dele o conteúdo daquela página. O sinal é ESPALHAMENTO.
122
+ *
123
+ * SÓ OS COMPONENTES DELE. Sem o filtro a lista abriria com `motion.div` e `LucideSearch`, que são de
124
+ * terceiros e não estão no sistema dele para serem oferecidas. Quem chama decide o que é dele,
125
+ * porque a régua que desempata `workspace:` contra semver mora no manifesto - ver `frontierKind`.
126
+ */
127
+ export function componentsPagesReachFor(pages, theirs) {
128
+ const spread = new Map();
129
+ for (const page of pages) {
130
+ const seen = new Set();
131
+ for (const piece of page.composes) {
132
+ if (!theirs(piece.from))
133
+ continue;
134
+ if (seen.has(piece.name))
135
+ continue;
136
+ seen.add(piece.name);
137
+ spread.set(piece.name, (spread.get(piece.name) ?? 0) + 1);
138
+ }
139
+ }
140
+ return [...spread.entries()]
141
+ .filter(([, n]) => n >= AGREE)
142
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
143
+ .map(([name, n]) => ({ name, pages: n }));
144
+ }
@@ -144,7 +144,15 @@
144
144
  * terminal dizendo qual arquivo e como tomar a versão nova por escolha. Antes desta versão a
145
145
  * reescrita era incondicional e o único aviso era prosa depois do fato.
146
146
  */
147
- export const MATERIALISER_SINCE = "0.16.319";
147
+ /**
148
+ * 0.16.319 -> 0.16.345 em 01/09 (`markup-only.spec`): o scanner de tags parou de ler comentário e
149
+ * literal de texto como marcação, e isso chega ao ARQUIVO que o `materialize` escreve.
150
+ *
151
+ * O sketch alimenta a anatomia e a anatomia alimenta o codegen, então um bloco JSX que ele
152
+ * comentou vinha sendo escrito na pasta dele como componente de verdade. Medido em 14 arquivos
153
+ * das duas populações o nó fantasma era a RAIZ - o elemento cujas classes viram a `base`.
154
+ */
155
+ export const MATERIALISER_SINCE = "0.16.345";
148
156
  /**
149
157
  * A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
150
158
  *
@@ -484,7 +492,47 @@ export const CHECKER_SINCE = "0.16.308";
484
492
  * parte do repositório. O corpus dourado NÃO se moveu por isso: nenhum dos 29 apps é medido com
485
493
  * escopo, e o hash parado aqui é ausência de cobertura, nunca ausência de efeito.
486
494
  */
487
- export const READER_SINCE = "0.16.341";
495
+ /**
496
+ * 0.16.341 -> 0.16.345 em 01/09 (`markup-only.spec`): o leitor deixou de tratar COMENTÁRIO e
497
+ * LITERAL DE TEXTO como marcação.
498
+ *
499
+ * O QUE O CLIENTE VIVIA: uma linha que ele comentou virava um elemento da anatomia dele. No
500
+ * `frontend-hub`, `icon, //<AddIcon iconColor={…} />` faz o `SidebarButton` nascer com um ícone
501
+ * que a marcação não tem; um `{/* … <StudioButtonNextStep …/> … *\/}` entrega um COMPONENTE
502
+ * inteiro que ele apagou; e `.replaceAll("<task_complete/>", …)` entrega uma tag que só existe
503
+ * dentro de uma string.
504
+ *
505
+ * A marca sobe porque o SKETCH é medido na máquina dele e viaja gravado no censo: o `sync`
506
+ * remede com `takeCensus`, então quem rodar recebe a anatomia sem os nós fantasma. Sem a marca,
507
+ * o `align` não chamaria ninguém.
508
+ *
509
+ * QUANTOS: 735 arquivos das duas populações, 47 mudaram, 111 nós saíram - os 111 verificados
510
+ * contra o AST do compilador como comentário ou literal, e zero marcação real perdida. Em 14
511
+ * arquivos o nó fantasma era a RAIZ, que é o que vira a `base` da receita.
512
+ *
513
+ * QUEM NÃO É AFETADO: quem não deixa código comentado na marcação. A biblioteca dele
514
+ * (`packages/ui`) dá 0 de 36 - e foi por isso que o defeito atravessou, porque ele não aparece
515
+ * onde a esteira é mais olhada. O corpus dourado não se movia por isso: nenhum dos 29 apps
516
+ * tinha a forma, e a fixture `markup-with-commented-out-code` entra no mesmo PR para o portão
517
+ * deixar de ser cego.
518
+ */
519
+ /**
520
+ * 0.16.345 -> 0.16.346 em 01/09 (`INV-COLETA-12`): o censo passou a carregar COMO ELE MONTA UMA
521
+ * PÁGINA - a sequência de peças de cada uma, com a origem de cada peça.
522
+ *
523
+ * O QUE ELE GANHA COM O `sync`: a plataforma passa a saber quais das peças DELE sustentam as telas
524
+ * dele. Sem isso, qualquer coisa que precise partir de uma página só tem o esqueleto que NÓS
525
+ * escrevemos - três layouts padrão contra os componentes que ele de fato usa.
526
+ *
527
+ * O campo é NOVO, então todo censo já guardado está sem ele: a marca sobe porque a ausência aqui
528
+ * não se distingue de "este projeto não tem páginas", e só o remedir separa os dois. O `sync`
529
+ * refaz com `takeCensus`.
530
+ *
531
+ * QUEM NÃO É AFETADO: quem apontou a leitura para uma biblioteca. Ela não compõe telas dentro de
532
+ * si, o campo fica ausente, e ausente ali continua sendo a resposta certa - o `codelevel-ui` dá
533
+ * 0 de 0 contra 145 arquivos de página do `frontend-hub`.
534
+ */
535
+ export const READER_SINCE = "0.16.346";
488
536
  /**
489
537
  * O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
490
538
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.344",
3
+ "version": "0.16.346",
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": {