synthesisui 0.16.374 → 0.16.376

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.
@@ -51,6 +51,8 @@ export function planFix(d, read) {
51
51
  const next = new Map();
52
52
  /** Linhas por arquivo, mutadas em memória: dois achados no mesmo arquivo compõem. */
53
53
  const lines = new Map();
54
+ /** A mesma chave, congelada antes da primeira troca - ver o skip de `not-written`. */
55
+ const originals = new Map();
54
56
  /**
55
57
  * DE BAIXO PARA CIMA no arquivo não é necessário - a troca não muda a contagem de linhas - mas a
56
58
  * ORDEM por arquivo é, para dois achados na mesma linha não se atropelarem: o segundo procura o seu
@@ -119,20 +121,84 @@ export function planFix(d, read) {
119
121
  continue;
120
122
  }
121
123
  lines.set(f.file, raw.split("\n"));
124
+ /**
125
+ * A LINHA COMO ELA ESTAVA ANTES DE QUALQUER TROCA DESTA RODADA - e é ela que separa dois
126
+ * skips que hoje se dizem com a mesma frase. Ver o bloco de `swapped === null`.
127
+ */
128
+ originals.set(f.file, raw.split("\n"));
122
129
  }
123
130
  const body = lines.get(f.file);
124
131
  if (!body)
125
132
  continue;
126
133
  const idx = f.line - 1;
127
134
  const current = body[idx];
135
+ /**
136
+ * A DECLARAÇÃO DE UM TOKEN NÃO É UM USO DELE - e trocar ali APAGA o token do app inteiro.
137
+ *
138
+ * O QUE ACONTECIA: `--color-brand-blue: #4f46e5;` é onde o token NASCE. O scan vê o literal, o
139
+ * sistema tem um nome para aquele valor - o próprio `--color-brand-blue` -, e a troca escrevia
140
+ * `--color-brand-blue: var(--color-brand-blue)`. Isso é um ciclo de custom property, e a regra do
141
+ * CSS é dura: a propriedade fica inválida no elemento E em todos os descendentes. Como esses
142
+ * tokens moram em `:root`, o app inteiro perde a cor.
143
+ *
144
+ * MEDIDO em 05/09, sobre uma cópia do repositório do dono: **26 declarações** viraram
145
+ * auto-referência numa passada. E cada uma delas é o destino de dezenas de trocas que o mesmo
146
+ * comando acabou de fazer - o conserto apagava aquilo que ele mesmo tinha acabado de apontar.
147
+ *
148
+ * É A MESMA FAMÍLIA DO CICLO DOS EASINGS que o dono encontrou em 04/09, e desta vez produzida
149
+ * pelo nosso comando de conserto. Sem erro no console: o navegador simplesmente descarta.
150
+ *
151
+ * A REGRA É ESTREITA DE PROPÓSITO. Apontar um token DELE para outro token DELE
152
+ * (`--brand-hover: #4f46e5` -> `var(--color-brand-blue)`) continua valendo e é bom. Só a
153
+ * auto-referência é fatal, e só ela é recusada.
154
+ *
155
+ * É GERAL: toda folha declara token com valor literal - 149 no repositório dele, 180 no
156
+ * frontend-hub.
157
+ */
158
+ const declaring = /^\s*(--[a-z0-9-]+)\s*:/i.exec(current ?? "");
159
+ if (declaring && declaring[1] === name) {
160
+ skipped.push({
161
+ file: f.file,
162
+ line: f.line,
163
+ literal: f.literal,
164
+ because: "self-reference",
165
+ });
166
+ continue;
167
+ }
128
168
  const swapped = current === undefined ? null : swap(current, f.literal, name);
129
169
  if (swapped === null) {
130
- /** O arquivo mudou desde a medição, ou o literal já foi trocado por outro achado. */
170
+ /**
171
+ * DOIS MOTIVOS DIFERENTES, E A TELA DIZIA UM SÓ - e o que ela dizia era falso quase sempre.
172
+ *
173
+ * O QUE O DONO VIU em 05/09: *"13 lines changed since the scan and were left alone - run it
174
+ * again"*, num repositório onde ele não tinha editado nada. Medido: **13 de 13** eram classe
175
+ * utilitária. A linha era `"transition-all duration-300 ease-[var(--ease-out-soft)]"`, o scan
176
+ * derivou `300ms` de `duration-300` - corretamente -, e o `swap` procura o texto `300ms`, que
177
+ * nunca esteve escrito ali. `grep 300ms` no arquivo devolve zero.
178
+ *
179
+ * O comentário que estava aqui já sabia disso: *"o arquivo mudou desde a medição, OU o literal
180
+ * já foi trocado por outro achado"*. A frase impressa contava só o primeiro caso, e mandava
181
+ * rodar de novo - o que não muda nada, porque nada mudou.
182
+ *
183
+ * A LINHA ORIGINAL DESEMPATA. Sem o literal nela, o valor veio de forma abreviada e trocá-lo é
184
+ * outra reescrita (`duration-[var(--duration-base)]`), não uma troca de literal. Com o literal
185
+ * nela, alguma coisa mexeu na linha depois - e aí rodar de novo é o conselho certo.
186
+ *
187
+ * É GERAL: `duration-*` é Tailwind puro - 15 arquivos no repositório dele, 37 no frontend-hub.
188
+ */
189
+ /**
190
+ * A LINHA SUMIU É OUTRA COISA - e é `moved` de verdade: um arquivo que encolheu desde a
191
+ * medição mudou, e rodar de novo é o conselho certo. `not-written` só quando a linha ESTÁ lá
192
+ * e o valor não está escrito nela.
193
+ */
194
+ const before = originals.get(f.file)?.[idx];
131
195
  skipped.push({
132
196
  file: f.file,
133
197
  line: f.line,
134
198
  literal: f.literal,
135
- because: "moved",
199
+ because: before === undefined || before.includes(f.literal)
200
+ ? "moved"
201
+ : "not-written",
136
202
  });
137
203
  continue;
138
204
  }
@@ -176,6 +242,10 @@ export async function readerFor(root, files) {
176
242
  export function describeFix(result, dry) {
177
243
  const { applied, skipped } = result;
178
244
  const moved = skipped.filter((s) => s.because === "moved").length;
245
+ /** O valor veio de forma abreviada - ver `not-written` em `planFix`. */
246
+ const notWritten = skipped.filter((s) => s.because === "not-written").length;
247
+ /** A linha declara o próprio token - ver `self-reference` em `planFix`. */
248
+ const selfRef = skipped.filter((s) => s.because === "self-reference").length;
179
249
  const coincidence = skipped.filter((s) => s.because === "cross-family").length;
180
250
  const unread = skipped.filter((s) => s.because === "unreadable").length;
181
251
  const decisions = skipped.filter((s) => s.because === "no-token").length;
@@ -187,23 +257,67 @@ export function describeFix(result, dry) {
187
257
  ? `Nothing to apply. All ${decisions} findings are values your system has no name for - those are design decisions, not fixes.`
188
258
  : relative > 0
189
259
  ? `Nothing to apply. All ${relative} findings are lengths in \`em\`, which follow the element's font size - swapping them can move the layout, so that call is yours.`
190
- : "Nothing to apply.");
260
+ : /**
261
+ * E QUANDO NADA É APLICÁVEL, O MOTIVO SAI AQUI TAMBÉM - senão a saída inteira é
262
+ * "Nothing to apply." e a pessoa não tem como saber que o valor existe, tem nome, e só
263
+ * não está escrito como texto.
264
+ */
265
+ notWritten > 0
266
+ ? `Nothing to apply. All ${notWritten} findings are written as a shorthand - a utility class like \`duration-300\` carries the value in its name, so there is no literal on the line to replace. Changing them is a different rewrite.`
267
+ : /**
268
+ * E quando tudo o que havia eram as linhas onde os tokens nascem, "Nothing to apply."
269
+ * sozinho manda a pessoa procurar um defeito. O comando passou por ali de propósito.
270
+ */
271
+ selfRef > 0
272
+ ? `Nothing to apply. ${selfRef === 1 ? "The one finding is" : `All ${selfRef} findings are`} on the line where the token itself is declared - replacing there would point it at itself, and the browser would drop it.`
273
+ : "Nothing to apply.");
191
274
  return lines;
192
275
  }
193
276
  lines.push(`${dry ? "Would replace" : "Replaced"} ${applied.length} hand-written value${applied.length === 1 ? "" : "s"} with the token your system already has, across ${result.files} file${result.files === 1 ? "" : "s"}.`);
194
277
  const byToken = new Map();
195
278
  for (const a of applied)
196
279
  byToken.set(a.token, (byToken.get(a.token) ?? 0) + 1);
197
- for (const [token, n] of [...byToken.entries()]
198
- .sort((a, b) => b[1] - a[1])
199
- .slice(0, 6))
280
+ const ranked = [...byToken.entries()].sort((a, b) => b[1] - a[1]);
281
+ const SHOWN = 6;
282
+ for (const [token, n] of ranked.slice(0, SHOWN))
200
283
  lines.push(` var(${token}) · ${n} time${n === 1 ? "" : "s"}`);
284
+ /**
285
+ * O QUE O CORTE ESCONDEU, DITO - e é a mesma lei que a linha abaixo já obedece.
286
+ *
287
+ * O QUE O DONO VIU em 05/09: `Would replace 143 ... across 20 files` e seis linhas somando 81. As
288
+ * outras 62 ocorrências não apareciam em lugar nenhum, e ele aprovaria a escrita sem saber que
289
+ * existiam.
290
+ *
291
+ * O comentário três linhas abaixo já enuncia a regra - *"DITO SEMPRE QUE ACONTECE, senão o número
292
+ * some da conta sem explicação"* -, aplicada ao `relative` e ao `moved` e não ao próprio corte
293
+ * desta lista. Terceira vez nesta família num dia: o `gaps` (#1327) e a tela de admin (#1304).
294
+ */
295
+ const rest = ranked.slice(SHOWN);
296
+ if (rest.length > 0) {
297
+ const restUses = rest.reduce((n, [, uses]) => n + uses, 0);
298
+ lines.push(` and ${restUses} more across ${rest.length} other token${rest.length === 1 ? "" : "s"}, not listed here`);
299
+ }
201
300
  /**
202
301
  * DITO SEMPRE QUE ACONTECE - senão o número de "trocado" some da conta sem explicação, e a pessoa
203
302
  * conclui que o comando falhou onde ele se recusou de propósito.
204
303
  */
205
304
  if (relative > 0)
206
305
  lines.push(` ${relative} more ${relative === 1 ? "is a length" : "are lengths"} in \`em\`, which follow the element's font size - your system names the same number, but the pixel may differ. Left for you to decide.`);
306
+ /**
307
+ * O VALOR QUE NÃO ESTÁ ESCRITO - a frase que substituiu uma afirmação falsa sobre o repositório.
308
+ *
309
+ * Isto saía como *"N lines changed since the scan - run it again"*, e na tela do dono nada tinha
310
+ * mudado: 13 de 13 eram `duration-300` e afins. Mandar rodar de novo não muda nada, e mandar
311
+ * alguém procurar uma edição que ela não fez é pior que calar.
312
+ */
313
+ /**
314
+ * DITO, e não calado: é a linha onde o token NASCE, e a pessoa precisa saber que o comando passou
315
+ * por ali de propósito - senão ela procura por que aquele valor não foi trocado.
316
+ */
317
+ if (selfRef > 0)
318
+ lines.push(` ${selfRef} ${selfRef === 1 ? "is" : "are"} the line where the token itself is declared - replacing there would point it at itself and the browser would drop it. Left alone.`);
319
+ if (notWritten > 0)
320
+ lines.push(` ${notWritten} more ${notWritten === 1 ? "is" : "are"} written as a shorthand - a utility class like \`duration-300\` carries the value in its name, so there is no literal on the line to replace. Changing ${notWritten === 1 ? "it" : "those"} is a different rewrite. Left alone.`);
207
321
  if (moved > 0)
208
322
  lines.push(`${moved} line${moved === 1 ? "" : "s"} changed since the scan and ${moved === 1 ? "was" : "were"} left alone - run it again.`);
209
323
  if (coincidence > 0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.374",
3
+ "version": "0.16.376",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {