synthesisui 0.16.131 → 0.16.132

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.
@@ -17,6 +17,7 @@ import { keyframeOffsets, moduleImports, readModuleCss, readModuleUsage, transcr
17
17
  import { dataContract } from "../doctor/data-contract.js";
18
18
  import { reconcile, scanDefinitions, } from "../doctor/definitions-scan.js";
19
19
  import { describeConvention, describeRemainder, detectConventions, } from "../doctor/idiom.js";
20
+ import { pageLaws } from "../doctor/page-laws.js";
20
21
  import { callSiteNode, placeLayers, } from "../doctor/place-layers.js";
21
22
  import { publicApi, requiredProps } from "../doctor/public-api.js";
22
23
  import { diagnose, scanSource } from "../doctor/scan.js";
@@ -263,6 +264,10 @@ export async function takeCensus(root, opts) {
263
264
  const defined = [];
264
265
  /** Nome → arquivo e fonte, para toda leitura por componente - ver o preenchimento abaixo. */
265
266
  const sourceOf = new Map();
267
+ /** Nome → o que a peça é, quando o portão soube dizer. Ver `CensusLook.kind`. */
268
+ const kindOf = new Map();
269
+ /** A raiz e a composição de cada página, para as leis - ver `pageLaws`. */
270
+ const pages = [];
266
271
  /** What the gate turned away, with the reason. Never dropped in silence. */
267
272
  const skips = [];
268
273
  // Kept for the reference check below, which needs every file at once: a name
@@ -391,9 +396,33 @@ export async function takeCensus(root, opts) {
391
396
  internal,
392
397
  });
393
398
  if (verdict.ok) {
394
- found.push(d);
399
+ /**
400
+ * O QUE A PEÇA É viaja com ela - ver `GateVerdict.kind`. Um template decide arranjo e
401
+ * não aparência, e três superfícies precisam saber disso para não cobrar cor de quem
402
+ * nunca prometeu cor.
403
+ */
404
+ found.push(verdict.kind ? { ...d, kind: verdict.kind } : d);
395
405
  continue;
396
406
  }
407
+ /**
408
+ * A RAIZ DE UMA PÁGINA E O QUE ELA COMPÕE, para as leis - ver `pageLaws`.
409
+ *
410
+ * Uma rota individualmente não vale receita: 5 elementos de média e a raiz repetindo o
411
+ * que outras oito já disseram. Juntas elas dizem como é a casca deste projeto, que é o
412
+ * que falta para alguém escrever a próxima (04/08).
413
+ */
414
+ if (verdict.why === "route" || verdict.why === "screen") {
415
+ const sketch = sketchOf(src, d.name);
416
+ const root = sketch[0];
417
+ pages.push({
418
+ root: root?.classes
419
+ ? transcribe(root.classes.split(/\s+/).filter(Boolean), declaredValues).base
420
+ : {},
421
+ composes: sketch
422
+ .filter((n) => /^[A-Z]/.test(n.tag))
423
+ .map((n) => n.tag.split(".")[0]),
424
+ });
425
+ }
397
426
  skips.push({
398
427
  name: d.name,
399
428
  file: rel,
@@ -426,8 +455,12 @@ export async function takeCensus(root, opts) {
426
455
  * numa lista feita para biblioteca. O look já nasce aqui, com o arquivo e a fonte na mão,
427
456
  * e é aqui que a leitura tem que se pendurar.
428
457
  */
429
- for (const d of found)
458
+ for (const d of found) {
430
459
  sourceOf.set(d.name, { file: rel, source: src });
460
+ /** O que a peça É, do portão - ver `GateVerdict.kind` e `CensusLook.kind`. */
461
+ if (d.kind)
462
+ kindOf.set(d.name, d.kind);
463
+ }
431
464
  // THE LOOK, from their own class names. One transcription per file, keyed
432
465
  // by the component it defines - the root element's classes are the
433
466
  // component's own look, and everything below it is a part this slice does
@@ -733,6 +766,18 @@ export async function takeCensus(root, opts) {
733
766
  if (hasScreens) {
734
767
  const keep = [];
735
768
  for (const d of defined) {
769
+ /**
770
+ * UM TEMPLATE VIVE NUMA TELA SÓ POR DEFINIÇÃO - ele É a tela.
771
+ *
772
+ * A regra de duas telas existe para separar "decisão que o produto compartilha" de
773
+ * "markup daquela tela", e está certa para uma PEÇA. Aplicada a um template ela recusa
774
+ * exatamente o que o portão acabou de aceitar: as 16 telas do dashboard dele passavam o
775
+ * portão como `template` e caíam aqui, aparecendo no relatório como `feature` (04/08).
776
+ */
777
+ if (d.kind === "template") {
778
+ keep.push(d);
779
+ continue;
780
+ }
736
781
  const screens = screensOf.get(d.name)?.size ?? 0;
737
782
  if (screens >= SCREENS_FOR_SYSTEM) {
738
783
  keep.push(d);
@@ -1126,6 +1171,19 @@ export async function takeCensus(root, opts) {
1126
1171
  * The skill asks which has priority; the chosen one becomes a system-wide rule that
1127
1172
  * reaches CLAUDE.md, so every prompt after this respects where a new component goes.
1128
1173
  */
1174
+ /**
1175
+ * AS LEIS DAS PÁGINAS, ditas onde a arquitetura já é dita - governam o sistema, não uma peça,
1176
+ * então `applies` é vazio e elas chegam a todo prompt. Ver `pageLaws`.
1177
+ */
1178
+ const pageRules = pageLaws(pages);
1179
+ if (pageRules.length > 0) {
1180
+ console.log("");
1181
+ console.log(section("What your pages agree on"));
1182
+ for (const law of pageRules) {
1183
+ console.log(body(law.text));
1184
+ console.log(body(paint.faint(` ${law.evidence}`)));
1185
+ }
1186
+ }
1129
1187
  if (architectures.length > 0) {
1130
1188
  console.log("");
1131
1189
  console.log(section("How this project is organised"));
@@ -1332,8 +1390,12 @@ export async function takeCensus(root, opts) {
1332
1390
  if (c)
1333
1391
  c.api = stamped;
1334
1392
  const look = looks[name];
1335
- if (look)
1393
+ if (look) {
1336
1394
  look.api = stamped;
1395
+ const kind = kindOf.get(name);
1396
+ if (kind)
1397
+ look.kind = kind;
1398
+ }
1337
1399
  }
1338
1400
  const brokenRefs = findBrokenRefs(sources, declaredNames);
1339
1401
  const conventions = detectConventions(sources);
@@ -43,6 +43,19 @@ const RESERVED_FILE_OTHER = /(^|[/\\])(\+page|\+layout|\+server|_app|_document|_
43
43
  * hundred libraries. It only counts when the file itself is a route.
44
44
  */
45
45
  const SCREEN_SUFFIX = /(Page|Screen|Route|Template|Layout)$/;
46
+ /**
47
+ * Quantos elementos uma tela tem e quantos deles são peça de alguém - o que separa um template
48
+ * de uma casca. Contado no arquivo, sem sketch: este portão roda antes de qualquer leitura.
49
+ */
50
+ function screenShape(source, name) {
51
+ const at = source.indexOf(name);
52
+ const body = at === -1 ? source : source.slice(at);
53
+ const tags = [...body.matchAll(/<([A-Za-z][\w.]*)/g)].map((m) => m[1]);
54
+ return {
55
+ nodes: tags.length,
56
+ composes: new Set(tags.filter((t) => /^[A-Z]/.test(t)).map((t) => t.split(".")[0])).size,
57
+ };
58
+ }
46
59
  /** Something that carries context down the tree rather than drawing. */
47
60
  const PROVIDER_SUFFIX = /(Provider|Providers|Context|Boundary|Guard|Wrapper)$/;
48
61
  /** Any JSX at all in the file. A capitalised export with none is a constant, a
@@ -174,11 +187,22 @@ export function gateComponent(input) {
174
187
  because: `\`${file}\` is a file name the framework reserves - it is a route, not a component somebody can import`,
175
188
  };
176
189
  }
190
+ /**
191
+ * UMA TELA ENTRA COMO TEMPLATE, e o que decide é o que ela FAZ.
192
+ *
193
+ * O nome é a pista (`…Template`, `…Screen`, `…Page`) e não a prova: uma tela que compõe três
194
+ * ou mais peças numa árvore de dez ou mais nós é uma decisão de arranjo que vale receita -
195
+ * `CurateTemplate` tem 89 nós e 237 declarações. Uma que não alcança isso é casca, e casca é
196
+ * lei, não peça.
197
+ */
177
198
  if (SCREEN_SUFFIX.test(name)) {
199
+ const shape = screenShape(source, name);
200
+ if (shape.composes >= 3 && shape.nodes >= 10)
201
+ return { ok: true, kind: "template" };
178
202
  return {
179
203
  ok: false,
180
204
  why: "screen",
181
- because: `\`${name}\` names a whole screen or the frame around one. A design system is made of the pieces a screen is built FROM`,
205
+ because: `\`${name}\` names a screen, and composes ${shape.composes} piece${shape.composes === 1 ? "" : "s"} across ${shape.nodes} element${shape.nodes === 1 ? "" : "s"} - too thin to be a template of yours. A design system is made of the pieces a screen is built FROM`,
182
206
  };
183
207
  }
184
208
  if (PROVIDER_SUFFIX.test(name) || IS_CONTEXT.test(source)) {
@@ -0,0 +1,106 @@
1
+ /**
2
+ * O QUE AS PÁGINAS DE UM APP CONCORDAM, quando nenhuma delas vale uma receita.
3
+ *
4
+ * As 129 rotas do dashboard dele têm média de 5 elementos e 2 peças compostas, e a raiz de uma
5
+ * repete o que outras oito já disseram. Cada uma individualmente não tem o que uma receita
6
+ * carrega - virar 129 entradas na vitrine seria o problema de escala sem o benefício.
7
+ *
8
+ * Mas juntas elas dizem uma coisa que hoje não existe em lugar nenhum do sistema. Medido nas 129
9
+ * (04/08):
10
+ *
11
+ * width: 100% 12 páginas
12
+ * position: sticky 8 ┐
13
+ * top: 0 8 ├ juntos: o cabeçalho fixo de página
14
+ * z-index: 50 8 ┘
15
+ * padding: 1rem 8
16
+ * min-height: 100vh 6 a casca da página
17
+ * ContentWrapper 22 páginas compõem
18
+ * PageHeader 14
19
+ *
20
+ * Três ou mais arquivos concordando é hábito - a mesma escada que o resto da esteira usa. E uma
21
+ * lei com a contagem atrás vale mais que a receita de qualquer uma das 129, porque é isso que
22
+ * alguém precisa saber para escrever a 130ª.
23
+ *
24
+ * O QUE ELA NUNCA FAZ: propor. Uma lei aqui é a descrição do que eles já fazem, com o número, e
25
+ * o que NÃO se repete não vira lei - duas páginas concordando é coincidência, e chamar isso de
26
+ * convenção do projeto seria inventar uma decisão que ninguém tomou.
27
+ */
28
+ /** O mínimo para um hábito, e é o mesmo número que o resto da esteira usa. */
29
+ const AGREE = 3;
30
+ /** Propriedades que descrevem a MOLDURA de uma página, e não a aparência de uma peça. */
31
+ const SHELL = /^(minHeight|height|width|maxWidth|padding|paddingTop|paddingBottom|margin|marginTop|display|gridTemplateColumns|gap|position|top|zIndex|overflow|justifyContent|alignItems)$/;
32
+ /**
33
+ * As leis que as páginas de um escopo concordam, ou nada quando não concordam.
34
+ *
35
+ * `pages` é uma entrada por página: o que a RAIZ dela declara e quais peças ela compõe. Quem
36
+ * chama já leu as duas coisas para o relatório do portão, então isto não abre nenhum arquivo.
37
+ */
38
+ export function pageLaws(pages) {
39
+ if (pages.length < AGREE)
40
+ return [];
41
+ const values = new Map();
42
+ const composed = new Map();
43
+ for (const page of pages) {
44
+ for (const [prop, value] of Object.entries(page.root)) {
45
+ if (!SHELL.test(prop))
46
+ continue;
47
+ const key = `${prop}: ${value}`;
48
+ values.set(key, (values.get(key) ?? 0) + 1);
49
+ }
50
+ for (const name of new Set(page.composes))
51
+ composed.set(name, (composed.get(name) ?? 0) + 1);
52
+ }
53
+ const agreed = [...values.entries()]
54
+ .filter(([, n]) => n >= AGREE)
55
+ .sort((a, b) => b[1] - a[1]);
56
+ const laws = [];
57
+ /**
58
+ * A CASCA: o que a raiz de uma página deste projeto declara. Uma altura mínima de viewport e um
59
+ * padding de container não são a aparência de uma peça - são a moldura, e é o que falta a quem
60
+ * vai escrever a próxima página.
61
+ */
62
+ const shell = agreed.filter(([key]) => !/^(position|top|zIndex):/.test(key));
63
+ if (shell.length > 0) {
64
+ laws.push({
65
+ text: `A page of this project is ${shell
66
+ .slice(0, 4)
67
+ .map(([key]) => `\`${key}\``)
68
+ .join(", ")}` +
69
+ `${composed.size > 0 && (composed.get(topOf(composed)) ?? 0) >= AGREE ? ` and puts its content inside \`${topOf(composed)}\`` : ""}` +
70
+ `. Write the next one the same way, or change all of them.`,
71
+ applies: [],
72
+ kind: "implementation",
73
+ evidence: shell
74
+ .slice(0, 4)
75
+ .map(([key, n]) => `${key} in ${n} of ${pages.length} pages`)
76
+ .join("; "),
77
+ });
78
+ }
79
+ /**
80
+ * O CABEÇALHO FIXO: os três juntos são um padrão, e separados não dizem nada. Só vale como lei
81
+ * quando os três concordam, porque `position: sticky` sozinho pode ser qualquer coisa.
82
+ */
83
+ const sticky = agreed.filter(([key]) => /^(position: sticky|top: 0|zIndex:)/.test(key));
84
+ if (sticky.length >= 3) {
85
+ const header = [...composed.entries()]
86
+ .filter(([name, n]) => n >= AGREE && /header/i.test(name))
87
+ .sort((a, b) => b[1] - a[1])[0];
88
+ laws.push({
89
+ text: `The header of a page is ${sticky
90
+ .map(([key]) => `\`${key}\``)
91
+ .join(", ")}` +
92
+ `${header ? ` and it is \`${header[0]}\`` : ""}` +
93
+ `. A second sticky element at another z-index is how two headers end up fighting.`,
94
+ applies: [],
95
+ kind: "implementation",
96
+ evidence: sticky
97
+ .map(([key, n]) => `${key} in ${n} of ${pages.length} pages`)
98
+ .join("; "),
99
+ });
100
+ }
101
+ return laws;
102
+ }
103
+ /** O nome mais composto, que é o candidato a "onde o conteúdo vai". */
104
+ function topOf(composed) {
105
+ return [...composed.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? "";
106
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.131",
3
+ "version": "0.16.132",
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": {