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.
- package/dist/claude-md.js +19 -38
- package/dist/commands/add.js +37 -97
- package/dist/commands/component.js +96 -201
- package/dist/commands/connect.js +41 -0
- package/dist/commands/doctor.js +74 -428
- package/dist/commands/generate.js +25 -4
- package/dist/commands/import.js +2 -2
- package/dist/commands/init.js +13 -10
- package/dist/commands/refit.js +13 -2
- package/dist/commands/summary.js +9 -5
- package/dist/commands/template.js +81 -53
- package/dist/commands/upgrade.js +5 -3
- package/dist/commands/use.js +7 -8
- package/dist/component-codegen.js +50 -23
- package/dist/copy/connect.pt-BR.js +3 -0
- package/dist/doctor/apply-fix.js +2 -2
- package/dist/doctor/scan.js +33 -2
- package/dist/doctor/their-names.js +8 -2
- package/dist/fonts.js +29 -5
- package/dist/global-sheet.js +14 -125
- package/dist/guide.js +131 -83
- package/dist/install-marks.js +4 -4
- package/dist/project-facts.js +138 -32
- package/dist/recipe-css.js +249 -0
- package/dist/skills.js +5 -16
- package/dist/their-tongue.js +180 -43
- package/dist/wired-slugs.js +31 -17
- package/package.json +1 -1
- package/dist/setup-prompt.js +0 -95
- package/dist/sheet-needed.js +0 -317
- package/dist/skill-configure.js +0 -13
- package/dist/wiring-read.js +0 -212
package/dist/doctor/scan.js
CHANGED
|
@@ -12,14 +12,40 @@
|
|
|
12
12
|
import { utilitiesOn } from "./idiom-names.js";
|
|
13
13
|
import { normalizeValue, tokenMatch } from "./tokens.js";
|
|
14
14
|
/**
|
|
15
|
-
* O NOME QUE SE ESCREVE NO ARQUIVO DELE - e
|
|
15
|
+
* O NOME QUE SE ESCREVE NO ARQUIVO DELE - e ele é SEMPRE do vocabulário dele (`INV-GERAL-13`).
|
|
16
16
|
*
|
|
17
17
|
* Seis lugares decidem o que a pessoa vê ou o que o `--fix` grava: o relatório, o plano, o hook, o
|
|
18
18
|
* MCP, a aplicação e a contagem. Deixar cada um lembrar de preferir `theirToken` é o padrão do
|
|
19
19
|
* argumento opcional que se esquece - e o sintoma seria o pior possível: o comando aconselhando um
|
|
20
20
|
* nome e o hook aconselhando outro, sobre o mesmo arquivo.
|
|
21
|
+
*
|
|
22
|
+
* O NOSSO NOME SAIU DAQUI EM 11/09, e ele era a metade que contrariava a decisão de 07/09.
|
|
23
|
+
*
|
|
24
|
+
* A regra é literal: *"tem nome no sistema DELE? SIM -> a nossa receita usa o nome dele. NÃO ->
|
|
25
|
+
* mantém HARDCODED, e a plataforma só AVISA"*. Esta função devolvia `theirToken ?? token`, e o
|
|
26
|
+
* `token` é o nome COMPILADO do sistema - a grafia `--ds-*`, que é invenção nossa. Então o comando
|
|
27
|
+
* que promete adotar o sistema trocava um literal que funciona por uma variável que só resolve com
|
|
28
|
+
* uma folha nossa carregada no app dele.
|
|
29
|
+
*
|
|
30
|
+
* MEDIDO no `codelevel` em 04/09, sobre as 216 trocas com nome esperando: **60 (28%) escreviam a
|
|
31
|
+
* nossa grafia**. Eram exatamente as que exigiam o `@import` - e para protegê-las o comando recusava
|
|
32
|
+
* as outras 156 por inteiro, cobrando a folha em troca.
|
|
33
|
+
*
|
|
34
|
+
* O valor que só o nosso lado nomeia não desaparece do relatório: ele vira AVISO, com o valor e a
|
|
35
|
+
* linha, e a decisão de batizá-lo continua sendo dele. Ver `ourNameOnly`.
|
|
21
36
|
*/
|
|
22
|
-
export const nameToWrite = (f) => f.theirToken ?? f.token;
|
|
37
|
+
export const nameToWrite = (f) => f.theirToken ?? (f.tokenIsTheirs ? f.token : null);
|
|
38
|
+
/**
|
|
39
|
+
* O VALOR QUE **SÓ** O NOSSO LADO NOMEIA - o que a plataforma AVISA em vez de escrever.
|
|
40
|
+
*
|
|
41
|
+
* É a outra metade de `nameToWrite`, e ela existe para o silêncio não acontecer: um achado sem nome
|
|
42
|
+
* dele e com nome nosso some das duas listas se ninguém perguntar por ele, e sumir é
|
|
43
|
+
* indistinguível de "está tudo nomeado" para quem lê (lei 8).
|
|
44
|
+
*
|
|
45
|
+
* Quem chama DIZ o valor e onde ele está - nunca propõe o nome. É a escolha dele de 10/09 entre as
|
|
46
|
+
* duas alternativas, e a mesma que o hook já segue.
|
|
47
|
+
*/
|
|
48
|
+
export const ourNameOnly = (f) => !f.theirToken && !f.tokenIsTheirs && Boolean(f.token);
|
|
23
49
|
/**
|
|
24
50
|
* `next/og` renders JSX to a PNG on the server. There is no document, so there
|
|
25
51
|
* is no `var(--ds-*)` to read: every colour in such a file MUST be a literal.
|
|
@@ -607,6 +633,8 @@ function scanCore(file, source, table) {
|
|
|
607
633
|
* `--spacing` no vocabulário dele, e qual dos dois está certo depende de onde o literal está.
|
|
608
634
|
*/
|
|
609
635
|
const theirs = table.aliases.get(`${kind}:${normalizeValue(literal, table.rootPx)}`);
|
|
636
|
+
/** A procedência do `token`, lida da tabela que o achou - ver `tokenIsTheirs`. */
|
|
637
|
+
const tableIsTheirs = table.source !== null && table.source !== "installed";
|
|
610
638
|
findings.push({
|
|
611
639
|
kind,
|
|
612
640
|
line: at,
|
|
@@ -617,6 +645,9 @@ function scanCore(file, source, table) {
|
|
|
617
645
|
? { fontRelative: true }
|
|
618
646
|
: {}),
|
|
619
647
|
...(theirs?.writable ? { theirToken: theirs.name } : {}),
|
|
648
|
+
...(tableIsTheirs && match?.token
|
|
649
|
+
? { tokenIsTheirs: true }
|
|
650
|
+
: {}),
|
|
620
651
|
/**
|
|
621
652
|
* O NOME QUE SÓ SE DIZ - `$gray_dark` do Sass, `@brand` do Less.
|
|
622
653
|
*
|
|
@@ -196,8 +196,14 @@ export function theirNames(ours, theirs) {
|
|
|
196
196
|
* 66 delas gravadas por um `--fix --write`, e a pior na PRIMEIRA página do relatório -
|
|
197
197
|
* `0.25em → --radius-xs` em 116 arquivos. Ver `FAMILY_WORDS`.
|
|
198
198
|
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
199
|
+
* E DESDE 11/09 RECUSAR AQUI SIGNIFICA SILÊNCIO, não um nome nosso no lugar.
|
|
200
|
+
*
|
|
201
|
+
* Esta linha dizia que recusar não perdia informação, *"porque sem alias `nameToWrite` cai no
|
|
202
|
+
* NOSSO token"*. Ele não cai mais: a nossa grafia deixou de ser escrita no código dele
|
|
203
|
+
* (`INV-GERAL-13`), então um valor cuja categoria não fecha sai da lista de trocas em vez de
|
|
204
|
+
* sair com o nosso nome. Isso é o desfecho certo - a troca errada era escrever um token de
|
|
205
|
+
* tipo num `border-radius` -, e o valor não desaparece: ele é DITO, com o valor e a linha,
|
|
206
|
+
* pelo caminho que `ourNameOnly` alimenta.
|
|
201
207
|
*/
|
|
202
208
|
const usable = formDecides(value)
|
|
203
209
|
? candidates
|
package/dist/fonts.js
CHANGED
|
@@ -89,7 +89,7 @@ export function googleFontsHref(families) {
|
|
|
89
89
|
*/
|
|
90
90
|
export const FAMILY_SEAM_PREFIX = "--ds-typography-families-";
|
|
91
91
|
export function nextFontSnippet(input) {
|
|
92
|
-
const { families,
|
|
92
|
+
const { families, appDir = "app", facts, declaredWeights } = input;
|
|
93
93
|
/**
|
|
94
94
|
* A ORDEM É DETERMINÍSTICA e os duplicados saem: isto vira uma linha de arquivo gerado, e um
|
|
95
95
|
* conjunto que muda de ordem entre rodadas produz diff onde nada mudou.
|
|
@@ -219,15 +219,39 @@ export function nextFontSnippet(input) {
|
|
|
219
219
|
* projeto dele deixaria de compilar por causa da linha que a gente mandou colar.
|
|
220
220
|
*/
|
|
221
221
|
const emitted = roles.filter((role) => constOf(role) !== undefined);
|
|
222
|
+
/**
|
|
223
|
+
* O `data-ds` SAIU DO LAYOUT DELE EM 11/09 - `INV-GERAL-13`.
|
|
224
|
+
*
|
|
225
|
+
* O atributo existia para a NOSSA folha aplicar dentro dele, e nenhum app dele carrega a nossa
|
|
226
|
+
* folha. O que resta na linha é o que o `next/font` exige e é dele: a classe que carrega a
|
|
227
|
+
* variável de cada fonte.
|
|
228
|
+
*/
|
|
222
229
|
const layout = [
|
|
223
230
|
`// ${appDir}/layout.tsx`,
|
|
224
231
|
`import { ${[...new Set(emitted.map(constOf))].join(", ")} } from "./fonts";`,
|
|
225
|
-
`<body
|
|
232
|
+
`<body className={\`${[...new Set(emitted.map((r) => `\${${constOf(r)}.variable}`))].join(" ")}\`}>`,
|
|
226
233
|
];
|
|
234
|
+
/**
|
|
235
|
+
* O BLOCO DE CSS PASSA A ESCREVER NO VOCABULÁRIO **DELE** - o conserto de 11/09.
|
|
236
|
+
*
|
|
237
|
+
* O QUE ELE ESCREVIA: um `[data-ds="<slug>"]` com `--ds-typography-families-<role>` apontando para
|
|
238
|
+
* a fonte. Nosso escopo, nossa variável, dentro da folha dele - e só resolvia com a nossa folha
|
|
239
|
+
* carregada, que é o que `INV-GERAL-13` proíbe.
|
|
240
|
+
*
|
|
241
|
+
* O QUE ELE ESCREVE AGORA: um `@theme` com `--font-<role>`, que é o namespace de fonte do Tailwind
|
|
242
|
+
* DELE. Duas coisas acontecem de uma vez, e as duas são dele: a utility `font-<role>` passa a
|
|
243
|
+
* existir no projeto dele carregando a fonte que o `next/font` baixou, e `theirThemeVars` -
|
|
244
|
+
* o leitor que decide o utilitário nomeado no codegen - passa a ver aquele nome. Daí em diante o
|
|
245
|
+
* componente que a plataforma escreve veste `font-<role>` em vez de um literal, porque o `@theme`
|
|
246
|
+
* DELE declara o nome.
|
|
247
|
+
*
|
|
248
|
+
* Nenhuma linha aqui menciona o sistema, o slug ou uma variável nossa: é a fonte dele, ligada ao
|
|
249
|
+
* Tailwind dele.
|
|
250
|
+
*/
|
|
227
251
|
const css = [
|
|
228
|
-
`/* ${appDir}/globals.css -
|
|
229
|
-
|
|
230
|
-
...emitted.map((role) => `
|
|
252
|
+
`/* ${appDir}/globals.css - in your own @theme */`,
|
|
253
|
+
`@theme {`,
|
|
254
|
+
...emitted.map((role) => ` --font-${role}: var(${roleVar(role)});`),
|
|
231
255
|
`}`,
|
|
232
256
|
];
|
|
233
257
|
/**
|
package/dist/global-sheet.js
CHANGED
|
@@ -1,134 +1,23 @@
|
|
|
1
|
-
import { readdir
|
|
2
|
-
import {
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
3
|
import { exists } from "./agent-wiring.js";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* AS PASTAS DE APP DESTE REPOSITÓRIO - e era um módulo sobre a folha global dele até 11/09.
|
|
6
6
|
*
|
|
7
|
-
* O QUE
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* styles/"*. A instrução apontava para o arquivo errado, e a convenção certa estava escrita ali do
|
|
11
|
-
* lado.
|
|
7
|
+
* O QUE MORAVA AQUI: `globalSheetOf`, `sheetChainOf`, `prefixFrom` e o resolvedor de `exports` do
|
|
8
|
+
* workspace. Os quatro existiam para uma coisa - achar a folha que os apps dele realmente carregam,
|
|
9
|
+
* e contar o `../` até a raiz, para a instrução de `@import` apontar para o arquivo certo.
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* Era a parte mais cuidadosa daquele texto. MEDIDO em 01/09 no `codelevel`: os dois apps dele fazem
|
|
12
|
+
* `@import "@repo/ui/styles/globals.css"`, e o `globals.css` do app diz, em comentário DELE, *"Do
|
|
13
|
+
* not redeclare tokens or fonts here"*. A instrução apontava para o arquivo errado, com o caminho
|
|
14
|
+
* relativo de outro, e o conserto foi seguir a cadeia em vez de assumir.
|
|
16
15
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* 2. procura `@import "<spec>"` que aponte para um CSS DENTRO deste repositório
|
|
22
|
-
* 3. resolve o `<spec>`: caminho relativo, ou pacote do workspace pelo `exports`
|
|
23
|
-
* 4. repete na folha encontrada, até ela não re-exportar mais
|
|
24
|
-
*
|
|
25
|
-
* O QUE NÃO É SEGUIDO: `tailwindcss` e qualquer coisa que não resolva para um arquivo daqui. Um
|
|
26
|
-
* `@import "tailwindcss"` é a biblioteca, e mandar o cliente editá-la seria pior que a instrução que
|
|
27
|
-
* isto conserta.
|
|
16
|
+
* O TEXTO INTEIRO DEIXOU DE EXISTIR (`INV-GERAL-13`): nada que a plataforma escreve no repositório
|
|
17
|
+
* dele depende de folha nossa, então não há `@import` para apontar. O que sobrevive é a única
|
|
18
|
+
* pergunta que não era sobre a nossa folha - *"quais pastas deste repositório são app?"* -, porque é
|
|
19
|
+
* onde o `fonts.ts` do `next/font` DELE entra, um por app.
|
|
28
20
|
*/
|
|
29
|
-
/** Quantos saltos seguir antes de desistir - um ciclo de imports não pode travar um `add`. */
|
|
30
|
-
const MAX_HOPS = 5;
|
|
31
|
-
const IMPORT = /@import\s+["']([^"']+)["']/g;
|
|
32
|
-
/** `@repo/ui/styles/globals.css` → `packages/ui/src/styles/globals.css`, pelo `exports` dele. */
|
|
33
|
-
async function fromWorkspace(root, spec) {
|
|
34
|
-
for (const group of ["packages", "apps", "libs"]) {
|
|
35
|
-
let entries;
|
|
36
|
-
try {
|
|
37
|
-
const { readdir } = await import("node:fs/promises");
|
|
38
|
-
entries = await readdir(join(root, group));
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
for (const entry of entries) {
|
|
44
|
-
const pkgPath = join(root, group, entry, "package.json");
|
|
45
|
-
const raw = await readFile(pkgPath, "utf8").catch(() => null);
|
|
46
|
-
if (!raw)
|
|
47
|
-
continue;
|
|
48
|
-
let pkg;
|
|
49
|
-
try {
|
|
50
|
-
pkg = JSON.parse(raw);
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
if (!pkg.name || !spec.startsWith(`${pkg.name}/`))
|
|
56
|
-
continue;
|
|
57
|
-
const sub = `./${spec.slice(pkg.name.length + 1)}`;
|
|
58
|
-
const target = pkg.exports?.[sub];
|
|
59
|
-
if (typeof target !== "string")
|
|
60
|
-
continue;
|
|
61
|
-
return posix.join(group, entry, target.replace(/^\.\//, ""));
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* A folha onde os tokens dele devem entrar, relativa à raiz - `appSheet` quando nada a re-exporta.
|
|
68
|
-
*
|
|
69
|
-
* DELEGA, e não caminha: quem caminha é `sheetChainOf`. Dois caminhadores sobre a mesma cadeia é
|
|
70
|
-
* como um deles para de seguir um salto que o outro segue, e ninguém descobre até um cliente
|
|
71
|
-
* receber a instrução apontando para a folha errada - que é exatamente o defeito que
|
|
72
|
-
* `INV-VOLTA-12` fechou.
|
|
73
|
-
*/
|
|
74
|
-
export async function globalSheetOf(root, appSheet) {
|
|
75
|
-
const chain = await sheetChainOf(root, appSheet);
|
|
76
|
-
return chain[chain.length - 1] ?? appSheet;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* A CADEIA INTEIRA, da folha do app até a última que ninguém re-exporta - e por que ela é pública.
|
|
80
|
-
*
|
|
81
|
-
* O QUE O CLIENTE GANHA: a resposta "este app carrega o meu design system?" medida no app DELE, e
|
|
82
|
-
* não no projeto. `readWiring` varria a raiz e devolvia "existe em algum lugar daqui": num monorepo
|
|
83
|
-
* com dois apps servidos, um fiado e outro não, o fiado respondia pelo outro - e a pessoa recebia
|
|
84
|
-
* "está tudo certo" sobre o app que não carrega nada.
|
|
85
|
-
*
|
|
86
|
-
* A pergunta dos IMPORTS não se responde varrendo pasta: ela se responde seguindo a cadeia de
|
|
87
|
-
* `@import` a partir da folha daquele app, porque a folha que carrega os tokens quase nunca é a do
|
|
88
|
-
* app - num monorepo é a do pacote compartilhado, e os dois apps chegam nela.
|
|
89
|
-
*
|
|
90
|
-
* A ordem é do app para fora, e o teto de saltos é o mesmo: um ciclo de imports não pode travar um
|
|
91
|
-
* comando.
|
|
92
|
-
*/
|
|
93
|
-
export async function sheetChainOf(root, appSheet) {
|
|
94
|
-
const chain = [];
|
|
95
|
-
let current = appSheet;
|
|
96
|
-
for (let hop = 0; hop < MAX_HOPS; hop += 1) {
|
|
97
|
-
chain.push(current);
|
|
98
|
-
const raw = await readFile(join(root, current), "utf8").catch(() => null);
|
|
99
|
-
if (!raw)
|
|
100
|
-
return chain;
|
|
101
|
-
let next = null;
|
|
102
|
-
for (const m of raw.matchAll(IMPORT)) {
|
|
103
|
-
const spec = m[1];
|
|
104
|
-
if (!spec.endsWith(".css"))
|
|
105
|
-
continue;
|
|
106
|
-
const candidate = spec.startsWith(".")
|
|
107
|
-
? posix.normalize(posix.join(posix.dirname(current), spec))
|
|
108
|
-
: await fromWorkspace(root, spec);
|
|
109
|
-
if (!candidate)
|
|
110
|
-
continue;
|
|
111
|
-
/** Fora da raiz não é folha dele para editar - e um `..` demais sai do repositório. */
|
|
112
|
-
if (candidate.startsWith(".."))
|
|
113
|
-
continue;
|
|
114
|
-
const readable = await readFile(join(root, candidate), "utf8").catch(() => null);
|
|
115
|
-
if (readable === null)
|
|
116
|
-
continue;
|
|
117
|
-
next = candidate;
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
/** Uma folha que aponta para si mesma encerra a cadeia em vez de gastar o teto de saltos. */
|
|
121
|
-
if (!next || next === current || chain.includes(next))
|
|
122
|
-
return chain;
|
|
123
|
-
current = next;
|
|
124
|
-
}
|
|
125
|
-
return chain;
|
|
126
|
-
}
|
|
127
|
-
/** O `../` que leva daquela folha até a raiz do repositório - o prefixo do `@import`. */
|
|
128
|
-
export function prefixFrom(sheet) {
|
|
129
|
-
const up = relative(dirname(resolve("/r", sheet)), "/r");
|
|
130
|
-
return up === "" ? "./" : `${up.split(/[\\/]/).join("/")}/`;
|
|
131
|
-
}
|
|
132
21
|
/**
|
|
133
22
|
* A RAIZ DE CADA APP DESTE REPOSITÓRIO - onde o escopo e a tipografia entram, um por app.
|
|
134
23
|
*
|
package/dist/guide.js
CHANGED
|
@@ -293,8 +293,49 @@ toolsReachable = false,
|
|
|
293
293
|
* essa porta; deixá-la entreaberta um nível acima seria vigiá-la em vez de fechá-la
|
|
294
294
|
* (CLAUDE.md, Parte I, seção 6).
|
|
295
295
|
*/
|
|
296
|
-
fontFacts
|
|
296
|
+
fontFacts,
|
|
297
|
+
/**
|
|
298
|
+
* O VOCABULÁRIO DESTE REPOSITÓRIO - `tongueFromArtifacts`, ou `null` quando não há folha para ler.
|
|
299
|
+
*
|
|
300
|
+
* O QUE ISTO CONSERTA, e é a razão de existir desta etapa: este arquivo é o que o agente DELE lê
|
|
301
|
+
* antes de escrever, e ele ensinava a nossa grafia. *"Color: `var(--ds-color-semantic-<role>)`"*
|
|
302
|
+
* é uma instrução para escrever a NOSSA variável no código dele - e ela só resolve com a nossa
|
|
303
|
+
* folha carregada, que é o que `INV-GERAL-13` proíbe. Um guia que ensina isso produz a violação
|
|
304
|
+
* sem que ninguém decida nada: o agente obedece.
|
|
305
|
+
*
|
|
306
|
+
* Com o vocabulário em mão, cada linha abaixo diz o nome que o código DELE dá àquele valor - ou o
|
|
307
|
+
* valor, quando o código dele não nomeia nenhum.
|
|
308
|
+
*
|
|
309
|
+
* OBRIGATÓRIO, pelo mesmo argumento de `fontFacts` logo acima: um argumento opcional aqui é um
|
|
310
|
+
* argumento que um chamador novo esquece, e o esquecimento devolve a nossa grafia ao arquivo que
|
|
311
|
+
* mais a propaga.
|
|
312
|
+
*/
|
|
313
|
+
tongue) {
|
|
297
314
|
const { document: doc, slug, name, version } = payload;
|
|
315
|
+
/**
|
|
316
|
+
* COMO ISTO SE ESCREVE NO CÓDIGO DELE - a mesma resposta que a materialização usa.
|
|
317
|
+
*
|
|
318
|
+
* `var(--nome-dele)` quando o repositório dele nomeia aquele valor; o valor literal quando não.
|
|
319
|
+
* Sem vocabulário (projeto que nunca buildou, folha ausente) a resposta é `null`, e quem chama
|
|
320
|
+
* DIZ isso em vez de imprimir uma grafia que ninguém pode escrever.
|
|
321
|
+
*/
|
|
322
|
+
const saysAs = (ours) => {
|
|
323
|
+
if (!tongue)
|
|
324
|
+
return null;
|
|
325
|
+
const theirs = tongue.names.get(ours);
|
|
326
|
+
if (theirs)
|
|
327
|
+
return `var(${theirs})`;
|
|
328
|
+
return tongue.values.get(ours) ?? null;
|
|
329
|
+
};
|
|
330
|
+
/** `spacing` -> `md → var(--gap-md)`, para cada degrau que o vocabulário alcança. */
|
|
331
|
+
const family = (prefix, keys) => {
|
|
332
|
+
const said = keys
|
|
333
|
+
.map((k) => ({ k, as: saysAs(`--ds-${prefix}-${kebab(k)}`) }))
|
|
334
|
+
.filter((x) => x.as !== null);
|
|
335
|
+
return said.length > 0
|
|
336
|
+
? said.map((x) => `\`${x.k}\` → \`${x.as}\``).join(", ")
|
|
337
|
+
: list(keys);
|
|
338
|
+
};
|
|
298
339
|
const { meta, foundations, motion, components } = doc;
|
|
299
340
|
const semanticRoles = Object.keys(foundations.color.semantic);
|
|
300
341
|
/**
|
|
@@ -339,8 +380,6 @@ fontFacts) {
|
|
|
339
380
|
*/
|
|
340
381
|
const fontPlan = nextFontSnippet({
|
|
341
382
|
families: foundations.typography.families,
|
|
342
|
-
slug,
|
|
343
|
-
seam: payload.fontSeam,
|
|
344
383
|
facts: fontFacts,
|
|
345
384
|
/** Os pesos que ELE declara - ver `declaredWeights`. O guia é o arquivo que o agente dele cola. */
|
|
346
385
|
declaredWeights: foundations.typography.weights,
|
|
@@ -548,7 +587,10 @@ It writes a **deterministic scaffold**: the page uses this DS's \`.ds-*\` recipe
|
|
|
548
587
|
path-classes, paired with a co-located scoped CSS (Next target) that carries the **responsive** media
|
|
549
588
|
queries and the **CSS-only hamburger** - so the page is mobile-ready out of the box. **Refine it in
|
|
550
589
|
place** - wire real data, split into components, swap the chart/icon/media placeholders - but keep the
|
|
551
|
-
|
|
590
|
+
wrapper and the \`.ds-*\` / layout classes: the stylesheet next to the page carries the rules for the
|
|
591
|
+
ones it wears, scoped to that wrapper - the command says how many came along, so the page stands on
|
|
592
|
+
its own with nothing imported. A \`.ds-*\` you ADD while refining has no rule there; bring that piece
|
|
593
|
+
in with \`synthesisui component\` instead. Run
|
|
552
594
|
\`synthesisui init\` once to set the target (next/general) and the output folder.
|
|
553
595
|
|
|
554
596
|
## Single components as YOUR code
|
|
@@ -560,8 +602,8 @@ synthesisui component ${slug} button
|
|
|
560
602
|
It writes \`<componentsDir>/button/\` with \`button.tsx\` (+ colocated \`button.css\` in the default
|
|
561
603
|
\`styles: "css"\` flavor, or Tailwind utilities inline with \`styles: "tailwind"\` - set once via
|
|
562
604
|
\`synthesisui init --styles tailwind\`). Import and render: \`import { Button } from "components/button"\`.
|
|
563
|
-
The boundary:
|
|
564
|
-
|
|
605
|
+
The boundary: everything a component owns lives in its own folder, and every value in it is a name
|
|
606
|
+
YOUR code declares or the value itself - there is nothing global to set up.
|
|
565
607
|
|
|
566
608
|
**A component's conditional classes are decided at the call site, not in the stylesheet.** A recipe
|
|
567
609
|
carries the look of a state as a rule keyed on a data attribute (\`[data-size="lg"]\`), and the caller
|
|
@@ -623,80 +665,73 @@ ${meta.narrative}
|
|
|
623
665
|
${rulesNote}
|
|
624
666
|
## How to apply
|
|
625
667
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
@import "../_synthesisui/ds/${slug}/tokens.css";
|
|
630
|
-
\`\`\`
|
|
631
|
-
(drop the \`../\` if your global CSS sits at the project root.)${hasTailwind
|
|
632
|
-
? `\n **Using Tailwind (this project's default)? You also need \`theme.css\` right after \`tokens.css\` -\n without it the DS-backed utilities (\`bg-primary\`, \`p-md\`…) aren't generated and the UI renders\n unstyled. See "Styling with Tailwind v4" below for the full import block.**`
|
|
633
|
-
: ""}
|
|
668
|
+
**Nothing here is imported into your app.** \`_synthesisui/ds/${slug}/\` is the reference this guide,
|
|
669
|
+
the check, the doctor and the MCP server read to interpret your design - your code never points at
|
|
670
|
+
it, and nothing we write into this repository needs it at runtime.
|
|
634
671
|
|
|
635
|
-
|
|
636
|
-
\`\`\`html
|
|
637
|
-
<div data-ds="${slug}">…your UI here…</div>
|
|
638
|
-
\`\`\`
|
|
639
|
-
All \`--ds-*\` custom properties and \`.ds-*\` classes only apply inside that scope.
|
|
640
|
-
Applying \`data-ds="${slug}"\` at the app root (e.g. \`<body>\` or the root layout)
|
|
641
|
-
is the simplest choice - the whole app then wears the system.
|
|
642
|
-
${hasAlt
|
|
643
|
-
? `
|
|
644
|
-
3. Light/dark: an ancestor with \`data-scheme="${altScheme}"\` switches the neutral roles to the opposite mode.
|
|
645
|
-
\`\`\`tsx
|
|
646
|
-
<div data-scheme="${altScheme}"><div data-ds="${slug}">…</div></div>
|
|
672
|
+
1. Ask for a component and it arrives as YOUR code:
|
|
647
673
|
\`\`\`
|
|
648
|
-
|
|
649
|
-
\`\`\`tsx
|
|
650
|
-
root.toggleAttribute("data-scheme"); // present = ${altScheme}, absent = ${meta.scheme}
|
|
674
|
+
npx synthesisui component ${slug} <name>
|
|
651
675
|
\`\`\`
|
|
652
|
-
|
|
676
|
+
It lands in your components directory speaking the names your own code declares - or carrying the
|
|
677
|
+
value itself, where your code names none. No import, no scope attribute, nothing at runtime.
|
|
678
|
+
|
|
679
|
+
2. Writing something by hand? Use the vocabulary below. Every name in it is a name **your own code
|
|
680
|
+
declares**, written exactly as you would write it.
|
|
681
|
+
${hasAlt
|
|
682
|
+
? `\n3. Light/dark: this system's roles have two readings, and a component that arrives from
|
|
683
|
+
\`component\` carries both. Which one shows is your app's own scheme switch - we do not add one.\n`
|
|
653
684
|
: ""}${fontsSection}${depsSection}${hasTailwind
|
|
654
685
|
? `
|
|
655
686
|
## Styling with Tailwind v4 (preferred in this project)
|
|
656
687
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
@import "./_synthesisui/ds/${slug}/tokens.css";
|
|
661
|
-
@import "./_synthesisui/ds/${slug}/theme.css";
|
|
662
|
-
\`\`\`
|
|
663
|
-
This maps the DS tokens onto Tailwind's theme, so inside \`[data-ds="${slug}"]\` you get utilities
|
|
664
|
-
backed by the design system: \`bg-*\`/\`text-*\`/\`border-*\` (semantic colors), \`p-*\`/\`m-*\`/\`gap-*\`
|
|
665
|
-
(spacing), \`rounded-*\`, \`shadow-*\`, \`font-*\` (families **and** weights), \`text-*\` (type scale), \`ease-*\`${seriesKeys.length > 0
|
|
666
|
-
? `, \`bg-series-*\`/\`text-series-*\`/\`fill-series-*\` (data-viz series)`
|
|
667
|
-
: ""}.
|
|
688
|
+
Your own \`@theme\` is what backs the utilities, and it is the one that decides. Where it declares a
|
|
689
|
+
name, the utility already paints your value and it is the most readable code you can write; where it
|
|
690
|
+
does not, write the value.
|
|
668
691
|
|
|
669
|
-
**Prefer
|
|
670
|
-
|
|
692
|
+
**Prefer utilities for layout and new composition** - they are this project's idiom and read far
|
|
693
|
+
better than inline \`style\`.
|
|
671
694
|
|
|
672
695
|
\`\`\`tsx
|
|
673
|
-
//
|
|
674
|
-
<main className="
|
|
675
|
-
<button className="
|
|
696
|
+
// preferred - your project's own utilities
|
|
697
|
+
<main className="p-2xl flex flex-col gap-md">
|
|
698
|
+
<button className="rounded-md px-md py-2xs">Save</button>
|
|
676
699
|
</main>
|
|
700
|
+
\`\`\`
|
|
701
|
+
|
|
702
|
+
To give a utility the system's value, declare the name in **your own** \`@theme\` - it is your
|
|
703
|
+
stylesheet, your namespace, and nothing of ours is involved:
|
|
677
704
|
|
|
678
|
-
|
|
679
|
-
|
|
705
|
+
\`\`\`css
|
|
706
|
+
@theme {
|
|
707
|
+
--radius-md: <the value you want rounded-md to paint>;
|
|
708
|
+
}
|
|
680
709
|
\`\`\`
|
|
681
710
|
|
|
711
|
+
Do that and \`synthesisui component\` starts writing \`rounded-md\` instead of the raw value, because
|
|
712
|
+
the name now exists in your project.
|
|
713
|
+
|
|
682
714
|
---
|
|
683
715
|
`
|
|
684
716
|
: ""}
|
|
685
|
-
This is **v${version}**. The
|
|
686
|
-
\`tokens.css\`/\`theme.css\` re-exports, plus \`.lock\`) always point at the active version - import
|
|
687
|
-
those, not the versioned ones. The pinned files for this version - ${artifactList},
|
|
717
|
+
This is **v${version}**. The pinned files for this version - ${artifactList},
|
|
688
718
|
\`design-system.json\` (canonical source of truth), \`GUIDE.md\` (this file) - live in
|
|
689
|
-
\`_synthesisui/ds/${slug}/v${version}
|
|
719
|
+
\`_synthesisui/ds/${slug}/v${version}/\`, and they are OURS to read: the check, the doctor and the MCP
|
|
720
|
+
server use them to interpret your design. Your app imports none of them.
|
|
690
721
|
|
|
691
722
|
---
|
|
692
723
|
${pagesSection}
|
|
693
724
|
## Building with the system
|
|
694
725
|
|
|
695
726
|
**This system is for building real product UI** - pages, layouts, dashboards, whole flows.
|
|
696
|
-
|
|
697
|
-
actual screens. There is **no "samples only" rule**: build the real
|
|
698
|
-
\`app/synthesisui-samples/<component>/\` page is a fine *optional* scratch space to eyeball a
|
|
699
|
-
component, but it is never required.
|
|
727
|
+
Bring the recipes in as YOUR code (\`npx synthesisui component ${slug} <name>\`) and compose them with
|
|
728
|
+
your own utilities to assemble actual screens. There is **no "samples only" rule**: build the real
|
|
729
|
+
app. An \`app/synthesisui-samples/<component>/\` page is a fine *optional* scratch space to eyeball a
|
|
730
|
+
single component, but it is never required.
|
|
731
|
+
|
|
732
|
+
The \`.ds-*\` class names below describe the recipe's SHAPE - which parts it has and which
|
|
733
|
+
\`data-*\` each one takes. They are what the materialized component wears, and the stylesheet that
|
|
734
|
+
declares them comes with it.
|
|
700
735
|
|
|
701
736
|
### Layout & composition
|
|
702
737
|
The system defines the scale; these are sensible defaults for spending it:
|
|
@@ -728,10 +763,9 @@ part classes and their \`data-*\` are listed per component below. Example - a ta
|
|
|
728
763
|
`
|
|
729
764
|
: ""}
|
|
730
765
|
### Overlays & portals
|
|
731
|
-
Dialogs, menus and toasts are often rendered through a portal at the end of \`<body>\` -
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
app root so everything (portals included) inherits it. Behavior (open/close, focus trap, positioning,
|
|
766
|
+
Dialogs, menus and toasts are often rendered through a portal at the end of \`<body>\` - outside
|
|
767
|
+
the tree they were written in. A component brought in by \`synthesisui component\` carries its own
|
|
768
|
+
stylesheet and works anywhere, portal included. Behavior (open/close, focus trap, positioning,
|
|
735
769
|
keyboard) is yours to wire - the system ships the **looks**, not the JavaScript.
|
|
736
770
|
|
|
737
771
|
### Interactive recipes - the behavior contract
|
|
@@ -756,34 +790,47 @@ styles it, you make it work.
|
|
|
756
790
|
## Rules (follow them when creating components)
|
|
757
791
|
${hasTailwind
|
|
758
792
|
? `
|
|
759
|
-
- **Styling mechanism:** prefer Tailwind utilities
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
793
|
+
- **Styling mechanism:** prefer your project's own Tailwind utilities for layout and new
|
|
794
|
+
composition, and bring the recipes in as code (\`synthesisui component ${slug} <name>\`) for the
|
|
795
|
+
components this system already covers. Where no utility fits, write the name from the vocabulary
|
|
796
|
+
below - it is a name your own code declares.`
|
|
763
797
|
: ""}
|
|
764
|
-
- **Always use
|
|
765
|
-
|
|
798
|
+
- **Always use the system's vocabulary**, never raw values picked by eye. Every name below is a
|
|
799
|
+
name **your own code declares** - written exactly as you would write it. Nothing here comes from
|
|
800
|
+
a stylesheet of ours, and nothing needs one.
|
|
801
|
+
Color${hasTailwind ? " (utility: `bg-<role>`/`text-<role>`)" : ""}: ${semanticRoles
|
|
802
|
+
.map((r) => {
|
|
766
803
|
const theirs = theirRole(r);
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
804
|
+
/** O papel com o nome que ELE dá a ele - e o parêntese só sai quando os dois diferem. */
|
|
805
|
+
const named = theirs === r ? `\`${r}\`` : `\`${r} (${theirs} in your code)\``;
|
|
806
|
+
const as = saysAs(`--ds-color-semantic-${kebab(r)}`);
|
|
807
|
+
return as ? `${named} → \`${as}\`` : named;
|
|
808
|
+
})
|
|
809
|
+
.join(", ")}.${namedColours.length > 0
|
|
810
|
+
? `\n- **Yours by name**: ${namedColours.length} colour${namedColours.length === 1 ? "" : "s"} your code names by PURPOSE rather than by step, so no scale could hold ${namedColours.length === 1 ? "it" : "them"} - ${family("color", namedColours.slice(0, 8))}${namedColours.length > 8 ? ", …" : ""}. These are yours: reach for them when the purpose matches, and prefer a semantic role when it does not.`
|
|
772
811
|
: ""}${seriesKeys.length > 0
|
|
773
|
-
? `\n- Data-viz
|
|
812
|
+
? `\n- Data-viz${hasTailwind ? " (utility: `bg-series-<n>`/`text-series-<n>`/`fill-series-<n>`)" : ""}: categorical chart/series colors, ${seriesKeys.length} of them - ${family("color-series", seriesKeys)}. Use them in order for multi-series charts; they re-paint with the system.`
|
|
774
813
|
: ""}
|
|
775
|
-
- Spacing →
|
|
776
|
-
- Radius →
|
|
777
|
-
- Shadow →
|
|
814
|
+
- Spacing → ${family("spacing", Object.keys(foundations.spacing))}.
|
|
815
|
+
- Radius → ${family("radius", Object.keys(foundations.radius))}.
|
|
816
|
+
- Shadow → ${family("shadow", Object.keys(foundations.shadow))}.
|
|
778
817
|
- Typography: families ${familySlots(foundations.typography.families)
|
|
779
|
-
.map((slot) =>
|
|
818
|
+
.map((slot) => {
|
|
819
|
+
const as = saysAs(`${FAMILY_SEAM_PREFIX}${kebab(slot)}`);
|
|
820
|
+
const face = foundations.typography.families[slot];
|
|
821
|
+
return as ? `\`${slot}\` → \`${as}\` (${face})` : `\`${slot}\` (${face})`;
|
|
822
|
+
})
|
|
780
823
|
.join(", ")};
|
|
781
824
|
weights${hasTailwind ? " (utility: `font-<key>`)" : ""}: ${list(weights)};
|
|
782
|
-
scale
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
825
|
+
scale${hasTailwind ? " (utility: `text-<key>`)" : ""}: ${Object.keys(foundations.typography.scale)
|
|
826
|
+
.map((k) => {
|
|
827
|
+
const as = saysAs(`--ds-typography-scale-${kebab(k)}-font-size`);
|
|
828
|
+
return as ? `\`${k}\` → \`${as}\`` : `\`${k}\``;
|
|
829
|
+
})
|
|
830
|
+
.join(", ")}.
|
|
831
|
+
- Motion: durations ${family("motion-durations", Object.keys(motion.durations))}
|
|
832
|
+
and easings ${family("motion-easings", Object.keys(motion.easings))}. Use them on
|
|
833
|
+
\`transition\` so timing stays on-brand. For ANIMATION, this system ships a named vocabulary - see
|
|
787
834
|
**Motion vocabulary** below; never hand-roll \`@keyframes\` or raw durations.
|
|
788
835
|
- When **creating a new component** the DS does not cover yet: compose it from these semantic
|
|
789
836
|
tokens to inherit the system's identity; do not invent colors/measures outside the scale.
|
|
@@ -817,7 +864,8 @@ tell the person what you chose from the vocabulary instead.`
|
|
|
817
864
|
|
|
818
865
|
## Ready-made components
|
|
819
866
|
|
|
820
|
-
Each recipe
|
|
867
|
+
Each recipe has a \`.ds-<name>\` class - the SHAPE it wears when \`synthesisui component\` writes it
|
|
868
|
+
into your project, with the stylesheet that declares it alongside. Variants are
|
|
821
869
|
\`data-<axis>="<option>"\` attributes; states (hover/focus/active/disabled) ship in the CSS;
|
|
822
870
|
multi-part components expose \`.ds-<name>-<part>\` classes (listed under each).
|
|
823
871
|
|
|
@@ -832,7 +880,7 @@ A small gamification library the AI advisor (\`synthesisui advise\`) can propose
|
|
|
832
880
|
\`.ds-<name>\` recipe shape as the components above, token-only so they wear the system. Use them
|
|
833
881
|
**only where they fit the product** (progress, retention, recognition); they're a library to compose
|
|
834
882
|
from, not a default - and lean against over-gamifying a serious B2B product. Each is a \`.ds-<name>\`
|
|
835
|
-
|
|
883
|
+
recipe; multi-part ones expose \`.ds-<name>-<part>\`.
|
|
836
884
|
|
|
837
885
|
${blockLines.join("\n\n")}
|
|
838
886
|
`
|
package/dist/install-marks.js
CHANGED
|
@@ -235,7 +235,7 @@
|
|
|
235
235
|
* chama `wireAgent`, então rodar o comando é o caminho de volta - e ele só alarga a string que era
|
|
236
236
|
* nossa, nunca um filtro que uma pessoa escreveu.
|
|
237
237
|
*/
|
|
238
|
-
export const MATERIALISER_SINCE = "0.16.
|
|
238
|
+
export const MATERIALISER_SINCE = "0.16.422";
|
|
239
239
|
/**
|
|
240
240
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
241
241
|
*
|
|
@@ -343,8 +343,8 @@ export const MATERIALISER_SINCE = "0.16.421";
|
|
|
343
343
|
* A frase viaja ao lado da versão porque as duas são uma coisa só: quem move a marca troca a
|
|
344
344
|
* explicação no mesmo lugar, em vez de deixar o CI citando a causa da marca anterior.
|
|
345
345
|
*/
|
|
346
|
-
export const COUNTED_DIFFERENTLY_SINCE = "0.16.
|
|
347
|
-
export const COUNTED_DIFFERENTLY = "this run counts
|
|
346
|
+
export const COUNTED_DIFFERENTLY_SINCE = "0.16.422";
|
|
347
|
+
export const COUNTED_DIFFERENTLY = "this run counts a value as named only when YOUR code names it - that version also counted the ones only the system named";
|
|
348
348
|
/**
|
|
349
349
|
* 0.16.408 -> 0.16.413 em 10/09: o hook passa a DIZER o valor que nada nomeia. Ele silenciava toda
|
|
350
350
|
* deriva sem token de destino - a decisão estava escrita como "não vale interromper, porque o único
|
|
@@ -366,7 +366,7 @@ export const COUNTED_DIFFERENTLY = "this run counts the utilities of your own th
|
|
|
366
366
|
* utility, um arquivo escrito inteiro no vocabulário do sistema recebia zero. A contagem em si é a
|
|
367
367
|
* outra marca - ver `COUNTED_DIFFERENTLY_SINCE`, que sobe no mesmo diff.
|
|
368
368
|
*/
|
|
369
|
-
export const CHECKER_SINCE = "0.16.
|
|
369
|
+
export const CHECKER_SINCE = "0.16.422";
|
|
370
370
|
/**
|
|
371
371
|
* A ÚLTIMA VERSÃO EM QUE OS LEITORES PASSARAM A PRODUZIR UM CENSO DIFERENTE.
|
|
372
372
|
*
|