synthesisui 0.16.421 → 0.16.422

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.
@@ -2,8 +2,9 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { generateComponentFiles } from "../component-codegen.js";
4
4
  import { readProjectConfig, resolveRegistry } from "../config.js";
5
+ import { detectAppDirs } from "../global-sheet.js";
5
6
  import { installedSlugs } from "../installed.js";
6
- import { installedThemeVars, reactMajorOf, readInstalledConvention, readInstalledScheme, } from "../project-facts.js";
7
+ import { reactMajorOf, readInstalledConvention, readInstalledScheme, theirThemeVars, } from "../project-facts.js";
7
8
  import { postGenerate, RegistryError } from "../registry.js";
8
9
  import { flavourResolver } from "../styles-flavour.js";
9
10
  import { projectTongue } from "../their-tongue.js";
@@ -75,7 +76,17 @@ export async function generate(description, opts) {
75
76
  // project as the stylesheet it has to match.
76
77
  await readInstalledConvention(root, slug), res.name, await readInstalledScheme(root, slug),
77
78
  /** O VOCABULÁRIO DELE - um componente gerado cai no mesmo projeto e fala a mesma língua. */
78
- await projectTongue(root, slug), await installedThemeVars(root, slug));
79
+ await projectTongue(root, slug),
80
+ /**
81
+ * AS PASTAS DE APP, para a resposta ser a INTERSEÇÃO delas - ver `theirThemeVars`.
82
+ *
83
+ * Um componente materializado vai para uma pasta que num monorepo os dois apps importam, e
84
+ * ele não sabe em qual será usado. Varrer da raiz faria o `@theme` do app A responder por
85
+ * um componente escrito no app B: emitimos `bg-brand`, aquele app não gera a classe, e a
86
+ * propriedade renderiza NADA - pior que o valor arbitrário, porque nada falta na tela. É a
87
+ * mesma lição de `INV-VOLTA-12`, que saiu com a instrução de import e não com a topologia.
88
+ */
89
+ await theirThemeVars(root, await detectAppDirs(root, config.pagesDir)));
79
90
  /**
80
91
  * A EDICAO DELE VENCE A REESCRITA (T7) - a leitura que faltava.
81
92
  *
@@ -109,8 +120,18 @@ export async function generate(description, opts) {
109
120
  console.log(` • <${comp} /> - compose its parts + content (see the recipe)`);
110
121
  }
111
122
  else {
112
- console.log(` • @import "_synthesisui/ds/${slug}/generated/${res.name}.css" in your CSS`);
113
- console.log(` • <div data-ds="${slug}"><div class="ds-${res.name}">…</div></div>`);
123
+ /**
124
+ * O ARQUIVO NÃO É IMPORTADO DA NOSSA PASTA - `INV-GERAL-13`, 11/09.
125
+ *
126
+ * A instrução mandava `@import "_synthesisui/ds/<slug>/generated/<name>.css"` e envolver a
127
+ * marcação num `data-ds` nosso. As duas apontavam para dentro da nossa pasta, e é a dependência
128
+ * que esta etapa fechou.
129
+ *
130
+ * A folha gerada já passou pela porta de tradução, então ela fala o vocabulário dele: copiá-la
131
+ * para onde as folhas dele vivem não perde nada, e o que fica aqui segue sendo a referência.
132
+ */
133
+ console.log(` • copy _synthesisui/ds/${slug}/generated/${res.name}.css next to your own stylesheets - it already speaks your vocabulary, and nothing of ours has to be loaded`);
134
+ console.log(` • <div class="ds-${res.name}">…</div>`);
114
135
  }
115
136
  console.log(` (${res.usage.inputTokens} in / ${res.usage.outputTokens} out tokens · AI action - additive; nothing else changed)`);
116
137
  }
@@ -4643,7 +4643,7 @@ export async function runImport(opts) {
4643
4643
  */
4644
4644
  if (payload?.slug) {
4645
4645
  /**
4646
- * A FIAÇÃO DELE CONTRA O SLUG QUE NASCEU - e este aviso sai do CLI, não da skill.
4646
+ * O QUE O CSS DELE AINDA IMPORTA - e este aviso sai do CLI, não da skill.
4647
4647
  *
4648
4648
  * MEDIDO em 24/08: o `globals.css` dele importa `_synthesisui/ds/codelevel/`, o nome escolhido
4649
4649
  * derivou `codelevel-ds`, ninguém mencionou, e o `next build` dele terminou em exit code 1 com
@@ -4658,7 +4658,7 @@ export async function runImport(opts) {
4658
4658
  const warning = wiringWarning(census.wiredSlugs ?? [], payload.slug);
4659
4659
  if (warning) {
4660
4660
  console.log("");
4661
- console.log(section("Your CSS points somewhere else"));
4661
+ console.log(section("Your CSS still imports a stylesheet of ours"));
4662
4662
  for (const line of warning.split("\n"))
4663
4663
  console.log(body(line));
4664
4664
  }
@@ -1,6 +1,5 @@
1
1
  import { DEFAULT_CONFIG, writeProjectConfig } from "../config.js";
2
2
  import { body, section, snippet } from "../output.js";
3
- import { setupPrompt } from "../setup-prompt.js";
4
3
  import { resolveDeps, stylesFor, tailwindMajor } from "../stack.js";
5
4
  import { formMix, measuredStyle } from "../styles-flavour.js";
6
5
  import { add } from "./add.js";
@@ -134,19 +133,23 @@ export async function init(opts) {
134
133
  dir: root,
135
134
  setupHints: false,
136
135
  });
136
+ /**
137
+ * ERAM DOIS PASSOS E É UM - `INV-GERAL-13`, 11/09.
138
+ *
139
+ * O passo 2 colava `setupPrompt` no agente dele: dois `@import` da nossa folha, um `data-ds` na
140
+ * árvore dele, e o mapa de tipografia. Ele não existe mais, porque nada que a plataforma escreve
141
+ * precisa de folha nossa carregada - e um comando de primeira vez é o pior lugar possível para
142
+ * ensinar uma dependência que o produto promete não criar.
143
+ */
137
144
  console.log("");
138
- console.log(section("Two steps left, and neither is a file you edit"));
139
- console.log(body("1. Turn the check and the tools on - run this BEFORE you open your agent,"));
140
- console.log(body(" because hooks and tools are read when a session starts:"));
145
+ console.log(section("One step left, and it is not a file you edit"));
146
+ console.log(body("Turn the check and the tools on - run this BEFORE you open your agent,"));
147
+ console.log(body("because hooks and tools are read when a session starts:"));
141
148
  console.log("");
142
149
  console.log(snippet(["npx synthesisui@latest connect"]));
143
150
  console.log("");
144
- console.log(body("2. Open your agent and paste this once - it does the setup for you:"));
145
- console.log("");
146
- console.log(snippet(setupPrompt(opts.ds).split("\n")));
147
- console.log("");
148
- console.log(body("Then ask it for something real. The check introduces itself on the first"));
149
- console.log(body("clean file and goes quiet after that."));
151
+ console.log(body("Then ask your agent for something real. The check introduces itself on the"));
152
+ console.log(body("first clean file and goes quiet after that."));
150
153
  return;
151
154
  }
152
155
  console.log("");
@@ -2,9 +2,10 @@ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { basename, join } from "node:path";
3
3
  import { generateComponentFiles } from "../component-codegen.js";
4
4
  import { readProjectConfig, resolveRegistry } from "../config.js";
5
+ import { detectAppDirs } from "../global-sheet.js";
5
6
  import { installedSlugs } from "../installed.js";
6
7
  import { body, section, snippet } from "../output.js";
7
- import { installedThemeVars, reactMajorOf, readInstalledConvention, readInstalledScheme, } from "../project-facts.js";
8
+ import { reactMajorOf, readInstalledConvention, readInstalledScheme, theirThemeVars, } from "../project-facts.js";
8
9
  import { fetchComponent, postRefit, postSaveComponent, RegistryError, } from "../registry.js";
9
10
  import { flavourResolver } from "../styles-flavour.js";
10
11
  import { projectTongue } from "../their-tongue.js";
@@ -121,7 +122,17 @@ export async function refit(file, opts) {
121
122
  const flavourOf = await flavourResolver(root, config.styles);
122
123
  const { files } = generateComponentFiles(slug, res.name, res.recipe, res.css, saved.version, flavourOf(res.name), await reactMajorOf(root), await readInstalledConvention(root, slug), res.name, await readInstalledScheme(root, slug),
123
124
  /** O VOCABULÁRIO DELE - o `refit` reescreve o componente e não passava pela porta. */
124
- await projectTongue(root, slug), await installedThemeVars(root, slug));
125
+ await projectTongue(root, slug),
126
+ /**
127
+ * AS PASTAS DE APP, para a resposta ser a INTERSEÇÃO delas - ver `theirThemeVars`.
128
+ *
129
+ * Um componente materializado vai para uma pasta que num monorepo os dois apps importam, e
130
+ * ele não sabe em qual será usado. Varrer da raiz faria o `@theme` do app A responder por
131
+ * um componente escrito no app B: emitimos `bg-brand`, aquele app não gera a classe, e a
132
+ * propriedade renderiza NADA - pior que o valor arbitrário, porque nada falta na tela. É a
133
+ * mesma lição de `INV-VOLTA-12`, que saiu com a instrução de import e não com a topologia.
134
+ */
135
+ await theirThemeVars(root, await detectAppDirs(root, config.pagesDir)));
125
136
  /**
126
137
  * A EDICAO DELE VENCE A REESCRITA (T7) - a leitura que faltava, a mesma do `generate`.
127
138
  *
@@ -68,12 +68,16 @@ export async function summary(slug, opts) {
68
68
  /**
69
69
  * LINHA 3 - O QUE FAZER AGORA, e é uma coisa só.
70
70
  *
71
- * As duas linhas de fiação são o portão de tudo o mais: sem elas nenhum token chega ao navegador e
72
- * todo número seguinte é vazio. O `doctor` diz isso melhor e com o estado real, então o próximo passo
73
- * é ele - não uma explicação nossa aqui.
71
+ * AS DUAS LINHAS DE FIAÇÃO SAÍRAM EM 11/09 - `INV-GERAL-13`. Elas eram descritas aqui como *"o
72
+ * portão de tudo o mais"*, e essa frase era verdadeira enquanto o que a plataforma escrevia
73
+ * dependia da nossa folha. Não depende mais: um componente chega falando os nomes que o código
74
+ * dele declara, ou carregando o valor.
75
+ *
76
+ * Este é o último ecrã da primeira corrida, e é o pior lugar do produto para ensinar uma
77
+ * dependência - ele é lido uma vez, com atenção, por alguém que está decidindo se fica.
74
78
  */
75
79
  console.log("");
76
- console.log(body("Two lines wire it up, and `init` printed both:"));
77
- console.log(body(paint.dim(` @import "./_synthesisui/ds/${slug}/tokens.css"; and data-ds="${slug}" on <html>`)));
80
+ console.log(body("Nothing of ours goes into your code - there is no"));
81
+ console.log(body("stylesheet to import and no attribute to add."));
78
82
  console.log(paint.blue(snippet(["npx synthesisui@latest doctor"])));
79
83
  }
@@ -1,11 +1,18 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { dirname, join, relative } from "node:path";
3
3
  import { readProjectConfig, resolveRegistry } from "../config.js";
4
- import { fetchTemplate } from "../registry.js";
5
- import { installedThemeCss, installedTokensCss, whatOnlyTheSheetResolves, } from "../sheet-needed.js";
6
- import { inTheirTongue, projectTongue } from "../their-tongue.js";
4
+ import { installedSheetCss, RECIPE_HEADER, recipeRulesFor, } from "../recipe-css.js";
5
+ import { fetchDesignSystem, fetchTemplate } from "../registry.js";
6
+ import { inTheirTongue, projectTongue, tongueFromArtifacts, } from "../their-tongue.js";
7
7
  import { keptLine, recordWritten, editedHere as theirEdits, } from "../written.js";
8
8
  /**
9
+ * A INSTRUÇÃO SAI DE TODA PÁGINA, SEMPRE - `INV-GERAL-13`, 11/09.
10
+ *
11
+ * Esta filtragem era CONDICIONAL: ela rodava só quando a medição dizia que a página não precisava
12
+ * mais da folha, e no caso contrário o comentário ficava no arquivo dele mandando importar. Não há
13
+ * mais caso contrário - as regras que a página veste são copiadas para o `.css` ao lado dela
14
+ * (`recipe-css.ts`), e nada no arquivo aponta para nós.
15
+ *
9
16
  * O QUE O ARQUIVO DIZ TAMBÉM É COBRANÇA - e ela é a metade que FICA no repositório dele.
10
17
  *
11
18
  * O DEFEITO, achado pela revisão de DX no fecho da etapa 11: o fecho no terminal parou de mandar
@@ -88,12 +95,28 @@ export async function template(slug, name, opts) {
88
95
  return;
89
96
  }
90
97
  /**
91
- * NENHUMA MATERIALIZAÇÃO VAZA VOCABULÁRIO INTERNO (INV-VOLTA-02) - a mesma porta do
92
- * `component`. Uma página inteira saía com `var(--ds-*)` cru enquanto um componente avulso
93
- * falava a língua dele; a promessa é uma só. Sem mapa no `.lock`, nada é traduzido e a folha
94
- * instalada continua sendo o caminho - e a saída diz qual dos dois aconteceu.
98
+ * NENHUMA MATERIALIZAÇÃO VAZA VOCABULÁRIO INTERNO (INV-VOLTA-02) - a mesma porta do `component`,
99
+ * e agora também o mesmo CAMINHO DE LEITURA.
100
+ *
101
+ * SEM `add`, O SISTEMA VEM DO REGISTRY. Uma página trazida antes do install - e o próprio guard
102
+ * de edição deste comando já reconhece esse fluxo - não tinha pasta para ler, `projectTongue`
103
+ * respondia `null`, e o arquivo ia a disco com `var(--ds-*)` cru e as classes `.ds-*` sem uma
104
+ * regra sequer. Calado: o relatório da tradução só existe quando houve tradução.
105
+ *
106
+ * O `component` recebeu este conserto em 10/09 (`tongueFromArtifacts`) e o `template` não - "a
107
+ * mesma promessa, duas implementações, uma delas ausente" é literalmente o defeito que
108
+ * `INV-VOLTA-02` nomeia, e ele estava aqui.
109
+ *
110
+ * UMA IDA A MAIS À REDE, e só neste caminho: quem já instalou lê do disco como sempre.
95
111
  */
96
- const tongue = await projectTongue(root, slug);
112
+ const installedTongue = await projectTongue(root, slug);
113
+ const fromRegistry = installedTongue
114
+ ? null
115
+ : await fetchDesignSystem(base, slug, opts.version).catch(() => null);
116
+ const tongue = installedTongue ??
117
+ (fromRegistry
118
+ ? await tongueFromArtifacts(root, fromRegistry.artifacts)
119
+ : null);
97
120
  const speak = (code) => (tongue ? inTheirTongue(code, tongue) : null);
98
121
  let named = 0;
99
122
  let inlined = 0;
@@ -130,38 +153,55 @@ export async function template(slug, name, opts) {
130
153
  content: translate(f.code),
131
154
  });
132
155
  /**
133
- * A FOLHA SÓ É COBRADA QUANDO ESTA PÁGINA PRECISA DELA (A2 da etapa 11) - ver
134
- * `sheet-needed.ts`, a mesma porta que o `component` usa.
156
+ * AS REGRAS DE RECEITA VÊM JUNTO, no `.css` ao lado da página - `INV-GERAL-13`, 11/09.
135
157
  *
136
- * O DEFEITO, medido em 10/09: o comando já contava em `still` quantas referências sobraram
137
- * apontando para a nossa folha depois da tradução, imprimia o número, e três linhas abaixo
138
- * mandava instalar a folha e colar um `@import` - inclusive quando o número era ZERO. Uma página
139
- * que renderiza igual sem os dois passava a pedi-los, e pedir cria exatamente a dependência que
140
- * a plataforma promete não criar: a gente é referência, não dependência.
158
+ * O QUE MUDOU PARA O CLIENTE: a página renderiza sem nada nosso carregado no app dele. Ela veste
159
+ * `.ds-hero`, `.ds-nav` e as outras classes que o sistema declara, e essas regras passam a morar
160
+ * no arquivo ao lado dela - traduzidas para o vocabulário dele, como todo o resto.
141
161
  *
142
- * E A PERGUNTA É MAIOR QUE `still`, que é por que ela não é feita com ele: a folha entrega as
143
- * variáveis, as classes que só o `@theme` gera E as classes das receitas (`.ds-hero`). Uma
144
- * página veste as três, e cortar o setup contando só as variáveis entregaria uma página pelada.
162
+ * POR QUE A PÁGINA ERA O ÚLTIMO CASO. Ela é a única materialização que dependia da folha por
163
+ * CLASSE, e não por variável: a tradução reescreve `var(--ds-*)` e não tem o que fazer com
164
+ * `.ds-hero`, porque o corpo daquela regra nunca esteve no arquivo. Enquanto isso fosse verdade,
165
+ * este comando terminava mandando instalar o sistema e colar um `@import` - e foi essa instrução
166
+ * que abriu esta etapa.
145
167
  *
146
- * SEM TRADUÇÃO, NADA MUDA: sem o mapa do `.lock` o `--ds-*` cru está no arquivo, a folha é o
147
- * caminho, e a cobrança sai inteira como sempre saiu.
168
+ * O CSS COPIADO PASSA PELA MESMA PORTA: `translate` é a tradução que todo byte deste comando
169
+ * atravessa, e um caminho que escreve CSS sem passá-la é o defeito que `INV-VOLTA-02` nomeia.
148
170
  */
149
- const need = tongue
150
- ? whatOnlyTheSheetResolves({
151
- source: pieces.map((f) => f.content).join("\n"),
152
- themeCss: await installedThemeCss(root, slug),
153
- sheetCss: await installedTokensCss(root, slug),
154
- theirNames: [...tongue.names.values()],
155
- })
156
- : null;
157
- const chargesTheSheet = !need || need.needed;
171
+ const recipes = recipeRulesFor({
172
+ source: pieces.map((f) => f.content).join("\n"),
173
+ /**
174
+ * A FOLHA DE ONDE AS REGRAS SÃO COPIADAS - do disco quando ele instalou, do registry quando não.
175
+ *
176
+ * Sem as duas, `recipes.rules` é 0 e a página sai vestindo `.ds-*` que ninguém declara - com o
177
+ * cabeçalho dela afirmando que a folha ao lado escopa cada regra. Uma frase falsa dentro do
178
+ * arquivo dele é pior que uma instrução: o terminal rola, o arquivo fica.
179
+ */
180
+ sheetCss: fromRegistry?.artifacts?.["tokens.css"] ??
181
+ (await installedSheetCss(root, slug)),
182
+ });
183
+ if (recipes.rules > 0) {
184
+ /**
185
+ * O ARQUIVO IRMÃO É O DESTINO, e ele já existe no alvo `next` - a página o importa desde que
186
+ * as media queries e o hambúrguer CSS-only passaram a viajar nele. Criar um terceiro arquivo
187
+ * daria à página dois CSS para importar e a ninguém uma razão.
188
+ */
189
+ const sibling = pieces.find((p) => p.filename.endsWith(".css"));
190
+ const block = `${RECIPE_HEADER}\n${translate(recipes.css)}\n`;
191
+ if (sibling)
192
+ sibling.content = `${block}\n${sibling.content}`;
193
+ else
194
+ pieces.push({
195
+ rel: join(dirname(pageRel), pageFile.filename.replace(/\.tsx$/, ".css")),
196
+ filename: pageFile.filename.replace(/\.tsx$/, ".css"),
197
+ content: block,
198
+ });
199
+ }
158
200
  await mkdir(pageDir, { recursive: true });
159
201
  /** OS BYTES QUE FORAM A DISCO, para o fingerprint lembrar EXATAMENTE o que escrevemos. */
160
202
  const landed = [];
161
203
  for (const piece of pieces) {
162
- const content = chargesTheSheet
163
- ? piece.content
164
- : withoutTheSheetInstruction(piece.content);
204
+ const content = withoutTheSheetInstruction(piece.content);
165
205
  await writeFile(join(root, piece.rel), content, "utf8");
166
206
  landed.push({ filename: piece.filename, content });
167
207
  console.log(piece.rel === pageRel
@@ -180,35 +220,23 @@ export async function template(slug, name, opts) {
180
220
  console.log(` ${named} reference${named === 1 ? "" : "s"} now speak${named === 1 ? "s" : ""} the name YOUR code gives the value${inlined > 0 ? `, and ${inlined} carr${inlined === 1 ? "ies" : "y"} the value because your code names no token for it` : ""}.`);
181
221
  if (still.size > 0) {
182
222
  const sample = [...still].sort().slice(0, 3).join(", ");
183
- console.log(` ${still.size} still point${still.size === 1 ? "s" : ""} at our stylesheet (${sample}${still.size > 3 ? ", …" : ""}), so tokens.css is still needed here.`);
223
+ console.log(` ${still.size} value${still.size === 1 ? "" : "s"} the system declares no value for (${sample}${still.size > 3 ? ", …" : ""}) - ${still.size === 1 ? "that declaration is" : "those declarations are"} dropped by the browser. Please report this: the sheet we read is incomplete.`);
184
224
  }
185
225
  }
226
+ if (recipes.rules > 0)
227
+ console.log(` ${recipes.rules} rule${recipes.rules === 1 ? "" : "s"} for ${recipes.classes.slice(0, 3).join(", ")}${recipes.classes.length > 3 ? `, +${recipes.classes.length - 3}` : ""} came along in the stylesheet next to it - nothing to import.`);
186
228
  console.log("");
187
229
  console.log("Next steps:");
188
230
  console.log(` • use it in a route, e.g. ${join(config.pagesDir, "page.tsx")}:`);
189
231
  console.log(` import Page from "@/${defaultDir.replace(/\\/g, "/")}/${pageFile.filename.replace(/\.tsx$/, "")}";`);
190
- if (chargesTheSheet) {
191
- /** E ELE DIZ O QUE PEDE A FOLHA - um setup cobrado sem motivo dito ensina a ignorar o próximo. */
192
- if (need)
193
- console.log(` • what still needs the sheet here: ${[...need.recipeClasses, ...need.classes, ...need.variables].slice(0, 3).join(", ")}`);
194
- console.log(` • ensure the DS is installed: synthesisui add ${slug} (provides tokens.css)`);
195
- console.log(` • @import "_synthesisui/ds/${slug}/tokens.css" in your global CSS`);
196
- }
197
- else {
198
- console.log(` • nothing to install: every value in this page is a name YOUR code declares, so it renders without our stylesheet`);
199
- }
232
+ console.log(` • nothing to install: this page and its stylesheet stand on their own`);
200
233
  console.log(" • refine the file: wire real data, split into components, swap placeholders");
201
234
  /**
202
- * E A LINHA DO ESCOPO SÓ SAI ONDE ELA É VERDADE - medido rodando o comando em 10/09.
235
+ * E A LINHA DO ESCOPO NÃO SAI MAIS - `INV-GERAL-13`, 11/09.
203
236
  *
204
- * Ela mandava *"keep the data-ds wrapper and the ds-* classes (stays on-system)"* sempre, e no
205
- * caminho isolado a saída ficava contradizendo a linha logo acima: *"nothing to install… it
206
- * renders without our stylesheet"* e, uma linha depois, uma instrução para manter um escopo que
207
- * não resolve nada naquele arquivo. É o mesmo defeito que este bloco existe para consertar - duas
208
- * frases que não podem ser verdadeiras ao mesmo tempo -, e a medição já responde qual das duas é:
209
- * chegar aqui com `chargesTheSheet` falso significa zero variável nossa e zero classe nossa no
210
- * que acabou de ser escrito.
237
+ * Ela mandava *"keep the data-ds wrapper and the ds-* classes (stays on-system)"*. O `data-ds`
238
+ * existia para a NOSSA folha aplicar dentro dele, e o app dele não a carrega: pedir que ele
239
+ * mantenha um atributo nosso na árvore dele é a mesma dependência com outro nome. As classes
240
+ * continuam valendo, e agora elas são declaradas pelo arquivo ao lado da própria página.
211
241
  */
212
- if (chargesTheSheet)
213
- console.log(` • keep the data-ds="${slug}" wrapper and the ds-* / layout classes (stays on-system)`);
214
242
  }
@@ -7,9 +7,10 @@ import { generateComponentFiles } from "../component-codegen.js";
7
7
  import { readProjectConfig, readToken, resolveRegistry } from "../config.js";
8
8
  import { unsentEvents } from "../doctor/ledger.js";
9
9
  import { diffLocalDocuments, localChangelogMarkdown, } from "../document-diff.js";
10
+ import { detectAppDirs } from "../global-sheet.js";
10
11
  import { installedBehind, MATERIALISER_SINCE } from "../install-marks.js";
11
12
  import { body, section, snippet } from "../output.js";
12
- import { installedThemeVars, reactMajorOf, readInstalledConvention, readInstalledScheme, } from "../project-facts.js";
13
+ import { reactMajorOf, readInstalledConvention, readInstalledScheme, theirThemeVars, } from "../project-facts.js";
13
14
  import { fetchChangelog, fetchComponent, fetchDesignSystem, RegistryError, } from "../registry.js";
14
15
  import { flavourResolver } from "../styles-flavour.js";
15
16
  import { projectTongue } from "../their-tongue.js";
@@ -419,8 +420,9 @@ export async function upgrade(asked, opts) {
419
420
  * língua do repositório. Resolvido uma vez para a corrida inteira, como o sabor.
420
421
  */
421
422
  const tongue = await projectTongue(root, slug);
422
- /** A folha instalada é a MESMA para a corrida inteira - ver `installedThemeVars`. */
423
- const themeVars = await installedThemeVars(root, slug);
423
+ /** O `@theme` DELE é o MESMO para a corrida inteira - ver `theirThemeVars`. */
424
+ /** As pastas de app - a resposta é a INTERSEÇÃO delas. Ver `theirThemeVars`. */
425
+ const themeVars = await theirThemeVars(root, await detectAppDirs(root, config.pagesDir));
424
426
  for (const entry of entries) {
425
427
  const tsxPath = join(componentsRoot, entry, `${entry}.tsx`);
426
428
  let head = "";
@@ -90,10 +90,12 @@ export async function use(slug, intent, opts) {
90
90
  // The styling contract differs by target: Next projects in this product use
91
91
  // Tailwind v4 backed by the DS; the "general" target is framework-agnostic CSS.
92
92
  const stylingRule = config.target === "general"
93
- ? `- Style with the design system only: reuse the \`.ds-*\` recipe classes and the ` +
94
- `\`var(--ds-*)\` custom properties. Never use raw hex/px outside the system's scale.`
95
- : `- Style with the design system only: reuse the \`.ds-*\` recipe classes and the ` +
96
- `DS-backed Tailwind utilities (${utilities}…). Never use raw hex/px outside the system's scale.`;
93
+ ? `- Style with the design system only: bring its recipes in as code ` +
94
+ `(\`npx synthesisui component ${slug} <name>\`) and write the names THIS project declares - ` +
95
+ `\`find_token\` answers with the exact one. Never use raw hex/px outside the system's scale.`
96
+ : `- Style with the design system only: bring its recipes in as code ` +
97
+ `(\`npx synthesisui component ${slug} <name>\`) and use this project's own Tailwind utilities ` +
98
+ `(${utilities}…). Never use raw hex/px outside the system's scale.`;
97
99
  const task = intent.trim() || "build the UI I describe next";
98
100
  const prompt = [
99
101
  `Use the "${name}" design system (slug: ${slug}, v${version}) to: ${task}`,
@@ -106,11 +108,8 @@ export async function use(slug, intent, opts) {
106
108
  `\`${config.componentsDir}/\` and \`${config.pagesDir}/\`). If it does, modify only what's ` +
107
109
  `needed and keep the rest of the file intact and on-system. If it doesn't, build it new - ` +
108
110
  `components in \`${config.componentsDir}/\`, pages in \`${config.pagesDir}/\`.`,
109
- `- Scope the markup with \`data-ds="${slug}"\` (or rely on it at the app root).`,
110
111
  stylingRule,
111
- config.target === "next"
112
- ? '- Make sure `tokens.css` + `theme.css` are imported in the global CSS (see the GUIDE\'s "How to apply").'
113
- : '- Make sure `tokens.css` is imported in the global CSS (see the GUIDE\'s "How to apply").',
112
+ "- Nothing of the platform's is imported into this project: a component brought in by `component` carries everything it needs.",
114
113
  "- Implement the behavior yourself (open/close, focus, routing) - the system ships the looks, not the JS.",
115
114
  "",
116
115
  "Composition (make it look composed, not just correct):",
@@ -470,8 +470,16 @@ const scaleKey = (v) => {
470
470
  const m = v.match(/^\{typography\.scale\.([a-zA-Z0-9-]+)\.fontSize\}$/);
471
471
  return m ? kebab(m[1]) : null;
472
472
  };
473
- /** "{color.semantic.primary}" → "var(--ds-color-semantic-primary)" - the raw
474
- * scoped vars always exist, so arbitrary-property fallbacks never dangle. */
473
+ /**
474
+ * "{color.semantic.primary}" → "var(--ds-color-semantic-primary)" - a grafia INTERNA, que a porta
475
+ * de tradução (`speak`) reescreve no nome dele ou no valor literal antes de qualquer byte ir a
476
+ * disco.
477
+ *
478
+ * ELA NUNCA SOBREVIVE ATÉ O ARQUIVO DELE (`INV-GERAL-13`): `speak` é a última coisa que roda em
479
+ * `generateComponentFiles`, e `nothing-of-ours-is-asked-for.spec.ts` reprova o texto que sair daqui
480
+ * com a nossa grafia dentro. Existe como forma intermediária porque é ela que carrega o caminho da
481
+ * referência (`color.semantic.primary`) até a folha que sabe o valor.
482
+ */
475
483
  const refToDsVar = (v) => v.replace(/\{([a-z0-9.-]+)\}/gi, (_, path) => {
476
484
  return `var(--ds-${path.split(".").map(kebab).join("-")})`;
477
485
  });
@@ -531,25 +539,28 @@ function remToTwScale(value) {
531
539
  return Number.isInteger(n) && n >= 0 && n <= 96 ? String(n) : null;
532
540
  }
533
541
  /**
534
- * O UTILITÁRIO NOMEADO SÓ VALE QUANDO A FOLHA INSTALADA O DECLARA - e é isso que decide o VALOR.
542
+ * O UTILITÁRIO NOMEADO SÓ VALE QUANDO O `@theme` **DELE** O DECLARA - e é isso que decide o VALOR.
535
543
  *
536
544
  * `rounded-lg` existe em qualquer projeto com Tailwind, então a classe nunca "falta". O que muda é
537
- * de quem é o valor: quando o `theme.css` que caiu na pasta dele declara `--radius-lg`, a classe
538
- * pinta o raio DO SISTEMA; quando não declara, ela pinta o default do Tailwind - e o componente
539
- * mente em silêncio, que é pior que uma classe morta, porque nada na tela parece faltar.
545
+ * de quem é o valor: quando o `@theme` do projeto dele declara `--radius-lg`, a classe pinta o raio
546
+ * DELE, e a decisão continua sendo dele; quando não declara, ela pinta o default do Tailwind - e o
547
+ * componente mente em silêncio, que é pior que uma classe morta, porque nada na tela parece faltar.
540
548
  *
541
- * A folha não declara por dois motivos, os dois legítimos: a redefinição roubaria uma classe que o
542
- * app dele já usa (`INV-VOLTA-13`, o alinhamento), ou o compilador não faz ponte para um nome que
543
- * não está em `meta.ownedNames` (o `--font-body` do codelevel). Nos dois casos a resposta certa é a
544
- * mesma: emitir o valor arbitrário com `var(--ds-*)`, que resolve sempre dentro de `[data-ds]`.
549
+ * A PERGUNTA ERA SOBRE A NOSSA FOLHA ATÉ 11/09, e era a última dependência de runtime que o código
550
+ * entregue carregava. `rounded-lg` escrito porque o NOSSO `theme.css` declara `--radius-lg` só pinta
551
+ * o sistema num app que importa aquele arquivo - e `INV-GERAL-13` diz que nenhum app dele importa. O
552
+ * componente saía com a aparência certa na nossa cabeça e o raio do Tailwind na tela dele.
545
553
  *
546
- * MEDIDO em 07/09 sobre TODOS os componentes das duas populações:
554
+ * QUANDO ELE NÃO DECLARA, a resposta é o valor arbitrário - e a porta de tradução o resolve no nome
555
+ * dele ou no literal, nunca numa variável nossa.
547
556
  *
548
- * codelevel, repo real 58 de 110 utilitários de token (53%) não carregavam o valor dele
557
+ * MEDIDO em 07/09 sobre TODOS os componentes das duas populações, com a folha NOSSA respondendo:
558
+ *
559
+ * codelevel, repo real 58 de 110 utilitários de token (53%) já caíam no arbitrário
549
560
  * ember, app novo 34 de 241 (14%)
550
561
  *
551
- * `null` é projeto sem folha instalada - `refit` e `generate` já dizem que nada pinta sem o `add`,
552
- * e ali o nome legível não custa nada.
562
+ * `null` é "ninguém mediu" - `refit` e `generate` já dizem que nada pinta sem o `add`, e ali o nome
563
+ * legível não custa nada.
553
564
  */
554
565
  const minted = (themeVars, cssVar) => themeVars === null || themeVars.has(cssVar);
555
566
  /** One declaration → Tailwind classes (pretty when mappable, arbitrary-property
@@ -890,15 +901,27 @@ function variantOwnedProps(variants, axis) {
890
901
  return [...props];
891
902
  }
892
903
  // ── Emission ─────────────────────────────────────────────────────────────────
904
+ /**
905
+ * O CABEÇALHO DO ARQUIVO DELE - e as duas linhas de setup saíram dele em 11/09.
906
+ *
907
+ * O QUE ELAS DIZIAM, dentro de TODO `.tsx` que a plataforma escreve no repositório dele:
908
+ * *"Global setup (once per app): import _synthesisui/ds/<slug>/tokens.css"* e *"put data-ds=<slug>
909
+ * on a root element"*. Era a instrução que o produto promete não dar, escrita no lugar onde ela
910
+ * sobrevive a qualquer conserto do terminal - e ela ficava lá, num arquivo dele, para todo agente
911
+ * e toda pessoa lerem depois (`INV-GERAL-13`).
912
+ *
913
+ * E ELA FAZIA UM SEGUNDO ESTRAGO, medido em 10/09: `readWiring` procurava essas mesmas strings no
914
+ * repositório para responder *"este projeto carrega o sistema?"*, então a partir do primeiro
915
+ * componente que NÓS escrevemos ela respondia `imported: true` sobre um projeto que não importa
916
+ * nada. Um comentário nosso nunca provou nada sobre o projeto dele.
917
+ *
918
+ * O que fica é a PROCEDÊNCIA - quem escreveu, de qual sistema, em que versão. Ela não pede nada; ela
919
+ * responde a pergunta de quem abre o arquivo daqui a seis meses.
920
+ */
893
921
  function header(slug, name, version, mode) {
894
- const setup = mode === "tailwind"
895
- ? `import _synthesisui/ds/${slug}/theme.css (Tailwind adapter) + tokens.css`
896
- : `import _synthesisui/ds/${slug}/tokens.css`;
897
922
  return [
898
923
  `// Generated by SynthesisUI - "${name}" from the "${slug}" design system (v${version}).`,
899
- `// On-system by construction: every style resolves to the DS tokens.`,
900
- `// Global setup (once per app): ${setup}`,
901
- `// and put data-ds="${slug}" on a root element (e.g. <body data-ds="${slug}">).`,
924
+ `// Every value here is a name YOUR code declares, or the value itself - nothing to import.`,
902
925
  ...(mode === "tailwind" ? [OVERRIDE_WARNING] : []),
903
926
  ].join("\n");
904
927
  }
@@ -1507,12 +1530,16 @@ scheme,
1507
1530
  */
1508
1531
  tongue,
1509
1532
  /**
1510
- * O QUE A FOLHA INSTALADA DECLARA NO `@theme` - `installedThemeVars(root, slug)`.
1533
+ * O QUE O `@theme` **DELE** DECLARA - `theirThemeVars(root)`.
1511
1534
  *
1512
1535
  * OBRIGATÓRIO, pelo mesmo argumento do `scheme` e do `tongue`: o utilitário nomeado que este
1513
- * módulo escreve só carrega o valor DO SISTEMA quando a folha que caiu na pasta dele declara a
1536
+ * módulo escreve só carrega um valor de design quando o `@theme` do PROJETO dele declara a
1514
1537
  * variável. Sem essa resposta o codegen escreve `rounded-lg` e a classe pinta o default do
1515
- * Tailwind - o componente mente sem nada faltar na tela. `null` é projeto sem folha instalada.
1538
+ * Tailwind - o componente mente sem nada faltar na tela.
1539
+ *
1540
+ * ERA A NOSSA FOLHA QUE RESPONDIA ISTO, e essa era a última dependência de runtime que o código
1541
+ * entregue ainda tinha (`INV-GERAL-13`, 11/09). `null` é "ninguém mediu", e continua significando
1542
+ * que o nome legível não custa nada - `refit` e `generate` já dizem que nada pinta sem o `add`.
1516
1543
  */
1517
1544
  themeVars) {
1518
1545
  const files = [];
@@ -66,6 +66,9 @@ register("pt-BR", {
66
66
  "already current": "já está em dia",
67
67
  "updated to this CLI's pipeline": "atualizada para esta versão",
68
68
  "removed - renamed to /sui-import-ds": "removida - virou /sui-import-ds",
69
+ /** A skill aposentada fica no repositório dele, nomeada - ver `RETIRED_SKILLS`. */
70
+ "retired - nothing of ours loads in your app any more": "aposentada - nada nosso carrega no seu app",
71
+ "it stays in your repo - yours to delete": "ela fica no seu repositório - sua para apagar",
69
72
  // ── onde o bloco de regras caiu ──
70
73
  "how to turn this repo into your system": "como transformar este repo no seu sistema",
71
74
  "rewritten for what is installed": "reescrito para o que está instalado",
@@ -437,7 +437,7 @@ export function describeFix(result, dry) {
437
437
  const lines = [];
438
438
  if (applied.length === 0) {
439
439
  lines.push(decisions > 0
440
- ? `Nothing to apply. All ${decisions} findings are values your system has no name for - those are design decisions, not fixes.`
440
+ ? `Nothing to apply. All ${decisions} findings are values your own code names nowhere - those are design decisions, not fixes. Name one and \`--fix\` picks it up.`
441
441
  : relative > 0
442
442
  ? `Nothing to apply. All ${relative} findings are lengths in \`em\`, which follow the element's font size - swapping them can move the layout, so that call is yours.`
443
443
  : /**
@@ -531,6 +531,6 @@ export function describeFix(result, dry) {
531
531
  const rewritten = new Set(applied.map((a) => `${a.file}:${a.line}`));
532
532
  const halfWritten = skipped.filter((s) => rewritten.has(`${s.file}:${s.line}`)).length;
533
533
  if (halfWritten > 0)
534
- lines.push(` ${halfWritten} of those ${halfWritten === 1 ? "sits" : "sit"} on a line this command just rewrote - a shorthand comes back with a token and a literal side by side (\`padding: 8px var(--ds-spacing-24)\`). See them by value: npx synthesisui doctor --migrate`);
534
+ lines.push(` ${halfWritten} of those ${halfWritten === 1 ? "sits" : "sit"} on a line this command just rewrote - a shorthand comes back with a token and a literal side by side (\`padding: 8px var(--spacing-24)\`). See them by value: npx synthesisui doctor --migrate`);
535
535
  return lines;
536
536
  }