synthesisui 0.16.286 → 0.16.290

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.
@@ -63,6 +63,15 @@ export async function repoStateOf(projectRoot, slug, cli) {
63
63
  const payload = {
64
64
  ...(typeof lock?.version === "number" ? { installed: lock.version } : {}),
65
65
  ...(lock?.adopted === true ? { adopted: true } : {}),
66
+ /**
67
+ * O MAPA, do `.lock` - ver `RepoStatePayload.tokenMap`.
68
+ *
69
+ * Lido e não recalculado: o cruzamento é caro (varre as folhas dele e o build) e já foi feito
70
+ * pelo `add`, que é quem tinha o payload em mãos. Aqui ele só viaja.
71
+ */
72
+ ...(lock?.tokenMap && lock.tokenMap.length > 0
73
+ ? { tokenMap: lock.tokenMap }
74
+ : {}),
66
75
  cli,
67
76
  ...(doctor?.at ? { doctorAt: doctor.at } : {}),
68
77
  ...(typeof doctor?.phantoms === "number"
@@ -0,0 +1,47 @@
1
+ /** `var(--ds-x)` e `var(--ds-x, fallback)` - o que a folha compilada escreve. */
2
+ const OUR_VAR = /var\(\s*(--ds-[a-zA-Z0-9_-]+)\s*(?:,([^()]*))?\)/g;
3
+ export function inTheirTongue(css, tongue) {
4
+ let named = 0;
5
+ let inlined = 0;
6
+ const left = new Set();
7
+ const out = css.replace(OUR_VAR, (whole, name, fallback) => {
8
+ const theirName = tongue.names.get(name);
9
+ if (theirName) {
10
+ named += 1;
11
+ /**
12
+ * O FALLBACK NÃO VIAJA. `var(--x, #valor)` sobreviveria a um rename DELE pintando o valor
13
+ * velho - a plataforma decidindo por cima da decisão dele. Se ele renomear, quebra, e o
14
+ * `doctor` diz qual (dono, 22/08).
15
+ */
16
+ return `var(${theirName})`;
17
+ }
18
+ const value = tongue.values.get(name);
19
+ if (value) {
20
+ inlined += 1;
21
+ return value;
22
+ }
23
+ left.add(name);
24
+ return fallback ? whole : whole;
25
+ });
26
+ return { css: out, named, inlined, left: [...left].sort() };
27
+ }
28
+ /**
29
+ * O VOCABULÁRIO DESTE REPOSITÓRIO, montado do que o `add` já deixou na pasta.
30
+ *
31
+ * `map` vem do `.lock` (`tokenMap`) e é o que a máquina dele descobriu; `declared` vem do
32
+ * `tokens.css` instalado, que é onde cada variável nossa tem o valor compilado. Nenhuma medição
33
+ * nova: as duas coisas já estão no disco.
34
+ */
35
+ export function tongueOf(map, installedCss) {
36
+ const names = new Map(map.map((p) => [p.ours, p.theirs]));
37
+ const values = new Map();
38
+ for (const m of installedCss.matchAll(/^\s*(--ds-[a-zA-Z0-9_-]+)\s*:\s*([^;]+);/gm)) {
39
+ const value = m[2].trim();
40
+ /** Uma linha que aponta para outra variável não é um valor - seguir a cadeia é do navegador. */
41
+ if (value.includes("var("))
42
+ continue;
43
+ if (!values.has(m[1]))
44
+ values.set(m[1], value);
45
+ }
46
+ return { names, values };
47
+ }
@@ -95,12 +95,18 @@ export async function resolvableVars(root) {
95
95
  * ou `null` quando não há build - e aí nada é reescrito.
96
96
  */
97
97
  export function pointAtTheirNames(css, theirs, resolvable) {
98
+ /**
99
+ * SEM BUILD, SEM MAPA - a mesma regra da reescrita, e pelo mesmo motivo: um repositório que nunca
100
+ * buildou não tem como provar o que resolve, e um par afirmado sem prova é pior que par nenhum.
101
+ */
98
102
  if (!resolvable || theirs.byName.size === 0)
99
- return { css, pointed: 0, pruned: 0 };
103
+ return { css, pointed: 0, pruned: 0, pairs: [] };
100
104
  const ours = buildTable({ css, source: "installed" });
101
105
  const alias = theirNames(ours, theirs);
102
106
  let pointed = 0;
103
107
  let pruned = 0;
108
+ /** Ver `PointedAt.pairs`: o mapa sai do mesmo casamento que reescreve a linha. */
109
+ const pairs = [];
104
110
  const out = css.replace(/^(\s*)(--ds-[a-zA-Z0-9_-]+)(\s*:\s*)([^;]+);/gm, (line, indent, name, sep, value) => {
105
111
  /** Uma linha que já aponta para outra variável não é uma cópia de valor. */
106
112
  if (value.includes("var("))
@@ -116,9 +122,10 @@ export function pointAtTheirNames(css, theirs, resolvable) {
116
122
  return line;
117
123
  }
118
124
  pointed += 1;
125
+ pairs.push({ ours: name, theirs: theirName, value: value.trim() });
119
126
  return `${indent}${name}${sep}var(${theirName});`;
120
127
  });
121
- return { css: out, pointed, pruned };
128
+ return { css: out, pointed, pruned, pairs };
122
129
  }
123
130
  /**
124
131
  * O PREFIXO DA NOSSA VARIÁVEL -> A FAMÍLIA, e a chave que `theirNames` devolve é `<família>:<valor>`.
@@ -142,7 +149,7 @@ const FAMILY_KIND = {
142
149
  export async function pointTokensAtTheirNames(root, payload) {
143
150
  const css = payload.artifacts["tokens.css"] ?? "";
144
151
  if (!css)
145
- return { css, pointed: 0, pruned: 0 };
152
+ return { css, pointed: 0, pruned: 0, pairs: [] };
146
153
  const [theirs, resolvable] = await Promise.all([
147
154
  harvestTheirCss(root),
148
155
  resolvableVars(root),
@@ -0,0 +1,93 @@
1
+ /**
2
+ * O NOME QUE UM TOKEN DELE TEM DENTRO DO DOCUMENTO - uma regra, num lugar só.
3
+ *
4
+ * O QUE O CLIENTE GANHA: mudar um valor no Studio repinta o componente que o usa. Um ref liga os
5
+ * dois; um `var()` literal não liga nada - ele renderiza certo hoje e fica órfão da edição.
6
+ *
7
+ * ESTA FUNÇÃO EXISTIA DUAS VEZES, em `transcribe.ts` e em `css-modules.ts`, e as duas divergiam: a
8
+ * segunda não conhecia `--font-*`, então a família de fonte dele virava referência quando lida de uma
9
+ * classe e `var()` quando lida de uma folha - a mesma decisão dele com dois destinos, dependendo de
10
+ * onde a esteira olhou. Gêmeo não declarado sempre acaba assim.
11
+ *
12
+ * E O GRADIENTE ERA DECIDIDO PELO NOME. A regra reconhecia `--gradient-*`; o cliente escreveu
13
+ * `--grad-cool`. O escritor da plataforma reconhece pelo VALOR - `linear-gradient(…)` - e guarda em
14
+ * `foundations.gradients.grad-cool`, então o token TINHA casa no documento e a classe que o usa
15
+ * apontava para o vazio. É a lei 13 aplicada a token: a origem decide, e aqui a origem é o valor.
16
+ *
17
+ * Medido: `--grad-*` são 5 no `codelevel-ui`, `--gradient-*` são 3 no `frontend-hub`, e as duas
18
+ * grafias passam a chegar onde o documento as guarda.
19
+ */
20
+ export function tokenRefFor(name,
21
+ /**
22
+ * O que cada token DELE vale. O mapa é o que permite reconhecer um gradiente pela forma em vez do
23
+ * nome; vazio, a decisão volta a ser só pelo nome, que é o comportamento de quem não tem o valor
24
+ * em mãos.
25
+ */
26
+ declared) {
27
+ const bare = name.replace(/^--/, "");
28
+ if (bare.startsWith("color-"))
29
+ return refFor(bare.slice("color-".length));
30
+ if (bare.startsWith("radius-"))
31
+ return `{radius.${bare.slice(7)}}`;
32
+ if (bare.startsWith("spacing-"))
33
+ return `{spacing.${bare.slice(8)}}`;
34
+ if (bare.startsWith("shadow-"))
35
+ return `{shadow.${bare.slice(7)}}`;
36
+ // `--gradient-ui` e `--background-image-gradient-ui` são um token em duas grafias - o namespace de
37
+ // utility do Tailwind v4 embrulha o primeiro. Os dois chegam a `{gradients.ui}`.
38
+ if (bare.startsWith("background-image-gradient-")) {
39
+ return `{gradients.${bare.slice("background-image-gradient-".length)}}`;
40
+ }
41
+ if (bare.startsWith("gradient-"))
42
+ return `{gradients.${bare.slice(9)}}`;
43
+ if (bare.startsWith("text-")) {
44
+ /**
45
+ * O TOKEN COMPANHEIRO. O Tailwind v4 escreve "a altura de linha DE text-body-s" como
46
+ * `--text-body-s--line-height` - um duplo hífen dentro de um nome. Lido como nome de passo ele
47
+ * produzia `{typography.scale.body-s--line-height.fontSize}`, cujo duplo hífen nenhuma gramática
48
+ * de ref aceita, e a receita inteira era RECUSADA na validação (test13, 01/08). O sufixo nomeia a
49
+ * propriedade; o meio nomeia o passo.
50
+ */
51
+ const companion = /^text-(.+?)--(line-height|letter-spacing|font-weight)$/.exec(bare);
52
+ if (companion) {
53
+ const prop = {
54
+ "line-height": "lineHeight",
55
+ "letter-spacing": "letterSpacing",
56
+ "font-weight": "weight",
57
+ }[companion[2]];
58
+ return `{typography.scale.${companion[1]}.${prop}}`;
59
+ }
60
+ // Qualquer outro duplo hífen é um nome que esta gramática não segura - o `var()` literal ainda
61
+ // resolve contra a folha dele, e um ref inválido não ajuda ninguém.
62
+ if (bare.slice(5).includes("--"))
63
+ return `var(${name})`;
64
+ return `{typography.scale.${bare.slice(5)}.fontSize}`;
65
+ }
66
+ if (bare.startsWith("font-"))
67
+ return `{typography.families.${bare.slice(5)}}`;
68
+ /**
69
+ * O GRADIENTE PELA FORMA DO VALOR, e é a última pergunta e não a primeira.
70
+ *
71
+ * Os namespaces acima são vocabulário conhecido e ganham deste teste - `--color-brand` que valha um
72
+ * `linear-gradient` continua sendo cor pelo nome que ele deu. Aqui embaixo estão os nomes que a
73
+ * gente NÃO modela, e é onde `--grad-cool` cai. A chave é o nome dele inteiro, que é exatamente o
74
+ * que `gradientsFromCensus` guarda no documento.
75
+ */
76
+ const value = declared.get(name);
77
+ if (value && /\b(?:linear|radial|conic)-gradient\(/.test(value)) {
78
+ return `{gradients.${bare}}`;
79
+ }
80
+ // Um namespace que não modelamos. O `var()` literal continua sendo CSS correto contra a folha
81
+ // dele, e inventar um ref apontaria para nada.
82
+ return `var(${name})`;
83
+ }
84
+ /**
85
+ * `ocean-500` → `{color.ocean.500}`. O passo é o número final; o que vem antes é a família, hífens
86
+ * intactos, porque `royal-blue-500` é uma família chamada `royal-blue`.
87
+ */
88
+ function refFor(name) {
89
+ const m = /^(.*)-(\d{2,4})$/.exec(name);
90
+ if (!m)
91
+ return `{color.${name}}`;
92
+ return `{color.${m[1]}.${m[2]}}`;
93
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.286",
3
+ "version": "0.16.290",
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": {