synthesisui 0.16.291 → 0.16.293
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/commands/add.js +72 -3
- package/dist/commands/align.js +16 -1
- package/dist/commands/import.js +39 -0
- package/dist/doctor/broken-refs.js +43 -2
- package/dist/install-marks.js +1 -1
- package/dist/their-theme.js +17 -0
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { access, mkdir, readdir, readFile, rm, writeFile, } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { syncClaudeMd } from "../claude-md.js";
|
|
4
|
-
import { readProjectConfig, resolveRegistry } from "../config.js";
|
|
4
|
+
import { readProjectConfig, readToken, resolveRegistry } from "../config.js";
|
|
5
5
|
import { customFontFamilies, googleFontsHref, nextFontSnippet, } from "../fonts.js";
|
|
6
6
|
import { lockReference } from "../group-role.js";
|
|
7
7
|
import { buildGuide } from "../guide.js";
|
|
8
8
|
import { censusScope } from "../measured-scope.js";
|
|
9
9
|
import { body as line, section, snippet } from "../output.js";
|
|
10
10
|
import { fetchDesignSystem } from "../registry.js";
|
|
11
|
+
import { repoStateOf } from "../repo-state.js";
|
|
11
12
|
import { describeFiltered, ruleApplies, rulesForProject, } from "../rule-filter.js";
|
|
12
13
|
import { detectStack } from "../stack.js";
|
|
14
|
+
import { onlyWhatMatched } from "../their-theme.js";
|
|
13
15
|
import { pointTokensAtTheirNames } from "../their-vars.js";
|
|
14
16
|
/**
|
|
15
17
|
* QUAL METADE DESTA PASTA UM TIME COMMITA.
|
|
@@ -203,9 +205,26 @@ export async function add(slug, opts) {
|
|
|
203
205
|
* do servidor. A folha nunca fica pior do que estava.
|
|
204
206
|
*/
|
|
205
207
|
const theirVars = await pointTokensAtTheirNames(projectRoot, payload);
|
|
208
|
+
/**
|
|
209
|
+
* O `theme.css` ALINHA ONDE HÁ O QUE ALINHAR - ver `onlyWhatMatched`.
|
|
210
|
+
*
|
|
211
|
+
* O QUE ISSO EVITA NA TELA DELE: `rounded-xl` valia 12px no código dele, o default do Tailwind, e
|
|
212
|
+
* a nossa folha o fazia valer 28px em 21 elementos. Ele não pediu e não declarou raio nenhum: a
|
|
213
|
+
* classe dele significava uma coisa e passava a significar outra. Medido em 23/08 - dos 68
|
|
214
|
+
* utilitários que o arquivo redefine, 4 apontavam para um token que casou com o vocabulário dele e
|
|
215
|
+
* 64 eram nossos.
|
|
216
|
+
*
|
|
217
|
+
* Só isto é possível porque o mapa existe. Sem ele, "este token é dele?" não tinha resposta na hora
|
|
218
|
+
* de escrever a folha.
|
|
219
|
+
*/
|
|
220
|
+
const aligned = onlyWhatMatched(payload.artifacts["theme.css"] ?? "", new Set(theirVars.pairs.map((p) => p.ours)));
|
|
206
221
|
// 1. server artifacts (tokens.css, theme.css, …) → pinned version folder
|
|
207
222
|
for (const [filename, content] of Object.entries(payload.artifacts)) {
|
|
208
|
-
await writeFile(join(versionDir, filename), filename === "tokens.css"
|
|
223
|
+
await writeFile(join(versionDir, filename), filename === "tokens.css"
|
|
224
|
+
? theirVars.css
|
|
225
|
+
: filename === "theme.css"
|
|
226
|
+
? aligned.css
|
|
227
|
+
: content, "utf8");
|
|
209
228
|
}
|
|
210
229
|
// 2. canonical source of truth
|
|
211
230
|
await writeFile(join(versionDir, "design-system.json"), `${JSON.stringify(payload.document, null, 2)}\n`, "utf8");
|
|
@@ -301,6 +320,33 @@ export async function add(slug, opts) {
|
|
|
301
320
|
fetchedAt: landed || !prev?.fetchedAt ? new Date().toISOString() : prev.fetchedAt,
|
|
302
321
|
};
|
|
303
322
|
await writeFile(rootLockPath, `${JSON.stringify(lock, null, 2)}\n`, "utf8");
|
|
323
|
+
/**
|
|
324
|
+
* O MAPA SOBE AGORA, e é o único momento em que ele é novo.
|
|
325
|
+
*
|
|
326
|
+
* O QUE ESTAVA FALTANDO, medido em 23/08: o `.lock` tinha 24 pares e a tabela `token_names` tinha
|
|
327
|
+
* zero. O `repo_state` viaja em três caminhos - o import, o ping do agente e o `sync` - e o import
|
|
328
|
+
* o envia ANTES de instalar, quando o `.lock` ainda não existe. Então o mapa ficava esperando o
|
|
329
|
+
* próximo comando que reportasse, e até lá o Studio continuava falando a nossa língua.
|
|
330
|
+
*
|
|
331
|
+
* Este é o comando que escreve o mapa. Ele é quem tem que contar.
|
|
332
|
+
*
|
|
333
|
+
* GUARDADO: o sistema está instalado e os arquivos estão no lugar. Uma rede que cai aqui custa o
|
|
334
|
+
* mapa no servidor até o próximo `sync`, e jamais o install que acabou de dar certo.
|
|
335
|
+
*/
|
|
336
|
+
if (opts.cli) {
|
|
337
|
+
const state = await repoStateOf(projectRoot, payload.slug, opts.cli).catch(() => null);
|
|
338
|
+
const token = await readToken();
|
|
339
|
+
if (state?.tokenMap && token) {
|
|
340
|
+
await fetch(`${base}/api/ledger`, {
|
|
341
|
+
method: "POST",
|
|
342
|
+
headers: {
|
|
343
|
+
"content-type": "application/json",
|
|
344
|
+
Authorization: `Bearer ${token}`,
|
|
345
|
+
},
|
|
346
|
+
body: JSON.stringify({ slug: payload.slug, repo: state, events: [] }),
|
|
347
|
+
}).catch(() => null);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
304
350
|
await writeGovernanceIgnore(projectRoot);
|
|
305
351
|
const retired = await retireMaterializedDoctrine(slugDir);
|
|
306
352
|
// 5b. governance rules (personal DS) → doctrine.json at the slug root (stable path,
|
|
@@ -419,6 +465,15 @@ export async function add(slug, opts) {
|
|
|
419
465
|
.map((r) => `${r.theirs} → ${theirVars.pairs.find((p) => p.ours === r.ours)?.theirs ?? "?"}`)
|
|
420
466
|
.join(", ")}${renamed.length > 3 ? ", …" : ""}. The system follows the new name from here.`);
|
|
421
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* E ELE FICA SABENDO DO QUE NÃO FOI ALINHADO - cortar em silêncio é a outra metade do erro.
|
|
470
|
+
*
|
|
471
|
+
* A linha diz quantos utilitários do Tailwind ficaram apontando para a decisão dele e quantos
|
|
472
|
+
* saíram por serem nossos, com os primeiros nomes. Quem lê pode discordar de qualquer um.
|
|
473
|
+
*/
|
|
474
|
+
if (aligned.dropped.length > 0) {
|
|
475
|
+
console.log(line(` ${aligned.kept} Tailwind utilit${aligned.kept === 1 ? "y" : "ies"} now point at your own decisions; ${aligned.dropped.length} were left alone because the value would be ours (${aligned.dropped.slice(0, 3).join(", ")}${aligned.dropped.length > 3 ? ", …" : ""}) - your classes keep meaning what they mean today.`));
|
|
476
|
+
}
|
|
422
477
|
if (theirVars.pointed > 0)
|
|
423
478
|
console.log(` ${theirVars.pointed} value${theirVars.pointed === 1 ? "" : "s"} in tokens.css now point at the name YOUR code already gives ${theirVars.pointed === 1 ? "it" : "them"} - change yours and the system follows${theirVars.pruned > 0 ? `; ${theirVars.pruned} matched but your build does not emit ${theirVars.pruned === 1 ? "that name" : "those names"}, so ${theirVars.pruned === 1 ? "it keeps" : "they keep"} the value` : ""}`);
|
|
424
479
|
/**
|
|
@@ -436,7 +491,21 @@ export async function add(slug, opts) {
|
|
|
436
491
|
/** Nomeado, nunca em silêncio: apagar arquivo no repo de alguém se diz em voz alta. */
|
|
437
492
|
if (retired.length > 0)
|
|
438
493
|
console.log(` removed ${retired.join(" and ")} - nothing rewrote them after an install, so they stated an older version's rules as current`);
|
|
439
|
-
|
|
494
|
+
/**
|
|
495
|
+
* "CREATED" PRECISA DIZER A CONSEQUÊNCIA, e a palavra sozinha não dizia.
|
|
496
|
+
*
|
|
497
|
+
* A distinção já existia aqui - `created` contra `updated` -, e três vezes em 23/08 o arquivo foi
|
|
498
|
+
* criado porque ele não estava no disco, com 78 linhas dele vivas no git. A palavra `created` é
|
|
499
|
+
* neutra: ela descreve o que o comando fez e não o que aconteceu com o trabalho de quem lê.
|
|
500
|
+
*
|
|
501
|
+
* `updated` significa "o seu arquivo continua aí, com o nosso bloco dentro". `created` significa
|
|
502
|
+
* "não havia arquivo aqui" - e num repositório que TEM um versionado, isso quer dizer que ele está
|
|
503
|
+
* ausente do diretório de trabalho, o que ninguém faz de propósito. A linha passa a dizer isso e o
|
|
504
|
+
* comando de volta.
|
|
505
|
+
*/
|
|
506
|
+
console.log(claudeMd.created
|
|
507
|
+
? ` CLAUDE.md created - there was none here (${claudeMd.count} system(s) indexed). If your repo has one in git, it is missing from your working tree: \`git checkout -- CLAUDE.md\` and run this again to keep both.`
|
|
508
|
+
: ` CLAUDE.md updated - your file is intact, with our block inside it (${claudeMd.count} system(s) indexed)`);
|
|
440
509
|
if (opts.setupHints === false)
|
|
441
510
|
return;
|
|
442
511
|
const hasTheme = cssArtifacts.includes("theme.css");
|
package/dist/commands/align.js
CHANGED
|
@@ -579,8 +579,23 @@ export async function nextStepFor(root) {
|
|
|
579
579
|
if (locks.length > 0)
|
|
580
580
|
return null;
|
|
581
581
|
const measured = await readFile(join(root, "_synthesisui", "census.json"), "utf8").then(() => true, () => false);
|
|
582
|
+
/**
|
|
583
|
+
* MEDIDO E SEM SISTEMA INSTALADO SÃO DUAS HISTÓRIAS, e a frase antiga contava a errada.
|
|
584
|
+
*
|
|
585
|
+
* Ela dizia *"continue the import"*, e em 23/08 foi exatamente o que o agente fez: mediu o
|
|
586
|
+
* repositório inteiro de novo, refez a leitura, reenviou. O sistema JÁ EXISTIA na conta dele - o
|
|
587
|
+
* que faltava eram os arquivos aqui, e `add` é o comando que os traz. Medir de novo custou sete
|
|
588
|
+
* minutos e não mudou nada do que a plataforma sabia.
|
|
589
|
+
*
|
|
590
|
+
* As duas situações que produzem este estado, e as duas terminam no mesmo comando: um import que
|
|
591
|
+
* criou o sistema e parou antes de instalar (o que este CLI já não faz - ver o fim de `runImport`),
|
|
592
|
+
* e um clone fresco de um repositório onde alguém apagou a pasta `ds/`.
|
|
593
|
+
*
|
|
594
|
+
* `list --mine` primeiro porque o slug nasce no servidor: ele não está em nenhum arquivo daqui, e
|
|
595
|
+
* chutá-lo seria pior que pedir para olhar.
|
|
596
|
+
*/
|
|
582
597
|
return measured
|
|
583
|
-
?
|
|
598
|
+
? "This repo has been measured and has no system installed here. If you already imported, the system is in your account: `synthesisui list --mine`, then `synthesisui add <slug>`."
|
|
584
599
|
: 'This repo has no design system contract yet. Ask me: "import my design system."';
|
|
585
600
|
}
|
|
586
601
|
/**
|
package/dist/commands/import.js
CHANGED
|
@@ -49,6 +49,7 @@ import { phase, startProgress } from "../progress.js";
|
|
|
49
49
|
import { repoStateOf } from "../repo-state.js";
|
|
50
50
|
import { detectStack, resolveDeps, stackVersions } from "../stack.js";
|
|
51
51
|
import { placeInWorkspace } from "../workspace-place.js";
|
|
52
|
+
import { add } from "./add.js";
|
|
52
53
|
import { walk, walkAll } from "./doctor.js";
|
|
53
54
|
/**
|
|
54
55
|
* How many distinct values travel, PER KIND.
|
|
@@ -3942,4 +3943,42 @@ export async function runImport(opts) {
|
|
|
3942
3943
|
if (payload?.url)
|
|
3943
3944
|
console.log(body(` ${paint.blue(payload.url)}`));
|
|
3944
3945
|
console.log("");
|
|
3946
|
+
/**
|
|
3947
|
+
* E O SISTEMA CHEGA NO REPOSITÓRIO DELE - quem manda importar quer usar.
|
|
3948
|
+
*
|
|
3949
|
+
* O QUE ELE VIVEU TRÊS VEZES EM 23/08: mandou importar, viu o sistema no ar com uma URL, e
|
|
3950
|
+
* concluiu que tinha acabado. O repositório ficava com o censo, o ledger e o relatório de
|
|
3951
|
+
* lacunas - sem `ds/`, sem `.lock`, sem `tokens.css`. O agente dele não sabia que o design
|
|
3952
|
+
* system existia, e o comando seguinte falhava com "no design system installed here". E nada
|
|
3953
|
+
* dizia que faltava um passo: nem o fim do import, nem a skill (que não cita `add` uma vez),
|
|
3954
|
+
* nem o `doctor`.
|
|
3955
|
+
*
|
|
3956
|
+
* A LINHA "não escrevemos sem pedir" já foi cruzada aqui: este comando escreve o censo, o
|
|
3957
|
+
* ledger, o `not-expressed.md` e um `.gitignore`. Parar antes de instalar não protegia nada -
|
|
3958
|
+
* interrompia. Quem quer olhar antes de escrever usa `--dry`, que retorna muito acima desta
|
|
3959
|
+
* linha e é onde essa responsabilidade mora.
|
|
3960
|
+
*
|
|
3961
|
+
* E É AQUI, DEPOIS DO RESUMO, e não antes: o resumo é sobre o que a leitura encontrou, e a
|
|
3962
|
+
* instalação é sobre o que passou a existir na máquina dele. Misturar as duas faria a saída
|
|
3963
|
+
* mais longa do produto ficar mais longa no lugar onde ela já é mais difícil de ler.
|
|
3964
|
+
*/
|
|
3965
|
+
if (payload?.slug) {
|
|
3966
|
+
try {
|
|
3967
|
+
await add(payload.slug, {
|
|
3968
|
+
registry: opts.registry,
|
|
3969
|
+
dir: root,
|
|
3970
|
+
});
|
|
3971
|
+
}
|
|
3972
|
+
catch (error) {
|
|
3973
|
+
/**
|
|
3974
|
+
* O SISTEMA FOI CRIADO NO SERVIDOR, e isso não se desfaz. Uma rede que cai na materialização
|
|
3975
|
+
* custa os arquivos locais e nada mais - então o comando diz o que rodar, e jamais devolve
|
|
3976
|
+
* um erro que faria a pessoa achar que perdeu a medição.
|
|
3977
|
+
*/
|
|
3978
|
+
console.log("");
|
|
3979
|
+
console.log(body(`Your system exists, and the files did not land here: ${error instanceof Error ? error.message : String(error)}`));
|
|
3980
|
+
console.log(body(` synthesisui add ${payload.slug}`));
|
|
3981
|
+
console.log("");
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3945
3984
|
}
|
|
@@ -21,8 +21,26 @@ import { frontierKind } from "../frontier-kind.js";
|
|
|
21
21
|
import { importMap } from "./imports.js";
|
|
22
22
|
/** Quantos lugares viajam por referência quebrada. Ver `BrokenRef.at`. */
|
|
23
23
|
const MAX_PLACES = 3;
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* `var(--x)`, including inside a Tailwind arbitrary value: `bg-[var(--x)]`.
|
|
26
|
+
*
|
|
27
|
+
* O SEGUNDO GRUPO É O FALLBACK, e é ele que separa uma referência quebrada de uma com rede:
|
|
28
|
+
* `var(--aurora-angle,125deg)` pinta 125deg. Chamar isso de "não pinta nada" é dizer ao cliente que
|
|
29
|
+
* o código dele está defeituoso quando ele escreveu a defesa.
|
|
30
|
+
*/
|
|
31
|
+
const VAR_REF = /var\(\s*(--[a-zA-Z0-9_-]+)\s*(,)?/g;
|
|
32
|
+
/**
|
|
33
|
+
* `el.style.setProperty("--tx", …)` - uma custom property DECLARADA em JavaScript.
|
|
34
|
+
*
|
|
35
|
+
* O QUE O CLIENTE RECEBIA SEM ISTO: `--tx` e `--ty` na lista de "referências que não pintam nada",
|
|
36
|
+
* quando um hook de tilt dele as escreve a cada movimento do mouse. O agente dele diagnosticou isso à
|
|
37
|
+
* mão em 23/08 e disse a frase que virou esta regra: *"o que um leitor não consegue ver é uma custom
|
|
38
|
+
* property escrita por JS"*. A resposta não é adivinhar - é procurar a escrita, e ela é literal.
|
|
39
|
+
*
|
|
40
|
+
* Medido em quatro populações: 2 nomes no `codelevel-ui`, 0 no `packages/ui` do `frontend-hub`, 2 no
|
|
41
|
+
* dashboard dele e 3 no nosso próprio app. Três de quatro escrevem variável por JS.
|
|
42
|
+
*/
|
|
43
|
+
const SET_PROPERTY = /setProperty\(\s*['"`](--[a-zA-Z0-9_-]+)/g;
|
|
26
44
|
/**
|
|
27
45
|
* VARIABLES A HEADLESS LIBRARY SETS AT RUNTIME, which no stylesheet can declare.
|
|
28
46
|
*
|
|
@@ -90,12 +108,35 @@ const NAMESPACES = [
|
|
|
90
108
|
*/
|
|
91
109
|
export function findBrokenRefs(sources, declared) {
|
|
92
110
|
const seen = new Map();
|
|
111
|
+
/**
|
|
112
|
+
* AS QUE O PRÓPRIO CÓDIGO DELE ESCREVE EM JAVASCRIPT - ver `SET_PROPERTY`.
|
|
113
|
+
*
|
|
114
|
+
* Colhidas de TODAS as fontes antes do laço, e não por arquivo: quem escreve `--tx` é o hook, e
|
|
115
|
+
* quem a lê é a folha global. São dois arquivos, e perguntar por arquivo diria que a folha
|
|
116
|
+
* referencia algo que ninguém declara.
|
|
117
|
+
*/
|
|
118
|
+
const setInJs = new Set();
|
|
119
|
+
for (const { source } of sources)
|
|
120
|
+
for (const m of source.matchAll(SET_PROPERTY))
|
|
121
|
+
setInJs.add(m[1]);
|
|
93
122
|
for (const { file, source } of sources) {
|
|
94
123
|
const runtime = importsHeadless(source);
|
|
95
124
|
for (const m of source.matchAll(VAR_REF)) {
|
|
96
125
|
const name = m[1];
|
|
97
126
|
if (declared.has(name))
|
|
98
127
|
continue;
|
|
128
|
+
/**
|
|
129
|
+
* UMA CHAMADA COM FALLBACK NÃO ESTÁ QUEBRADA - ela pinta o fallback.
|
|
130
|
+
*
|
|
131
|
+
* Por CHAMADA e não por nome: a mesma variável pode ser lida com rede num lugar e sem rede em
|
|
132
|
+
* outro, e é a segunda que precisa de conserto. Contar as duas juntas ou descartar as duas
|
|
133
|
+
* juntas erra em direções opostas.
|
|
134
|
+
*/
|
|
135
|
+
if (m[2])
|
|
136
|
+
continue;
|
|
137
|
+
/** Declarada em JS - só não em CSS. Ver `SET_PROPERTY`. */
|
|
138
|
+
if (setInJs.has(name))
|
|
139
|
+
continue;
|
|
99
140
|
if (runtime && (RUNTIME_ANCHOR.has(name) || RUNTIME_PREFIX.test(name)))
|
|
100
141
|
continue;
|
|
101
142
|
const hit = seen.get(name) ?? {
|
package/dist/install-marks.js
CHANGED
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
* é sempre o bump deste PR - nunca o número que o `package.json` já carrega, porque alguém pode
|
|
129
129
|
* publicar no meio.
|
|
130
130
|
*/
|
|
131
|
-
export const MATERIALISER_SINCE = "0.16.
|
|
131
|
+
export const MATERIALISER_SINCE = "0.16.293";
|
|
132
132
|
/**
|
|
133
133
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
134
134
|
*
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** `--radius-lg: var(--ds-radius-lg);` - uma redefinição de utilitário do Tailwind pelo nosso token. */
|
|
2
|
+
const ALIGNMENT = /^([ \t]*)(--[a-zA-Z0-9-]+)(\s*:\s*)var\(\s*(--ds-[a-zA-Z0-9-]+)\s*\)\s*;[ \t]*\n?/gm;
|
|
3
|
+
export function onlyWhatMatched(css,
|
|
4
|
+
/** Os nossos tokens que casaram com um nome do código dele - ver `PointedAt.pairs`. */
|
|
5
|
+
matched) {
|
|
6
|
+
let kept = 0;
|
|
7
|
+
const dropped = [];
|
|
8
|
+
const out = css.replace(ALIGNMENT, (whole, _indent, tailwind, _sep, ours) => {
|
|
9
|
+
if (matched.has(ours)) {
|
|
10
|
+
kept += 1;
|
|
11
|
+
return whole;
|
|
12
|
+
}
|
|
13
|
+
dropped.push(tailwind);
|
|
14
|
+
return "";
|
|
15
|
+
});
|
|
16
|
+
return { css: out, kept, dropped: dropped.sort() };
|
|
17
|
+
}
|
package/package.json
CHANGED