synthesisui 0.16.154 → 0.16.155

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.
@@ -857,6 +857,16 @@ export async function takeCensus(root, opts) {
857
857
  ...(v.unslotted.length > 0
858
858
  ? { unplaced: [...new Set(v.unslotted)] }
859
859
  : {}),
860
+ /**
861
+ * AS NOTAS SÃO OUTRA LISTA - ver `notes` em `variant-read.ts`.
862
+ *
863
+ * Um layer que FICOU e precisa de um `data-*` não é um look sem lugar: ele está na receita.
864
+ * As duas viajavam em `unplaced`, e a tela de causas cobrava conserto de 16 linhas que
865
+ * diziam, no próprio texto, que nada tinha sido perdido (dono, 06/08).
866
+ */
867
+ ...(v.notes.length > 0
868
+ ? { renderNotes: [...new Set(v.notes)] }
869
+ : {}),
860
870
  ...(Object.keys(v.axes).length > 0 ? { declaredAxes: v.axes } : {}),
861
871
  ...(Object.keys(defaults).length > 0 ? { defaults } : {}),
862
872
  };
@@ -1253,6 +1263,30 @@ export async function takeCensus(root, opts) {
1253
1263
  console.log(body(paint.faint(` and ${unplacedByComponent.length - 8} more components`)));
1254
1264
  }
1255
1265
  }
1266
+ /**
1267
+ * E AS NOTAS DE RENDERIZAÇÃO, que são o oposto de uma perda.
1268
+ *
1269
+ * O layer ESTÁ na receita: o que falta é um atributo no elemento para ele aplicar. Até 06/08 estas
1270
+ * linhas viajavam junto com as de "não achei onde pôr" - 16 de 33 num repo real -, e a tela de causas
1271
+ * cobrava conserto de quem não tinha o que consertar. Separadas, elas viram o que sempre foram: uma
1272
+ * instrução para quem for renderizar.
1273
+ */
1274
+ const notesByComponent = Object.entries(looks)
1275
+ .filter(([, look]) => (look.renderNotes ?? []).length > 0)
1276
+ .map(([component, look]) => ({
1277
+ component,
1278
+ lines: look.renderNotes ?? [],
1279
+ }));
1280
+ if (notesByComponent.length > 0) {
1281
+ const total = notesByComponent.reduce((n, u) => n + u.lines.length, 0);
1282
+ console.log("");
1283
+ console.log(section("Looks that are in the recipe and need an attribute"));
1284
+ console.log(body(`${total} decision${total === 1 ? "" : "s"} across ${notesByComponent.length} component${notesByComponent.length === 1 ? "" : "s"} were read and KEPT - they apply once the element carries the attribute the line names:`));
1285
+ for (const u of notesByComponent.slice(0, 6))
1286
+ console.log(body(paint.faint(` ${u.component}: ${u.lines[0]}`)));
1287
+ if (notesByComponent.length > 6)
1288
+ console.log(body(paint.faint(` and ${notesByComponent.length - 6} more components`)));
1289
+ }
1256
1290
  /**
1257
1291
  * THE DROPS, SAID OUT LOUD. `p-spacing-sm` on a real menu popup read as nothing and
1258
1292
  * the popup shipped with no padding, invisibly - "if the compiler drops them, the drop
@@ -79,9 +79,16 @@ export function conditionOf(modifiers) {
79
79
  const out = {};
80
80
  for (const raw of modifiers) {
81
81
  if (raw === "dark") {
82
- // The dark half has its own home in the transcription and no slot inside a
83
- // layer. Saying so beats writing the light value into a dark rule.
84
- out.lost = true;
82
+ /**
83
+ * O ESQUEMA É UMA CONDIÇÃO, e o layer tem slot para ele.
84
+ *
85
+ * O comentário anterior dizia "the dark half has its own home in the transcription and no slot
86
+ * inside a layer" - verdade quando foi escrito, e falso desde que o layer passou a carregar
87
+ * `scheme`. A "casa na transcrição" só existe para a classe INCONDICIONAL: uma `dark:` dentro de
88
+ * um record por variante passava por aqui e morria, e é assim que 9 utilities do Button e do
89
+ * Toggle sumiram com uma linha de aviso e outras 66 sem nenhuma (dono, 06/08).
90
+ */
91
+ out.scheme = "dark";
85
92
  continue;
86
93
  }
87
94
  const relational = RELATIONAL.exec(raw);
@@ -121,7 +128,8 @@ function keyOf(c, variant) {
121
128
  .sort(([a], [b]) => a.localeCompare(b))
122
129
  .map(([k, o]) => `${k}=${o}`)
123
130
  .join("&");
124
- return `${v}|${c.state ?? ""}|${c.within ?? ""}|${c.at ?? ""}`;
131
+ /** O ESQUEMA ENTRA NA CHAVE: sem ele, `bg-white` e `dark:bg-black` caem no mesmo layer e um vence. */
132
+ return `${v}|${c.state ?? ""}|${c.within ?? ""}|${c.at ?? ""}|${c.scheme ?? ""}`;
125
133
  }
126
134
  /**
127
135
  * Split a class list into the base and the conditional layers it contains.
@@ -157,12 +165,13 @@ export function splitByCondition(classes, variant) {
157
165
  const key = keyOf(cond, variant);
158
166
  const layer = layers.get(key) ??
159
167
  {
160
- ...(variant || cond.state || cond.within
168
+ ...(variant || cond.state || cond.within || cond.scheme
161
169
  ? {
162
170
  when: {
163
171
  ...(variant ? { variant } : {}),
164
172
  ...(cond.state ? { state: cond.state } : {}),
165
173
  ...(cond.within ? { within: cond.within } : {}),
174
+ ...(cond.scheme ? { scheme: cond.scheme } : {}),
166
175
  },
167
176
  }
168
177
  : {}),
@@ -623,6 +632,31 @@ function objectFields(value) {
623
632
  return [];
624
633
  return topLevelEntries(trimmed.slice(1, end - 1));
625
634
  }
635
+ /**
636
+ * TEM A FORMA DE UMA LISTA DE CLASSES?
637
+ *
638
+ * Usada só no resíduo: quando nenhuma prop de classe consome o campo, esta é a última pergunta antes de
639
+ * afirmar que ele é estilo. Ela é deliberadamente TÍMIDA - dois tokens no mínimo, tudo minúsculo, nada
640
+ * começando com `#`:
641
+ *
642
+ * "px-2 py-2 gap-0.5" 3 tokens, minusculo -> lista de classes
643
+ * "overline" 1 token -> silencio (e o `variant` de um <Text>)
644
+ * "body-md" 1 token -> silencio, e indistinguivel de `text-xs`
645
+ * "#1394DC" comeca com # -> silencio (cor de grafico)
646
+ * "Waiting" maiuscula -> silencio (conteudo)
647
+ *
648
+ * Ela erra CALANDO sobre uma classe única e não-consumida, e esse é o lado certo de errar: uma linha
649
+ * inventada num relatório custa a confiança na linha seguinte, que estava certa.
650
+ */
651
+ function looksLikeClassList(value) {
652
+ const text = value.trim().replace(/^[`'"]|[`'"]$/g, "");
653
+ if (!text || text.startsWith("#"))
654
+ return false;
655
+ const tokens = text.split(/\s+/).filter(Boolean);
656
+ if (tokens.length < 2)
657
+ return false;
658
+ return tokens.every((t) => /^[a-z0-9[\]()/:._!-]+$/.test(t));
659
+ }
626
660
  /**
627
661
  * THE STATIC CLASSES OF THE `className` CALL THAT CONSUMES A LOOKUP.
628
662
  *
@@ -636,12 +670,34 @@ function objectFields(value) {
636
670
  * local first (`const config = statusConfig[status]`); a record whose value is a plain
637
671
  * string has no field, and there the indexing expression is what is written at the call
638
672
  * site (`variantColors[variant]`), so the SYMBOL is the reliable half.
673
+ *
674
+ * E DUAS COISAS QUE FALTAVAM, medidas em 8 de 8 casos de um repo real (dono, 06/08) - nenhum deles
675
+ * escrevia `simbolo.campo` no ponto de consumo:
676
+ *
677
+ * 1. A PROP NÃO É SÓ `className`. O MetricCard deles passa o campo em
678
+ * `contentClassName={cn("p-0 flex flex-col", currentSize.container)}` - é o Card deles que recebe
679
+ * a classe por outro nome, e exigir `className` literal descartava a linha inteira.
680
+ * 2. O CAMPO PODE VIR DESESTRUTURADO. O CircularProgress escreve
681
+ * `const { diameter, strokeWidth, fontSize } = sizeConfig[size]` e depois usa `fontSize` NU dentro
682
+ * do `cn()` - sem ponto nenhum, então a forma `<algo>.campo` não casa.
683
+ *
684
+ * As duas juntas são a diferença entre colocar o look no elemento certo e reportá-lo como "não há
685
+ * elemento onde pôr".
639
686
  */
640
687
  function callSiteClasses(source, symbol, field) {
688
+ /** Desestruturado da própria tabela: `const { fontSize } = sizeConfig[size]`. */
689
+ const destructured = field != null &&
690
+ new RegExp(`\\{[^}]*\\b${field}\\b[^}]*\\}\\s*=\\s*${symbol}\\s*\\[`).test(source);
641
691
  const use = field
642
- ? new RegExp(`\\b[A-Za-z_$][\\w$]*(?:\\[[^\\]]*\\])?\\.${field}\\b`)
692
+ ? new RegExp(destructured
693
+ ? `\\b${field}\\b`
694
+ : `\\b[A-Za-z_$][\\w$]*(?:\\[[^\\]]*\\])?\\.${field}\\b`)
643
695
  : new RegExp(`\\b${symbol}\\s*\\[`);
644
- for (const m of source.matchAll(/className=\{\s*(?:[A-Za-z_$][\w$]*\.)?(?:cn|clsx|classNames|cx|twMerge|twJoin|classcat)\(/g)) {
696
+ /**
697
+ * `className` E QUALQUER `*ClassName`, que é como um componente deles recebe a classe de um filho.
698
+ * A borda `(?:^|[^A-Za-z])` existe para não casar no meio de um identificador maior.
699
+ */
700
+ for (const m of source.matchAll(/(?:^|[^A-Za-z])[A-Za-z]*[Cc]lassName=\{\s*(?:[A-Za-z_$][\w$]*\.)?(?:cn|clsx|classNames|cx|twMerge|twJoin|classcat)\(/g)) {
645
701
  const open = (m.index ?? 0) + m[0].length - 1;
646
702
  const end = endOf(source, open);
647
703
  if (end === -1)
@@ -787,11 +843,23 @@ unattributed) {
787
843
  const on = callSiteClasses(clean, symbol, field.key);
788
844
  if (!on) {
789
845
  /**
790
- * NOWHERE TO PUT IT IS NOT THE ROOT. A field no `className` claims is a
791
- * decision we cannot place, and placing it on the component would paint
792
- * the wrong element - which is the whole bug this reads around.
846
+ * NOWHERE TO PUT IT IS NOT THE ROOT. A field no class prop claims is a decision we cannot
847
+ * place, and placing it on the component would paint the wrong element - which is the whole
848
+ * bug this reads around.
849
+ *
850
+ * MAS SÓ SE FOR UMA LISTA DE CLASSES. Medido em 8 de 8 casos de um repo real (dono, 06/08),
851
+ * SEIS dos campos não eram estilo nenhum: `labelVariant: "overline"` e `valueVariant:
852
+ * "body-md"` são o `variant` de um `<Text>`, `text: "Waiting"` é conteúdo, e
853
+ * `start: "#1394DC"` é a cor de um gráfico. Chamá-los de "look que não pôde ser colocado" é
854
+ * inventar um problema, e foi o que fez uma causa de 33 ocorrências ter 6 falsos positivos.
855
+ *
856
+ * A regra é o que sobra depois do consumo: se NENHUMA prop de classe consome o campo E o
857
+ * valor não tem a forma de uma lista de classes, a gente não sabe que é estilo - e o que não
858
+ * se sabe não se afirma. Um valor de um token só fica em silêncio de propósito: `overline` e
859
+ * `text-xs` são indistinguíveis pela forma, e o consumo era a única prova.
793
860
  */
794
- unattributed?.push(`\`${symbol}.${field.key}\` is a field of a per-element table that no \`className\` call consumes, so there is no element to put it on`);
861
+ if (looksLikeClassList(field.value))
862
+ unattributed?.push(`\`${symbol}.${field.key}\` is a field of a per-element table that no class prop consumes, so there is no element to put it on`);
795
863
  continue;
796
864
  }
797
865
  const per = splitByCondition(classes, { [axis]: option.key });
@@ -856,6 +924,7 @@ export function transcribeVariants(source, declared) {
856
924
  const reads = readVariants(source);
857
925
  const fromRecords = {};
858
926
  const unslotted = [];
927
+ const notes = [];
859
928
  const inline = [
860
929
  ...readInlineConditions(source),
861
930
  // The two shapes the enumeration found to be five times more common than `cva`.
@@ -925,7 +994,11 @@ export function transcribeVariants(source, declared) {
925
994
  continue;
926
995
  }
927
996
  const attr = axis.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
928
- unslotted.push(named.length === 0
997
+ /**
998
+ * NOTA, NÃO PERDA. O layer está na receita; o que falta é um atributo no elemento para ele aplicar.
999
+ * Enquanto isto viajava em `unslotted`, a tela contava 16 destes como pendência de conserto.
1000
+ */
1001
+ notes.push(named.length === 0
929
1002
  ? `\`${axis}\` gates a look and is a boolean rather than an axis - the layer is kept, and \`data-${attr}\` has to be set for it to apply`
930
1003
  : `\`${axis}\` gates a look on one option (\`${named[0]}\`) - one option is not an axis, so the layer is kept and \`data-${attr}="${named[0]}"\` has to be set for it to apply`);
931
1004
  }
@@ -957,5 +1030,6 @@ export function transcribeVariants(source, declared) {
957
1030
  layers,
958
1031
  base: transcribe(baseClasses, declared),
959
1032
  unslotted,
1033
+ notes,
960
1034
  };
961
1035
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.154",
3
+ "version": "0.16.155",
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": {