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.
@@ -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
  }
@@ -8,6 +8,7 @@ import { markSent, readEvents } from "../doctor/ledger.js";
8
8
  import { checkableName, closeRequest, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
9
9
  import { sampledForUpload } from "../doctor/style-ledger.js";
10
10
  import { describeDelta, fingerprintReadings, readSyncMark, writeSyncMark, } from "../last-sync.js";
11
+ import { installedVersion, landedLine, localEdits, readInstalledPair, sentLine, settleBaseline, } from "../local-edits.js";
11
12
  import { measuredScope, rememberScope } from "../measured-scope.js";
12
13
  import { fromCensus } from "../memory/observation.js";
13
14
  import { reportMeasurement } from "../memory/report.js";
@@ -551,6 +552,43 @@ export async function remeasure(args) {
551
552
  return;
552
553
  }
553
554
  }
555
+ /**
556
+ * O QUE ELE MUDOU NO DESIGN SYSTEM INSTALADO, lido do disco e sem rede.
557
+ *
558
+ * A comparação é contra `.installed.json`, a cópia que o `add` deixa ao lado. Um sistema
559
+ * instalado antes desta etapa não a tem, e aí o comando DIZ o que falta em vez de tratar o
560
+ * documento inteiro como mudado - ver `readInstalledPair`.
561
+ */
562
+ let edits = [];
563
+ let localDocument = null;
564
+ let localSaid = null;
565
+ {
566
+ const version = await installedVersion(root, slug);
567
+ /**
568
+ * UM ERRO INESPERADO AQUI É DITO, e não engolido. A primeira escrita caía em
569
+ * "not-installed" para qualquer exceção - um disco cheio, uma permissão, um link quebrado -
570
+ * e a saída ficava idêntica à de um repositório sem sistema instalado.
571
+ */
572
+ const pair = version
573
+ ? await readInstalledPair(root, slug, version).catch((error) => ({
574
+ kind: "unreadable",
575
+ where: `_synthesisui/ds/${slug}/v${version}`,
576
+ why: error instanceof Error ? error.message : String(error),
577
+ }))
578
+ : { kind: "not-installed" };
579
+ if (pair.kind === "pair") {
580
+ edits = localEdits(pair.installed, pair.local);
581
+ if (edits.length > 0)
582
+ localDocument = pair.local;
583
+ localSaid = sentLine(edits, slug);
584
+ }
585
+ else if (pair.kind === "unreadable") {
586
+ localSaid = `your local ${slug} could not be read, so nothing of it went up: ${pair.why}\n ${pair.where}`;
587
+ }
588
+ else if (pair.kind === "no-baseline") {
589
+ localSaid = `we cannot tell what changed in your local ${slug}: ${pair.why}`;
590
+ }
591
+ }
554
592
  const res = await fetch(`${base}/api/registry/ds/${slug}/census`, {
555
593
  method: "POST",
556
594
  headers: {
@@ -568,6 +606,17 @@ export async function remeasure(args) {
568
606
  census: census.ledger
569
607
  ? { ...census, ledger: sampledForUpload(census.ledger) }
570
608
  : census,
609
+ /**
610
+ * E O QUE O AGENTE DELE MUDOU NOS ARQUIVOS DO DESIGN SYSTEM - a outra metade do ciclo.
611
+ *
612
+ * O prompt que a plataforma monta termina mandando rodar este comando, e até aqui ele
613
+ * media só o CÓDIGO dele: o design system instalado aparecia apenas para descobrir quais
614
+ * slugs existem. O documento só sobe quando há diferença de verdade contra o que o `add`
615
+ * instalou - sem edição, este campo não existe e nada muda do lado de lá.
616
+ */
617
+ ...(edits.length > 0 && localDocument
618
+ ? { document: localDocument, edits }
619
+ : {}),
571
620
  }),
572
621
  }).catch(() => null);
573
622
  if (!res?.ok) {
@@ -580,6 +629,20 @@ export async function remeasure(args) {
580
629
  console.log("");
581
630
  console.log(section("Sent"));
582
631
  console.log(body(`${out.written ?? 0} of ${out.total ?? 0} components written.`));
632
+ /**
633
+ * O QUE VEIO DOS ARQUIVOS DELE, DITO - e antes de qualquer outra nota, porque é a única parte
634
+ * desta saída que fala do trabalho que ELE acabou de fazer.
635
+ *
636
+ * Um "ok" mudo depois de um agente ter mexido em doze peças é pior que nenhuma saída: ele não
637
+ * tem como saber se o comando entendeu o trabalho, e roda de novo para conferir.
638
+ */
639
+ if (localSaid)
640
+ console.log(body(localSaid));
641
+ /** A única frase no passado, e ela vem DEPOIS da resposta do servidor. */
642
+ if (out.hisEdits?.applied)
643
+ console.log(body(landedLine(out.hisEdits.applied, slug)));
644
+ if (out.hisEdits?.refused)
645
+ console.log(body(paint.strong(` ✕ your local changes were refused, and nothing of them was written: ${out.hisEdits.refused}`)));
583
646
  for (const note of out.notes ?? [])
584
647
  console.log(body(paint.faint(note)));
585
648
  /**
@@ -662,6 +725,27 @@ export async function remeasure(args) {
662
725
  ...(args.cli ? { cli: args.cli } : {}),
663
726
  readings: fingerprintReadings(census.components),
664
727
  };
728
+ /**
729
+ * E O BASELINE PASSA A SER O QUE ESTÁ NO DISCO - a metade que fecha o ciclo.
730
+ *
731
+ * Sem esta linha, todo `sync` reenviaria a mesma alteração para sempre, e o documento na
732
+ * plataforma ganharia uma revisão nova a cada rodada sem nada ter mudado. Só depois de o
733
+ * servidor ter aceitado: uma recusa mantém o baseline velho, para a próxima rodada tentar de
734
+ * novo em vez de esquecer o que ele fez.
735
+ */
736
+ /**
737
+ * A CONDIÇÃO É `applied`, e não "não recusou" - a diferença apaga o trabalho dele.
738
+ *
739
+ * Um servidor que responde 200 SEM o campo `hisEdits` é todo deploy anterior a esta etapa, e
740
+ * essa janela existe de verdade: o CLI é publicado antes de a plataforma subir. Com
741
+ * `!refused`, o CLI dava o baseline por assentado, o disco esquecia o que o agente fez, e a
742
+ * plataforma nunca tinha recebido. A alteração some dos dois lados.
743
+ */
744
+ if (out.hisEdits?.applied && localDocument) {
745
+ const version = await installedVersion(root, slug);
746
+ if (version)
747
+ await settleBaseline(root, slug, version, localDocument).catch(() => { });
748
+ }
665
749
  console.log(body(paint.faint(describeDelta(mark, await readSyncMark(root)))));
666
750
  await writeSyncMark(root, mark);
667
751
  /**
@@ -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):",