synthesisui 0.16.321 → 0.16.322

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.
@@ -37,6 +37,7 @@ import { MUI_DEFAULT_SPACING, readStyledComponents, spacingOf, } from "../doctor
37
37
  import { buildTable } from "../doctor/tokens.js";
38
38
  import { definitionSpan, parseClass, readInlineStyle, rootClasses, rootTag, transcribe, } from "../doctor/transcribe.js";
39
39
  import { accountClasses, invariantHolds, sumAccounts, } from "../doctor/value-ledger.js";
40
+ import { variantCallsIn } from "../doctor/variant-calls.js";
40
41
  import { transcribeVariants } from "../doctor/variant-read.js";
41
42
  import { frontierKind, packageRoot } from "../frontier-kind.js";
42
43
  import { keyframesInSheets } from "../global-keyframes.js";
@@ -787,6 +788,10 @@ export async function takeCensus(root, opts) {
787
788
  * variant tables) belong to the PRIMARY definition; the root classes and
788
789
  * the root tag are per component, scoped by name.
789
790
  */
791
+ /** Os componentes deste arquivo - quem mora dentro do span de outro é recortado dele. */
792
+ const siblings = found.map((f) => f.name);
793
+ /** As tabelas que ESTE ARQUIVO declara - o conjunto contra o qual um consumo é casado. */
794
+ const declaredTables = new Set(transcribeVariants(src, declaredValues, globalClasses).reads.map((r) => r.symbol));
790
795
  for (const extra of found.slice(1)) {
791
796
  const ecls = rootClasses(src, extra.name);
792
797
  const et = transcribe(ecls, declaredValues);
@@ -800,6 +805,24 @@ export async function takeCensus(root, opts) {
800
805
  // sketch belonged to the first component and the second read an empty
801
806
  // one (not-expressed.md, dono, 01/08).
802
807
  const esketch = sketchOf(src, extra.name);
808
+ /**
809
+ * E AS TABELAS QUE ELE CHAMA (T42a, 27/08) - a família que faltava aqui.
810
+ *
811
+ * Este caminho nunca viu `transcribeVariants`, e o comentário abaixo dizia isso em voz
812
+ * alta: "roda para o primeiro componente do arquivo, então aqui não há tabela de
813
+ * variantes". Era limitação descrita, não decisão - e por causa dela o `TooltipContent`
814
+ * e o `Layer` chegavam sem uma camada sequer enquanto o vizinho ficava com os eixos
815
+ * deles.
816
+ *
817
+ * O CSS module continua FORA daqui de propósito: uma folha `.module.css` é do arquivo, e
818
+ * `parts`/`tree` seguem sendo montados só no caminho primário. A fronteira é a origem do
819
+ * dado - tabela pertence a quem chama, folha pertence ao arquivo.
820
+ */
821
+ const ev = transcribeVariants(src, declaredValues, globalClasses, {
822
+ tables: variantCallsIn(src, extra.name, declaredTables, siblings),
823
+ /** Só as tabelas dele: as formas de arquivo seguem sendo do primeiro exportado. */
824
+ fileWideShapes: false,
825
+ });
803
826
  const esize = Object.keys(et.base).length +
804
827
  Object.keys(et.dark).length +
805
828
  Object.keys(et.states).length;
@@ -825,6 +848,25 @@ export async function takeCensus(root, opts) {
825
848
  }
826
849
  : {}),
827
850
  ...et,
851
+ /** A base dele soma o que a tabela que ELE chama traz - nunca a do vizinho. */
852
+ base: { ...ev.base.base, ...et.base },
853
+ ...(Object.keys({ ...ev.base.states, ...et.states }).length > 0
854
+ ? { states: { ...ev.base.states, ...et.states } }
855
+ : {}),
856
+ ...(ev.layers.length > 0 ? { layers: ev.layers } : {}),
857
+ ...(ev.raw.length > 0 ? { rawLayers: ev.raw } : {}),
858
+ ...(Object.keys(ev.axes).length > 0
859
+ ? { declaredAxes: { ...extra.axes, ...ev.axes } }
860
+ : {}),
861
+ ...(Object.keys(ev.defaults).length > 0
862
+ ? { defaults: ev.defaults }
863
+ : {}),
864
+ ...(ev.unslotted.length > 0
865
+ ? { unplaced: [...new Set(ev.unslotted)] }
866
+ : {}),
867
+ ...(ev.notes.length > 0
868
+ ? { renderNotes: [...new Set(ev.notes)] }
869
+ : {}),
828
870
  ...(etag ? { rootTag: etag } : {}),
829
871
  ...rootPackage(etag, esketch),
830
872
  ...rootBehaviour(etag, esketch),
@@ -858,7 +900,23 @@ export async function takeCensus(root, opts) {
858
900
  * for the result, which is the "valid and unread" failure this pipeline has
859
901
  * paid for twice already.
860
902
  */
861
- const v = transcribeVariants(src, declaredValues, globalClasses);
903
+ /**
904
+ * A TRANSCRIÇÃO É DO COMPONENTE QUE CONSOME A TABELA, não do arquivo (T42a, 27/08).
905
+ *
906
+ * Antes: `transcribeVariants(src, …)` fundia TODAS as tabelas do arquivo e o resultado era
907
+ * gravado aqui, no primeiro componente exportado. Num arquivo com dois, o segundo perdia
908
+ * tudo e o primeiro recebia o que não era dele - o `Tooltip` chegou ao censo com o eixo
909
+ * `side`, que pertence ao `TooltipContent`.
910
+ *
911
+ * `variantCallsIn` responde quais tabelas ESTE componente chama, pelo span da definição
912
+ * dele e só dentro do `className`. Um arquivo de um componente só - que é o caso do
913
+ * `Button` e de todo app do corpus - devolve exatamente o mesmo resultado de antes.
914
+ */
915
+ const v = transcribeVariants(src, declaredValues, globalClasses, {
916
+ tables: variantCallsIn(src, found[0].name, declaredTables, siblings),
917
+ /** O primeiro exportado continua levando as formas de arquivo, como hoje. */
918
+ fileWideShapes: true,
919
+ });
862
920
  /**
863
921
  * THE RESTING OPTION, from the definition itself. `cva` says it in
864
922
  * `defaultVariants`; a lookup-record component says it in the destructuring -
@@ -0,0 +1,117 @@
1
+ import { definitionSpan } from "./transcribe.js";
2
+ /** Um literal que uma chamada pode travar: `true`, `false`, `"sm"`, `'md'`. */
3
+ const LITERAL = /^(?:true|false|"[^"]*"|'[^']*'|`[^`$]*`)$/;
4
+ /** Do delimitador de abertura até o que o fecha, respeitando aninhamento. */
5
+ function through(source, open, pair = "{}") {
6
+ let depth = 0;
7
+ for (let i = open; i < source.length; i += 1) {
8
+ const c = source[i];
9
+ if (c === pair[0])
10
+ depth += 1;
11
+ else if (c === pair[1]) {
12
+ depth -= 1;
13
+ if (depth === 0)
14
+ return i;
15
+ }
16
+ }
17
+ return -1;
18
+ }
19
+ /**
20
+ * Os argumentos que a chamada TRAVA. `{ size, ring: true }` deixa `size` aberto - é o prop do
21
+ * componente viajando adiante - e trava `ring`, que é a diferença entre um eixo e uma decisão.
22
+ */
23
+ function fixedArgs(args) {
24
+ const brace = args.indexOf("{");
25
+ if (brace === -1)
26
+ return [];
27
+ const end = through(args, brace);
28
+ if (end === -1)
29
+ return [];
30
+ const parts = [];
31
+ let depth = 0;
32
+ let start = brace + 1;
33
+ for (let i = brace + 1; i <= end; i += 1) {
34
+ const c = args[i];
35
+ if (c === "{" || c === "[" || c === "(")
36
+ depth += 1;
37
+ else if (c === "}" || c === "]" || c === ")") {
38
+ if (i === end) {
39
+ parts.push(args.slice(start, i));
40
+ break;
41
+ }
42
+ depth -= 1;
43
+ }
44
+ else if (c === "," && depth === 0) {
45
+ parts.push(args.slice(start, i));
46
+ start = i + 1;
47
+ }
48
+ }
49
+ const out = [];
50
+ for (const part of parts) {
51
+ const colon = part.indexOf(":");
52
+ /** Sem `:` é atalho de objeto (`{ size }`): o prop viaja e o eixo continua aberto. */
53
+ if (colon === -1)
54
+ continue;
55
+ const axis = part.slice(0, colon).trim();
56
+ const value = part.slice(colon + 1).trim();
57
+ if (!axis || !LITERAL.test(value))
58
+ continue;
59
+ out.push({ axis, option: value.replace(/^["'`]|["'`]$/g, "") });
60
+ }
61
+ return out;
62
+ }
63
+ /**
64
+ * As tabelas que ESTE componente consome, dentro do `className` dele.
65
+ *
66
+ * `symbols` é o conjunto que o arquivo declara - recebê-lo em vez de descobrir aqui é o que impede
67
+ * esta função de casar uma chamada qualquer (`cn`, `clsx`, um helper dele) com uma tabela.
68
+ */
69
+ export function variantCallsIn(source, component, symbols, siblings = []) {
70
+ const span = definitionSpan(source, component);
71
+ if (!span)
72
+ return [];
73
+ /**
74
+ * O CORPO DELE, MENOS O DE QUEM MORA DENTRO. Um componente pode ser declarado dentro de outro, e
75
+ * então o span do de fora contém o do de dentro - herdar dali seria recriar a atribuição errada
76
+ * um nível abaixo. Recortar os irmãos é o que impede isso, e é por lista de nomes, nunca por
77
+ * heurística de posição.
78
+ */
79
+ const cuts = siblings
80
+ .filter((name) => name !== component)
81
+ .map((name) => definitionSpan(source, name))
82
+ .filter((s) => s !== null && s.from > span.from && s.to <= span.to)
83
+ .sort((a, b) => a.from - b.from);
84
+ let body = "";
85
+ let at = span.from;
86
+ for (const cut of cuts) {
87
+ if (cut.from > at)
88
+ body += source.slice(at, cut.from);
89
+ at = Math.max(at, cut.to);
90
+ }
91
+ body += source.slice(at, span.to);
92
+ const out = [];
93
+ const seen = new Set();
94
+ for (const symbol of symbols) {
95
+ /**
96
+ * TODA CHAMADA DENTRO DO CORPO DELE, não só a que está escrita no `className`.
97
+ *
98
+ * A primeira versão exigia a chamada dentro do atributo, e o `Card` do cliente monta as classes
99
+ * numa variável antes - `const classes = cn(cardVariants({…}), …)` e depois `className={classes}`.
100
+ * Ele perdia os cinco eixos dele de uma vez. O que associa é o SPAN da definição; onde a linha
101
+ * mora dentro dele é estilo de quem escreveu.
102
+ */
103
+ for (const call of body.matchAll(new RegExp(`\\b${symbol}\\s*\\(`, "g"))) {
104
+ const paren = (call.index ?? 0) + call[0].length - 1;
105
+ const close = through(body, paren, "()");
106
+ if (close === -1)
107
+ continue;
108
+ const key = `${symbol}@${paren}`;
109
+ if (seen.has(key))
110
+ continue;
111
+ seen.add(key);
112
+ /** Só o que está ENTRE os parênteses da chamada - um `{` de depois é de outra linha. */
113
+ out.push({ symbol, fixed: fixedArgs(body.slice(paren + 1, close)) });
114
+ }
115
+ }
116
+ return out;
117
+ }
@@ -1157,33 +1157,76 @@ export function transcribeVariants(source, declared,
1157
1157
  * Sem default de propósito. Um `Map` vazio é a resposta de quem não tem folha global, e ela se
1158
1158
  * escreve em voz alta; um parâmetro que se pode esquecer é o defeito que isto está desfazendo.
1159
1159
  */
1160
- globals) {
1161
- const reads = readVariants(source);
1160
+ globals,
1161
+ /**
1162
+ * O QUE ESTE COMPONENTE CONSOME - e a razão de este parâmetro existir.
1163
+ *
1164
+ * Sem ele, a transcrição é do ARQUIVO: todas as tabelas fundidas num resultado só, que o import
1165
+ * gravava no primeiro componente exportado. Num arquivo com dois, o segundo perdia tudo e o
1166
+ * primeiro recebia o que não era dele (T42a, medido em 27/08).
1167
+ *
1168
+ * `undefined` mantém o comportamento de arquivo, que é o certo para quem pergunta sobre o
1169
+ * arquivo. Uma lista restringe às tabelas que aquele componente CHAMA, e aplica o que a chamada
1170
+ * dele trava - `avatarVariants({ size, ring: true })` fixa `ring`, então a camada do `ring` é
1171
+ * base dele e o eixo sai da lista de eixos abertos.
1172
+ */
1173
+ consumed) {
1174
+ const all = readVariants(source);
1175
+ /**
1176
+ * SÓ AS TABELAS QUE ELE CHAMA. Um componente que não chama nenhuma fica sem camadas de variante -
1177
+ * que é a verdade sobre ele, e não um efeito de ser o segundo do arquivo.
1178
+ */
1179
+ const wanted = consumed
1180
+ ? new Map(consumed.tables.map((c) => [c.symbol, c.fixed]))
1181
+ : null;
1182
+ const reads = wanted ? all.filter((r) => wanted.has(r.symbol)) : all;
1162
1183
  const fromRecords = {};
1163
1184
  const unslotted = [];
1164
1185
  const notes = [];
1165
- const inline = [
1166
- ...readInlineConditions(source),
1167
- // The two shapes the enumeration found to be five times more common than `cva`.
1168
- ...readLookupRecords(source, fromRecords, unslotted),
1169
- ...readClassTernaries(source),
1170
- ];
1186
+ const fileWide = consumed ? consumed.fileWideShapes : true;
1187
+ const inline = fileWide
1188
+ ? [
1189
+ ...readInlineConditions(source),
1190
+ // The two shapes the enumeration found to be five times more common than `cva`.
1191
+ ...readLookupRecords(source, fromRecords, unslotted),
1192
+ ...readClassTernaries(source),
1193
+ ]
1194
+ : [];
1171
1195
  /**
1172
1196
  * O record que um `style=` consome - valores prontos, sem tabela de escala no meio.
1173
1197
  * Entram direto nas camadas finais (e registram o eixo), nunca em `raw`: `raw` é
1174
1198
  * matéria-prima de reinterpretação por classes, e estes já são o valor final.
1175
1199
  */
1176
- const styleRecords = readStyleRecords(source, fromRecords);
1200
+ const styleRecords = fileWide ? readStyleRecords(source, fromRecords) : [];
1177
1201
  const axes = {};
1178
1202
  const defaults = {};
1179
1203
  const allLayers = [];
1180
1204
  const baseClasses = [];
1181
1205
  for (const read of reads) {
1206
+ /**
1207
+ * O QUE A CHAMADA DELE TRAVA. `avatarVariants({ size, ring: true })` diz que este componente
1208
+ * SEMPRE tem `ring` - então a camada daquele eixo aplica sempre (é base dele) e `ring` deixa de
1209
+ * ser um eixo que alguém escolhe. Sem isto, o `AvatarStack` herdaria um eixo `ring` que o
1210
+ * código dele não oferece a ninguém.
1211
+ */
1212
+ const fixed = new Map((wanted?.get(read.symbol) ?? []).map((f) => [f.axis, f.option]));
1182
1213
  baseClasses.push(...read.base);
1183
- allLayers.push(...read.layers);
1214
+ for (const [axis, option] of fixed)
1215
+ baseClasses.push(...(read.variants[axis]?.[option] ?? []));
1216
+ allLayers.push(...(fixed.size === 0
1217
+ ? read.layers
1218
+ : read.layers.filter((l) => {
1219
+ /** A travada já entrou como base; a de OUTRA opção do mesmo eixo não se aplica. */
1220
+ for (const [axis] of fixed)
1221
+ if (l.when?.variant?.[axis] !== undefined)
1222
+ return false;
1223
+ return true;
1224
+ })));
1184
1225
  unslotted.push(...read.unslotted);
1185
1226
  Object.assign(defaults, read.defaults);
1186
1227
  for (const [axis, options] of Object.entries(read.variants)) {
1228
+ if (fixed.has(axis))
1229
+ continue;
1187
1230
  const names = Object.keys(options);
1188
1231
  // Two options is what makes a closed set somebody chose from - the same rule
1189
1232
  // the contract writer and the doctor already follow.
@@ -335,8 +335,16 @@ export const CHECKER_SINCE = "0.16.308";
335
335
  * forte dos três - um produto quase preto entrava na medição como página clara. O que não dá para
336
336
  * avaliar (`var(…)`, gradiente) deixa de ser emitido em vez de virar uma face. Isso muda a
337
337
  * EVIDÊNCIA que o relatório de import entrega ao agente, e é sobre ela que a face é julgada.
338
+ *
339
+ * 0.16.321 -> 0.16.322 em 27/08: uma tabela de variantes passa a pertencer ao componente que a
340
+ * CHAMA, e não ao primeiro componente exportado do arquivo. Num arquivo com dois, o segundo
341
+ * chegava sem uma camada sequer e o primeiro chegava com o estilo do vizinho - medido no
342
+ * `codelevel-ui`, o `Tooltip` carregava o fundo escuro e o eixo `side` do balão. O hash do corpus
343
+ * NÃO se moveu, e isso é a informação: os 21 apps dourados têm um componente por arquivo, então a
344
+ * estabilidade ali prova ausência de regressão e não ausência de efeito. Quem tem dois componentes
345
+ * num arquivo só vê a correção depois de remedir.
338
346
  */
339
- export const READER_SINCE = "0.16.321";
347
+ export const READER_SINCE = "0.16.322";
340
348
  /**
341
349
  * O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
342
350
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.321",
3
+ "version": "0.16.322",
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": {