synthesisui 0.16.421 → 0.16.423

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.
@@ -7,7 +7,7 @@ const OUR_VAR = /var\(\s*(--ds-[a-zA-Z0-9_-]+)\s*(?:,([^()]*))?\)/g;
7
7
  * verdade quando nenhum dos três aponta. Somar aqui, e não em cada comando, é o que impede duas
8
8
  * contas diferentes para a mesma pergunta.
9
9
  *
10
- * `null` quando nada foi traduzido (projeto sem mapa) - e aí quem chama DIZ isso.
10
+ * `null` quando nada foi traduzido - e aí quem chama DIZ isso.
11
11
  */
12
12
  export function sumSpoken(reports) {
13
13
  const real = reports.filter((r) => r !== null);
@@ -20,13 +20,16 @@ export function sumSpoken(reports) {
20
20
  return {
21
21
  named: real.reduce((n, r) => n + r.named, 0),
22
22
  inlined: real.reduce((n, r) => n + r.inlined, 0),
23
+ froze: real.reduce((n, r) => n + r.froze, 0),
23
24
  left: [...left].sort(),
24
25
  };
25
26
  }
26
27
  export function inTheirTongue(css, tongue, destination = "stylesheet") {
27
28
  let named = 0;
28
29
  let inlined = 0;
30
+ let froze = 0;
29
31
  const left = new Set();
32
+ const write = (value) => destination === "tailwind-class" ? value.replace(/\s+/g, "_") : value;
30
33
  const out = css.replace(OUR_VAR, (whole, name, fallback) => {
31
34
  const theirName = tongue.names.get(name);
32
35
  if (theirName) {
@@ -41,14 +44,28 @@ export function inTheirTongue(css, tongue, destination = "stylesheet") {
41
44
  const value = tongue.values.get(name);
42
45
  if (value) {
43
46
  inlined += 1;
44
- return destination === "tailwind-class"
45
- ? value.replace(/\s+/g, "_")
46
- : value;
47
+ if (tongue.frozen.has(name))
48
+ froze += 1;
49
+ return write(value);
50
+ }
51
+ /**
52
+ * O FALLBACK ESCRITO NA PRÓPRIA REFERÊNCIA É A ÚLTIMA RESPOSTA, e ela é do autor do texto.
53
+ *
54
+ * Quem escreve `var(--ds-motion-durations-base, 200ms)` já disse o que aquilo vale quando a
55
+ * folha não responde - é o idioma de `motion.ts` e de `interactive-templates.ts`, os dois
56
+ * módulos que montam animação sem ter o documento em mão. Antes disto a referência inteira
57
+ * sobrevivia no arquivo dele, levando junto a nossa grafia e uma dependência da nossa folha
58
+ * que o fallback existia justamente para dispensar.
59
+ */
60
+ const declared = fallback?.trim();
61
+ if (declared) {
62
+ inlined += 1;
63
+ return write(declared);
47
64
  }
48
65
  left.add(name);
49
- return fallback ? whole : whole;
66
+ return whole;
50
67
  });
51
- return { css: out, named, inlined, left: [...left].sort() };
68
+ return { css: out, named, inlined, froze, left: [...left].sort() };
52
69
  }
53
70
  /**
54
71
  * O VOCABULARIO DESTE REPOSITORIO **SEM O `add` TER RODADO** - a folha deixa de ser pre-requisito.
@@ -63,39 +80,137 @@ export function inTheirTongue(css, tongue, destination = "stylesheet") {
63
80
  * DELE e o build dele, e responde quais das nossas variaveis tem nome dele. A diferenca e' que
64
81
  * nada disso vai para o disco - nem o mapa, nem a folha.
65
82
  *
66
- * `null` quando o repositorio dele nao nomeia nada do que este sistema declara. Ai' a folha
67
- * continua sendo o caminho, e quem chama DIZ isso - em vez de escrever `var(--ds-*)` cru e
68
- * deixar a pessoa descobrir por que a cor nao apareceu.
83
+ * E UM REPOSITORIO QUE NAO NOMEIA NADA CONTINUA TENDO RESPOSTA: os pares podem ser zero e a folha
84
+ * ainda carrega o valor compilado de cada variavel, que e' o destino 2. Enquanto este caminho
85
+ * respondia `null` nesse caso, o arquivo saia com `var(--ds-*)` cru - a grafia que `INV-GERAL-13`
86
+ * proibe, escrita justamente no projeto que tem menos chance de ter a folha carregada.
87
+ *
88
+ * `null` so' quando nem o `tokens.css` veio - e ai' nao ha' o que responder.
89
+ */
90
+ export async function tongueFromArtifacts(root,
91
+ /**
92
+ * Os artefatos do payload - e `undefined` é um estado real, não um descuido do chamador.
93
+ *
94
+ * Uma leitura que chegou sem eles é indistinguível de uma que não chegou: a resposta é `null`
95
+ * nos dois casos, e quem chama DIZ isso. Sem esta tolerância o comando estoura em vez de
96
+ * relatar, que é a pior forma de uma lacuna aparecer.
69
97
  */
70
- export async function tongueFromArtifacts(root, artifacts) {
71
- const tokens = artifacts["tokens.css"] ?? "";
98
+ artifacts) {
99
+ const tokens = artifacts?.["tokens.css"] ?? "";
72
100
  if (!tokens)
73
101
  return null;
74
102
  const { pointTokensAtTheirNames } = await import("./their-vars.js");
75
- const { pairs } = await pointTokensAtTheirNames(root, { artifacts });
76
- if (pairs.length === 0)
77
- return null;
103
+ const { pairs } = await pointTokensAtTheirNames(root, {
104
+ artifacts: artifacts ?? {},
105
+ }).catch(() => ({ pairs: [] }));
78
106
  return tongueOf(pairs, tokens);
79
107
  }
80
108
  /**
81
- * O VOCABULÁRIO DESTE REPOSITÓRIO, montado do que o `add` já deixou na pasta.
109
+ * O VOCABULÁRIO DESTE REPOSITÓRIO, LIDO DA FOLHA - e o mapa é confirmação, nunca pré-requisito.
110
+ *
111
+ * `installedCss` é a folha compilada deste install, e ela responde as duas perguntas sozinha porque
112
+ * `their-vars.ts` já a reescreveu nesta máquina:
82
113
  *
83
- * `map` vem do `.lock` (`tokenMap`) e é o que a máquina dele descobriu; `declared` vem do
84
- * `tokens.css` instalado, que é onde cada variável nossa tem o valor compilado. Nenhuma medição
85
- * nova: as duas coisas já estão no disco.
114
+ * ```
115
+ * --ds-color-ink-50: var(--color-ink-50); -> ele nomeia: o nome DELE
116
+ * --ds-motion-durations-base: 180ms; -> ele não nomeia: o VALOR
117
+ * --ds-color-semantic-primary: var(--ds-color-brand-500); -> cadeia nossa: segue até uma das duas
118
+ * ```
119
+ *
120
+ * A CADEIA É SEGUIDA, e antes ela era descartada. A linha que aponta para outra variável nossa é a
121
+ * forma como o compilador escreve um papel semântico - `primary` é `brand-500` -, e pular essas
122
+ * linhas deixava sem resposta justamente os nomes que um componente mais usa. Medido na folha do
123
+ * `codelevel`: 53 das 119 variáveis eram puladas por essa razão, e todas elas tinham resposta uma
124
+ * indireção adiante.
125
+ *
126
+ * `map` vem do `.lock` (`tokenMap`) quando existe, e tem PRECEDÊNCIA: ele é o que a máquina dele
127
+ * mediu contra as folhas e o build, enquanto a folha reescrita é o efeito daquela medição. Onde os
128
+ * dois concordam a resposta é a mesma; onde só um tem resposta, ela vale.
86
129
  */
87
130
  export function tongueOf(map, installedCss) {
88
131
  const names = new Map(map.map((p) => [p.ours, p.theirs]));
89
132
  const values = new Map();
90
- for (const m of installedCss.matchAll(/^\s*(--ds-[a-zA-Z0-9_-]+)\s*:\s*([^;]+);/gm)) {
91
- const value = m[2].trim();
92
- /** Uma linha que aponta para outra variável não é um valor - seguir a cadeia é do navegador. */
93
- if (value.includes("var("))
133
+ /**
134
+ * O que cada variável nossa declara na folha, antes de qualquer indireção ser seguida.
135
+ *
136
+ * SEM ÂNCORA NO INÍCIO DA LINHA, e a âncora era um buraco de verdade - a mesma lição que
137
+ * `apply-fix.ts` já pagou uma vez. A regra antiga era `^\s*--nome:`, que só enxerga CSS formatado
138
+ * com uma declaração por linha. Numa folha minificada - e um `tokens.css` compilado pode chegar
139
+ * assim - a linha inteira é `[data-ds="x"]{--ds-a:#111;--ds-b:1rem}`: nenhuma declaração começa a
140
+ * linha, o mapa sai VAZIO, e o componente materializado leva a nossa grafia inteira para o
141
+ * repositório dele sem nada acusar.
142
+ *
143
+ * Encontrado pelo spec ponta a ponta de A3 em 11/09, rodando `add` e `component` de verdade: o
144
+ * `button.css` saiu com as duas referências cruas.
145
+ *
146
+ * O que delimita uma declaração é o que vem ANTES dela: o começo do texto, uma `{` que abre a
147
+ * regra, ou o `;` da declaração anterior. É a mesma pontuação em qualquer formatação.
148
+ *
149
+ * E O FECHO É UM LOOKAHEAD, não um consumo - senão o `;` que TERMINA uma declaração some do texto
150
+ * disponível e deixa de poder ABRIR a próxima. Numa folha minificada isso faz o laço ler uma
151
+ * declaração sim, outra não: `--ds-a` entra, `--ds-b` desaparece, e o resultado é pior que não ler
152
+ * nada, porque metade do mapa parece um mapa.
153
+ */
154
+ const declared = new Map();
155
+ /** Quantas vezes a folha declara cada variável nossa - ver `Tongue.frozen`. */
156
+ const times = new Map();
157
+ for (const m of installedCss.matchAll(/(?:^|[{;])\s*(--ds-[a-zA-Z0-9_-]+)\s*:\s*([^;}]+)(?=[;}])/g)) {
158
+ times.set(m[1], (times.get(m[1]) ?? 0) + 1);
159
+ if (!declared.has(m[1]))
160
+ declared.set(m[1], m[2].trim());
161
+ }
162
+ /** `var(--alvo)` ou `var(--alvo, fallback)` como VALOR INTEIRO de uma declaração. */
163
+ const INDIRECTION = /^var\(\s*(--[a-zA-Z0-9_-]+)\s*(?:,([\s\S]*))?\)$/;
164
+ /**
165
+ * O DESTINO DE UMA VARIÁVEL NOSSA, seguindo a cadeia até uma das duas metades.
166
+ *
167
+ * `seen` corta o ciclo: uma folha que se referencia em volta é um defeito do compilador, e o
168
+ * preço dele não pode ser um `component` que nunca termina.
169
+ */
170
+ const destinationOf = (ours, seen) => {
171
+ if (seen.has(ours))
172
+ return null;
173
+ seen.add(ours);
174
+ const value = declared.get(ours);
175
+ if (value === undefined)
176
+ return null;
177
+ const indirect = INDIRECTION.exec(value);
178
+ if (!indirect)
179
+ return { value };
180
+ const target = indirect[1];
181
+ /** Outra variável NOSSA: a resposta está mais adiante, nunca aqui. */
182
+ if (target.startsWith("--ds-")) {
183
+ const next = destinationOf(target, seen);
184
+ if (next)
185
+ return next;
186
+ /** A cadeia morreu sem resposta - o fallback escrito na indireção é a última palavra. */
187
+ const written = indirect[2]?.trim();
188
+ return written ? { value: written } : null;
189
+ }
190
+ return { name: target };
191
+ };
192
+ for (const ours of declared.keys()) {
193
+ if (names.has(ours))
194
+ continue;
195
+ const destination = destinationOf(ours, new Set());
196
+ if (!destination)
94
197
  continue;
95
- if (!values.has(m[1]))
96
- values.set(m[1], value);
198
+ if ("name" in destination)
199
+ names.set(ours, destination.name);
200
+ else
201
+ values.set(ours, destination.value);
97
202
  }
98
- return { names, values };
203
+ /**
204
+ * AS QUE MUDAM ENTRE ESQUEMAS E VIRAM LITERAL - o subconjunto que congela.
205
+ *
206
+ * Só entra aqui o que caiu em `values`: onde há nome dele, o mecanismo de esquema DELE vira o
207
+ * valor junto e não há nada a dizer.
208
+ */
209
+ const frozen = new Set();
210
+ for (const [ours, n] of times)
211
+ if (n > 1 && values.has(ours))
212
+ frozen.add(ours);
213
+ return { names, values, frozen };
99
214
  }
100
215
  /**
101
216
  * NENHUMA MATERIALIZAÇÃO VAZA VOCABULÁRIO INTERNO - a regra da camada, num lugar só (dono, 26/08).
@@ -106,34 +221,56 @@ export function tongueOf(map, installedCss) {
106
221
  * ausente. Um mecanismo novo de materialização que chamar esta função já nasce coberto; um que não
107
222
  * chamar é reprovado pelo gate (ver contracts/camada-6-volta.md, INV-VOLTA-02).
108
223
  *
109
- * `null` quando não há mapa: projeto de destino, repositório que nunca buildou, ou install anterior
110
- * a 0.16.290. Nos três casos nada é traduzido e a folha instalada continua sendo o caminho - e quem
111
- * chama DIZ qual dos dois aconteceu.
224
+ * A FOLHA RESPONDE MESMO SEM MAPA - o conserto de 11/09, e ele tem três estados reais por trás.
225
+ *
226
+ * Esta função exigia um `.lock` com `tokenMap` não vazio e respondia `null` fora disso. Os três
227
+ * casos que caíam ali - install anterior a 0.16.290, repositório que nunca buildou, e projeto de
228
+ * destino que não nomeia nada nosso - recebiam o arquivo com `var(--ds-*)` cru, e o comando dizia
229
+ * que a folha continuava sendo o caminho. Era a única porta do produto que ainda PEDIA o import, e
230
+ * ela pedia exatamente a quem menos podia atender.
231
+ *
232
+ * Agora o `.lock` decide só a VERSÃO da folha a ler. Sem ele, a folha da raiz é o re-export que o
233
+ * `add` escreve e serve igual - e um projeto sem pasta nenhuma continua respondendo `null`, porque
234
+ * ali não há folha para ler.
112
235
  */
113
236
  export async function projectTongue(root, slug) {
114
237
  const { readFile } = await import("node:fs/promises");
115
238
  const { join } = await import("node:path");
116
239
  const dir = join(root, "_synthesisui", "ds", slug);
117
240
  const lock = await readFile(join(dir, ".lock"), "utf8").catch(() => null);
118
- if (!lock)
119
- return null;
120
241
  let map = [];
121
- let version = 0;
122
- try {
123
- const parsed = JSON.parse(lock);
124
- map = parsed.tokenMap ?? [];
125
- version = parsed.version ?? 0;
126
- }
127
- catch {
128
- return null;
242
+ let version = null;
243
+ if (lock) {
244
+ try {
245
+ const parsed = JSON.parse(lock);
246
+ map = parsed.tokenMap ?? [];
247
+ version = parsed.version ?? 0;
248
+ }
249
+ catch {
250
+ /** Um `.lock` ilegível não apaga a folha ao lado dele - ver a leitura logo abaixo. */
251
+ map = [];
252
+ version = null;
253
+ }
129
254
  }
130
- if (map.length === 0)
131
- return null;
132
255
  /**
133
256
  * O VALOR COMPILADO MORA NA PASTA DA VERSÃO - a folha da raiz é um re-export de uma linha, escrito
134
- * assim de propósito para que o `@import` dele nunca mude entre updates (ver `add.ts`). É de lá
135
- * que sai o literal para as variáveis que não têm nome dele.
257
+ * assim de propósito para que o caminho nunca mude entre updates (ver `add.ts`). Sem `.lock` legível
258
+ * não há versão para abrir, e aí a folha da raiz é o que existe: ela pode ser o re-export (e não
259
+ * declara nada) ou a folha inteira de um install antigo, e ler as duas custa uma chamada.
136
260
  */
137
- const installed = await readFile(join(dir, `v${version}`, "tokens.css"), "utf8").catch(() => "");
261
+ const sheets = [
262
+ version === null ? null : join(dir, `v${version}`, "tokens.css"),
263
+ join(dir, "tokens.css"),
264
+ ].filter((p) => p !== null);
265
+ let installed = "";
266
+ for (const path of sheets) {
267
+ const css = await readFile(path, "utf8").catch(() => "");
268
+ if (css.includes("--ds-")) {
269
+ installed = css;
270
+ break;
271
+ }
272
+ }
273
+ if (map.length === 0 && !installed)
274
+ return null;
138
275
  return tongueOf(map, installed);
139
276
  }
@@ -47,21 +47,27 @@ export function wiredSlugs(css) {
47
47
  return out;
48
48
  }
49
49
  /**
50
- * O QUE DIZER A ELE QUANDO O SISTEMA JÁ NASCEU COM OUTRO NOME.
50
+ * O QUE DIZER A ELE QUANDO O CSS DELE AINDA IMPORTA UMA FOLHA NOSSA.
51
51
  *
52
52
  * Roda DEPOIS do envio, com o slug que o servidor devolveu - então não é previsão, é fato: a pasta
53
53
  * vai ser escrita com este nome e os `@import` dele apontam para outro.
54
54
  *
55
- * `null` quando o CSS não cabeia nada (todo primeiro import limpo) e quando o slug que nasceu já é
56
- * um dos cabeados. Um aviso que aparece sempre é um aviso que ninguém lê.
55
+ * A SAÍDA MUDOU EM 11/09, e ela é o oposto do que era. Este aviso terminava com as duas linhas de
56
+ * `@import` para ele colar apontando para o slug novo - ou seja, consertava o import mantendo-o.
57
+ * Nenhum projeto precisa mais daquele import (`INV-GERAL-13`): o que a plataforma escreve no
58
+ * repositório dele não depende de folha nossa nenhuma. Então a saída é REMOVER as linhas, e o aviso
59
+ * diz isso com a consequência que ele precisa para decidir - o build já está quebrado hoje.
57
60
  *
58
- * A frase carrega as três coisas que a decisão precisa: o que o CSS espera, o que existe agora, e o
59
- * que acontece se ele não fizer nada. Sem a terceira isto é trivia - e foi a terceira que faltou na
60
- * corrida que parou o build dele.
61
+ * E ELE PODE TER ESCRITO CÓDIGO SOBRE AQUELA FOLHA. Um `var(--ds-*)` à mão num arquivo dele, de uma
62
+ * versão anterior do produto, para de resolver quando o import sai - e a linha diz o que o `doctor`
63
+ * REALMENTE responde: ele CONTA essas referências na linha de cobertura (`N from <sistema>`), e não
64
+ * as lista uma a uma. Prometer a lista seria a família de defeito que o `ARCHITECTURE.md` abre
65
+ * citando - afirmar que um comando conserta o que ele não conserta.
66
+ *
67
+ * `null` quando o CSS não cabeia nada, que é todo projeto novo.
61
68
  *
62
69
  * DIZER, E NÃO CONSERTAR. O CSS é dele; reescrevê-lo por conta própria seria a plataforma editando
63
- * o repositório de alguém sem pedir. É o mesmo padrão do `add`, que imprime o `@import` e não o
64
- * cola.
70
+ * o repositório de alguém sem pedir.
65
71
  */
66
72
  export function wiringWarning(
67
73
  /**
@@ -71,16 +77,24 @@ export function wiringWarning(
71
77
  wired, slugThatWasBorn) {
72
78
  if (wired.length === 0)
73
79
  return null;
74
- if (wired.includes(slugThatWasBorn.toLowerCase()))
75
- return null;
80
+ const stale = wired.filter((w) => w !== slugThatWasBorn.toLowerCase());
76
81
  return [
77
- `Your CSS imports _synthesisui/ds/${wired[0]}/, and this system is "${slugThatWasBorn}".`,
78
- `Those @import lines point at a folder that does not exist, and the build fails on them -`,
79
- `not a missing colour, a build that stops. Point them at ${slugThatWasBorn} to fix it:`,
80
- "",
81
- ` @import ".../_synthesisui/ds/${slugThatWasBorn}/tokens.css";`,
82
- ` @import ".../_synthesisui/ds/${slugThatWasBorn}/theme.css";`,
83
- "",
82
+ `Your CSS imports _synthesisui/ds/${wired[0]}/ - nothing needs that any more.`,
83
+ ...(stale.length > 0
84
+ ? [
85
+ `It points at a folder this install does not write (the system is "${slugThatWasBorn}"),`,
86
+ `so \`next build\` fails on it today - not a missing colour, a build that stops.`,
87
+ ]
88
+ : []),
89
+ ``,
90
+ `Delete those @import lines. Everything we write into this repository speaks the names your`,
91
+ `own code declares, or carries the value itself - there is no stylesheet of ours to load.`,
92
+ ``,
93
+ `If you wrote --ds-* by hand against an older version, \`npx synthesisui doctor\` COUNTS them -`,
94
+ `the "from <system>" number on the coverage line is how many references still point at us. Those`,
95
+ `stop resolving when the import goes; re-run \`component\` on whatever wrote them and they come`,
96
+ `back in your own names.`,
97
+ ``,
84
98
  "Yours to change - we do not edit your CSS.",
85
99
  ].join("\n");
86
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.421",
3
+ "version": "0.16.423",
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": {
@@ -1,95 +0,0 @@
1
- /**
2
- * O TEXTO QUE TIRA AS EDIÇÕES DE SETUP DA MÃO DA PESSOA - e que agora USA a medição.
3
- *
4
- * O QUE O CLIENTE GANHA: um design system instalado não muda um pixel até que alguma folha que o app
5
- * carrega importe os tokens e algum elemento carregue o escopo. Em 27/07 isso foi medido em 0% - a
6
- * lista numerada de três passos existia e ninguém a executava. A pessoa que roda o comando tem um
7
- * agente aberto ao lado, então o fecho deixou de ser tarefa e passou a ser um texto para colar.
8
- *
9
- * O DEFEITO QUE ISTO CORRIGE (dono, 07/09): o texto era FIXO. O `doctor` acabava de imprimir
10
- * `✗ ✓ ✓` - só o import faltava, o escopo e a tipografia já estavam lá - e o parágrafo seguinte
11
- * mandava *"Do the ONE-TIME SETUP it names, all of it"*, listando as três. Um agente obediente
12
- * reescreve o que já estava correto, e o próprio texto termina com "não mexa nos meus estilos": ele
13
- * se contradizia dentro de si mesmo.
14
- *
15
- * Então o que falta é ARGUMENTO. Sem ele o texto pede tudo, que é a verdade nos dois lugares onde
16
- * ninguém mediu nada: o fecho do `init` (o projeto acabou de receber o sistema) e a tela do
17
- * onboarding (a plataforma não vê o disco de ninguém). Com ele, o texto pede o que falta e NOMEIA o
18
- * que já está pronto para o agente deixar em paz.
19
- *
20
- * GÊMEO POR SPEC com `apps/web/src/lib/ds/install-mission.ts`, que serve o mesmo texto na tela -
21
- * `setup-prompt.spec.ts` reprova o drift. O CLI é publicado standalone e não pode importar de
22
- * `apps/web`, então a cópia é inevitável; o que não é inevitável é ela divergir em silêncio, e dois
23
- * textos diferentes para o mesmo setup ensinam dois setups.
24
- */
25
- export function initCommand(slug) {
26
- return `npx synthesisui@latest init --styles tailwind --ds ${slug}`;
27
- }
28
- /**
29
- * A LINHA QUE DÁ SAÍDA QUANDO UMA VERSÃO ESTÁ PROPAGANDO (dono, 07/09).
30
- *
31
- * Ele rodou `npx synthesisui@latest doctor` e recebeu `ETARGET / No matching version found`: o
32
- * `dist-tag` do npm já apontava para a versão nova e o tarball ainda não estava na réplica que a
33
- * máquina dele consultou. O erro é do npm e acontece ANTES do nosso código rodar, então não há como
34
- * interceptá-lo - o que está na nossa mão é a instrução não deixar quem lê sem saída, porque
35
- * `ETARGET` não diz que é transitório nem o que fazer.
36
- */
37
- const PROPAGATING = "If npm answers `ETARGET` or `No matching version found`, a release is still propagating - wait a minute and run it again, or pin the version the registry does have (`npm view synthesisui version`).";
38
- const CONNECT_STEP = `Run: npx synthesisui@latest connect
39
- ${PROPAGATING}
40
- If it answers "already had it" on both lines, everything is live and you are done.
41
- If it WROTE either of them, then they are not live in this session, because hooks and tools are only read at startup. Stop there and tell me, in bold, on its own line: **RESTART THIS SESSION - the check is installed but not running yet.** Do not keep writing files after that; a session that cannot be checked is the state this whole setup exists to avoid.`;
42
- /** As linhas exatas quando a folha é conhecida; a descrição do arquivo quando não é. */
43
- function importStep(slug, gap) {
44
- const lines = [
45
- `@import "${gap.sheet ? gap.sheet.prefix : "<path to the repo root>/"}_synthesisui/ds/${slug}/tokens.css";`,
46
- ...(gap.imports === 2
47
- ? [
48
- `@import "${gap.sheet ? gap.sheet.prefix : "<path to the repo root>/"}_synthesisui/ds/${slug}/theme.css";`,
49
- ]
50
- : []),
51
- ];
52
- const where = gap.sheet
53
- ? `\`${gap.sheet.path}\``
54
- : "the global stylesheet this project's apps actually load (in a monorepo that is usually the shared package's, not the app's)";
55
- return `Add ${gap.imports === 2 ? "these two lines" : "this line"} to ${where}, RIGHT AFTER its \`@import "tailwindcss"\` line:
56
-
57
- ${lines.map((l) => ` ${l}`).join("\n")}
58
-
59
- The position is not cosmetic. Read there, this sheet comes BEFORE any \`@theme\` this project declares, so wherever both name the same variable the project's own value wins. Moved to the end of the file, ours would win instead - silently.`;
60
- }
61
- export function setupPrompt(slug, gap) {
62
- const g = gap ?? {
63
- tokens: true,
64
- scope: true,
65
- type: true,
66
- imports: 2,
67
- };
68
- const done = [
69
- g.tokens ? null : "a stylesheet already imports the system's tokens",
70
- g.scope ? null : `\`data-ds="${slug}"\` is already on a root element`,
71
- g.type
72
- ? null
73
- : "this project's type is already mapped onto the system's family tokens",
74
- ].filter(Boolean);
75
- const steps = [];
76
- if (g.tokens)
77
- steps.push(importStep(slug, g));
78
- if (g.scope)
79
- steps.push(`Put \`data-ds="${slug}"\` on a root element of each app - the element every page renders inside. Keep any classes it already has; a background they chose is a decision they made.`);
80
- if (g.type)
81
- steps.push(`Map this project's type onto the system's family tokens: import from the fonts file the install wrote and point the family variables at it, in the same stylesheet. This is the step people skip, and without it the pages render in the framework's default face.`);
82
- steps.push(`Run: npx synthesisui@latest doctor
83
- ${PROPAGATING}
84
- If it says "no system installed", the install never happened - run \`${initCommand(slug)}\` and then start over from step 1.
85
- It must NOT say "this project does not load it yet". If it does, it names exactly which piece is still missing - fix that one and run it again.`);
86
- steps.push(CONNECT_STEP);
87
- const alreadyDone = done.length > 0
88
- ? `\nAlready in place - LEAVE THESE ALONE:\n${done.map((d) => `- ${d}`).join("\n")}\n`
89
- : "";
90
- return `Set up the "${slug}" design system in this project, so its tokens reach the browser. I already ran the install in my terminal.
91
- ${alreadyDone}
92
- ${steps.map((s, i) => `${i + 1}. ${s}`).join("\n")}
93
-
94
- Do not change any of my existing styles.`;
95
- }