synthesisui 0.16.412 → 0.16.413
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/component.js +11 -0
- package/dist/commands/doctor.js +7 -0
- package/dist/commands/hook.js +57 -4
- package/dist/commands/template.js +117 -30
- package/dist/doctor/ci-format.js +7 -5
- package/dist/install-marks.js +31 -1
- package/dist/sheet-needed.js +89 -2
- package/package.json +1 -1
|
@@ -536,6 +536,17 @@ export async function component(slug, name, opts) {
|
|
|
536
536
|
(await installedThemeCss(root, slug)),
|
|
537
537
|
/** O vocabulário DELE sai da conta - ver `theirNames`. O mapa do `.lock` é a via mais barata. */
|
|
538
538
|
theirNames: tongue?.names ? [...tongue.names.values()] : [],
|
|
539
|
+
/**
|
|
540
|
+
* A TERCEIRA FONTE NÃO É MEDIDA AQUI, e o vazio é a resposta EXPLÍCITA disso.
|
|
541
|
+
*
|
|
542
|
+
* As classes de receita que a folha declara entrariam nesta conta se esta linha entregasse o
|
|
543
|
+
* `tokens.css` instalado - e o efeito está medido em `steps/11-fora-do-escopo.md`: este comando
|
|
544
|
+
* diz *"This file needs nothing else… It is yours."* sobre um componente cujo estilo inteiro vem
|
|
545
|
+
* de `.ds-button`. Ligar a medição aqui é mudar o que este comando promete, com o bloco de setup
|
|
546
|
+
* dele para redesenhar: é etapa própria. O que o argumento obrigatório garante é que a omissão
|
|
547
|
+
* está ESCRITA, e não escondida num parâmetro que ninguém passou.
|
|
548
|
+
*/
|
|
549
|
+
sheetCss: "",
|
|
539
550
|
});
|
|
540
551
|
if (!need.needed) {
|
|
541
552
|
console.log(section("This file needs nothing else"));
|
package/dist/commands/doctor.js
CHANGED
|
@@ -916,6 +916,13 @@ export async function doctor(opts) {
|
|
|
916
916
|
source: scannedSource,
|
|
917
917
|
themeCss,
|
|
918
918
|
theirNames: installed.theirs.byName.keys(),
|
|
919
|
+
/**
|
|
920
|
+
* A TERCEIRA FONTE NÃO É MEDIDA AQUI - ver o mesmo comentário em `component.ts` e o número em
|
|
921
|
+
* `steps/11-fora-do-escopo.md`. Este comando decide `unwired` e `blocked` por `needed`, então
|
|
922
|
+
* ligar a medição muda o veredito do `doctor` num repositório real: é etapa própria, e o vazio
|
|
923
|
+
* deixa a omissão escrita em vez de escondida.
|
|
924
|
+
*/
|
|
925
|
+
sheetCss: "",
|
|
919
926
|
});
|
|
920
927
|
/**
|
|
921
928
|
* BLOQUEADO SÓ QUANDO A FOLHA É NECESSÁRIA - a mesma pergunta que decide `unwired`.
|
package/dist/commands/hook.js
CHANGED
|
@@ -57,6 +57,20 @@ async function report(root, filePath) {
|
|
|
57
57
|
const rel = relative(root, filePath);
|
|
58
58
|
const d = diagnose([scanSource(rel, src, table)]);
|
|
59
59
|
const named = d.findings.filter((f) => nameToWrite(f));
|
|
60
|
+
/**
|
|
61
|
+
* O VALOR QUE NADA NOMEIA - dito, e nunca batizado (A1 da etapa 11, escolha dele em 10/09).
|
|
62
|
+
*
|
|
63
|
+
* ERA SILÊNCIO, e o silêncio era a única resposta errada disponível. O comentário que estava aqui
|
|
64
|
+
* dizia que a deriva sem nome "não vale interromper, porque o único conselho honesto é pergunte a
|
|
65
|
+
* uma pessoa" - e a conclusão não segue da premissa: quem lê um relatório que só fala de valores
|
|
66
|
+
* nomeáveis não tem como distinguir "não sobrou nada" de "sobrou, e nós engolimos".
|
|
67
|
+
*
|
|
68
|
+
* ENTÃO ELE DIZ O VALOR E A LINHA, E PARA AÍ. Nenhum nome nosso é proposto: `--ds-<algo>`
|
|
69
|
+
* inventado aqui entraria no código dele sem uma pessoa ter decidido, e este comando roda depois
|
|
70
|
+
* de CADA escrita do agente - é o pior lugar do produto para inventar vocabulário. A seção 0 da
|
|
71
|
+
* jornada é literal: sem nome no sistema DELE, o valor fica onde está.
|
|
72
|
+
*/
|
|
73
|
+
const unnamed = d.findings.filter((f) => !nameToWrite(f));
|
|
60
74
|
const phantoms = d.files.flatMap((f) => f.phantoms ?? []);
|
|
61
75
|
// Every check leaves one line in the ledger - INCLUDING the clean ones,
|
|
62
76
|
// because a fix is itself a write, so the clean re-check of a file that was
|
|
@@ -70,21 +84,60 @@ async function report(root, filePath) {
|
|
|
70
84
|
named: named.length,
|
|
71
85
|
phantoms: phantoms.length,
|
|
72
86
|
});
|
|
73
|
-
|
|
74
|
-
// no token to move to, so the only honest advice is "ask a person" - and
|
|
75
|
-
// saying that after every edit trains the reader to skip the block.
|
|
76
|
-
if (named.length === 0 && phantoms.length === 0)
|
|
87
|
+
if (named.length === 0 && phantoms.length === 0 && unnamed.length === 0)
|
|
77
88
|
return greet(root, rel);
|
|
78
89
|
// A report IS the evidence the greeting exists to provide, so it counts as the
|
|
79
90
|
// introduction. Otherwise a project whose first file had drift would get the
|
|
80
91
|
// "it is live" sentence afterwards, telling somebody who just watched it work.
|
|
81
92
|
await markGreeted(root);
|
|
93
|
+
/**
|
|
94
|
+
* QUANDO NÃO HÁ NADA A FAZER NAQUELE ARQUIVO, O RELATÓRIO É DE DUAS LINHAS - e isto é o que
|
|
95
|
+
* mantém o comando instalado.
|
|
96
|
+
*
|
|
97
|
+
* MEDIDO pela revisão de DX no fecho da etapa 11, rodando o scanner deste binário sobre os 38
|
|
98
|
+
* arquivos de uma biblioteca real com o vocabulário mais generoso possível (as 94 custom
|
|
99
|
+
* properties que o próprio repositório declara): **20 arquivos (53%) disparam pelo menos um
|
|
100
|
+
* achado sem nome, e 18 desses 20 têm ZERO achado nomeado** - o relatório inteiro seria o bloco
|
|
101
|
+
* novo. O cabeçalho deste arquivo é explícito sobre o risco: *"a hook that speaks on every edit
|
|
102
|
+
* is noise, and noise is what gets a hook removed"*.
|
|
103
|
+
*
|
|
104
|
+
* Então a MESMA informação sai em duas formas. Sozinha, ela é uma frase - o valor, a linha, e a
|
|
105
|
+
* ação, que é não agir. Ao lado de algo acionável, ela é um bloco, porque aí o contraste com a
|
|
106
|
+
* seta do bloco de cima é o que ensina a diferença entre os dois conjuntos.
|
|
107
|
+
*
|
|
108
|
+
* O que NÃO está resolvido, e é decisão dele: a REPETIÇÃO no tempo. Um valor cuja resposta certa
|
|
109
|
+
* é "deixe como está" volta a ser dito na próxima escrita naquele arquivo, porque não existe
|
|
110
|
+
* memória por linha e criar uma é comportamento novo. Está medido em `steps/11-fora-do-escopo.md`.
|
|
111
|
+
*/
|
|
112
|
+
const sample = unnamed
|
|
113
|
+
.slice(0, 3)
|
|
114
|
+
.map((f) => `line ${f.line} ${f.literal}`)
|
|
115
|
+
.join(", ");
|
|
116
|
+
if (named.length === 0 && phantoms.length === 0)
|
|
117
|
+
return [
|
|
118
|
+
`${rel} - ${unnamed.length} value${unnamed.length === 1 ? "" : "s"} here ${unnamed.length === 1 ? "has" : "have"} no name in this system, and nothing to replace ${unnamed.length === 1 ? "it" : "them"} with: ${sample}${unnamed.length > 3 ? `, +${unnamed.length - 3} more` : ""}.`,
|
|
119
|
+
"Do not invent a name - leave them, or ask the person what they would call it.",
|
|
120
|
+
].join("\n");
|
|
82
121
|
const lines = [`${rel} - checked against ${table.name ?? table.slug}.`];
|
|
83
122
|
if (named.length > 0) {
|
|
84
123
|
lines.push("", "Values written by hand that this system already has a name for:", ...named
|
|
85
124
|
.slice(0, 20)
|
|
86
125
|
.map((f) => ` line ${f.line} ${f.literal} → ${nameToWrite(f)}`), "", "Replace them now, while you still have this file in mind.");
|
|
87
126
|
}
|
|
127
|
+
if (unnamed.length > 0) {
|
|
128
|
+
/**
|
|
129
|
+
* O TÍTULO CARREGA A PROIBIÇÃO, e não a terceira frase de um parágrafo - achado da revisão de
|
|
130
|
+
* DX: a lista tinha a MESMA forma da lista acionável de cima (`Values written by hand that…`,
|
|
131
|
+
* uma linha por achado), e um agente que acabou de aprender *"lista de valores → proponha o
|
|
132
|
+
* token"* recebia aqui uma lista igual com a única frase que interrompe esse reflexo escondida
|
|
133
|
+
* no fim.
|
|
134
|
+
*/
|
|
135
|
+
lines.push("", "Not yet named - do NOT invent a name for these:", ...unnamed.slice(0, 20).map((f) => ` line ${f.line} ${f.literal}`),
|
|
136
|
+
/** O QUE FOI CORTADO É DITO - medido num `.css` real: 20 de 252, e as 232 sumiam caladas. */
|
|
137
|
+
...(unnamed.length > 20
|
|
138
|
+
? [` … and ${unnamed.length - 20} more in this file`]
|
|
139
|
+
: []), "", "This system declares no name for them, so there is nothing to replace them with.", "Leave them as they are, or tell the person the value and what THEY would call it.");
|
|
140
|
+
}
|
|
88
141
|
if (phantoms.length > 0) {
|
|
89
142
|
lines.push("", "Names this system does not declare. These look tokenized and apply nothing at all:", ...phantoms.slice(0, 20).map((p) => ` line ${p.line} ${p.name}`), "", "Use a name the system has, or say which value you need and what you would call it. Do NOT invent a token.");
|
|
90
143
|
}
|
|
@@ -2,8 +2,36 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, join, relative } from "node:path";
|
|
3
3
|
import { readProjectConfig, resolveRegistry } from "../config.js";
|
|
4
4
|
import { fetchTemplate } from "../registry.js";
|
|
5
|
+
import { installedThemeCss, installedTokensCss, whatOnlyTheSheetResolves, } from "../sheet-needed.js";
|
|
5
6
|
import { inTheirTongue, projectTongue } from "../their-tongue.js";
|
|
6
7
|
import { keptLine, recordWritten, editedHere as theirEdits, } from "../written.js";
|
|
8
|
+
/**
|
|
9
|
+
* O QUE O ARQUIVO DIZ TAMBÉM É COBRANÇA - e ela é a metade que FICA no repositório dele.
|
|
10
|
+
*
|
|
11
|
+
* O DEFEITO, achado pela revisão de DX no fecho da etapa 11: o fecho no terminal parou de mandar
|
|
12
|
+
* instalar a folha quando a tradução a tornou desnecessária, e o `.tsx` que acabou de ser escrito
|
|
13
|
+
* continuava abrindo com *"Ensure the DS tokens.css is imported"* e *"Keep the … ds-* classes
|
|
14
|
+
* (on-system…)"*. O terminal role uma vez; o comentário fica no arquivo que o time dele abre
|
|
15
|
+
* depois, sem o terminal por perto para desmentir. É a mesma contradição de duas frases que o
|
|
16
|
+
* critério conserta, um arquivo adiante.
|
|
17
|
+
*
|
|
18
|
+
* QUEM SABE A RESPOSTA É ESTE LADO, e é por isso que a linha sai aqui e não no servidor: a
|
|
19
|
+
* pergunta é *"o REPOSITÓRIO dele ainda precisa da folha?"*, e o codegen do servidor não conhece o
|
|
20
|
+
* repositório dele. Lá o header é escrito completo, que é a resposta certa para quem não traduziu
|
|
21
|
+
* nada.
|
|
22
|
+
*
|
|
23
|
+
* E O PAR NÃO É VIGIADO POR ACIDENTE: `apps/web/src/lib/ds/the-page-header-can-be-uncharged.spec.ts`
|
|
24
|
+
* assere que o header do servidor traz exatamente estas duas marcas. Se alguém reescrever a frase
|
|
25
|
+
* lá, aquele spec fica VERMELHO - em vez de este filtro passar a não casar em silêncio, que é
|
|
26
|
+
* exatamente como um `replace` por string falha aqui.
|
|
27
|
+
*/
|
|
28
|
+
const SHEET_INSTRUCTION = /tokens\.css is imported|\(on-system/;
|
|
29
|
+
function withoutTheSheetInstruction(code) {
|
|
30
|
+
return code
|
|
31
|
+
.split("\n")
|
|
32
|
+
.filter((line) => !SHEET_INSTRUCTION.test(line))
|
|
33
|
+
.join("\n");
|
|
34
|
+
}
|
|
7
35
|
/**
|
|
8
36
|
* Materializes a whole page from a DS template into the project (hybrid
|
|
9
37
|
* codegen-first): the server codegens deterministic files, we write them, and
|
|
@@ -70,36 +98,75 @@ export async function template(slug, name, opts) {
|
|
|
70
98
|
let named = 0;
|
|
71
99
|
let inlined = 0;
|
|
72
100
|
const still = new Set();
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
101
|
+
/**
|
|
102
|
+
* TRADUZ TUDO ANTES DE ESCREVER NADA, porque a pergunta *"esta pagina precisa da nossa folha?"*
|
|
103
|
+
* se responde sobre os bytes TRADUZIDOS - e a resposta decide o que vai a disco, nao so' o que o
|
|
104
|
+
* terminal diz.
|
|
105
|
+
*
|
|
106
|
+
* A ordem anterior escrevia e depois media. Funcionava enquanto a medicao servia so' ao fecho no
|
|
107
|
+
* terminal; no momento em que ela passou a decidir uma linha do ARQUIVO, escrever primeiro era
|
|
108
|
+
* gravar a decisao errada e corrigi-la na frase seguinte.
|
|
109
|
+
*/
|
|
110
|
+
const pieces = [];
|
|
111
|
+
const translate = (code) => {
|
|
112
|
+
const spoken = speak(code);
|
|
113
|
+
if (!spoken)
|
|
114
|
+
return code;
|
|
115
|
+
named += spoken.named;
|
|
116
|
+
inlined += spoken.inlined;
|
|
117
|
+
for (const l of spoken.left)
|
|
81
118
|
still.add(l);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
119
|
+
return spoken.css;
|
|
120
|
+
};
|
|
121
|
+
pieces.push({
|
|
122
|
+
rel: pageRel,
|
|
86
123
|
filename: pageRel.split(/[\\/]/).pop() ?? pageFile.filename,
|
|
87
|
-
content:
|
|
124
|
+
content: translate(pageFile.code),
|
|
88
125
|
});
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
126
|
+
for (const f of siblings)
|
|
127
|
+
pieces.push({
|
|
128
|
+
rel: join(dirname(pageRel), f.filename),
|
|
129
|
+
filename: f.filename,
|
|
130
|
+
content: translate(f.code),
|
|
131
|
+
});
|
|
132
|
+
/**
|
|
133
|
+
* A FOLHA SÓ É COBRADA QUANDO ESTA PÁGINA PRECISA DELA (A2 da etapa 11) - ver
|
|
134
|
+
* `sheet-needed.ts`, a mesma porta que o `component` usa.
|
|
135
|
+
*
|
|
136
|
+
* O DEFEITO, medido em 10/09: o comando já contava em `still` quantas referências sobraram
|
|
137
|
+
* apontando para a nossa folha depois da tradução, imprimia o número, e três linhas abaixo
|
|
138
|
+
* mandava instalar a folha e colar um `@import` - inclusive quando o número era ZERO. Uma página
|
|
139
|
+
* que renderiza igual sem os dois passava a pedi-los, e pedir cria exatamente a dependência que
|
|
140
|
+
* a plataforma promete não criar: a gente é referência, não dependência.
|
|
141
|
+
*
|
|
142
|
+
* E A PERGUNTA É MAIOR QUE `still`, que é por que ela não é feita com ele: a folha entrega as
|
|
143
|
+
* variáveis, as classes que só o `@theme` gera E as classes das receitas (`.ds-hero`). Uma
|
|
144
|
+
* página veste as três, e cortar o setup contando só as variáveis entregaria uma página pelada.
|
|
145
|
+
*
|
|
146
|
+
* SEM TRADUÇÃO, NADA MUDA: sem o mapa do `.lock` o `--ds-*` cru está no arquivo, a folha é o
|
|
147
|
+
* caminho, e a cobrança sai inteira como sempre saiu.
|
|
148
|
+
*/
|
|
149
|
+
const need = tongue
|
|
150
|
+
? whatOnlyTheSheetResolves({
|
|
151
|
+
source: pieces.map((f) => f.content).join("\n"),
|
|
152
|
+
themeCss: await installedThemeCss(root, slug),
|
|
153
|
+
sheetCss: await installedTokensCss(root, slug),
|
|
154
|
+
theirNames: [...tongue.names.values()],
|
|
155
|
+
})
|
|
156
|
+
: null;
|
|
157
|
+
const chargesTheSheet = !need || need.needed;
|
|
158
|
+
await mkdir(pageDir, { recursive: true });
|
|
159
|
+
/** OS BYTES QUE FORAM A DISCO, para o fingerprint lembrar EXATAMENTE o que escrevemos. */
|
|
160
|
+
const landed = [];
|
|
161
|
+
for (const piece of pieces) {
|
|
162
|
+
const content = chargesTheSheet
|
|
163
|
+
? piece.content
|
|
164
|
+
: withoutTheSheetInstruction(piece.content);
|
|
165
|
+
await writeFile(join(root, piece.rel), content, "utf8");
|
|
166
|
+
landed.push({ filename: piece.filename, content });
|
|
167
|
+
console.log(piece.rel === pageRel
|
|
168
|
+
? `✓ wrote ${piece.rel} (${slug} v${generated.version})`
|
|
169
|
+
: `✓ wrote ${piece.rel}`);
|
|
103
170
|
}
|
|
104
171
|
/**
|
|
105
172
|
* E O REGISTRO E' GRAVADO - a outra metade da guarda, e sem ela a primeira nunca dispara.
|
|
@@ -120,8 +187,28 @@ export async function template(slug, name, opts) {
|
|
|
120
187
|
console.log("Next steps:");
|
|
121
188
|
console.log(` • use it in a route, e.g. ${join(config.pagesDir, "page.tsx")}:`);
|
|
122
189
|
console.log(` import Page from "@/${defaultDir.replace(/\\/g, "/")}/${pageFile.filename.replace(/\.tsx$/, "")}";`);
|
|
123
|
-
|
|
124
|
-
|
|
190
|
+
if (chargesTheSheet) {
|
|
191
|
+
/** E ELE DIZ O QUE PEDE A FOLHA - um setup cobrado sem motivo dito ensina a ignorar o próximo. */
|
|
192
|
+
if (need)
|
|
193
|
+
console.log(` • what still needs the sheet here: ${[...need.recipeClasses, ...need.classes, ...need.variables].slice(0, 3).join(", ")}`);
|
|
194
|
+
console.log(` • ensure the DS is installed: synthesisui add ${slug} (provides tokens.css)`);
|
|
195
|
+
console.log(` • @import "_synthesisui/ds/${slug}/tokens.css" in your global CSS`);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
console.log(` • nothing to install: every value in this page is a name YOUR code declares, so it renders without our stylesheet`);
|
|
199
|
+
}
|
|
125
200
|
console.log(" • refine the file: wire real data, split into components, swap placeholders");
|
|
126
|
-
|
|
201
|
+
/**
|
|
202
|
+
* E A LINHA DO ESCOPO SÓ SAI ONDE ELA É VERDADE - medido rodando o comando em 10/09.
|
|
203
|
+
*
|
|
204
|
+
* Ela mandava *"keep the data-ds wrapper and the ds-* classes (stays on-system)"* sempre, e no
|
|
205
|
+
* caminho isolado a saída ficava contradizendo a linha logo acima: *"nothing to install… it
|
|
206
|
+
* renders without our stylesheet"* e, uma linha depois, uma instrução para manter um escopo que
|
|
207
|
+
* não resolve nada naquele arquivo. É o mesmo defeito que este bloco existe para consertar - duas
|
|
208
|
+
* frases que não podem ser verdadeiras ao mesmo tempo -, e a medição já responde qual das duas é:
|
|
209
|
+
* chegar aqui com `chargesTheSheet` falso significa zero variável nossa e zero classe nossa no
|
|
210
|
+
* que acabou de ser escrito.
|
|
211
|
+
*/
|
|
212
|
+
if (chargesTheSheet)
|
|
213
|
+
console.log(` • keep the data-ds="${slug}" wrapper and the ds-* / layout classes (stays on-system)`);
|
|
127
214
|
}
|
package/dist/doctor/ci-format.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* checagem que telefona para casa é uma checagem que alguém desinstala.
|
|
24
24
|
*/
|
|
25
25
|
import { isOlderCli } from "../cli-version.js";
|
|
26
|
-
import {
|
|
26
|
+
import { COUNTED_DIFFERENTLY, COUNTED_DIFFERENTLY_SINCE, } from "../install-marks.js";
|
|
27
27
|
const byFileOf = (d) => {
|
|
28
28
|
const out = {};
|
|
29
29
|
for (const f of d.files)
|
|
@@ -75,10 +75,12 @@ export function compareToBaseline(d, base, now) {
|
|
|
75
75
|
* prometia o contrário desde o primeiro dia (*"um número que sobe porque o leitor melhorou não é
|
|
76
76
|
* regressão"*), e `incomparable` existia para exatamente isto.
|
|
77
77
|
*
|
|
78
|
-
* A RÉGUA É
|
|
79
|
-
* compara mudou de resposta
|
|
78
|
+
* A RÉGUA É `COUNTED_DIFFERENTLY_SINCE`, em `install-marks.ts`: a última versão em que a CONTAGEM
|
|
79
|
+
* que o CI compara mudou de resposta. Ela mora na casa das outras marcas porque é lá que está a
|
|
80
|
+
* disciplina de mantê-las - uma marca à mão sem portão é uma marca errada esperando a hora -, e
|
|
81
|
+
* não é a MESMA das outras: emprestar `CHECKER_SINCE` foi exatamente o defeito que a criou.
|
|
80
82
|
*/
|
|
81
|
-
const readerMoved = base.cli !== undefined && isOlderCli(base.cli,
|
|
83
|
+
const readerMoved = base.cli !== undefined && isOlderCli(base.cli, COUNTED_DIFFERENTLY_SINCE);
|
|
82
84
|
return {
|
|
83
85
|
worse: !scopeChanged &&
|
|
84
86
|
!readerMoved &&
|
|
@@ -95,7 +97,7 @@ export function compareToBaseline(d, base, now) {
|
|
|
95
97
|
}
|
|
96
98
|
: readerMoved
|
|
97
99
|
? {
|
|
98
|
-
incomparable: `the baseline was written by CLI ${base.cli}, and
|
|
100
|
+
incomparable: `the baseline was written by CLI ${base.cli}, and ${COUNTED_DIFFERENTLY}. The rise is in the reading, not in your code. Rewrite the line before comparing: npx synthesisui doctor --write-baseline`,
|
|
99
101
|
}
|
|
100
102
|
: {}),
|
|
101
103
|
};
|
package/dist/install-marks.js
CHANGED
|
@@ -275,7 +275,37 @@ export const MATERIALISER_SINCE = "0.16.412";
|
|
|
275
275
|
* não via, 14% e 16% das declarações de espaço/raio. Um hook pinado antes desta versão aconselha
|
|
276
276
|
* MENOS, que é a régua desta marca.
|
|
277
277
|
*/
|
|
278
|
-
|
|
278
|
+
/**
|
|
279
|
+
* A ÚLTIMA VERSÃO EM QUE A CONTAGEM QUE O CI COMPARA MUDOU DE RESPOSTA - a régua da catraca, e ela
|
|
280
|
+
* mora aqui porque é onde a disciplina mora.
|
|
281
|
+
*
|
|
282
|
+
* ELA ERA `CHECKER_SINCE`, e as duas perguntas não são a mesma. Aquela significa *"o que o hook
|
|
283
|
+
* ENTREGA mudou"*, e sobe também quando ele passa a DIZER algo novo sobre os mesmos achados - o que
|
|
284
|
+
* aconteceu em 10/09. Com a régua emprestada, aquela subida declararia INCOMPARÁVEL todo baseline
|
|
285
|
+
* gravado entre as duas versões: a catraca pararia de reprovar regressão de verdade até o time
|
|
286
|
+
* regravar a linha, e a frase abaixo diria a ele um motivo que não é o dele. Foi o caso negativo do
|
|
287
|
+
* `doctor/ci-format.spec.ts` que acendeu isso, sozinho.
|
|
288
|
+
*
|
|
289
|
+
* A PERGUNTA DO PASSO 1 GANHA UMA SEGUNDA METADE por causa dela, e as duas se fazem no mesmo
|
|
290
|
+
* vermelho do fingerprint do CHECKER (que já inclui `doctor/scan.ts`, quem conta):
|
|
291
|
+
*
|
|
292
|
+
* o hook aconselha diferente? -> `CHECKER_SINCE`
|
|
293
|
+
* a CONTAGEM mudou de valor? -> `COUNTED_DIFFERENTLY_SINCE`, e a FRASE junto
|
|
294
|
+
*
|
|
295
|
+
* A frase viaja ao lado da versão porque as duas são uma coisa só: quem move a marca troca a
|
|
296
|
+
* explicação no mesmo lugar, em vez de deixar o CI citando a causa da marca anterior.
|
|
297
|
+
*/
|
|
298
|
+
export const COUNTED_DIFFERENTLY_SINCE = "0.16.408";
|
|
299
|
+
export const COUNTED_DIFFERENTLY = "this run counts every length in a shorthand - that version counted only the first";
|
|
300
|
+
/**
|
|
301
|
+
* 0.16.408 -> 0.16.413 em 10/09: o hook passa a DIZER o valor que nada nomeia. Ele silenciava toda
|
|
302
|
+
* deriva sem token de destino - a decisão estava escrita como "não vale interromper, porque o único
|
|
303
|
+
* conselho honesto é pergunte a uma pessoa" -, e o silêncio é a única resposta errada disponível:
|
|
304
|
+
* quem lê um relatório que só fala de valores nomeáveis não distingue "não sobrou nada" de "sobrou,
|
|
305
|
+
* e nós engolimos". Agora sai `line 12 #ff00aa`, sem uma única proposta de nome nosso. Um hook
|
|
306
|
+
* pinado antes desta versão aconselha MENOS, que é a régua desta marca.
|
|
307
|
+
*/
|
|
308
|
+
export const CHECKER_SINCE = "0.16.413";
|
|
279
309
|
/**
|
|
280
310
|
* A ÚLTIMA VERSÃO EM QUE OS LEITORES PASSARAM A PRODUZIR UM CENSO DIFERENTE.
|
|
281
311
|
*
|
package/dist/sheet-needed.js
CHANGED
|
@@ -1,6 +1,74 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
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
|
+
}
|
|
4
72
|
/** `--animate-shimmer` no `@theme` -> o nome `shimmer`, que é o que uma classe carrega. */
|
|
5
73
|
function themeNames(themeCss) {
|
|
6
74
|
const out = new Set();
|
|
@@ -207,14 +275,33 @@ export function whatOnlyTheSheetResolves(input) {
|
|
|
207
275
|
const classes = [
|
|
208
276
|
...new Set([...written].filter((c) => names.some((n) => c === n || c.endsWith(`-${n}`)))),
|
|
209
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
|
+
];
|
|
210
283
|
return {
|
|
211
284
|
variables,
|
|
212
285
|
classes,
|
|
213
|
-
|
|
286
|
+
recipeClasses,
|
|
287
|
+
needed: variables.length > 0 || classes.length > 0 || recipeClasses.length > 0,
|
|
214
288
|
};
|
|
215
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
|
+
}
|
|
216
294
|
/** O `theme.css` da versão instalada - a folha da raiz é um re-export de uma linha. */
|
|
217
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) {
|
|
218
305
|
const dir = join(root, "_synthesisui", "ds", slug);
|
|
219
306
|
const lock = await readFile(join(dir, ".lock"), "utf8").catch(() => null);
|
|
220
307
|
let version = 0;
|
|
@@ -226,5 +313,5 @@ export async function installedThemeCss(root, slug) {
|
|
|
226
313
|
version = 0;
|
|
227
314
|
}
|
|
228
315
|
}
|
|
229
|
-
return readFile(join(dir, `v${version}`,
|
|
316
|
+
return readFile(join(dir, `v${version}`, filename), "utf8").catch(() => "");
|
|
230
317
|
}
|
package/package.json
CHANGED