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/sheet-needed.js
DELETED
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { TAILWIND_THEME_VARS } from "./tailwind-theme-vars.js";
|
|
4
|
-
/**
|
|
5
|
-
* AS CLASSES QUE A FOLHA COMPILADA DECLARA COMO RECEITA - o nome, não o prefixo.
|
|
6
|
-
*
|
|
7
|
-
* A convenção viaja com o sistema (`ds-`, `sui-`, o que o projeto dele usa), então perguntar pelo
|
|
8
|
-
* prefixo seria fixar a forma de um repositório. O que é estrutural é onde a regra MORA: o
|
|
9
|
-
* compilador emite uma regra por receita dentro de `@layer components`, e é só essa camada que a
|
|
10
|
-
* folha traz e o projeto dele não tem.
|
|
11
|
-
*
|
|
12
|
-
* A PRIMEIRA VERSÃO VARRIA O TEXTO INTEIRO, e a revisão do fecho mediu o preço na folha viva do
|
|
13
|
-
* `codelevel` (143.409 bytes, versão instalada): **147 nomes extraídos, 3 deles não são receita** -
|
|
14
|
-
* `dark`, que é a classe DELE em `@layer base`; `layer-3d`, um utilitário DELE espelhado em
|
|
15
|
-
* `@layer utilities`; e `background`, que aparecia como VALOR de uma declaração e nem seletor era.
|
|
16
|
-
* Rodada sobre o repositório dele, a função devolvia `["layer-3d", "dark"]` - duas classes DELE
|
|
17
|
-
* sustentando a cobrança da NOSSA folha. É o defeito de 07/09 voltando pela porta do balde novo, e
|
|
18
|
-
* o oposto exato do que esta medição existe para permitir.
|
|
19
|
-
*
|
|
20
|
-
* O ESCAPE CONTA como parte do nome, porque no CSS ele é como se escreve um caractere que o
|
|
21
|
-
* seletor não aceita cru: `.ds-w-\[10px\]` é a classe `ds-w-[10px]`, e `.sm\:ds-hero` é a variante
|
|
22
|
-
* `sm:` da classe `ds-hero` - que é como o código dele a escreve. Truncar no `\` produziria os
|
|
23
|
-
* nomes `ds-w-` e `sm`, que ninguém escreve: uma dependência real deixaria de ser contada, e esse é
|
|
24
|
-
* o único lado do erro que o cabeçalho deste módulo declara inaceitável.
|
|
25
|
-
*/
|
|
26
|
-
export function classesTheSheetDeclares(css) {
|
|
27
|
-
const out = new Set();
|
|
28
|
-
for (const body of layerBodies(css, "components"))
|
|
29
|
-
for (const prelude of selectorPreludes(body))
|
|
30
|
-
for (const m of prelude.matchAll(/\.((?:\\.|[\w-])+)/g)) {
|
|
31
|
-
const bare = m[1].replace(/\\(.)/g, "$1");
|
|
32
|
-
/** A MESMA normalização que `classesIn` aplica ao que ele ESCREVE - `sm:ds-hero` é `ds-hero`. */
|
|
33
|
-
out.add((bare.split(":").pop() ?? bare).replace(/^[!-]/, ""));
|
|
34
|
-
}
|
|
35
|
-
return [...out];
|
|
36
|
-
}
|
|
37
|
-
/** O corpo de cada `@layer <nome> { … }`, com as chaves de dentro contadas. */
|
|
38
|
-
function layerBodies(css, name) {
|
|
39
|
-
const out = [];
|
|
40
|
-
for (const m of css.matchAll(new RegExp(`@layer\\s+${name}\\s*\\{`, "g"))) {
|
|
41
|
-
let depth = 1;
|
|
42
|
-
let i = (m.index ?? 0) + m[0].length;
|
|
43
|
-
const start = i;
|
|
44
|
-
for (; i < css.length && depth > 0; i += 1) {
|
|
45
|
-
const c = css[i];
|
|
46
|
-
if (c === "{")
|
|
47
|
-
depth += 1;
|
|
48
|
-
else if (c === "}")
|
|
49
|
-
depth -= 1;
|
|
50
|
-
}
|
|
51
|
-
out.push(css.slice(start, i - 1));
|
|
52
|
-
}
|
|
53
|
-
return out;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* O PRELÚDIO DE CADA REGRA - o texto entre o fim da regra anterior e a `{` desta.
|
|
57
|
-
*
|
|
58
|
-
* É o que separa um SELETOR de um valor de declaração: `background: p.background` mora depois da
|
|
59
|
-
* `{`, e nunca chega aqui. Um prelúdio que começa com `@` é uma regra de agrupamento (`@media`,
|
|
60
|
-
* `@supports`) e não declara classe nenhuma - o corpo dela é varrido pela mesma volta do laço.
|
|
61
|
-
*/
|
|
62
|
-
function selectorPreludes(body) {
|
|
63
|
-
const out = [];
|
|
64
|
-
const parts = body.split("{");
|
|
65
|
-
for (const part of parts.slice(0, -1)) {
|
|
66
|
-
const tail = part.slice(part.lastIndexOf("}") + 1).trim();
|
|
67
|
-
if (tail && !tail.startsWith("@"))
|
|
68
|
-
out.push(tail);
|
|
69
|
-
}
|
|
70
|
-
return out;
|
|
71
|
-
}
|
|
72
|
-
/** `--animate-shimmer` no `@theme` -> o nome `shimmer`, que é o que uma classe carrega. */
|
|
73
|
-
function themeNames(themeCss) {
|
|
74
|
-
const out = new Set();
|
|
75
|
-
for (const block of themeCss.matchAll(/@theme[^{]*\{([\s\S]*?)\n\}/g))
|
|
76
|
-
for (const m of block[1].matchAll(/^\s*--([a-z]+)-([a-zA-Z0-9_-]+)\s*:/gm))
|
|
77
|
-
out.add(m[2]);
|
|
78
|
-
return [...out];
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* O VALOR DE UM ATRIBUTO DE CLASSE, QUANDO ELE É UMA EXPRESSÃO - e a regra é o ATRIBUTO, não a
|
|
82
|
-
* função.
|
|
83
|
-
*
|
|
84
|
-
* O DEFEITO, medido em 08/09 nas duas árvores do `frontend-hub`: o leitor via `className="…"` e o
|
|
85
|
-
* template direto, e não via `className={cn("…")}`.
|
|
86
|
-
*
|
|
87
|
-
* ```
|
|
88
|
-
* packages/ui/src apps/web-dashboard
|
|
89
|
-
* className=" literal 346 2639
|
|
90
|
-
* className={cn( ou {clsx( 66 239 <- invisíveis
|
|
91
|
-
* ```
|
|
92
|
-
*
|
|
93
|
-
* São **305 sítios** onde o projeto dele escreve classe e a pergunta *"algo aqui precisa da nossa
|
|
94
|
-
* folha?"* era respondida sobre um texto que não continha a resposta. E o erro caía para o lado
|
|
95
|
-
* ERRADO: o cabeçalho deste módulo declara que *"o lado seguro do erro é PRECISA"*, porque um falso
|
|
96
|
-
* positivo mantém o setup sendo pedido e um falso NEGATIVO entrega um componente sem a animação que
|
|
97
|
-
* ele veste. `cn()` é o idioma de quem parte do shadcn, então a metade que faltava era a maior parte
|
|
98
|
-
* de quem chega.
|
|
99
|
-
*
|
|
100
|
-
* POR QUE A REGRA NÃO É O NOME DA FUNÇÃO. Casar `cn|clsx|twMerge` seria a lista dos casos de hoje: o
|
|
101
|
-
* próximo cliente chama a dele de `classes`, `cx` ou nada - passa um ternário direto. O que é
|
|
102
|
-
* estrutural é o ATRIBUTO: dentro do valor de um `class`/`className`, toda string literal é
|
|
103
|
-
* candidata a classe, seja ela argumento de função, ramo de ternário ou pedaço de template.
|
|
104
|
-
*
|
|
105
|
-
* O QUE ISTO NÃO ALCANÇA, e está declarado com o número: a tabela de variantes que mora numa const
|
|
106
|
-
* separada - `cva(...)` e `tv(...)`, medidos em 08/09 como **4 + 41 e 0 + 85** nas duas árvores. Ali
|
|
107
|
-
* as classes chegam ao atributo por VARIÁVEL, e cobri-las exige seguir a variável até a declaração:
|
|
108
|
-
* é outra frente, e o erro dela continua caindo para o lado de cobrar.
|
|
109
|
-
*/
|
|
110
|
-
function attributeExpressions(source) {
|
|
111
|
-
const out = [];
|
|
112
|
-
const ATTR = /class(?:Name)?=\{/g;
|
|
113
|
-
for (const m of source.matchAll(ATTR)) {
|
|
114
|
-
let depth = 0;
|
|
115
|
-
let i = (m.index ?? 0) + m[0].length - 1;
|
|
116
|
-
const start = i;
|
|
117
|
-
/** Aspas contam para NÃO deixar uma chave dentro de string fechar a expressão cedo. */
|
|
118
|
-
let quote = null;
|
|
119
|
-
for (; i < source.length; i += 1) {
|
|
120
|
-
const c = source[i];
|
|
121
|
-
if (quote) {
|
|
122
|
-
if (c === "\\")
|
|
123
|
-
i += 1;
|
|
124
|
-
else if (c === quote)
|
|
125
|
-
quote = null;
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (c === '"' || c === "'" || c === "`")
|
|
129
|
-
quote = c;
|
|
130
|
-
else if (c === "{")
|
|
131
|
-
depth += 1;
|
|
132
|
-
else if (c === "}") {
|
|
133
|
-
depth -= 1;
|
|
134
|
-
if (depth === 0)
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (depth === 0)
|
|
139
|
-
out.push(source.slice(start + 1, i));
|
|
140
|
-
}
|
|
141
|
-
return out;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* A DECLARAÇÃO DE UMA CONST QUE O ATRIBUTO USA - o salto que faltava, e a mesma regra do atributo.
|
|
145
|
-
*
|
|
146
|
-
* O QUE FICAVA DE FORA, e estava declarado no contrato com o número: a tabela de variantes mora
|
|
147
|
-
* numa const separada e chega ao atributo por VARIÁVEL. O caso real, medido no `frontend-hub`:
|
|
148
|
-
*
|
|
149
|
-
* ```
|
|
150
|
-
* const buttonVariants = cva("… rounded-[var(--radius-md)] …", { variants: { … } });
|
|
151
|
-
* …
|
|
152
|
-
* className={twUtils.cn(buttonVariants({ variant, size, className }))}
|
|
153
|
-
* ```
|
|
154
|
-
*
|
|
155
|
-
* As classes estão na declaração, não no atributo - então o leitor de 08/09, que já colhia toda
|
|
156
|
-
* string DENTRO da expressão, via `buttonVariants(...)` e não via nenhuma classe. Medido nas duas
|
|
157
|
-
* árvores: **4 + 41** declarações `cva(` e **0 + 85** `tv(`.
|
|
158
|
-
*
|
|
159
|
-
* A REGRA CONTINUA SENDO A MESMA, um salto adiante: o atributo diz quais identificadores importam,
|
|
160
|
-
* e a declaração daqueles identificadores é lida no mesmo arquivo. Nada aqui conhece `cva`, `tv` ou
|
|
161
|
-
* `cn` - o próximo cliente escreve a tabela dele num objeto pelado, e cai na mesma regra.
|
|
162
|
-
*
|
|
163
|
-
* E O ERRO CONTINUA CAINDO PARA O LADO SEGURO: uma const que não é classe - um rótulo, uma
|
|
164
|
-
* mensagem - entrega palavras que o `@theme` não declara, então elas somem na peneira final. O
|
|
165
|
-
* cabeçalho deste módulo diz que entre errar cobrando e errar calando, cobrar é o erro que a pessoa
|
|
166
|
-
* percebe.
|
|
167
|
-
*/
|
|
168
|
-
function declarationsBehind(source, exprs) {
|
|
169
|
-
/**
|
|
170
|
-
* TODO IDENTIFICADOR DA EXPRESSÃO, e não só os que ela CHAMA.
|
|
171
|
-
*
|
|
172
|
-
* A primeira versão pegava `nome(`, e o objeto pelado - `pick(byTone, tone)` - passa a tabela
|
|
173
|
-
* como ARGUMENTO. O que decide é o atributo mencionar o identificador; se ele é chamado ou
|
|
174
|
-
* passado é sintaxe, e sintaxe é o que muda de projeto para projeto.
|
|
175
|
-
*/
|
|
176
|
-
const names = new Set();
|
|
177
|
-
for (const expr of exprs)
|
|
178
|
-
for (const m of expr.matchAll(/\b([A-Za-z_$][\w$]*)\b/g))
|
|
179
|
-
names.add(m[1]);
|
|
180
|
-
if (names.size === 0)
|
|
181
|
-
return [];
|
|
182
|
-
const out = [];
|
|
183
|
-
for (const name of names) {
|
|
184
|
-
/** `const <nome> = ` - e o valor vai até a linha que fecha no MESMO nível de indentação. */
|
|
185
|
-
const at = source.search(new RegExp(`\\b(?:const|let|var)\\s+${name}\\s*=`));
|
|
186
|
-
if (at < 0)
|
|
187
|
-
continue;
|
|
188
|
-
let depth = 0;
|
|
189
|
-
let i = source.indexOf("=", at);
|
|
190
|
-
let quote = null;
|
|
191
|
-
let started = false;
|
|
192
|
-
for (; i < source.length; i += 1) {
|
|
193
|
-
const c = source[i];
|
|
194
|
-
if (quote) {
|
|
195
|
-
if (c === "\\")
|
|
196
|
-
i += 1;
|
|
197
|
-
else if (c === quote)
|
|
198
|
-
quote = null;
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
if (c === '"' || c === "'" || c === "`")
|
|
202
|
-
quote = c;
|
|
203
|
-
else if (c === "(" || c === "{" || c === "[") {
|
|
204
|
-
depth += 1;
|
|
205
|
-
started = true;
|
|
206
|
-
}
|
|
207
|
-
else if (c === ")" || c === "}" || c === "]") {
|
|
208
|
-
depth -= 1;
|
|
209
|
-
if (started && depth <= 0)
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
else if (c === ";" && !started)
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
out.push(source.slice(at, i + 1));
|
|
216
|
-
}
|
|
217
|
-
return out;
|
|
218
|
-
}
|
|
219
|
-
/** As classes escritas no código - `hover:animate-shimmer` conta como `animate-shimmer`. */
|
|
220
|
-
function classesIn(source) {
|
|
221
|
-
const out = new Set();
|
|
222
|
-
const collect = (body) => {
|
|
223
|
-
for (const raw of body.split(/\s+/)) {
|
|
224
|
-
if (!raw)
|
|
225
|
-
continue;
|
|
226
|
-
const bare = raw.split(":").pop() ?? raw;
|
|
227
|
-
out.add(bare.replace(/^[!-]/, ""));
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
for (const m of source.matchAll(/class(?:Name)?=(?:"([^"]*)"|'([^']*)'|\{`([^`]*)`\}|\{"([^"]*)"\})/g))
|
|
231
|
-
collect(m[1] ?? m[2] ?? m[3] ?? m[4] ?? "");
|
|
232
|
-
/**
|
|
233
|
-
* E TODA STRING DENTRO DA EXPRESSÃO - ver `attributeExpressions`. As três formas de escrever uma
|
|
234
|
-
* string em JS/TS, porque quem escolhe a aspa é o formatador dele, não a gente.
|
|
235
|
-
*/
|
|
236
|
-
const exprs = attributeExpressions(source);
|
|
237
|
-
const strings = (text) => {
|
|
238
|
-
for (const lit of text.matchAll(/"([^"]*)"|'([^']*)'|`([^`]*)`/g))
|
|
239
|
-
collect(lit[1] ?? lit[2] ?? lit[3] ?? "");
|
|
240
|
-
};
|
|
241
|
-
for (const expr of exprs)
|
|
242
|
-
strings(expr);
|
|
243
|
-
/** E a declaração de quem o atributo chama - ver `declarationsBehind`. */
|
|
244
|
-
for (const decl of declarationsBehind(source, exprs))
|
|
245
|
-
strings(decl);
|
|
246
|
-
return out;
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* O QUE, NESTE TEXTO, SÓ A FOLHA RESOLVE.
|
|
250
|
-
*
|
|
251
|
-
* Puro de propósito: quem varre o disco é quem chama, e um spec precisa poder afirmar a regra sem
|
|
252
|
-
* um diretório temporário.
|
|
253
|
-
*/
|
|
254
|
-
export function whatOnlyTheSheetResolves(input) {
|
|
255
|
-
const variables = [
|
|
256
|
-
...new Set([...input.source.matchAll(/var\(\s*(--ds-[a-zA-Z0-9_-]+)/g)].map((m) => m[1])),
|
|
257
|
-
];
|
|
258
|
-
/**
|
|
259
|
-
* `--animate-shimmer` declarado por ele -> o nome `shimmer` sai da conta.
|
|
260
|
-
*
|
|
261
|
-
* E O QUE O PRÓPRIO TAILWIND DECLARA sai junto, pela mesma razão e pela lista que já existe:
|
|
262
|
-
* `animate-pulse` existe em qualquer projeto que importe o Tailwind, e acusá-la de exigir a nossa
|
|
263
|
-
* folha é o mesmo aviso falso que `TAILWIND_THEME_VARS` foi escrito para acabar. Medido no
|
|
264
|
-
* `codelevel` (07/09): sem esta linha, `animate-pulse` era a ÚNICA coisa sustentando a cobrança
|
|
265
|
-
* do import naquele repositório.
|
|
266
|
-
*/
|
|
267
|
-
const theirs = new Set();
|
|
268
|
-
for (const name of [...TAILWIND_THEME_VARS, ...(input.theirNames ?? [])]) {
|
|
269
|
-
const m = /^--[a-z]+-(.+)$/.exec(name.toLowerCase());
|
|
270
|
-
if (m)
|
|
271
|
-
theirs.add(m[1]);
|
|
272
|
-
}
|
|
273
|
-
const names = themeNames(input.themeCss).filter((n) => !theirs.has(n));
|
|
274
|
-
const written = classesIn(input.source);
|
|
275
|
-
const classes = [
|
|
276
|
-
...new Set([...written].filter((c) => names.some((n) => c === n || c.endsWith(`-${n}`)))),
|
|
277
|
-
];
|
|
278
|
-
/** As classes que a folha declara e que ESTE texto veste - ver `recipeClasses`. */
|
|
279
|
-
const declared = new Set(classesTheSheetDeclares(input.sheetCss));
|
|
280
|
-
const recipeClasses = [
|
|
281
|
-
...new Set([...written].filter((c) => declared.has(c))),
|
|
282
|
-
];
|
|
283
|
-
return {
|
|
284
|
-
variables,
|
|
285
|
-
classes,
|
|
286
|
-
recipeClasses,
|
|
287
|
-
needed: variables.length > 0 || classes.length > 0 || recipeClasses.length > 0,
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
/** A folha compilada da versão instalada - as receitas moram nela, não no `theme.css`. */
|
|
291
|
-
export async function installedTokensCss(root, slug) {
|
|
292
|
-
return installedSheet(root, slug, "tokens.css");
|
|
293
|
-
}
|
|
294
|
-
/** O `theme.css` da versão instalada - a folha da raiz é um re-export de uma linha. */
|
|
295
|
-
export async function installedThemeCss(root, slug) {
|
|
296
|
-
return installedSheet(root, slug, "theme.css");
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* UM ARQUIVO DA VERSÃO PINADA - o `.lock` diz qual é, e a folha da raiz é um re-export de uma linha.
|
|
300
|
-
*
|
|
301
|
-
* Uma leitura só para as duas folhas: duas cópias da mesma decisão de versão é a forma de uma delas
|
|
302
|
-
* ficar atrás no dia em que o formato do `.lock` mudar.
|
|
303
|
-
*/
|
|
304
|
-
async function installedSheet(root, slug, filename) {
|
|
305
|
-
const dir = join(root, "_synthesisui", "ds", slug);
|
|
306
|
-
const lock = await readFile(join(dir, ".lock"), "utf8").catch(() => null);
|
|
307
|
-
let version = 0;
|
|
308
|
-
if (lock) {
|
|
309
|
-
try {
|
|
310
|
-
version = JSON.parse(lock).version ?? 0;
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
version = 0;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return readFile(join(dir, `v${version}`, filename), "utf8").catch(() => "");
|
|
317
|
-
}
|
package/dist/skill-configure.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* O STUB DA SKILL - o corpo é SERVIDO, não embarcado (R1, dono 16/08).
|
|
3
|
-
*
|
|
4
|
-
* Mesma doutrina das outras quatro: o frontmatter fica inteiro aqui, porque é a `description` que
|
|
5
|
-
* faz a skill ser invocada e ela não é segredo; a esteira mora em `apps/web/src/lib/ds/playbooks.ts`
|
|
6
|
-
* e chega pelo catalogue autenticado.
|
|
7
|
-
*
|
|
8
|
-
* O QUE ELA RESOLVE: um sistema instalado e sincronizado pode não mudar um pixel na tela dele. Até
|
|
9
|
-
* uma folha de estilo importar os tokens e um elemento carregar `data-ds`, nada chega ao navegador -
|
|
10
|
-
* e essa era a única parte da instalação que ninguém escrevia, só imprimia.
|
|
11
|
-
*/
|
|
12
|
-
export const CONFIGURE_SKILL_PATH = ".claude/skills/sui-configure-ds/SKILL.md";
|
|
13
|
-
export const CONFIGURE_SKILL = '---\nname: sui-configure-ds\ndescription: Set up an installed design system in the app so its tokens actually reach the browser - the stylesheet import, the data-ds scope, and the type. Use when a system is installed and the app does not look themed, when `doctor` reports "installed - but this project does not load it yet", or when somebody asks to set up / connect / configure the design system in their app ("configura o design system aqui", "why aren\'t the tokens working", "/sui-configure-ds"). Asks the doctor first and does nothing when the three checks already pass.\n---\n\n# Setting the system up in the app - served live\n\nThis playbook is served from the platform, not shipped in this file - it is\nalways current, and your context only carries the step you are on.\n\n1. Call the `playbook` tool on the `synthesisui` MCP server with\n { "skill": "configure" } - you get the framing and a table of contents.\n2. Fetch ONLY the chapter for the step you are on:\n { "skill": "configure", "section": "<id from the toc>" }. Never fetch more\n than the current step needs.\n3. Follow it exactly. When the step is done, fetch the next chapter.\n\nStart by running `npx synthesisui doctor`: the three lines it prints are what\ndecide the work, and three ticks means there is nothing to do.\n\nIf the tool answers that you are not signed in, run `npx synthesisui login`\nin the terminal and call it again. If the `synthesisui` MCP server is not\navailable at all, run `npx synthesisui connect`, restart the session, and\ninvoke this skill again.\n';
|
package/dist/wiring-read.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { walk } from "./commands/doctor.js";
|
|
4
|
-
import { FAMILY_SEAM_PREFIX } from "./fonts.js";
|
|
5
|
-
import { detectAppDirs, sheetChainOf } from "./global-sheet.js";
|
|
6
|
-
/**
|
|
7
|
-
* A FIAÇÃO DESTE PROJETO, MEDIDA - e a mesma medição para todos os comandos que a citam.
|
|
8
|
-
*
|
|
9
|
-
* O QUE O CLIENTE VIA (medido em 08/09, nos dois apps do ato 2B): ele colou os dois `@import` no
|
|
10
|
-
* `globals.css`, pôs `data-ds="codelevel"` no `<body>`, mapeou as quatro famílias - e o
|
|
11
|
-
* `component` terminou imprimindo **One-time setup**, com os passos 1 e 2 para colar de novo. O
|
|
12
|
-
* comando media "este arquivo PRECISA da folha?" (`sheet-needed.ts`) e nunca "a folha JÁ está
|
|
13
|
-
* aqui?".
|
|
14
|
-
*
|
|
15
|
-
* As duas perguntas são diferentes e as duas importam: a primeira decide se o setup é necessário, e
|
|
16
|
-
* a segunda decide se ele ainda está PENDENTE. Cobrar o que já foi feito gasta a confiança de quem
|
|
17
|
-
* fez - ele fica sem saber se errou algo, e o texto que deveria orientar passa a ser ruído.
|
|
18
|
-
*
|
|
19
|
-
* POR QUE ESTE MÓDULO EXISTE, em vez de o `component` medir por conta: esta leitura já existia
|
|
20
|
-
* dentro do `doctor`, com o histórico inteiro dela. Uma segunda medição da mesma coisa é uma
|
|
21
|
-
* segunda medição livre de discordar da primeira - dois comandos dizendo coisas opostas sobre o
|
|
22
|
-
* mesmo repositório é exatamente o defeito que este PR conserta.
|
|
23
|
-
*/
|
|
24
|
-
/**
|
|
25
|
-
* Wiring is a property of the PROJECT, never of the scope being read.
|
|
26
|
-
*
|
|
27
|
-
* This ran inside the scoped walk, so `doctor app/login` read only that folder
|
|
28
|
-
* - and the import lives in `app/globals.css`. It concluded "installed, but not
|
|
29
|
-
* wired up yet" about a project that was perfectly wired (my-test4, 27/07).
|
|
30
|
-
*
|
|
31
|
-
* Which would be a mild annoyance, except the managed block now tells the agent
|
|
32
|
-
* to run `doctor <the file you just wrote>` after every edit. The false alarm
|
|
33
|
-
* fires on every loop, and an agent that believes the system is unwired starts
|
|
34
|
-
* repairing wiring that was already correct - editing the one file we most need
|
|
35
|
-
* it to leave alone.
|
|
36
|
-
*
|
|
37
|
-
* The system itself was always resolved from the root. So is this now.
|
|
38
|
-
*/
|
|
39
|
-
/**
|
|
40
|
-
* O QUE NOS ESCREVEMOS NAO E' FIACAO DELE - e isto era um FALSO SILENCIO, o pior dos dois.
|
|
41
|
-
*
|
|
42
|
-
* MEDIDO EM 10/09: o cabecalho que `component-codegen.ts` poe em todo `.tsx` materializado diz,
|
|
43
|
-
* em comentario, *"Global setup (once per app): import _synthesisui/ds/<slug>/tokens.css"* e
|
|
44
|
-
* *"put data-ds=<slug> on a root element"*. A varredura abaixo procura exatamente essas duas
|
|
45
|
-
* strings em QUALQUER arquivo do repositorio - entao, a partir do primeiro componente que nos
|
|
46
|
-
* mesmos escrevemos, ela passava a responder `imported: true` e `scoped: true` sobre um projeto
|
|
47
|
-
* que nao importa nada e nao tem escopo nenhum.
|
|
48
|
-
*
|
|
49
|
-
* O QUE O CLIENTE VIVIA: ele traz um componente, a tela diz *"This project already imports
|
|
50
|
-
* <slug>'s tokens.css and carries data-ds - nothing to set up"*, ele cola o componente na pagina,
|
|
51
|
-
* e ve' um bloco sem cor, sem tipografia e sem sombra. O comando acabou de garantir que estava
|
|
52
|
-
* tudo certo. O `doctor` le' pela mesma porta, entao dizia o mesmo.
|
|
53
|
-
*
|
|
54
|
-
* A instrucao que ESTE arquivo carrega e' uma instrucao, nao um cumprimento dela. Um comentario
|
|
55
|
-
* nosso nunca prova nada sobre o projeto dele.
|
|
56
|
-
*/
|
|
57
|
-
const OURS = "Generated by SynthesisUI";
|
|
58
|
-
export async function readWiring(root, slug) {
|
|
59
|
-
const w = {
|
|
60
|
-
imported: false,
|
|
61
|
-
/**
|
|
62
|
-
* O ADAPTADOR DO TAILWIND, e ele é uma pergunta SEPARADA de `imported`.
|
|
63
|
-
*
|
|
64
|
-
* O DEFEITO QUE ISTO CONSERTA, apontado na revisão deste PR: `imported` olha só o
|
|
65
|
-
* `tokens.css`, e o bloco que consulta esta leitura fala dos UTILITÁRIOS - que só o `@theme`
|
|
66
|
-
* do `theme.css` gera. Num projeto com um import e não o outro, o comando nomeava a falta
|
|
67
|
-
* (`what needs it: the bg-canvas utility`) e na linha seguinte declarava que não faltava nada.
|
|
68
|
-
*
|
|
69
|
-
* Um falso silêncio é pior que o falso alarme que este PR veio consertar: o cliente cola o
|
|
70
|
-
* componente, vê um bloco sem tipografia e sem sombra, e o comando acabou de dizer que estava
|
|
71
|
-
* tudo certo.
|
|
72
|
-
*/
|
|
73
|
-
themed: false,
|
|
74
|
-
scoped: false,
|
|
75
|
-
/** `init` wrote a fonts file for this project (Next targets only). */
|
|
76
|
-
fontsWritten: false,
|
|
77
|
-
/** …and the stylesheet actually maps it onto the system's type tokens. */
|
|
78
|
-
fontsMapped: false,
|
|
79
|
-
/**
|
|
80
|
-
* THE FEATURE NOBODY KNEW WE SHIPPED.
|
|
81
|
-
*
|
|
82
|
-
* Every system compiles a `shadcn.css` mapping shadcn's whole variable
|
|
83
|
-
* contract onto its own tokens, so shadcn components wear the system instead
|
|
84
|
-
* of shadcn's defaults. It has been generated into every project since it
|
|
85
|
-
* was built, and until 28/07 nothing mentioned it: not `add`, not the
|
|
86
|
-
* managed block, not the GUIDE except as a filename in a list. The only
|
|
87
|
-
* documentation was a comment inside the file, which is the same as none.
|
|
88
|
-
*
|
|
89
|
-
* The person it mattered most to is the one who asked "I don't get the use
|
|
90
|
-
* case, I can just build a design system with shadcn" - and the answer was
|
|
91
|
-
* sitting unimported in his own repo.
|
|
92
|
-
*
|
|
93
|
-
* Reported only when the project HAS shadcn. Telling everybody about a
|
|
94
|
-
* bridge they will never use is how a report earns the skim.
|
|
95
|
-
*/
|
|
96
|
-
hasShadcn: false,
|
|
97
|
-
bridged: false,
|
|
98
|
-
};
|
|
99
|
-
if (!slug)
|
|
100
|
-
return w;
|
|
101
|
-
w.hasShadcn = await readFile(join(root, "components.json"), "utf8").then(() => true, () => false);
|
|
102
|
-
for await (const file of walk(root)) {
|
|
103
|
-
const src = await readFile(file, "utf8").catch(() => "");
|
|
104
|
-
if (!src)
|
|
105
|
-
continue;
|
|
106
|
-
/** Ver `OURS`: o nosso proprio cabecalho respondia por ele. */
|
|
107
|
-
if (src.slice(0, 300).includes(OURS))
|
|
108
|
-
continue;
|
|
109
|
-
if (src.includes(`_synthesisui/ds/${slug}/tokens.css`))
|
|
110
|
-
w.imported = true;
|
|
111
|
-
if (src.includes(`_synthesisui/ds/${slug}/theme.css`))
|
|
112
|
-
w.themed = true;
|
|
113
|
-
if (src.includes(`_synthesisui/ds/${slug}/shadcn.css`))
|
|
114
|
-
w.bridged = true;
|
|
115
|
-
if (src.includes(`data-ds="${slug}"`))
|
|
116
|
-
w.scoped = true;
|
|
117
|
-
// The requirement that stayed invisible. `init` writes a fonts file and
|
|
118
|
-
// asks for two more edits; an agent told to check only the first two did
|
|
119
|
-
// exactly that, stopped, and left the project rendering in the framework's
|
|
120
|
-
// default face (my-test2, 27/07). What the checker checks is what gets done.
|
|
121
|
-
if (src.includes("--font-ds-") && /next\/font/.test(src))
|
|
122
|
-
w.fontsWritten = true;
|
|
123
|
-
/** O prefixo vem da const única da costura - um literal aqui divergiria em silêncio (26/08). */
|
|
124
|
-
if (new RegExp(`${FAMILY_SEAM_PREFIX}\\w+\\s*:\\s*var\\(\\s*--font-ds-`).test(src))
|
|
125
|
-
w.fontsMapped = true;
|
|
126
|
-
// The bridge joins the early exit, otherwise the walk can stop before the
|
|
127
|
-
// stylesheet that imports it and report a wired project as unbridged.
|
|
128
|
-
if (w.imported &&
|
|
129
|
-
w.themed &&
|
|
130
|
-
w.scoped &&
|
|
131
|
-
w.fontsMapped &&
|
|
132
|
-
(!w.hasShadcn || w.bridged))
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
return w;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* A FIAÇÃO DE CADA APP, e num monorepo é ela que decide - `readWiring` acima não decide.
|
|
139
|
-
*
|
|
140
|
-
* O QUE O CLIENTE VIA: `readWiring` varre da RAIZ e responde "existe, em algum lugar daqui". Num
|
|
141
|
-
* monorepo com dois apps servidos, um fiado e outro não, o fiado respondia pelo outro: a pessoa
|
|
142
|
-
* abria o app que não carrega nada e o comando dizia que estava tudo certo. O contrato já declarava
|
|
143
|
-
* isso como lacuna (`INV-VOLTA-12`, "a resposta é do PROJETO, e num monorepo ela não decide") e o
|
|
144
|
-
* comando dizia o que mediu em vez de afirmar - honesto, e ainda a resposta errada.
|
|
145
|
-
*
|
|
146
|
-
* COMO A RESPOSTA CERTA É ENCONTRADA, e cada metade vem de onde ela mora:
|
|
147
|
-
*
|
|
148
|
-
* os `@import` seguindo a CADEIA de folhas a partir da folha daquele app (`sheetChainOf`).
|
|
149
|
-
* A folha que carrega os tokens quase nunca é a do app: num monorepo é a do
|
|
150
|
-
* pacote compartilhado, e os dois apps chegam nela - então varrer pasta daria
|
|
151
|
-
* "não" para os dois, e varrer a raiz dá "sim" para todos
|
|
152
|
-
* o `data-ds` na árvore DAQUELE app, porque o escopo mora no layout dele
|
|
153
|
-
* as fontes idem - `init` escreve o arquivo de fonte por app
|
|
154
|
-
*
|
|
155
|
-
* O QUE ESTA FUNÇÃO NÃO GENERALIZA, declarado com o N de hoje (medido em 09/09):
|
|
156
|
-
*
|
|
157
|
-
* `detectAppDirs` só procura em `apps/` e `packages/` cobre 4 de 4 monorepos medidos;
|
|
158
|
-
* `pnpm-workspace.yaml` aparece em 0 de 10
|
|
159
|
-
* e só reconhece app pelo `layout` do App Router cobre 7 de 7 raízes de app medidas;
|
|
160
|
-
* `vite` aparece só em PACOTE de
|
|
161
|
-
* biblioteca, corretamente ignorado
|
|
162
|
-
*
|
|
163
|
-
* As duas são estreitezas REAIS e as duas têm N=0 de evidência contrária hoje. Generalizá-las sem
|
|
164
|
-
* uma população que as exija seria dimensionar pela imaginação - o erro que `INV-GERAL-07` nomeia
|
|
165
|
-
* pelo avesso. Quando aparecer um repositório que caia fora, o número dele é que abre a frente.
|
|
166
|
-
*/
|
|
167
|
-
export async function readWiringPerApp(root, slug, pagesDir) {
|
|
168
|
-
if (!slug)
|
|
169
|
-
return [];
|
|
170
|
-
const apps = await detectAppDirs(root, pagesDir);
|
|
171
|
-
const out = [];
|
|
172
|
-
const hasShadcn = await readFile(join(root, "components.json"), "utf8").then(() => true, () => false);
|
|
173
|
-
for (const app of apps) {
|
|
174
|
-
const w = {
|
|
175
|
-
imported: false,
|
|
176
|
-
themed: false,
|
|
177
|
-
scoped: false,
|
|
178
|
-
fontsWritten: false,
|
|
179
|
-
fontsMapped: false,
|
|
180
|
-
hasShadcn,
|
|
181
|
-
bridged: false,
|
|
182
|
-
};
|
|
183
|
-
/** Os imports: a cadeia daquele app, e não a pasta dele. */
|
|
184
|
-
for (const sheet of await sheetChainOf(root, `${app}/globals.css`)) {
|
|
185
|
-
const css = await readFile(join(root, sheet), "utf8").catch(() => "");
|
|
186
|
-
if (!css)
|
|
187
|
-
continue;
|
|
188
|
-
if (css.includes(`_synthesisui/ds/${slug}/tokens.css`))
|
|
189
|
-
w.imported = true;
|
|
190
|
-
if (css.includes(`_synthesisui/ds/${slug}/theme.css`))
|
|
191
|
-
w.themed = true;
|
|
192
|
-
if (css.includes(`_synthesisui/ds/${slug}/shadcn.css`))
|
|
193
|
-
w.bridged = true;
|
|
194
|
-
}
|
|
195
|
-
/** O escopo e as fontes: a árvore DAQUELE app - é onde o layout dele mora. */
|
|
196
|
-
for await (const file of walk(join(root, app))) {
|
|
197
|
-
const src = await readFile(file, "utf8").catch(() => "");
|
|
198
|
-
if (!src)
|
|
199
|
-
continue;
|
|
200
|
-
if (src.includes(`data-ds="${slug}"`))
|
|
201
|
-
w.scoped = true;
|
|
202
|
-
if (src.includes("--font-ds-") && /next\/font/.test(src))
|
|
203
|
-
w.fontsWritten = true;
|
|
204
|
-
if (new RegExp(`${FAMILY_SEAM_PREFIX}\\w+\\s*:\\s*var\\(\\s*--font-ds-`).test(src))
|
|
205
|
-
w.fontsMapped = true;
|
|
206
|
-
if (w.scoped && w.fontsMapped)
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
out.push({ app, wiring: w });
|
|
210
|
-
}
|
|
211
|
-
return out;
|
|
212
|
-
}
|