synthesisui 0.16.222 → 0.16.227
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/doctor.js +58 -3
- package/dist/commands/sync.js +76 -3
- package/dist/doctor/root-size.js +101 -0
- package/dist/doctor/scan.js +1 -1
- package/dist/doctor/tokens.js +41 -3
- package/dist/install-marks.js +7 -1
- package/dist/skill-adapt.js +104 -37
- package/package.json +1 -1
package/dist/commands/doctor.js
CHANGED
|
@@ -11,6 +11,7 @@ import { findFrozenBindings } from "../doctor/frozen.js";
|
|
|
11
11
|
import { appendEvent, COVERAGE_RULE, readEvents, suggestionsFrom, summarize, } from "../doctor/ledger.js";
|
|
12
12
|
import { bindingsFromDocument, countComponents, findOverrides, } from "../doctor/overrides.js";
|
|
13
13
|
import { checkableName, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
|
|
14
|
+
import { DEFAULT_ROOT_PX, rootSizeOf, saidOfRoot, } from "../doctor/root-size.js";
|
|
14
15
|
import { diagnose, scanSource, siblingTokens, } from "../doctor/scan.js";
|
|
15
16
|
import { findSelfConflicts, forbiddenProps, isReset, propMatchesLabel, } from "../doctor/self-conflict.js";
|
|
16
17
|
import { buildTable, EMPTY_TABLE, nearestToken, } from "../doctor/tokens.js";
|
|
@@ -144,7 +145,20 @@ function requestLabel(r) {
|
|
|
144
145
|
return `rule "${r.name}"`;
|
|
145
146
|
return `component "${r.name}"`;
|
|
146
147
|
}
|
|
147
|
-
export async function loadSystem(root
|
|
148
|
+
export async function loadSystem(root,
|
|
149
|
+
/**
|
|
150
|
+
* A RAIZ JÁ MEDIDA, quando quem chama já varreu as folhas de estilo.
|
|
151
|
+
*
|
|
152
|
+
* O `doctor` mede (ele já anda no repositório inteiro) e passa. O `hook` NÃO mede, e isso é uma
|
|
153
|
+
* escolha declarada: ele roda depois de cada escrita com orçamento de ~50ms, e varrer css a cada
|
|
154
|
+
* edição trocaria um relatório instantâneo por um que a pessoa desliga.
|
|
155
|
+
*
|
|
156
|
+
* A consequência, dita em voz alta: num projeto que redefine a raiz (`html { font-size: 62.5% }`),
|
|
157
|
+
* o hook compara px e rem por 16 e deixa de sugerir algumas trocas que o `doctor` sugere. Ele erra
|
|
158
|
+
* para o lado de oferecer MENOS, nunca de oferecer a troca errada - e o `--fix`, que é quem escreve,
|
|
159
|
+
* vem sempre do `doctor`.
|
|
160
|
+
*/
|
|
161
|
+
measured) {
|
|
148
162
|
const dsDir = join(root, "_synthesisui", "ds");
|
|
149
163
|
let slugs;
|
|
150
164
|
try {
|
|
@@ -262,7 +276,12 @@ export async function loadSystem(root) {
|
|
|
262
276
|
}
|
|
263
277
|
}
|
|
264
278
|
return {
|
|
265
|
-
table: buildTable({
|
|
279
|
+
table: buildTable({
|
|
280
|
+
css,
|
|
281
|
+
lock,
|
|
282
|
+
source: adopted ? "adopted" : "installed",
|
|
283
|
+
...(measured ? { rootPx: measured.px, rootFrom: measured.from } : {}),
|
|
284
|
+
}),
|
|
266
285
|
recipes,
|
|
267
286
|
documents,
|
|
268
287
|
requires,
|
|
@@ -283,6 +302,19 @@ export async function loadSystem(root) {
|
|
|
283
302
|
* Reads stylesheets only, and only when nothing of ours is installed, so the
|
|
284
303
|
* common path pays nothing for it.
|
|
285
304
|
*/
|
|
305
|
+
/** As folhas de estilo dele, para a medição da raiz. Só css - nada de `.tsx`. */
|
|
306
|
+
async function sheetsIn(roots) {
|
|
307
|
+
const out = [];
|
|
308
|
+
for await (const file of walkAll(roots)) {
|
|
309
|
+
if (!/\.(css|scss|sass|less)$/i.test(file))
|
|
310
|
+
continue;
|
|
311
|
+
const css = await readFile(file, "utf8").catch(() => "");
|
|
312
|
+
/** Só carrega adiante o que pode conter a declaração - o resto é peso à toa. */
|
|
313
|
+
if (/(?:html|:root)[^{]*\{[^}]*font-size/i.test(css))
|
|
314
|
+
out.push({ file: relative(roots[0] ?? file, file) || file, css });
|
|
315
|
+
}
|
|
316
|
+
return out;
|
|
317
|
+
}
|
|
286
318
|
async function harvestOwnTokens(roots) {
|
|
287
319
|
let css = "";
|
|
288
320
|
for await (const file of walkAll(roots)) {
|
|
@@ -483,7 +515,18 @@ export async function doctor(opts) {
|
|
|
483
515
|
const fullRun = (opts.scopes ?? []).length === 0;
|
|
484
516
|
/** A intenção ordena o relatório, e a precedência é flag > config > default. */
|
|
485
517
|
const intent = intentOf(await readProjectConfig(root), opts.intent);
|
|
486
|
-
|
|
518
|
+
/**
|
|
519
|
+
* A RAIZ, MEDIDA ANTES DE COMPARAR VALOR NENHUM - ver `rootSizeOf`.
|
|
520
|
+
*
|
|
521
|
+
* `4px` e `0.25rem` só são o mesmo valor se alguém disser quantos pixels vale 1rem AQUI, e supor 16
|
|
522
|
+
* num projeto que escreve `html { font-size: 62.5% }` erraria em todos os lugares de uma vez, com a
|
|
523
|
+
* confiança de quem acertou. Uma passada barata: só folhas de estilo, e só o bloco `html`/`:root`.
|
|
524
|
+
*/
|
|
525
|
+
const rootSize = rootSizeOf(await sheetsIn(scopes.length > 0 ? scopes : [root]));
|
|
526
|
+
const installed = await loadSystem(root, {
|
|
527
|
+
px: rootSize.ambiguous ? DEFAULT_ROOT_PX : rootSize.px,
|
|
528
|
+
from: rootSize.from,
|
|
529
|
+
});
|
|
487
530
|
const { recipes, documents } = installed;
|
|
488
531
|
let table = installed.table;
|
|
489
532
|
// Nothing of ours here does not mean nothing to measure against. Fall back
|
|
@@ -614,6 +657,18 @@ export async function doctor(opts) {
|
|
|
614
657
|
const said = measured.from === "census" ? describeScope(measured) : null;
|
|
615
658
|
console.log(body(said ?? `scope: ${relScopes.join(", ")}`));
|
|
616
659
|
}
|
|
660
|
+
/**
|
|
661
|
+
* A RAIZ QUE ESTA RODADA USOU - e ela é impressa SEMPRE, inclusive quando é o padrão.
|
|
662
|
+
*
|
|
663
|
+
* Pedido do dono em 13/08: *"analisar antes se tem algo que interfere no size, e deixar registrado
|
|
664
|
+
* como uma regra que está sendo usada"*. É o que transforma uma suposição nossa num fato que ele
|
|
665
|
+
* pode conferir: `4px` e `0.25rem` só são o mesmo valor por causa deste número, e ele decide quantas
|
|
666
|
+
* trocas o comando oferece.
|
|
667
|
+
*
|
|
668
|
+
* Impressa mesmo no caso padrão porque é aí que ela é mais fácil de esquecer - e um relatório que só
|
|
669
|
+
* fala quando é exceção ensina que o silêncio significa "não olhei".
|
|
670
|
+
*/
|
|
671
|
+
console.log(body(saidOfRoot(rootSize)));
|
|
617
672
|
/**
|
|
618
673
|
* E A INTENÇÃO, em toda rodada, com a data e o jeito de inverter.
|
|
619
674
|
*
|
package/dist/commands/sync.js
CHANGED
|
@@ -16,11 +16,26 @@ import { resolveReadParts, siblingProjects, takeCensus } from "./import.js";
|
|
|
16
16
|
* after `closeRequest` runs here, so the person knows what happened and what
|
|
17
17
|
* (if anything) is theirs to do next.
|
|
18
18
|
*/
|
|
19
|
-
export function decisionLine(d, slug
|
|
19
|
+
export function decisionLine(d, slug,
|
|
20
|
+
/** Onde o sistema dele vive - o `publish` mora lá, e sem o endereço a frase manda procurar. */
|
|
21
|
+
base) {
|
|
20
22
|
const note = d.note ? ` - "${d.note}"` : "";
|
|
21
23
|
switch (d.status) {
|
|
24
|
+
/**
|
|
25
|
+
* AUTORIZAR ESCREVE O RASCUNHO, e esta linha dizia o contrário.
|
|
26
|
+
*
|
|
27
|
+
* `authorPersonalToken` termina em `writeDraft`, e o registry serve a última PUBLICADA - por lei,
|
|
28
|
+
* desde 29/07: se o rascunho fluísse para o repo, o botão Publish não seguraria nada.
|
|
29
|
+
*
|
|
30
|
+
* Então "Get it: upgrade" mandava a pessoa rodar um comando que responde `already at the latest
|
|
31
|
+
* version` e não traz o token. Ela seguiu a instrução, não recebeu nada, e fica sem saber se
|
|
32
|
+
* autorizou errado ou se a ferramenta falhou - que é o pior lugar para deixar alguém que acabou
|
|
33
|
+
* de fazer exatamente o que a gente pediu (dono, 13/08, vendo isso no terminal dele).
|
|
34
|
+
*
|
|
35
|
+
* A frase agora nomeia os DOIS passos, na ordem, e o primeiro é dele.
|
|
36
|
+
*/
|
|
22
37
|
case "authored":
|
|
23
|
-
return ` ✓ ${d.id} authored
|
|
38
|
+
return ` ✓ ${d.id} authored into your draft${note}. It reaches this repo once you publish${base ? `: ${base}/dashboard/mine/${slug}/publish` : ""}\n then: npx synthesisui@latest upgrade ${slug}`;
|
|
24
39
|
case "declined":
|
|
25
40
|
return ` ✕ ${d.id} declined${note}. Now a RULE of the system - it travels with the next upgrade, and agents obey it.`;
|
|
26
41
|
case "platform":
|
|
@@ -150,7 +165,7 @@ export async function sync(opts) {
|
|
|
150
165
|
console.log(body("Answered on the platform, closed here:"));
|
|
151
166
|
for (const d of decisions) {
|
|
152
167
|
await closeRequest(root, d.id);
|
|
153
|
-
console.log(body(decisionLine(d, slug)));
|
|
168
|
+
console.log(body(decisionLine(d, slug, base)));
|
|
154
169
|
}
|
|
155
170
|
}
|
|
156
171
|
console.log("");
|
|
@@ -276,6 +291,64 @@ export async function remeasure(args) {
|
|
|
276
291
|
if (stored.reading)
|
|
277
292
|
census.reading = stored.reading;
|
|
278
293
|
await resolveReadParts(census, root, !args.full).catch(() => { });
|
|
294
|
+
/**
|
|
295
|
+
* O QUE A RE-MEDIÇÃO NÃO MEDE, ELA NÃO PODE APAGAR - e apagava quatro campos de uma vez.
|
|
296
|
+
*
|
|
297
|
+
* O `sync` mede o CÓDIGO dele de novo. Ele não mede polaridade, não pergunta escopo e não refaz a
|
|
298
|
+
* leitura do agente: esses três são respostas que alguém já deu, e `runImport` é quem as grava.
|
|
299
|
+
* Escrever por cima com um censo que não os tem não é re-medir - é esquecer.
|
|
300
|
+
*
|
|
301
|
+
* O que isso custava, medido no censo do dono em 13/08 (`scope`, `usage`, `scheme` e `reading`
|
|
302
|
+
* ausentes depois de um sync):
|
|
303
|
+
*
|
|
304
|
+
* scheme `censusToPatch` lê `reading.themes.default ?? census.scheme ?? "light"`. Sem os
|
|
305
|
+
* dois primeiros ele cai em CLARO - e o sistema dele abre ESCURO. Uma
|
|
306
|
+
* re-interpretação a partir desse censo aterra a face errada, no servidor, sem
|
|
307
|
+
* ninguém tocar em nada
|
|
308
|
+
* scope o próprio comentário de `takeCensus` chama isso de veneno de ação lenta: a
|
|
309
|
+
* primeira re-medição parece perfeita e a segunda mede o repositório inteiro
|
|
310
|
+
* reading a leitura que o agente autorou, que é justamente o que o `sync` promete não mexer
|
|
311
|
+
*
|
|
312
|
+
* Complemento, nunca correção: o que a medição de hoje TEM continua ganhando. Isto só recoloca o
|
|
313
|
+
* que ela não tinha como saber.
|
|
314
|
+
*/
|
|
315
|
+
const before = await readFile(join(root, "_synthesisui", "census.json"), "utf8")
|
|
316
|
+
.then((raw) => JSON.parse(raw))
|
|
317
|
+
.catch(() => null);
|
|
318
|
+
const fresh = census;
|
|
319
|
+
/** `scope` e `usage` o `sync` SABE - ele acabou de resolvê-los. Os outros vêm do que já existia. */
|
|
320
|
+
if (scope)
|
|
321
|
+
fresh.scope = scope;
|
|
322
|
+
/**
|
|
323
|
+
* E A CONTRADIÇÃO GRITA, porque uma vez ela aconteceu e ninguém viu.
|
|
324
|
+
*
|
|
325
|
+
* Em 13/08 o censo do dono foi gravado SEM escopo tendo sido medido COM ele - 36 looks, a
|
|
326
|
+
* biblioteca, não os ~340 do repositório inteiro. Nenhum caminho de código explica: `takeCensus`
|
|
327
|
+
* emite o campo desde 06/08, o `sync` passa o rótulo desde 07/08, e a 0.16.222 publicada - a que
|
|
328
|
+
* carimbou o arquivo - emite quando testada. O arquivo foi sobrescrito antes de eu poder abri-lo.
|
|
329
|
+
*
|
|
330
|
+
* Uma causa que não se achou não está fechada. Então o que estava mudo vira barulho: gravar sem
|
|
331
|
+
* escopo enquanto o `.lock` tem um é o estado impossível, e ele passa a se anunciar em vez de
|
|
332
|
+
* envenenar a próxima re-medição em silêncio (`import.ts`: sem escopo, a medição seguinte lê o
|
|
333
|
+
* repositório inteiro e o rascunho curado de 37 vira 339).
|
|
334
|
+
*/
|
|
335
|
+
if (!fresh.scope && local.system)
|
|
336
|
+
console.log(body(`⚠ the census was written without a scope while "${local.system}" is the one recorded - the next re-measure would read the whole repo. Please report this: it is a state we have not reproduced.`));
|
|
337
|
+
if (usage.length > 0)
|
|
338
|
+
fresh.usage = usage;
|
|
339
|
+
for (const key of ["scheme", "reading"])
|
|
340
|
+
if (fresh[key] == null && before?.[key] != null)
|
|
341
|
+
fresh[key] = before[key];
|
|
342
|
+
/**
|
|
343
|
+
* E A POLARIDADE VEM DA PLATAFORMA quando nem a medição nem o arquivo a têm.
|
|
344
|
+
*
|
|
345
|
+
* Carregar do arquivo anterior conserta quem ainda não perdeu. Quem já perdeu - o censo do dono,
|
|
346
|
+
* medido em 13/08 - ficaria sem polaridade para sempre, porque o `sync` não a mede: ela é uma
|
|
347
|
+
* resposta dada no import. O documento guardado sabe (`meta.scheme`), então a rota devolve e isto
|
|
348
|
+
* recoloca. O próximo `sync` de quem estava furado repara o censo dele.
|
|
349
|
+
*/
|
|
350
|
+
if (fresh.scheme == null && stored.scheme)
|
|
351
|
+
fresh.scheme = stored.scheme;
|
|
279
352
|
/**
|
|
280
353
|
* O CENSO FRESCO TAMBÉM FICA NO DISCO.
|
|
281
354
|
*
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QUAL É A RAIZ DESTE PROJETO - medida antes de comparar valor nenhum.
|
|
3
|
+
*
|
|
4
|
+
* `4px` e `0.25rem` são o mesmo valor, e o comparador tratava os dois como textos diferentes. Medido
|
|
5
|
+
* no repo real em 13/08: 1320 literais em px que um token `--ds-*` já nomeia em rem, em 364 arquivos,
|
|
6
|
+
* contra 253 trocas que o doctor conseguia oferecer no repositório inteiro. A maior parte do trabalho
|
|
7
|
+
* fácil estava escondida atrás de uma comparação de string.
|
|
8
|
+
*
|
|
9
|
+
* A conversão exige uma raiz, e `1rem = 16px` é o padrão do navegador - mas é só o padrão. Um projeto
|
|
10
|
+
* que escreve `html { font-size: 62.5% }` tem raiz de 10px, e converter por 16 ali erraria em todos os
|
|
11
|
+
* lugares de uma vez, com a confiança de quem acertou.
|
|
12
|
+
*
|
|
13
|
+
* Então a raiz não é suposta: é MEDIDA no css dele, e DITA em voz alta no relatório (dono, 13/08:
|
|
14
|
+
* *"analisar antes se tem algo que interfere no size, e deixar registrado como uma regra que está
|
|
15
|
+
* sendo usada"*). Uma suposição escondida num comentário do nosso código não é lida por ninguém; um
|
|
16
|
+
* fato impresso no cabeçalho é conferível por quem conhece o projeto.
|
|
17
|
+
*
|
|
18
|
+
* E QUANDO NÃO DÁ PARA SABER, NÃO CONVERTE. Duas raízes diferentes, um `calc()`, um `var()`: a
|
|
19
|
+
* resposta é dizer que não sabe. Deixar de oferecer uma troca custa uma troca; oferecer a errada em
|
|
20
|
+
* 1320 lugares custa a confiança no comando.
|
|
21
|
+
*/
|
|
22
|
+
/** O padrão do navegador, e o que vale quando ninguém redefine. */
|
|
23
|
+
export const DEFAULT_ROOT_PX = 16;
|
|
24
|
+
/** `html`/`:root` com uma declaração de `font-size` dentro - o bloco inteiro, para ler o valor. */
|
|
25
|
+
const ROOT_BLOCK = /(?:^|[},;])\s*(html|:root)\s*(?:,[^{]*)?\{([^}]*)\}/gi;
|
|
26
|
+
const FONT_SIZE = /(?:^|;)\s*font-size\s*:\s*([^;}]+)/i;
|
|
27
|
+
/**
|
|
28
|
+
* O valor de uma declaração de raiz em pixels, ou `null` quando não dá para saber.
|
|
29
|
+
*
|
|
30
|
+
* `%` e `em` na RAIZ são relativos ao padrão do navegador, que é o único ancestral que ela tem - por
|
|
31
|
+
* isso `62.5%` é 10px e não uma incógnita. `rem` na raiz é a mesma coisa, e é como alguns projetos
|
|
32
|
+
* escrevem `1rem` só para deixar explícito.
|
|
33
|
+
*/
|
|
34
|
+
export function rootPxOf(raw) {
|
|
35
|
+
const v = raw.trim().toLowerCase();
|
|
36
|
+
const m = /^(\d*\.?\d+)(px|%|r?em)$/.exec(v);
|
|
37
|
+
if (!m)
|
|
38
|
+
return null;
|
|
39
|
+
const n = Number.parseFloat(m[1]);
|
|
40
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
41
|
+
return null;
|
|
42
|
+
if (m[2] === "px")
|
|
43
|
+
return n;
|
|
44
|
+
if (m[2] === "%")
|
|
45
|
+
return (n / 100) * DEFAULT_ROOT_PX;
|
|
46
|
+
return n * DEFAULT_ROOT_PX;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A raiz deste projeto, lida das folhas de estilo dele.
|
|
50
|
+
*
|
|
51
|
+
* Recebe os arquivos já lidos porque quem varre é o comando - esta função não sabe andar em disco, e
|
|
52
|
+
* é isso que a torna testável com um objeto em vez de um diretório temporário.
|
|
53
|
+
*/
|
|
54
|
+
export function rootSizeOf(sheets) {
|
|
55
|
+
/** Valor em px → onde ele foi declarado pela primeira vez. */
|
|
56
|
+
const seen = new Map();
|
|
57
|
+
const unreadable = [];
|
|
58
|
+
for (const sheet of sheets) {
|
|
59
|
+
ROOT_BLOCK.lastIndex = 0;
|
|
60
|
+
for (const block of sheet.css.matchAll(ROOT_BLOCK)) {
|
|
61
|
+
const decl = FONT_SIZE.exec(block[2]);
|
|
62
|
+
if (!decl)
|
|
63
|
+
continue;
|
|
64
|
+
const px = rootPxOf(decl[1]);
|
|
65
|
+
if (px == null) {
|
|
66
|
+
unreadable.push(`${decl[1].trim()} (${sheet.file})`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!seen.has(px))
|
|
70
|
+
seen.set(px, `${block[1]}, ${sheet.file}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* UM VALOR ILEGÍVEL SÓ CONTAMINA SE FOR O ÚNICO SINAL - ou se discordar do que foi lido.
|
|
75
|
+
*
|
|
76
|
+
* Um `font-size: var(--app-root)` ao lado de um `10px` declarado não torna o projeto ambíguo: o
|
|
77
|
+
* que se sabe continua sabido. O que torna é não haver resposta, ou haver duas.
|
|
78
|
+
*/
|
|
79
|
+
if (seen.size > 1)
|
|
80
|
+
return {
|
|
81
|
+
px: DEFAULT_ROOT_PX,
|
|
82
|
+
from: null,
|
|
83
|
+
ambiguous: {
|
|
84
|
+
saw: [...seen.entries()].map(([px, at]) => `${px}px (${at})`),
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
if (seen.size === 0 && unreadable.length > 0)
|
|
88
|
+
return { px: DEFAULT_ROOT_PX, from: null, ambiguous: { saw: unreadable } };
|
|
89
|
+
const only = [...seen.entries()][0];
|
|
90
|
+
return only
|
|
91
|
+
? { px: only[0], from: only[1] }
|
|
92
|
+
: { px: DEFAULT_ROOT_PX, from: null };
|
|
93
|
+
}
|
|
94
|
+
/** A linha do relatório - o fato, e de onde ele veio. Nunca um número pelado. */
|
|
95
|
+
export function saidOfRoot(root) {
|
|
96
|
+
if (root.ambiguous)
|
|
97
|
+
return `root font-size: not one answer (${root.ambiguous.saw.slice(0, 3).join(" · ")}) - px and rem are not compared here`;
|
|
98
|
+
return root.from
|
|
99
|
+
? `root font-size: ${root.px}px (${root.from}) - px and rem are compared against this`
|
|
100
|
+
: `root font-size: ${DEFAULT_ROOT_PX}px (browser default - nothing here redefines it)`;
|
|
101
|
+
}
|
package/dist/doctor/scan.js
CHANGED
|
@@ -316,7 +316,7 @@ function scanCore(file, source, table) {
|
|
|
316
316
|
* e é por VALOR, não por nome: `--color-ocean-500` e `--ds-color-ocean-500` só são a mesma
|
|
317
317
|
* decisão porque os dois seguram `#059aed`.
|
|
318
318
|
*/
|
|
319
|
-
...(table.byValue.has(normalizeValue(value))
|
|
319
|
+
...(table.byValue.has(normalizeValue(value, table.rootPx))
|
|
320
320
|
? { mirrored: true }
|
|
321
321
|
: null),
|
|
322
322
|
});
|
package/dist/doctor/tokens.js
CHANGED
|
@@ -11,6 +11,17 @@
|
|
|
11
11
|
* Pure and dependency-free on purpose: every function here takes text and
|
|
12
12
|
* returns data, so the whole diagnosis is testable without a filesystem.
|
|
13
13
|
*/
|
|
14
|
+
/**
|
|
15
|
+
* Where the vocabulary being measured against came from.
|
|
16
|
+
*
|
|
17
|
+
* `"installed"` is a system we wrote. `"yours"` is the project's OWN custom
|
|
18
|
+
* properties, harvested from its stylesheets - the case that matters most,
|
|
19
|
+
* because the people who feel this problem hardest already have a design
|
|
20
|
+
* system and had no reason to adopt ours before seeing a number.
|
|
21
|
+
*/
|
|
22
|
+
/** `"adopted"` is theirs too, but described by `adopt` and therefore NAMED -
|
|
23
|
+
* it must not be offered a system to install, having just adopted one. */
|
|
24
|
+
import { DEFAULT_ROOT_PX } from "./root-size.js";
|
|
14
25
|
export const EMPTY_TABLE = {
|
|
15
26
|
source: null,
|
|
16
27
|
name: null,
|
|
@@ -18,6 +29,8 @@ export const EMPTY_TABLE = {
|
|
|
18
29
|
version: null,
|
|
19
30
|
byName: new Map(),
|
|
20
31
|
byValue: new Map(),
|
|
32
|
+
rootPx: DEFAULT_ROOT_PX,
|
|
33
|
+
rootFrom: null,
|
|
21
34
|
declared: new Set(),
|
|
22
35
|
keyframes: new Set(),
|
|
23
36
|
};
|
|
@@ -90,8 +103,30 @@ function args(body) {
|
|
|
90
103
|
*
|
|
91
104
|
* Anything that is not a colour passes through lowercased and collapsed.
|
|
92
105
|
*/
|
|
93
|
-
export function normalizeValue(raw) {
|
|
106
|
+
export function normalizeValue(raw, rootPx) {
|
|
94
107
|
const v = raw.trim().toLowerCase().replace(/\s+/g, " ");
|
|
108
|
+
/**
|
|
109
|
+
* COMPRIMENTO CAI EM PIXELS, pelo mesmo motivo que tempo cai em milissegundos logo abaixo.
|
|
110
|
+
*
|
|
111
|
+
* `--ds-radius-xs: 0.25rem` e um `borderRadius: "4px"` no código dele são o MESMO valor, e o
|
|
112
|
+
* comparador os tratava como textos diferentes. Medido no repo real (13/08): 1320 literais em px
|
|
113
|
+
* que um token já nomeia em rem, contra 253 trocas que o comando conseguia oferecer no repositório
|
|
114
|
+
* inteiro.
|
|
115
|
+
*
|
|
116
|
+
* `rootPx` é obrigatório de propósito. O padrão do navegador é 16, mas um projeto que escreve
|
|
117
|
+
* `html { font-size: 62.5% }` tem raiz de 10 - e converter por 16 ali erra em todos os lugares de
|
|
118
|
+
* uma vez. Quem chama tem que dizer qual raiz mediu; ver `rootSizeOf`.
|
|
119
|
+
*
|
|
120
|
+
* A FAMÍLIA CONTINUA MANDANDO: `4px` passa a casar com `--ds-radius-xs` E com `--ds-spacing-3xs`,
|
|
121
|
+
* e é `tokenMatch` quem escolhe pelo `kind` - um raio não vira espaçamento dentro de um `gap`.
|
|
122
|
+
*/
|
|
123
|
+
const len = /^(-?\d*\.?\d+)(px|rem)$/.exec(v);
|
|
124
|
+
if (len) {
|
|
125
|
+
const n = Number.parseFloat(len[1]);
|
|
126
|
+
const px = len[2] === "rem" ? n * rootPx : n;
|
|
127
|
+
/** Arredonda o rastro binário: `0.35rem * 16` é 5.6000000000000005. */
|
|
128
|
+
return `${Math.round(px * 1e4) / 1e4}px`;
|
|
129
|
+
}
|
|
95
130
|
// Time lands on milliseconds: `.3s`, `0.3s` and `300ms` are one value, the
|
|
96
131
|
// same way every colour lands on 8-digit hex. Without this, a system that
|
|
97
132
|
// authors `--ds-motion-durations-base: 0.3s` never names an author's `300ms`.
|
|
@@ -353,6 +388,7 @@ export function parseRootTokens(css) {
|
|
|
353
388
|
}
|
|
354
389
|
export function buildTable(input) {
|
|
355
390
|
const source = input.source ?? "installed";
|
|
391
|
+
const rootPx = input.rootPx ?? DEFAULT_ROOT_PX;
|
|
356
392
|
const byName = source === "installed"
|
|
357
393
|
? parseTokens(input.css)
|
|
358
394
|
: parseRootTokens(input.css);
|
|
@@ -377,7 +413,7 @@ export function buildTable(input) {
|
|
|
377
413
|
const isAlias = (name) => /^var\(/.test((raw.get(name) ?? "").trim());
|
|
378
414
|
const byValue = new Map();
|
|
379
415
|
for (const [name, value] of resolved) {
|
|
380
|
-
const key = normalizeValue(value);
|
|
416
|
+
const key = normalizeValue(value, rootPx);
|
|
381
417
|
const list = byValue.get(key);
|
|
382
418
|
if (!list)
|
|
383
419
|
byValue.set(key, [name]);
|
|
@@ -393,6 +429,8 @@ export function buildTable(input) {
|
|
|
393
429
|
version: input.lock?.version ?? null,
|
|
394
430
|
byName,
|
|
395
431
|
byValue,
|
|
432
|
+
rootPx,
|
|
433
|
+
rootFrom: input.rootFrom ?? null,
|
|
396
434
|
declared: parseDeclaredNames(input.css),
|
|
397
435
|
keyframes: new Set([...input.css.matchAll(/@keyframes\s+([a-zA-Z0-9_-]+)/g)].map((m) => m[1])),
|
|
398
436
|
};
|
|
@@ -480,7 +518,7 @@ const FAMILY = {
|
|
|
480
518
|
* `family`.
|
|
481
519
|
*/
|
|
482
520
|
export function tokenMatch(table, literal, kind) {
|
|
483
|
-
const hit = table.byValue.get(normalizeValue(literal));
|
|
521
|
+
const hit = table.byValue.get(normalizeValue(literal, table.rootPx));
|
|
484
522
|
if (!hit || hit.length === 0)
|
|
485
523
|
return null;
|
|
486
524
|
const prefix = kind ? FAMILY[kind] : undefined;
|
package/dist/install-marks.js
CHANGED
|
@@ -90,7 +90,13 @@ export const MATERIALISER_SINCE = "0.16.220";
|
|
|
90
90
|
* agente dele que o fundo do `card` deveria apontar para `foreground` - o papel do TEXTO -, e é
|
|
91
91
|
* exatamente uma verificação que o pinado faz diferente.
|
|
92
92
|
*/
|
|
93
|
-
|
|
93
|
+
/**
|
|
94
|
+
* 0.16.219 -> 0.16.223 em 13/08: `px` e `rem` viraram o mesmo valor na comparação. O hook roda a
|
|
95
|
+
* mesma varredura depois de cada escrita, e um pin anterior lê o mesmo arquivo e diz "o sistema não
|
|
96
|
+
* tem nome para isto" sobre um valor que o sistema dela nomeia - medido no repo real, 243 trocas
|
|
97
|
+
* viraram 764 sobre os mesmos 4433 valores à mão.
|
|
98
|
+
*/
|
|
99
|
+
export const CHECKER_SINCE = "0.16.223";
|
|
94
100
|
/**
|
|
95
101
|
* A ÚLTIMA VERSÃO EM QUE OS LEITORES PASSARAM A PRODUZIR UM CENSO DIFERENTE.
|
|
96
102
|
*
|
package/dist/skill-adapt.js
CHANGED
|
@@ -20,6 +20,11 @@ description: Confronta UM componente (ou uma página, ou uma pasta) contra o des
|
|
|
20
20
|
|
|
21
21
|
# Adaptar uma peça ao sistema
|
|
22
22
|
|
|
23
|
+
**Seu primeiro comando é a medição.** Não leia \`.mcp.json\`, não abra o censo, não liste
|
|
24
|
+
arquivo: \`check_file\` ou \`doctor <alvo>\` responde tudo isso em um passo, e é o que o resto
|
|
25
|
+
desta skill consome. Um agente que sai explorando antes gasta a paciência de quem pediu e
|
|
26
|
+
chega ao mesmo lugar.
|
|
27
|
+
|
|
23
28
|
O propósito do produto, que decide todo empate abaixo: **ler o repositório do cliente e
|
|
24
29
|
devolver receitas com paridade visual, semântica e funcional, sem supor e sem inventar
|
|
25
30
|
nada.**
|
|
@@ -61,6 +66,13 @@ ao lado é justamente onde os valores à mão se escondem.
|
|
|
61
66
|
|
|
62
67
|
Nunca meça os dois e escolha o maior. Diga o que mediu.
|
|
63
68
|
|
|
69
|
+
**O alvo fora da pasta importada é o caso COMUM, e não um erro.** O sistema nasceu de uma
|
|
70
|
+
pasta (o \`scope\` no \`.lock\`), e uma tela de app que consome o DS está fora dela - é
|
|
71
|
+
literalmente o pedido *"conserta essa página pro meu design system"*. Ali a cobertura de
|
|
72
|
+
token vale igual, porque a camada de token é global; o que não existe é receita daquele
|
|
73
|
+
componente. Diga isso em uma linha e siga - e quando faltar uma peça, o destino é
|
|
74
|
+
\`request component\`.
|
|
75
|
+
|
|
64
76
|
## 2. MEÇA - e a medida é determinística, não sua
|
|
65
77
|
|
|
66
78
|
Duas portas, mesma resposta. Use a que a sessão tiver:
|
|
@@ -91,32 +103,56 @@ no name for it o sistema não nomeia -> DECISÃO dele, é o passo 5
|
|
|
91
103
|
|
|
92
104
|
Não recalcule nada disso de cabeça. O número que você reporta é o que o comando disse.
|
|
93
105
|
|
|
94
|
-
## 3.
|
|
106
|
+
## 3. MONTE A FILA, E CONTE OS ITENS ANTES DE COMEÇAR
|
|
95
107
|
|
|
96
|
-
|
|
108
|
+
Aqui é onde esta skill se ganha ou se perde. Despejar tudo de uma vez - duas regras, quatro
|
|
109
|
+
decisões, dez trocas - não é um relatório, é uma parede. Quem lê não tem como agir; só
|
|
110
|
+
concordar ou fechar a aba.
|
|
111
|
+
|
|
112
|
+
Junte tudo o que você achou (o mecânico do passo 2, as regras do passo 4, o que sobrou do
|
|
113
|
+
passo 5) numa fila ÚNICA, e **ordene por custo**:
|
|
97
114
|
|
|
98
115
|
\`\`\`
|
|
99
|
-
|
|
116
|
+
1º não muda um pixel troca por token de mesmo valor
|
|
117
|
+
2º muda o pixel colapsar um passo, adotar um token semântico
|
|
118
|
+
3º não é troca, é pedido token novo, peça nova, regra nova
|
|
100
119
|
\`\`\`
|
|
101
120
|
|
|
121
|
+
Assim ele despacha o barato primeiro e para quando quiser, sem ficar devendo nada.
|
|
122
|
+
|
|
123
|
+
Anuncie o tamanho antes do primeiro item, sempre:
|
|
124
|
+
|
|
102
125
|
\`\`\`
|
|
103
|
-
|
|
104
|
-
var(--ds-color-ocean-50) · 1 time
|
|
105
|
-
1 finding left: values your system has no name for. Those are decisions.
|
|
126
|
+
7 itens nesta fila: 2 sem mudar pixel, 3 que mudam, 2 pedidos.
|
|
106
127
|
\`\`\`
|
|
107
128
|
|
|
108
|
-
|
|
129
|
+
## 4. UM ITEM POR VEZ, COM OPÇÕES - e espere a resposta
|
|
130
|
+
|
|
131
|
+
Nunca apresente o item 2 antes de ele responder o 1. O cabeçalho carrega a posição, para
|
|
132
|
+
ele saber onde está e quanto falta:
|
|
109
133
|
|
|
110
134
|
\`\`\`
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
135
|
+
item 1 de 7 · radius 4px · 2 lugares · não muda um pixel
|
|
136
|
+
|
|
137
|
+
DeliveredBox/index.tsx:76 borderRadius: "4px" -> var(--ds-radius-xs)
|
|
138
|
+
TotalSentBox/index.tsx:71 borderRadius: "4px" -> var(--ds-radius-xs)
|
|
139
|
+
|
|
140
|
+
[aplicar] [pular] [ver o diff] [parar por aqui]
|
|
114
141
|
\`\`\`
|
|
115
142
|
|
|
116
|
-
|
|
117
|
-
que o conserto pode ser mecânico.
|
|
143
|
+
Quatro opções, e nenhuma a mais:
|
|
118
144
|
|
|
119
|
-
|
|
145
|
+
\`\`\`
|
|
146
|
+
aplicar você roda o comando ou faz a edição, e confirma em uma linha
|
|
147
|
+
pular segue para o próximo, e ele entra no resumo como não mexido
|
|
148
|
+
ver o diff mostre e volte a perguntar - não conte isso como resposta
|
|
149
|
+
parar por aqui fecha o resumo com o que andou até aqui. É sempre legítimo
|
|
150
|
+
\`\`\`
|
|
151
|
+
|
|
152
|
+
Para um item mecânico o "aplicar" é \`npx synthesisui doctor <alvo> --fix --write\`. Para os
|
|
153
|
+
outros é edição, e você mostra exatamente as linhas antes.
|
|
154
|
+
|
|
155
|
+
## 5. AS REGRAS, QUE É A METADE QUE NENHUM COMANDO FAZ
|
|
120
156
|
|
|
121
157
|
O passo 2 é determinístico e sai de graça. Este não: o sistema carrega uma doutrina em
|
|
122
158
|
prosa, e **nada a verifica mecanicamente**. É aqui que você trabalha.
|
|
@@ -130,53 +166,84 @@ Leia as regras e confronte o componente com cada uma. Três respostas possíveis
|
|
|
130
166
|
e a terceira é a que interessa:
|
|
131
167
|
|
|
132
168
|
\`\`\`
|
|
133
|
-
cumpre
|
|
134
|
-
NÃO cumpre
|
|
135
|
-
a regra não fala sobre isto
|
|
169
|
+
cumpre some do relatório. Diga só o total no fim
|
|
170
|
+
NÃO cumpre vira um ITEM da fila, com arquivo, linha e a troca proposta
|
|
171
|
+
a regra não fala sobre isto vira um item de PEDIDO - \`request rule\`
|
|
136
172
|
\`\`\`
|
|
137
173
|
|
|
138
174
|
Uma regra que você teve que interpretar para aplicar não é "cumpre". É o terceiro caso.
|
|
139
175
|
|
|
140
|
-
|
|
176
|
+
E nunca escreva "3 de 5 cumpridas". Isso lê como boletim, e as 2 que faltam são justamente
|
|
177
|
+
as que TÊM conserto pronto - é a melhor notícia do relatório vestida como a pior.
|
|
141
178
|
|
|
142
|
-
|
|
143
|
-
|
|
179
|
+
## 6. OS PEDIDOS - você mostra o comando, ele roda
|
|
180
|
+
|
|
181
|
+
Três destinos, e todos existem:
|
|
144
182
|
|
|
145
183
|
\`\`\`
|
|
146
184
|
valor sem nome no sistema
|
|
147
185
|
-> npx synthesisui request token --value "<valor>" --name "<como se chamaria>" --for "<o caso>"
|
|
148
|
-
ou, pelo MCP: request_token
|
|
149
|
-
|
|
150
186
|
peça que falta
|
|
151
187
|
-> npx synthesisui request component --name "<nome>" --for "<o caso>"
|
|
152
|
-
ou: request_component
|
|
153
|
-
|
|
154
188
|
a doutrina não cobre este caso
|
|
155
189
|
-> npx synthesisui request rule --name "<o que a regra diria>" --for "<o caso que pediu>"
|
|
156
|
-
ou: request_rule
|
|
157
190
|
\`\`\`
|
|
158
191
|
|
|
159
|
-
**
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
tirar dele a chance de dizer "não, isso fica local mesmo".
|
|
192
|
+
**Arquivar em nome dele seria decidir por ele** - e tirar dele a chance de dizer "não, isso
|
|
193
|
+
fica local mesmo". Antes de propor \`request rule\`, leia a doutrina inteira: uma regra que já
|
|
194
|
+
existe e você não achou vira duplicata na fila, e a fila perde valor na terceira.
|
|
163
195
|
|
|
164
|
-
|
|
165
|
-
achou vira uma regra duplicada na fila, e a fila perde valor na terceira duplicata.
|
|
196
|
+
## 6b. A RÉGUA É O ALCANÇÁVEL, E O TETO SE DIZ JUNTO
|
|
166
197
|
|
|
167
|
-
|
|
198
|
+
**100% quase nunca é alcançável hoje, e isso não é falha dele.** Se o sistema não tem nome
|
|
199
|
+
para \`#555\`, ninguém chega a 100% sem antes decidir criar esse nome. Um medidor que mostra
|
|
200
|
+
0% contra um teto imaginário faz trabalho completo parecer trabalho pela metade.
|
|
168
201
|
|
|
169
|
-
|
|
202
|
+
Os números para a conta certa já vêm do comando:
|
|
203
|
+
|
|
204
|
+
\`\`\`
|
|
205
|
+
3 valores à mão
|
|
206
|
+
2 o sistema já nomeia -> alcançável hoje: 67%
|
|
207
|
+
1 o sistema não nomeia -> precisa de uma decisão dele
|
|
208
|
+
\`\`\`
|
|
209
|
+
|
|
210
|
+
Então o teto de hoje é 67%, e aplicar as duas trocas é **chegar no teto** - 100% do que dá
|
|
211
|
+
para fazer com o vocabulário que existe.
|
|
212
|
+
|
|
213
|
+
## 6c. FECHE PELO QUE ANDOU, E MOSTRE O CAMINHO ATÉ 100%
|
|
170
214
|
|
|
171
215
|
\`\`\`
|
|
172
216
|
<Componente> <n> arquivos
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
217
|
+
|
|
218
|
+
alcançável hoje 67% é o que o vocabulário do sistema cobre
|
|
219
|
+
aplicado 67% ✓ no teto - nada mecânico ficou para trás
|
|
220
|
+
pulado 8 espaçamentos que mudam layout (item 5, quando quiser)
|
|
221
|
+
na sua fila 1 #555, que o sistema ainda não nomeia
|
|
176
222
|
\`\`\`
|
|
177
223
|
|
|
178
|
-
Se nada
|
|
179
|
-
|
|
224
|
+
Nunca "0% -> 0%". Se nada era mecânico, o teto era zero, e a frase é *"não havia nada que
|
|
225
|
+
o vocabulário de hoje resolvesse - o que existe são N decisões suas"*.
|
|
226
|
+
|
|
227
|
+
E quando sobrou pedido, termine com o caminho, porque ele é de dois passos e o primeiro é
|
|
228
|
+
dele:
|
|
229
|
+
|
|
230
|
+
\`\`\`
|
|
231
|
+
para chegar a 100%, faltam dois passos:
|
|
232
|
+
1. autorize o pedido no dashboard (ele já está na fila; \`sync\` o levou)
|
|
233
|
+
2. publique, e rode \`npx synthesisui upgrade\` aqui
|
|
234
|
+
depois disso, /sui-adapt neste componente fecha em 100%
|
|
235
|
+
\`\`\`
|
|
236
|
+
|
|
237
|
+
Autorizar escreve o RASCUNHO, e o repo recebe a última PUBLICADA - por isso os dois passos,
|
|
238
|
+
e por isso o \`sync\` também diz isso quando a decisão volta. Prometer que \`upgrade\` sozinho
|
|
239
|
+
traz o token é mandar a pessoa rodar um comando que responde "already at the latest version".
|
|
240
|
+
|
|
241
|
+
Se a fila esvaziou, o fim é uma linha só:
|
|
242
|
+
|
|
243
|
+
\`\`\`
|
|
244
|
+
alcançável hoje 100%
|
|
245
|
+
aplicado 100% ✓ este componente está inteiro no sistema
|
|
246
|
+
\`\`\`
|
|
180
247
|
|
|
181
248
|
## 7. ROTINA
|
|
182
249
|
|
package/package.json
CHANGED