synthesisui 0.16.127 → 0.16.129

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.
@@ -288,6 +288,15 @@ sketch) {
288
288
  if (measured?.behaviourProps &&
289
289
  Object.keys(measured.behaviourProps).length > 0)
290
290
  node_.behaviourProps = measured.behaviourProps;
291
+ /**
292
+ * O QUE ELE RECEBE E O QUE ELE REPETE, pela mesma consulta. As duas peças que fazem uma
293
+ * receita de TELA carregar a fiação de dados, e não só a casca - ver `SketchNode.passes`
294
+ * e `SketchNode.repeats`.
295
+ */
296
+ if (measured?.passes && Object.keys(measured.passes).length > 0)
297
+ node_.passes = measured.passes;
298
+ if (measured?.repeats)
299
+ node_.repeats = measured.repeats;
291
300
  }
292
301
  else {
293
302
  if (node.when)
@@ -1076,7 +1076,21 @@ export async function takeCensus(root, opts) {
1076
1076
  console.log(body(paint.faint(` and ${unreadableByComponent.length - 8} more components`)));
1077
1077
  }
1078
1078
  }
1079
- const coverageLines = describeCoverage(coverage);
1079
+ /**
1080
+ * QUANTOS DECIDEM ARRANJO EM VEZ DE APARÊNCIA - a mesma conta que a plataforma faz para
1081
+ * marcar `composition: true`: uma receita sem uma única declaração nunca prometeu cor.
1082
+ * Dito ANTES de importar, porque "36 de 271 saíram com aparência" faz parecer que 235 se
1083
+ * perderam, e eles não se perderam (04/08).
1084
+ */
1085
+ const composition = Object.values(looks).filter((look) => {
1086
+ const l = look;
1087
+ return (Object.keys(l.base ?? {}).length === 0 &&
1088
+ Object.keys(l.dark ?? {}).length === 0 &&
1089
+ Object.keys(l.states ?? {}).length === 0 &&
1090
+ Object.keys(l.parts ?? {}).length === 0 &&
1091
+ (l.layers ?? []).length === 0);
1092
+ }).length;
1093
+ const coverageLines = describeCoverage(coverage, composition);
1080
1094
  if (coverageLines.length > 0) {
1081
1095
  console.log("");
1082
1096
  console.log(section("What this version reads, on your files"));
@@ -1444,12 +1458,12 @@ function sayBrokenRefs(c) {
1444
1458
  const uses = broken.reduce((n, b) => n + b.count, 0);
1445
1459
  console.log("");
1446
1460
  console.log(section("These resolve to nothing"));
1447
- console.log(body(`${uses} reference${uses === 1 ? "" : "s"} to ${broken.length} token${broken.length === 1 ? "" : "s"} your CSS never declares. A ${paint.strong("var()")} with no declaration paints no colour - not a different colour.`));
1461
+ console.log(body(`${uses} reference${uses === 1 ? "" : "s"} to ${broken.length} token${broken.length === 1 ? "" : "s"} your CSS never declares. A ${paint.strong("var()")} with no declaration paints no colour - not a different colour. Where exactly one declared token differs by a namespace, the recipe now uses THAT one, so the look survives; the rest travel as the value you typed.`));
1448
1462
  console.log("");
1449
1463
  for (const b of broken.slice(0, 8)) {
1450
1464
  const where = `${b.count}× in ${b.files} file${b.files === 1 ? "" : "s"}`;
1451
1465
  console.log(body(b.meant
1452
- ? `${paint.strong(`var(${b.name})`)} ${paint.faint(where)} - you declare ${paint.strong(b.meant)}`
1466
+ ? `${paint.strong(`var(${b.name})`)} ${paint.faint(where)} - you declare ${paint.strong(b.meant)}, and that is what the recipe carries. Fix the class and the two agree.`
1453
1467
  : `${paint.strong(`var(${b.name})`)} ${paint.faint(`${where}, and nothing close is declared`)}`));
1454
1468
  }
1455
1469
  if (broken.length > 8) {
@@ -105,12 +105,30 @@ export function summarizeCoverage(counts, components, read, declarations) {
105
105
  * then names every shape that is not fully read, with the reason. No percentages without
106
106
  * the count behind them: "75%" of an unknown total is a marketing number.
107
107
  */
108
- export function describeCoverage(c) {
108
+ export function describeCoverage(c,
109
+ /**
110
+ * QUANTOS SÃO COMPOSIÇÃO, quando quem chama sabe - ver `componentRecipe.composition`.
111
+ *
112
+ * Numa biblioteca é zero e a frase não aparece. Num app é a maioria, e dizer só "36 de 271
113
+ * saíram com aparência" faz parecer que 235 se perderam. Eles não se perderam: decidem
114
+ * arranjo e fluxo, e é isso que a receita deles carrega. Medido no dashboard dele: 235 de
115
+ * 271 (04/08).
116
+ */
117
+ composition) {
109
118
  if (c.components === 0)
110
119
  return [];
111
120
  const lines = [];
112
121
  const pct = Math.round((c.read / c.components) * 100);
113
122
  lines.push(`${c.read} of ${c.components} components came out with a look - ${pct}%, ${c.declarations} declarations in total. This is measured on your files, not an estimate.`);
123
+ if (composition && composition > 0) {
124
+ lines.push(
125
+ /**
126
+ * DIZ O QUE FOI MEDIDO, e não o que a plataforma vai concluir. Este número é "não tem
127
+ * look próprio nos seus arquivos"; a receita final é decidida depois, com a paleta em
128
+ * mão, e as duas contas não são a mesma. Prometer a segunda aqui seria inventar precisão.
129
+ */
130
+ ` and ${composition} have no look of their own in your files - a screen that composes others, in a grid, passing data down. Those arrive as COMPOSITION recipes: the tree, the slots, and what each child receives, with no palette to grade. Nothing was lost; it is a different kind of recipe.`);
131
+ }
114
132
  for (const { shape, files, examples } of c.counts) {
115
133
  const share = Math.round((files / c.components) * 100);
116
134
  const head = ` ${files} (${share}%) ${shape.label}`;
@@ -351,6 +351,82 @@ export function conditionalPropsOf(body) {
351
351
  out[m[1]] = m[2].trim();
352
352
  return Object.keys(out).length > 0 ? out : null;
353
353
  }
354
+ /**
355
+ * Toda prop de um elemento, com o valor como eles escreveram - ver `SketchNode.passes`.
356
+ *
357
+ * Quatro formas atravessam, porque as quatro são evidência do que a tela liga: a string
358
+ * literal (a cópia dele), o identificador ou caminho (a fiação), o literal numérico ou
359
+ * booleano, e a expressão composta curta. Duas não atravessam: função inline e objeto inline.
360
+ */
361
+ export function passedProps(body,
362
+ /** O que `behaviourProps` já levou, para cada fato viver num lugar só. */
363
+ taken) {
364
+ const out = {};
365
+ const SKIP = /^(className|class|key|ref|style|children)$/;
366
+ for (const m of body.matchAll(/(\w+)=(?:"([^"]{0,80})"|'([^']{0,80})'|\{([^{}]{1,80})\})/g)) {
367
+ const prop = m[1];
368
+ if (SKIP.test(prop) || taken?.[prop] || out[prop])
369
+ continue;
370
+ const literal = m[2] ?? m[3];
371
+ if (literal != null) {
372
+ out[prop] = literal;
373
+ continue;
374
+ }
375
+ const value = (m[4] ?? "").replace(/\s+/g, " ").trim();
376
+ // Um corpo de função é código que chama o que a receita não tem; um objeto é estilo.
377
+ if (!value || /=>|\bfunction\b/.test(value) || value.startsWith("{"))
378
+ continue;
379
+ out[prop] = value;
380
+ }
381
+ return Object.keys(out).length > 0 ? out : null;
382
+ }
383
+ /**
384
+ * A expressão que um `map` percorre, quando este elemento está dentro de um - ver `repeats`.
385
+ *
386
+ * Mesma forma de `guardAt`: uma varredura para trás até a chamada que envolve este offset, e
387
+ * o alvo é o que vem antes do `.map(`. Nada é avaliado; a expressão é evidência de que este nó
388
+ * é uma LISTA.
389
+ */
390
+ export function repeatOf(source, at) {
391
+ const MAP = /([A-Za-z_$][\w$.?[\]]{0,60})\s*\.map\s*\(/g;
392
+ MAP.lastIndex = 0;
393
+ let found = null;
394
+ for (const m of source.matchAll(MAP)) {
395
+ const open = (m.index ?? 0) + m[0].length - 1;
396
+ if (open > at)
397
+ break;
398
+ const end = closeOf(source, open);
399
+ if (end === -1 || end <= at)
400
+ continue;
401
+ found = m[1].replace(/\?$/, "");
402
+ }
403
+ return found;
404
+ }
405
+ /** O índice do fecha-parêntese que corresponde ao abre em `open`, ou -1. */
406
+ function closeOf(source, open) {
407
+ let depth = 0;
408
+ let quote = "";
409
+ for (let i = open; i < source.length; i++) {
410
+ const c = source[i];
411
+ if (quote) {
412
+ if (c === "\\")
413
+ i += 1;
414
+ else if (c === quote)
415
+ quote = "";
416
+ continue;
417
+ }
418
+ if (c === '"' || c === "'" || c === "`")
419
+ quote = c;
420
+ else if (c === "(")
421
+ depth += 1;
422
+ else if (c === ")") {
423
+ depth -= 1;
424
+ if (depth === 0)
425
+ return i;
426
+ }
427
+ }
428
+ return -1;
429
+ }
354
430
  export function sketchOf(source, name) {
355
431
  /**
356
432
  * READ THE IMPORTS BEFORE SLICING - they live at the top of the file, and the
@@ -433,6 +509,16 @@ export function sketchOf(source, name) {
433
509
  const conditional = conditionalPropsOf(event.body);
434
510
  if (conditional)
435
511
  node.behaviourProps = conditional;
512
+ /**
513
+ * O QUE ELE RECEBE E O QUE ELE REPETE - ver `passes` e `repeats`. As duas peças que faltavam
514
+ * para a receita de uma TELA carregar a fiação, e não só a casca.
515
+ */
516
+ const given = passedProps(event.body, conditional);
517
+ if (given)
518
+ node.passes = given;
519
+ const list = repeatOf(source, event.at);
520
+ if (list)
521
+ node.repeats = list;
436
522
  // What has to be true for this element to exist at all - see `guardAt`.
437
523
  const guard = guardAt(source, event.at);
438
524
  if (guard) {
@@ -1179,10 +1179,44 @@ function resolveArbitrary(inner, declared) {
1179
1179
  return /[<>{}@;]/.test(raw) ? null : { value: raw };
1180
1180
  }
1181
1181
  const name = v[1];
1182
- if (!declared.has(name))
1183
- return { value: raw };
1184
- return { value: tokenRefFor(name), token: name };
1182
+ if (declared.has(name))
1183
+ return { value: tokenRefFor(name), token: name };
1184
+ /**
1185
+ * A `var()` THEY NEVER DECLARED, WHEN THEY DECLARED IT UNDER ITS NAMESPACE.
1186
+ *
1187
+ * `bg-[var(--ocean-200)]` where the theme declares `--color-ocean-200` is their own
1188
+ * defect - Tailwind v4 namespaces the property and they wrote the bare name - and it is
1189
+ * ALSO unambiguous: exactly one declared token differs from it by a namespace. The census
1190
+ * already computes that answer for the report (`brokenRefs.meant`); carrying the broken
1191
+ * name into the recipe instead meant the declaration reached the document and painted
1192
+ * nothing, because `var(--ocean-200)` exists in their stylesheet and in none of ours.
1193
+ *
1194
+ * Measured on his library (test50, dono, 04/08): 6 declarations across 5 components, all
1195
+ * colours, all dead - and invisible to the ref validator, which only ever looks for `{…}`.
1196
+ *
1197
+ * A name that matches NO namespace stays the literal it was: that one is broken in their
1198
+ * repo too, and inventing a target would be the guess. It travels as the value they typed
1199
+ * and is reported by `brokenRefs`.
1200
+ */
1201
+ const meant = MEANT_NAMESPACES.map((ns) => `--${ns}-${name.slice(2)}`).find((candidate) => declared.has(candidate));
1202
+ if (meant)
1203
+ return { value: tokenRefFor(meant), token: meant };
1204
+ return { value: raw };
1185
1205
  }
1206
+ /**
1207
+ * The namespaces a bare name could have been written under, in the order Tailwind v4 uses
1208
+ * them. Mirrors `NAMESPACES` in `broken-refs.ts`, which computes the same answer for the
1209
+ * report - two readers of one fact, and the fact is "exactly one declared token differs
1210
+ * from this by a prefix".
1211
+ */
1212
+ const MEANT_NAMESPACES = [
1213
+ "color",
1214
+ "spacing",
1215
+ "radius",
1216
+ "shadow",
1217
+ "font",
1218
+ "text",
1219
+ ];
1186
1220
  /**
1187
1221
  * A declared custom property, as a token ref in the document's own namespace.
1188
1222
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.127",
3
+ "version": "0.16.129",
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": {