synthesisui 0.16.249 → 0.16.250

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.
@@ -170,15 +170,26 @@ export function describeAbsorb(plan) {
170
170
  return [
171
171
  "Nothing to absorb: every repeated value your code writes by hand already has a name in the system.",
172
172
  ];
173
- lines.push(`${ready.length} of ${plan.entries.length} values already have a name in YOUR vocabulary - those travel as they are:`);
173
+ /**
174
+ * A SEÇÃO DOS QUE JÁ TÊM NOME SÓ EXISTE SE ALGUM TIVER - e no dia 1 nenhum tem.
175
+ *
176
+ * Quem começa do zero não declarou um token ainda, então isto abria com "0 of 4 values
177
+ * already have a name" e uma lista vazia: a primeira frase do comando dizendo um zero que
178
+ * não é notícia nenhuma, antes da única seção que importa para ela.
179
+ */
180
+ if (ready.length > 0)
181
+ lines.push(`${ready.length} of ${plan.entries.length} values already have a name in YOUR vocabulary - those travel as they are:`);
174
182
  for (const e of ready.slice(0, 8))
175
183
  lines.push(` ${e.value.padEnd(24)} ${e.path.padEnd(28)} ${e.files} file${e.files === 1 ? "" : "s"}${e.theirName ? ` · your ${e.theirName}` : ""}`);
176
184
  if (ready.length > 8)
177
185
  lines.push(` (${ready.length - 8} more)`);
178
186
  if (pending.length > 0) {
179
187
  const close = pending.filter((e) => e.near);
180
- lines.push("");
181
- lines.push(`${pending.length} nobody names yet. Naming is a design decision, so those wait for a word from you:`);
188
+ if (ready.length > 0)
189
+ lines.push("");
190
+ lines.push(ready.length > 0
191
+ ? `${pending.length} nobody names yet. Naming is a design decision, so those wait for a word from you:`
192
+ : `${pending.length} repeated values, and none of them has a name yet. Naming is a design decision, so each waits for a word from you:`);
182
193
  for (const e of pending.slice(0, 8))
183
194
  lines.push(` ${e.value.padEnd(24)} ${`<${e.kind}, ${e.files} file${e.files === 1 ? "" : "s"}>`.padEnd(28)} ${e.near ? `≈ your ${e.near.name} (${e.near.away})` : 'path: ""'}`);
184
195
  if (pending.length > 8)
@@ -32,11 +32,23 @@ export async function absorb(opts) {
32
32
  if (opts.send)
33
33
  return sendProposal(root, path, opts.registry);
34
34
  const installed = await loadSystem(root);
35
- if (!installed.table.slug) {
36
- console.log(section("Absorb"));
37
- console.log(body("No system installed here, so there is nothing to absorb INTO. `synthesisui list` shows what you can add."));
38
- return;
39
- }
35
+ /**
36
+ * SEM SISTEMA INSTALADO ISTO CONTINUA VALENDO - e recusar aqui fechava o dia 1.
37
+ *
38
+ * Este comando recusava com "não há nada para absorver PARA DENTRO", o que confundia o
39
+ * DESTINO com o TRABALHO. O trabalho é ler os valores que ela escreveu à mão e propor um
40
+ * nome para cada um; ele não depende de sistema nenhum, e as quatro coisas de que ele
41
+ * precisa já funcionam sem um: a tabela vem vazia (nada tem nome, então tudo é candidato),
42
+ * o vocabulário lido é o DELA, e a fundação existente é um conjunto vazio.
43
+ *
44
+ * Quem começa do zero era justamente quem mais precisava disto e quem menos podia rodar:
45
+ * ela escreve o primeiro componente, o doctor diz "9 valores à mão, nenhum tem nome", e o
46
+ * comando que dá nome respondia que não. Caminhado num projeto em branco em 18/08.
47
+ *
48
+ * O DESTINO É QUE MUDA, e ele se resolve sozinho: sem sistema, os tokens pertencem ao CSS
49
+ * dela - o mesmo caminho que `absorb: "code"` já servia a quem escolheu. Ver `sendProposal`.
50
+ */
51
+ const hasSystem = Boolean(installed.table.slug);
40
52
  const measured = await measuredScope(root);
41
53
  const rel = scopePaths(measured);
42
54
  const roots = rel.length > 0 ? rel.map((s) => resolve(root, s)) : [root];
@@ -61,7 +73,9 @@ export async function absorb(opts) {
61
73
  reports.push(scanSource(file.slice(root.length + 1), source, installed.table));
62
74
  }
63
75
  const plan = absorbPlan(diagnose(reports), theirs, pathsInSystem(installed.documents), opts.cap ?? 40);
64
- console.log(section("What the system could absorb"));
76
+ console.log(section(hasSystem
77
+ ? "What the system could absorb"
78
+ : "Name what you wrote by hand"));
65
79
  console.log(body(paint.dim(`read from ${rel.length > 0 ? rel.join(", ") : "this project"} · your own vocabulary read from the root`)));
66
80
  console.log("");
67
81
  for (const line of describeAbsorb(plan))
@@ -69,7 +83,7 @@ export async function absorb(opts) {
69
83
  if (plan.entries.length === 0)
70
84
  return;
71
85
  const proposal = {
72
- slug: installed.table.slug,
86
+ slug: installed.table.slug ?? null,
73
87
  at: new Date().toISOString(),
74
88
  scope: rel,
75
89
  entries: plan.entries,
@@ -146,17 +160,25 @@ async function sendProposal(root, path, registry) {
146
160
  * colar. Nunca escrevemos no arquivo do cliente - e o import seguinte lê os tokens de volta, o que faz
147
161
  * o caminho fechar sem nós tocarmos em nada.
148
162
  */
149
- const target = (await readProjectConfig(root)).absorb ?? "system";
163
+ /**
164
+ * E SEM SISTEMA O DESTINO NÃO É UMA PREFERÊNCIA, é a única resposta que existe: os tokens
165
+ * pertencem ao CSS dela, porque não há outro lugar. Um `system` configurado aqui apontaria
166
+ * para um slug nulo e a chamada morreria num 404 depois de ela ter nomeado tudo à mão.
167
+ */
168
+ const configured = (await readProjectConfig(root)).absorb ?? "system";
169
+ const target = proposal.slug ? configured : "code";
150
170
  if (target === "code") {
151
171
  console.log(section("Paste this into your own vocabulary"));
152
- console.log(body(paint.dim("Your config says absorbed values belong in your CSS, so nothing was sent.")));
172
+ console.log(body(paint.dim(proposal.slug
173
+ ? "Your config says absorbed values belong in your CSS, so nothing was sent."
174
+ : "You have no system yet, so these belong in your own CSS - nothing was sent, and nothing needed an account.")));
153
175
  console.log("");
154
176
  console.log(" :root {");
155
177
  for (const e of ready)
156
178
  console.log(` --${e.path.replace(/\./g, "-")}: ${e.value};`);
157
179
  console.log(" }");
158
180
  console.log("");
159
- console.log(body("The next import or `sync` reads these back as tokens you declared, and from then on the doctor cobra them by your own names."));
181
+ console.log(body("The next import or `sync` reads these back as tokens you declared, and from then on the doctor holds your code to them by your own names."));
160
182
  return;
161
183
  }
162
184
  const token = await readToken();
@@ -401,6 +401,18 @@ function verdict(d, hasSystem, overruled, conflicts) {
401
401
  body("installed. Nothing to fix, and nothing to compare against."),
402
402
  ];
403
403
  }
404
+ /**
405
+ * O DIA ZERO - e o comando que serve a ele não era dito em lugar nenhum.
406
+ *
407
+ * "Dê um nome a eles" seguido de `init --ds <slug>` e o link da galeria oferece UMA saída, e
408
+ * ela é adotar o vocabulário de outra pessoa. A outra saída existe, faz exatamente o que a
409
+ * frase promete, e roda aqui mesmo sem conta e sem rede: `absorb` lê os valores repetidos e
410
+ * pede um nome para cada um. Ele passou a rodar sem sistema instalado neste mesmo PR, e uma
411
+ * capacidade que a pessoa precisa adivinhar não existe.
412
+ *
413
+ * A ordem é a da decisão dela: nomear o que ela já escreveu vem antes de escolher o de
414
+ * alguém, porque o primeiro é sobre o código que está na tela dela agora.
415
+ */
404
416
  if (!hasSystem) {
405
417
  const distinct = new Set(d.findings.map((f) => f.literal.toLowerCase()));
406
418
  return [
@@ -408,7 +420,10 @@ function verdict(d, hasSystem, overruled, conflicts) {
408
420
  body(`${distinct.size} distinct design values are written by hand here.`),
409
421
  body("No design system is installed, so none of them has a name yet."),
410
422
  "",
411
- body("Give them one:"),
423
+ body("Name them yourself - this reads them and asks you for the words:"),
424
+ snippet(["npx synthesisui@latest absorb"]),
425
+ "",
426
+ body("Or start from a system somebody already published:"),
412
427
  snippet(["npx synthesisui@latest init --ds <slug>"]),
413
428
  body("Browse systems at https://www.synthesisui.com/gallery"),
414
429
  ];
@@ -784,7 +799,19 @@ export async function doctor(opts) {
784
799
  * na mesma tela (dono: "fiquei perdido"). Ou o número não significa nada, ou existe uma lista de
785
800
  * prioridades; as duas não cabem juntas.
786
801
  */
787
- const blocked = hasSystem && (!wiring.imported || !wiring.scoped);
802
+ /**
803
+ * A FIAÇÃO SÓ EXISTE PARA UM SISTEMA NOSSO - e usar "tem nomes" no lugar disso recusava o
804
+ * `--fix` a quem nunca instalou nada (medido em 18/08, no caminho de quem começa do zero).
805
+ *
806
+ * Ela escreve os tokens dela no próprio `:root`, o doctor passa a dizer "7 dos seus valores
807
+ * têm nome", e o `--fix` respondia "trocar agora apontaria para variáveis que o navegador não
808
+ * resolve". Ele resolve: o CSS é dela e já está na página. A frase era literalmente falsa, e
809
+ * ela aparece no primeiro comando que promete adotar.
810
+ *
811
+ * `table.source` é a distinção certa e ela já estava aqui, uma linha abaixo, em `unwired`:
812
+ * `"yours"` e `"adopted"` são o vocabulário dela, e não há duas linhas para ligar.
813
+ */
814
+ const blocked = table.source === "installed" && (!wiring.imported || !wiring.scoped);
788
815
  const unwired = table.source === "installed" &&
789
816
  (!wiring.imported || !wiring.scoped || fontsPending);
790
817
  if (unwired) {
@@ -897,7 +924,15 @@ export async function doctor(opts) {
897
924
  if (hasSystem && measurable) {
898
925
  console.log("");
899
926
  console.log(body(`Token coverage ${meter(d.coverage)} ${paint.strong(`${String(d.coverage).padStart(3)}%`)}`));
900
- console.log(body(paint.dim(` ${d.tokenUses} from ${systemName}${d.ownUses > 0 ? `, ${d.ownUses} from your own tokens` : ""}, ${d.findings.length} by hand${d.phantomUses > 0 ? `, ${d.phantomUses} naming nothing` : ""}`)));
927
+ console.log(body(paint.dim(
928
+ /**
929
+ * DE QUEM É O VOCABULÁRIO, dito na própria linha.
930
+ *
931
+ * `systemName` cai em "this system" quando não há um instalado - e aí a tabela é a DELA,
932
+ * lida das folhas dela. "8 from this system" lê como se existisse um sistema nosso no
933
+ * projeto, e não existe: são os tokens que ela mesma declarou, usados no código dela.
934
+ */
935
+ ` ${d.tokenUses} ${table.source === "installed" ? `from ${systemName}` : "through your own tokens"}${d.ownUses > 0 ? `, ${d.ownUses} from your own tokens` : ""}, ${d.findings.length} by hand${d.phantomUses > 0 ? `, ${d.phantomUses} naming nothing` : ""}`)));
901
936
  /**
902
937
  * A CAMADA DE TOKEN DELE, CONTADA - e a decisão é do dono, em 13/08.
903
938
  *
@@ -911,7 +946,22 @@ export async function doctor(opts) {
911
946
  * `--ds-*` também segura. Apontar uma para a outra é uma linha por variável, num arquivo só - e
912
947
  * os 122 usos seguem o sistema sem tocar em um componente sequer.
913
948
  */
914
- if (d.ownTokens > 0) {
949
+ /**
950
+ * E ESTE BLOCO SÓ EXISTE CONTRA UM SISTEMA NOSSO - ele estava se comparando consigo mesmo.
951
+ *
952
+ * `mirrored` marca um token dela quando a tabela segura aquele valor. Quando a tabela É a
953
+ * dela, ela foi construída a partir daquelas mesmas declarações, então TODO token dela sai
954
+ * espelhado por construção - 4 de 4 no projeto que caminhei em 18/08. E a linha seguinte
955
+ * mandava apontá-los para `--ds-*` num projeto que não tem um único `--ds-*`: um conselho
956
+ * impossível de seguir, no primeiro relatório que ela lê.
957
+ *
958
+ * O `used 0x` era falso pelo mesmo motivo: os usos dos tokens dela foram contados na linha
959
+ * de cima, porque contra a tabela dela eles SÃO o vocabulário conhecido.
960
+ *
961
+ * Quando não há sistema nosso, "os seus tokens" e "o sistema" são a mesma coisa, e não há
962
+ * nada a apontar - a linha de cima já disse tudo o que é verdade.
963
+ */
964
+ if (d.ownTokens > 0 && table.source === "installed") {
915
965
  console.log(body(paint.dim(` ${d.ownTokens} token${d.ownTokens === 1 ? "" : "s"} of your own, used ${d.ownUses}x${d.ownMirrored > 0 ? ` - ${d.ownMirrored} hold a value ${systemName} also names` : ""}`)));
916
966
  if (d.ownMirrored > 0)
917
967
  console.log(body(paint.dim(` point those at the \`--ds-*\` that holds it: ${d.ownMirrored} lines, one file`)));
@@ -1671,16 +1721,31 @@ export async function doctor(opts) {
1671
1721
  */
1672
1722
  if (table.source === "yours") {
1673
1723
  console.log("");
1674
- // Careful about what is being offered. This reader ALREADY has a design
1675
- // system, so "install ours" would mean replacing theirs, and a CTA that
1676
- // pretends otherwise is the kind of confident overclaim that costs
1677
- // trust the first time somebody follows it. What they are missing is
1678
- // not tokens - it is a contract their agent reads before writing UI.
1724
+ /**
1725
+ * E O COMANDO OFERECIDO É O QUE FAZ O SISTEMA DELA.
1726
+ *
1727
+ * O comentário que estava aqui já dizia a coisa certa - *"este leitor JÁ TEM um design
1728
+ * system, então 'instale o nosso' significaria substituir o dele, e um CTA que finge o
1729
+ * contrário é o tipo de exagero confiante que custa confiança"* - e a linha logo abaixo
1730
+ * oferecia `init --ds <slug>` mais a galeria, que é literalmente instalar o de outra
1731
+ * pessoa. O diagnóstico estava escrito e o conserto não (medido em 18/08).
1732
+ *
1733
+ * O comando que serve a ela existe e é outro: `import` lê o repositório DELA e devolve
1734
+ * um sistema com o vocabulário dela dentro. Caminhado num projeto em branco: quatro
1735
+ * tokens próprios e um componente saíram como "1 of 1 components came out with a
1736
+ * blueprint - 100%".
1737
+ *
1738
+ * A galeria continua dita, uma linha abaixo e em voz baixa: forkar um sistema pronto é
1739
+ * uma escolha legítima, só não é a primeira coisa a oferecer a quem já tem o dela.
1740
+ */
1679
1741
  console.log(body("Your tokens exist. What your agent is missing is a contract:"));
1680
1742
  console.log(body("rules it reads BEFORE writing UI, and a manifest of what exists."));
1681
1743
  console.log("");
1682
- console.log(paint.blue(snippet(["npx synthesisui@latest init --ds <slug>"])));
1683
- console.log(body(paint.dim("starts you on a system that ships one - browse them at")));
1744
+ console.log(paint.blue(snippet(["npx synthesisui@latest import --dry"])));
1745
+ console.log(body(paint.dim("reads THIS repo and turns your own vocabulary into that contract -")));
1746
+ console.log(body(paint.dim("local, free, and it writes nothing but the measurement.")));
1747
+ console.log("");
1748
+ console.log(body(paint.dim("Or start from a system somebody already published, if you would rather:")));
1684
1749
  console.log(body(paint.blue("https://www.synthesisui.com/gallery")));
1685
1750
  }
1686
1751
  console.log("");
@@ -1147,6 +1147,45 @@ const BY_COMPONENT = {
1147
1147
  describe_component: "describe",
1148
1148
  add_component: "add",
1149
1149
  };
1150
+ /**
1151
+ * QUAL ARGUMENTO CARREGA O ASSUNTO DE CADA CHAMADA - e sem isto a medição não responde nada.
1152
+ *
1153
+ * Medido em 18/08: 2 das 15 ferramentas gravavam O QUE foi pedido; as outras 13 gravavam o
1154
+ * próprio nome. `compose_context` estava entre as 13 - e ela é a ÚNICA que emite `composed`,
1155
+ * que é o sinal inteiro de "aqui faltou uma receita". O banco acumulava "compose_context: 47"
1156
+ * e nunca "a família `field`: 12", e é o segundo número que abre uma candidata a receita.
1157
+ *
1158
+ * Esperar volume não consertaria isso: o dado que estava sendo guardado não responde a
1159
+ * pergunta, então o relógio da espera não estava correndo.
1160
+ *
1161
+ * ─────────────────────────────────────────────────────────────────────────
1162
+ * O QUE ENTRA, E O QUE NÃO ENTRA DE PROPÓSITO
1163
+ *
1164
+ * Entra o que é um NOME - do vocabulário dela ou do nosso. Ficam de fora, e não por
1165
+ * esquecimento:
1166
+ *
1167
+ * find_token `value` é um valor do repositório dele (`#4f46e5`)
1168
+ * check_file `path` é um caminho do repositório dele
1169
+ * playbook `section` é navegação nossa, e não diz nada sobre o sistema dele
1170
+ *
1171
+ * Os dois primeiros são dado DELE, e a nossa tabela de contadores não é lugar para eles. O
1172
+ * `SLUG` do outro lado já os rejeitaria em silêncio; não mandar é dizer a mesma coisa em voz
1173
+ * alta. `list_components`, `system_doctrine`, `recipe_vocabulary` e `refresh_system` não têm
1174
+ * assunto nenhum - a pergunta É o sistema inteiro.
1175
+ *
1176
+ * `validate_recipes` no plural é um LOTE, e um lote não tem um assunto só.
1177
+ */
1178
+ const SUBJECT_ARG = {
1179
+ describe_component: "name",
1180
+ add_component: "name",
1181
+ /** A família pedida - o sinal que o A1 precisa e que não estava sendo guardado. */
1182
+ compose_context: "family",
1183
+ /** O que ela pediu e o sistema não tinha: a lacuna, com nome. */
1184
+ request_component: "name",
1185
+ request_rule: "name",
1186
+ request_token: "name",
1187
+ validate_recipe: "name",
1188
+ };
1150
1189
  /**
1151
1190
  * O ÚNICO PONTO DE INSTRUMENTAÇÃO - e é isto que o dono pediu (18/08): *"vira
1152
1191
  * comportamento padrão e não depende de disciplina futura."*
@@ -1163,8 +1202,9 @@ async function callTool(root, name, args,
1163
1202
  cli) {
1164
1203
  const answer = await runTool(root, name, args);
1165
1204
  const gesture = BY_COMPONENT[name];
1166
- const target = gesture
1167
- ? String(args.name ?? "")
1205
+ const subject = SUBJECT_ARG[name];
1206
+ const target = subject
1207
+ ? String(args[subject] ?? "")
1168
1208
  .trim()
1169
1209
  .toLowerCase()
1170
1210
  : "";
@@ -52,7 +52,21 @@ export async function status(opts) {
52
52
  }
53
53
  console.log(section("Status"));
54
54
  if (installed.length === 0) {
55
+ /**
56
+ * NADA INSTALADO NÃO SIGNIFICA NADA A OFERECER, e mandar `list` era o conselho da outra pessoa.
57
+ *
58
+ * `list` mostra a GALERIA - sistemas que outros publicaram. Quem roda isto num repositório que
59
+ * já tem o próprio vocabulário recebia "escolha o de alguém", quando o comando que serve a ela
60
+ * lê o repositório DELA. O `doctor` cometia o mesmo desvio no parágrafo final, e ali o
61
+ * comentário até diagnosticava (medido em 18/08).
62
+ *
63
+ * As duas continuam ditas, na ordem certa: o dela primeiro, o de outra pessoa como alternativa.
64
+ */
55
65
  console.log(body("No design system installed in this project, so there is nothing to be in sync with."));
66
+ console.log("");
67
+ console.log(body("Make one out of this repo:"));
68
+ console.log(paint.blue(snippet(["npx synthesisui@latest import --dry"])));
69
+ console.log(body("Or bring in one somebody published:"));
56
70
  console.log(paint.blue(snippet(["npx synthesisui@latest list"])));
57
71
  return;
58
72
  }
@@ -1,4 +1,4 @@
1
- import { FAMILY_PREFIX, familySays, normalizeValue, } from "./tokens.js";
1
+ import { FAMILY_PREFIX, familySays, formDecides, normalizeValue, UNAMBIGUOUS, } from "./tokens.js";
2
2
  /**
3
3
  * COMO ELE CHAMA O VALOR - para a gente parar de renomear as variáveis dele.
4
4
  *
@@ -47,14 +47,6 @@ import { FAMILY_PREFIX, familySays, normalizeValue, } from "./tokens.js";
47
47
  * que o próprio css dele nomeia `--dashboard-font-family`. É pouco, e é o único grupo que sobrou do
48
48
  * item que eu tinha nomeado - os `em` saíram na leva anterior.
49
49
  */
50
- const UNAMBIGUOUS = {
51
- /** `#rrggbbaa`, que é onde toda a família de dialetos cai. */
52
- color: /^#[0-9a-f]{8}$/,
53
- /** `s` e `ms` não aparecem em nenhuma outra família do contrato. */
54
- motion: /^-?[\d.]+ms$/,
55
- /** Uma pilha de fontes: tem vírgula, ou é uma das palavras genéricas do CSS. */
56
- font: /,|^(?:serif|sans-serif|monospace|cursive|fantasy|system-ui)$/,
57
- };
58
50
  const segments = (name) => name.replace(/^--/, "").split("-");
59
51
  /**
60
52
  * QUAL DOS NOMES DELE, quando mais de um segura o mesmo valor - e são 18 no repo real.
@@ -142,8 +134,7 @@ export function theirNames(ours, theirs) {
142
134
  * Recusar aqui não perde informação: sem alias, `nameToWrite` cai no NOSSO token, que é da
143
135
  * família certa porque foi o prefixo dela que o trouxe a este laço.
144
136
  */
145
- const formDecides = Object.values(UNAMBIGUOUS).some((f) => f.test(value));
146
- const usable = formDecides
137
+ const usable = formDecides(value)
147
138
  ? candidates
148
139
  : candidates.filter((n) => familySays(kind, n));
149
140
  if (usable.length === 0)
@@ -569,6 +569,27 @@ export const FAMILY_WORDS = {
569
569
  * `--dashboard-radius-lg` é um raio e o primeiro segmento é o namespace dele. Olhar só o começo
570
570
  * recusaria um nome certo por causa de um prefixo de produto, que é a metade oposta do mesmo erro.
571
571
  */
572
+ /**
573
+ * OS VALORES CUJA FORMA JÁ DIZ A FAMÍLIA - e por isso o nome não precisa dizer.
574
+ *
575
+ * Morava em `their-names.ts`, privada, e a falta dela aqui foi medida no corpus: filtrar por
576
+ * palavra sem esta porta trocou `--brand-500` por `--loader-color` num hex. Os dois são cor, e
577
+ * o segundo só ganhou por ter a palavra "color" no nome - a doc de `FAMILY_WORDS` já avisava
578
+ * que 748 dos 755 nomes sem palavra de categoria são cor.
579
+ *
580
+ * O que fica de fora é o COMPRIMENTO, e fica de propósito: `24px` pode ser raio, espaçamento ou
581
+ * tamanho de fonte, e ali o nome é a única pista que existe.
582
+ */
583
+ export const UNAMBIGUOUS = {
584
+ /** `#rrggbbaa`, que é onde toda a família de dialetos cai. */
585
+ color: /^#[0-9a-f]{8}$/,
586
+ /** `s` e `ms` não aparecem em nenhuma outra família do contrato. */
587
+ motion: /^-?[\d.]+ms$/,
588
+ /** Uma pilha de fontes: tem vírgula, ou é uma das palavras genéricas do CSS. */
589
+ font: /,|^(?:serif|sans-serif|monospace|cursive|fantasy|system-ui)$/,
590
+ };
591
+ /** A forma deste valor já decide a família dele, então a palavra no nome não precisa. */
592
+ export const formDecides = (value) => Object.values(UNAMBIGUOUS).some((f) => f.test(value));
572
593
  export const familySays = (kind, name) => {
573
594
  const words = FAMILY_WORDS[kind] ?? [];
574
595
  const segments = name.replace(/^--/, "").toLowerCase().split("-");
@@ -596,8 +617,33 @@ export function tokenMatch(table, literal, kind) {
596
617
  const hit = table.byValue.get(normalizeValue(literal, table.rootPx));
597
618
  if (!hit || hit.length === 0)
598
619
  return null;
620
+ /**
621
+ * O PREFIXO É NOSSO; O NOME PODE SER DELA - e olhar só o prefixo reprovava todo o vocabulário
622
+ * dela como coincidência entre famílias (medido em 18/08, no caminho de quem começa do zero).
623
+ *
624
+ * `--ds-radius-md` carrega a família por construção, e é isso que o prefixo lê. `--radius-card`
625
+ * não carrega prefixo nenhum, então nenhum token dela casava e TODO achado saía marcado como
626
+ * erro de categoria - com a frase se contradizendo na mesma linha: "no radius named for it ·
627
+ * the value lives as --radius-card". O `--fix` então deixava de trocar exatamente os valores
628
+ * que ela acabou de nomear.
629
+ *
630
+ * `familySays` é a resposta e ela já existia para o outro lado da mesma pergunta (`their-names`,
631
+ * `absorb-plan`): a família sai de QUALQUER segmento do nome, então o namespace de produto dela
632
+ * não atrapalha. Os dois caminhos somam - a garantia por construção continua valendo primeiro.
633
+ */
599
634
  const prefix = kind ? FAMILY_PREFIX[kind] : undefined;
600
- const family = prefix ? hit.filter((n) => n.startsWith(prefix)) : [];
635
+ /**
636
+ * E A PALAVRA SÓ DECIDE QUANDO A FORMA NÃO DECIDE - a mesma porta que `their-names` usa.
637
+ *
638
+ * Sem ela, filtrar por palavra num hex escolhia `--loader-color` em vez de `--brand-500`
639
+ * (medido no corpus dourado): os dois são cor, e o segundo perdia por não ter a palavra
640
+ * "color" no nome. Num comprimento cru é o contrário - ali a palavra é a única pista.
641
+ */
642
+ const byWord = kind && !formDecides(normalizeValue(literal, table.rootPx));
643
+ const family = kind
644
+ ? hit.filter((n) => (prefix ? n.startsWith(prefix) : false) ||
645
+ (byWord ? familySays(kind, n) : false))
646
+ : [];
601
647
  const pool = family.length > 0 ? family : hit;
602
648
  // Semantic roles name intent; primitives name a shelf. Prefer intent.
603
649
  const semantic = pool.find((n) => n.includes("-semantic-"));
@@ -159,7 +159,7 @@ export const MATERIALISER_SINCE = "0.16.241";
159
159
  * `var(--text-caption)` - um token de tipo numa posição de raio. Medido no repositório real: 290
160
160
  * sugestões com a categoria trocada, 66 delas graváveis por um `--fix --write`.
161
161
  */
162
- export const CHECKER_SINCE = "0.16.232";
162
+ export const CHECKER_SINCE = "0.16.250";
163
163
  /**
164
164
  * A ÚLTIMA VERSÃO EM QUE OS LEITORES PASSARAM A PRODUZIR UM CENSO DIFERENTE.
165
165
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.249",
3
+ "version": "0.16.250",
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": {