synthesisui 0.16.399 → 0.16.401
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/absorb-plan.js +49 -3
- package/dist/commands/component.js +31 -1
- package/dist/doctor/signals.js +20 -0
- package/dist/proposed-name.js +121 -0
- package/package.json +1 -1
package/dist/absorb-plan.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { deltaE, JND } from "./doctor/color-distance.js";
|
|
2
2
|
import { familySays, nearestToken, normalizeValue, } from "./doctor/tokens.js";
|
|
3
|
+
import { proposeFromTheirScale, tooCloseToPropose } from "./proposed-name.js";
|
|
3
4
|
/** Onde cada tipo de valor mora na fundação. `color` é o único com dois segmentos. */
|
|
4
5
|
const HOME = {
|
|
5
6
|
color: "color",
|
|
@@ -180,7 +181,26 @@ export function absorbPlan(d, theirs, have, cap = 40) {
|
|
|
180
181
|
const value = normalizeValue(r.literal, theirs.rootPx);
|
|
181
182
|
const theirName = nameOf(r.kind, theirs.byValue.get(value)) ??
|
|
182
183
|
nameOf(r.kind, theirs.compiledAway.get(value)?.map((c) => c.name));
|
|
183
|
-
|
|
184
|
+
/**
|
|
185
|
+
* A LISTA NÃO CHEGA EM BRANCO - e é o que faz o vocabulário dele CRESCER.
|
|
186
|
+
*
|
|
187
|
+
* Especificado em 07/09 por entrevista (`steps/02-jornada-projeto-novo.md`): a plataforma
|
|
188
|
+
* PROPÕE, ele aprova ou edita, e nada entra sem o aval dele. Sem proposta, cada valor novo custa
|
|
189
|
+
* um batismo manual - e num repositório com 553 valores distintos ninguém batiza 553 vezes. O
|
|
190
|
+
* vocabulário para de crescer e o próximo componente nasce com outro literal, que é o oposto do
|
|
191
|
+
* que a plataforma existe para fazer.
|
|
192
|
+
*
|
|
193
|
+
* A ORDEM É A DE SEMPRE: o nome que ele JÁ deu vence. Só onde ninguém nomeia é que a derivação
|
|
194
|
+
* entra - e ela devolve `null` na dúvida (ver `proposed-name.ts`).
|
|
195
|
+
*/
|
|
196
|
+
/**
|
|
197
|
+
* UMA FONTE POR ENQUANTO - a escala dele. A segunda está especificada e NÃO entrou, e o motivo
|
|
198
|
+
* está medido: ver `proposed-name.ts`, no fim.
|
|
199
|
+
*/
|
|
200
|
+
const proposal = theirName || tooCloseToPropose(r.literal, theirs)
|
|
201
|
+
? null
|
|
202
|
+
: proposeFromTheirScale(r.literal, theirs);
|
|
203
|
+
const path = pathFor(r.kind, theirName) || proposal?.path || "";
|
|
184
204
|
/** "≈ perto de um token seu" só interessa a quem não tem um EXATO - com o nome na mão, a dica
|
|
185
205
|
* vira ruído, e no arquivo de proposta ela vira uma segunda opção que não é opção. */
|
|
186
206
|
const near = theirName ? undefined : nearestOwn(theirs, r.literal, r.kind);
|
|
@@ -194,6 +214,8 @@ export function absorbPlan(d, theirs, have, cap = 40) {
|
|
|
194
214
|
count: r.count,
|
|
195
215
|
path,
|
|
196
216
|
...(theirName ? { theirName: clean(theirName) } : {}),
|
|
217
|
+
/** A PROCEDÊNCIA VIAJA COM A PROPOSTA - ele julga pela origem, nunca pela palavra. */
|
|
218
|
+
...(proposal ? { proposed: proposal.because } : {}),
|
|
197
219
|
...(near ? { near } : {}),
|
|
198
220
|
});
|
|
199
221
|
}
|
|
@@ -213,7 +235,17 @@ export const needingName = (plan) => plan.entries.filter((e) => !e.path);
|
|
|
213
235
|
* nada.
|
|
214
236
|
*/
|
|
215
237
|
export function describeAbsorb(plan) {
|
|
216
|
-
|
|
238
|
+
/**
|
|
239
|
+
* O QUE ELE JÁ NOMEIA E O QUE A PLATAFORMA PROPÔS SÃO DUAS LISTAS - e juntá-las é dizer que ele
|
|
240
|
+
* nomeou o que ele não nomeou.
|
|
241
|
+
*
|
|
242
|
+
* A primeira versão da proposta caiu direto em `ready`, e a linha de cima passou a dizer
|
|
243
|
+
* *"1 of 1 values already have a name in YOUR vocabulary"* sobre um nome que a PLATAFORMA acabou
|
|
244
|
+
* de derivar. É a mesma família de defeito que o dia inteiro de 07/09 consertou: uma frase que
|
|
245
|
+
* afirma sobre o repositório dele algo que veio da nossa dedução.
|
|
246
|
+
*/
|
|
247
|
+
const ready = plan.entries.filter((e) => e.path && !e.proposed);
|
|
248
|
+
const proposed = plan.entries.filter((e) => e.proposed);
|
|
217
249
|
const pending = needingName(plan);
|
|
218
250
|
const lines = [];
|
|
219
251
|
/**
|
|
@@ -245,6 +277,15 @@ export function describeAbsorb(plan) {
|
|
|
245
277
|
lines.push(` ${e.value.padEnd(24)} ${e.path.padEnd(28)} ${e.files} file${e.files === 1 ? "" : "s"}${e.theirName ? ` · your ${e.theirName}` : ""}`);
|
|
246
278
|
if (ready.length > 8)
|
|
247
279
|
lines.push(` (${ready.length - 8} more)`);
|
|
280
|
+
if (proposed.length > 0) {
|
|
281
|
+
if (ready.length > 0)
|
|
282
|
+
lines.push("");
|
|
283
|
+
lines.push(`${proposed.length} ${proposed.length === 1 ? "value has" : "values have"} a name PROPOSED from your own scale - approve, edit, or leave ${proposed.length === 1 ? "it" : "them"} hard-coded:`);
|
|
284
|
+
for (const e of proposed.slice(0, 8))
|
|
285
|
+
lines.push(` ${e.value.padEnd(24)} ${e.path.padEnd(28)} ${e.proposed}`);
|
|
286
|
+
if (proposed.length > 8)
|
|
287
|
+
lines.push(` (${proposed.length - 8} more)`);
|
|
288
|
+
}
|
|
248
289
|
if (pending.length > 0) {
|
|
249
290
|
const close = pending.filter((e) => e.near);
|
|
250
291
|
if (ready.length > 0)
|
|
@@ -262,7 +303,12 @@ export function describeAbsorb(plan) {
|
|
|
262
303
|
? `${pending.length} nobody names yet. Naming is a design decision, so those wait for a word from you:`
|
|
263
304
|
: `${pending.length} ${everyOneRepeats ? "repeated values" : "values written by hand"}, and none of them has a name yet. Naming is a design decision, so each waits for a word from you:`);
|
|
264
305
|
for (const e of pending.slice(0, 8))
|
|
265
|
-
lines.push(` ${e.value.padEnd(24)} ${`<${e.kind}, ${e.files} file${e.files === 1 ? "" : "s"}>`.padEnd(28)} ${e.near
|
|
306
|
+
lines.push(` ${e.value.padEnd(24)} ${`<${e.kind}, ${e.files} file${e.files === 1 ? "" : "s"}>`.padEnd(28)} ${e.near
|
|
307
|
+
? `≈ your ${e.near.name} (${e.near.away})`
|
|
308
|
+
: /** A proposta chega COM a procedência: ele julga pela origem, não pela palavra. */
|
|
309
|
+
e.proposed
|
|
310
|
+
? `${e.path} · ${e.proposed}`
|
|
311
|
+
: 'path: ""'}`);
|
|
266
312
|
if (pending.length > 8)
|
|
267
313
|
lines.push(` (${pending.length - 8} more)`);
|
|
268
314
|
/**
|
|
@@ -12,7 +12,7 @@ import { installedThemeCss, whatOnlyTheSheetResolves, } from "../sheet-needed.js
|
|
|
12
12
|
import { flavourResolver } from "../styles-flavour.js";
|
|
13
13
|
import { inTheirTongue, projectTongue, sumSpoken, } from "../their-tongue.js";
|
|
14
14
|
import { readCensus, unreadComment, unreadForComponent, } from "../unread-for-component.js";
|
|
15
|
-
import { recordWritten } from "../written.js";
|
|
15
|
+
import { editedSinceWritten, readWritten, recordWritten } from "../written.js";
|
|
16
16
|
/**
|
|
17
17
|
* Writes the shared `cn.ts` next to the components, built from THIS project's
|
|
18
18
|
* installed theme.
|
|
@@ -233,6 +233,36 @@ export async function component(slug, name, opts) {
|
|
|
233
233
|
* a mesma pergunta - qual nome este comando vai ocupar - e duas respostas para ela é o defeito.
|
|
234
234
|
*/
|
|
235
235
|
const local = localName(res.name, opts.as);
|
|
236
|
+
/**
|
|
237
|
+
* A EDIÇÃO DELE NO QUE NÓS ESCREVEMOS - a mesma promessa do `upgrade`, no comando que a violava.
|
|
238
|
+
*
|
|
239
|
+
* O QUE ACONTECIA, caminhado em 07/09 com o binário publicado: gerei `btn2`, escrevi uma linha
|
|
240
|
+
* à mão no topo do arquivo, rodei `component codelevel button --as btn2` de novo - e a linha
|
|
241
|
+
* sumiu. Sem aviso, sem `--force`, sem uma frase.
|
|
242
|
+
*
|
|
243
|
+
* O GUARD QUE EXISTE ACIMA RESPONDE OUTRA PERGUNTA: *"você já tem este componente no seu
|
|
244
|
+
* repositório?"*, sobre o arquivo de ORIGEM (`recipe.api.file`), de onde o sistema foi
|
|
245
|
+
* importado. Ele nada sabe sobre o que NÓS escrevemos depois.
|
|
246
|
+
*
|
|
247
|
+
* E a informação existia o tempo todo: `recordWritten` grava o fingerprint de cada arquivo a
|
|
248
|
+
* cada escrita, e o `upgrade` a lê para dizer `kept YOUR file(s)`. Este comando gravava e nunca
|
|
249
|
+
* lia - a promessa valia num caminho e não no outro, e o outro é o mais percorrido: alguém traz
|
|
250
|
+
* o componente, ajusta, e roda de novo para pegar uma correção.
|
|
251
|
+
*
|
|
252
|
+
* A saída oferecida é a mesma que o `upgrade` oferece, e é a que ele já tem na mão: `--force`.
|
|
253
|
+
*/
|
|
254
|
+
const slugDir = join(root, "_synthesisui", "ds", slug);
|
|
255
|
+
const editedHere = await editedSinceWritten((await readWritten(slugDir))[local], join(root, config.componentsDir, local));
|
|
256
|
+
if (editedHere && editedHere.length > 0 && !opts.force) {
|
|
257
|
+
console.log(section("You edited this one"));
|
|
258
|
+
console.log(body(`${editedHere.join(", ")} in ${config.componentsDir}/${local} changed after we wrote ${editedHere.length === 1 ? "it" : "them"} - kept YOUR file(s).`));
|
|
259
|
+
console.log("");
|
|
260
|
+
console.log(body("To take the new version anyway:"));
|
|
261
|
+
console.log(snippet([
|
|
262
|
+
`npx synthesisui component ${slug} ${res.name}${opts.as ? ` --as ${opts.as}` : ""} --force`,
|
|
263
|
+
]));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
236
266
|
const localPascal = local
|
|
237
267
|
.split(/[^a-zA-Z0-9]+/)
|
|
238
268
|
.filter(Boolean)
|
package/dist/doctor/signals.js
CHANGED
|
@@ -360,6 +360,26 @@ export function describeSignals(s) {
|
|
|
360
360
|
else if (t.prefersColorScheme > 0) {
|
|
361
361
|
out.push(`no dark utilities, but ${t.prefersColorScheme} \`prefers-color-scheme\` rule${t.prefersColorScheme === 1 ? "" : "s"} - the second scheme lives in CSS rather than in class names.`);
|
|
362
362
|
}
|
|
363
|
+
else if (t.library) {
|
|
364
|
+
/**
|
|
365
|
+
* NENHUM SINAL DE SEGUNDO ESQUEMA, MAS UM ALTERNADOR INSTALADO - e as duas frases se negavam.
|
|
366
|
+
*
|
|
367
|
+
* MEDIDO EM 07/09 no `~/projects/web-subscribe`, caminhando o ATO 3: o relatório imprimia, em
|
|
368
|
+
* linhas CONSECUTIVAS, *"one scheme, and adding a second is a decision rather than a
|
|
369
|
+
* discovery"* e *"switched by next-themes"*. Quem lê recebe as duas ao mesmo tempo: não existe
|
|
370
|
+
* segundo esquema, e existe uma biblioteca que alterna entre esquemas.
|
|
371
|
+
*
|
|
372
|
+
* AS DUAS METADES ESTÃO CERTAS separadamente, e é isso que torna a contradição fácil de
|
|
373
|
+
* escrever: uma conta utilitários `dark:` e regras `prefers-color-scheme` no CSS; a outra lê o
|
|
374
|
+
* `package.json` e o provider. O que faltava era uma olhar para a outra.
|
|
375
|
+
*
|
|
376
|
+
* E O FATO COMBINADO É MAIS ÚTIL QUE QUALQUER UMA DAS DUAS: o alternador está instalado e não
|
|
377
|
+
* há nada para ele alternar. Isso é uma pendência do projeto dele - e é acionável, ao contrário
|
|
378
|
+
* de "adicionar um segundo esquema é uma decisão", que ele já tomou quando instalou a
|
|
379
|
+
* biblioteca.
|
|
380
|
+
*/
|
|
381
|
+
out.push(`no dark utilities and no \`prefers-color-scheme\` - so the second scheme has nothing to paint yet, even though ${t.library} is installed to switch between them.`);
|
|
382
|
+
}
|
|
363
383
|
else {
|
|
364
384
|
out.push("no dark utilities and no `prefers-color-scheme` - one scheme, and adding a second is a decision rather than a discovery.");
|
|
365
385
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { chroma, deltaE, lightness } from "./doctor/color-distance.js";
|
|
2
|
+
import { normalizeValue } from "./doctor/tokens.js";
|
|
3
|
+
function stepsOf(theirs) {
|
|
4
|
+
const out = [];
|
|
5
|
+
for (const [name, value] of theirs.byName) {
|
|
6
|
+
const m = /^--(.*[a-z0-9])-(\d{2,4})$/.exec(name.toLowerCase());
|
|
7
|
+
if (!m)
|
|
8
|
+
continue;
|
|
9
|
+
out.push({ family: m[1], step: Number(m[2]), value });
|
|
10
|
+
}
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* O DEGRAU QUE FALTA NA ESCALA DELE.
|
|
15
|
+
*
|
|
16
|
+
* A proposta só existe quando o valor cai ENTRE dois degraus vizinhos da MESMA família dele, e o
|
|
17
|
+
* degrau proposto é o ponto médio entre os dois. A palavra é inteiramente dele; só a posição é
|
|
18
|
+
* calculada.
|
|
19
|
+
*
|
|
20
|
+
* AS TRÊS PORTAS, e cada uma existe para o lado do "propor de menos":
|
|
21
|
+
*
|
|
22
|
+
* mesma família dois degraus de famílias diferentes não formam escala - `ink-700` e
|
|
23
|
+
* `brand-900` não têm um meio
|
|
24
|
+
* entre eles um valor fora do intervalo não tem degrau intermediário; extrapolar seria
|
|
25
|
+
* inventar a continuação da escala dele
|
|
26
|
+
* lugar livre se o degrau do meio já existe, o valor não é um degrau novo - é outra
|
|
27
|
+
* decisão que por acaso caiu perto, e propor sobrescreveria a dele
|
|
28
|
+
*/
|
|
29
|
+
export function proposeFromTheirScale(literal, theirs) {
|
|
30
|
+
const l = lightness(normalizeValue(literal, theirs.rootPx));
|
|
31
|
+
if (l === null)
|
|
32
|
+
return null;
|
|
33
|
+
/**
|
|
34
|
+
* O CROMA ENTRA JUNTO COM A LUMINOSIDADE, e sem ele a derivação INVENTA.
|
|
35
|
+
*
|
|
36
|
+
* MEDIDO NA PRIMEIRA VERSÃO DISTO, em 07/09: um verde (`#0d5c2a`) recebeu `color.ink.800`, porque
|
|
37
|
+
* a luminosidade dele cai entre dois cinzas dele. Um degrau que falta numa escala está perto dos
|
|
38
|
+
* vizinhos NA COR, não só no claro-escuro - propor o contrário é dizer que o verde dele é um tom
|
|
39
|
+
* de cinza.
|
|
40
|
+
*
|
|
41
|
+
* O INTERVALO É O DOS PRÓPRIOS VIZINHOS, nunca um limiar escolhido: um cinza tem croma perto de
|
|
42
|
+
* zero e um verde não, então a porta se fecha sozinha. Numa escala de azul os dois vizinhos têm
|
|
43
|
+
* croma alto e o degrau novo passa. Sem folga, porque o lado seguro do erro é hard-coded.
|
|
44
|
+
*/
|
|
45
|
+
const c = chroma(normalizeValue(literal, theirs.rootPx));
|
|
46
|
+
if (c === null)
|
|
47
|
+
return null;
|
|
48
|
+
const steps = stepsOf(theirs)
|
|
49
|
+
.map((s) => {
|
|
50
|
+
const v = normalizeValue(s.value, theirs.rootPx);
|
|
51
|
+
return { ...s, l: lightness(v), c: chroma(v) };
|
|
52
|
+
})
|
|
53
|
+
.filter((s) => s.l !== null && s.c !== null);
|
|
54
|
+
if (steps.length < 2)
|
|
55
|
+
return null;
|
|
56
|
+
let best = null;
|
|
57
|
+
for (const a of steps) {
|
|
58
|
+
for (const b of steps) {
|
|
59
|
+
if (a.family !== b.family || a.step >= b.step)
|
|
60
|
+
continue;
|
|
61
|
+
/** O valor tem de cair ENTRE os dois - extrapolar seria inventar a continuação da escala. */
|
|
62
|
+
const lo = Math.min(a.l, b.l);
|
|
63
|
+
const hi = Math.max(a.l, b.l);
|
|
64
|
+
if (l <= lo || l >= hi)
|
|
65
|
+
continue;
|
|
66
|
+
/** E na COR também: fora do croma dos dois vizinhos, não é um degrau desta escala. */
|
|
67
|
+
const cLo = Math.min(a.c, b.c);
|
|
68
|
+
const cHi = Math.max(a.c, b.c);
|
|
69
|
+
if (c < cLo || c > cHi)
|
|
70
|
+
continue;
|
|
71
|
+
const mid = Math.round((a.step + b.step) / 2 / 50) * 50;
|
|
72
|
+
if (mid === a.step || mid === b.step)
|
|
73
|
+
continue;
|
|
74
|
+
/** O degrau já existe: então este valor não é um degrau que falta, é outra decisão. */
|
|
75
|
+
if (steps.some((s) => s.family === a.family && s.step === mid))
|
|
76
|
+
continue;
|
|
77
|
+
const gap = b.step - a.step;
|
|
78
|
+
if (!best || gap < best.gap)
|
|
79
|
+
best = { family: a.family, step: mid, gap };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!best)
|
|
83
|
+
return null;
|
|
84
|
+
return {
|
|
85
|
+
path: `${best.family.replace(/-/g, ".")}.${best.step}`,
|
|
86
|
+
because: `between your ${best.family}-${best.step - Math.round(best.gap / 2)} and ${best.family}-${best.step + Math.round(best.gap / 2)}`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* PERTO DEMAIS DE UM TOKEN DELE NÃO É UM DEGRAU NOVO - é a mesma decisão escrita duas vezes.
|
|
91
|
+
*
|
|
92
|
+
* `nearestOwn` já diz isso na proposta do `absorb` (o `≈` que aparece na lista), e propor um nome
|
|
93
|
+
* ali empurraria para BATIZAR o que a leitura já diz para NORMALIZAR. As duas linhas se
|
|
94
|
+
* contradiriam na mesma tela.
|
|
95
|
+
*/
|
|
96
|
+
export function tooCloseToPropose(literal, theirs, jnd = 2) {
|
|
97
|
+
const v = normalizeValue(literal, theirs.rootPx);
|
|
98
|
+
for (const value of theirs.byName.values()) {
|
|
99
|
+
const d = deltaE(v, normalizeValue(value, theirs.rootPx));
|
|
100
|
+
if (d !== null && d < jnd)
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* A SEGUNDA FONTE - ESPECIFICADA, TENTADA, E NÃO ENTREGUE. O motivo é medido, e ele é o valor deste
|
|
107
|
+
* comentário.
|
|
108
|
+
*
|
|
109
|
+
* A especificação da PARTE 2 (07/09, por entrevista) admite derivar *"da função observada no código
|
|
110
|
+
* dele - o valor aparece 12× em `background` e 0× em texto, então é proposto como uma superfície"*.
|
|
111
|
+
*
|
|
112
|
+
* O QUE A TENTATIVA ENCONTROU, no mesmo dia: `scanSource` reporta UMA ocorrência por literal POR
|
|
113
|
+
* LINHA. Numa linha com `background: "#7a1f1f"` e `borderColor: "#7a1f1f"`, ele devolve um achado -
|
|
114
|
+
* e a contagem por propriedade opera sobre uma amostra que não é a contagem real. A proposta então
|
|
115
|
+
* afirmaria *"todos os seus 2 usos são texto"* sobre um valor que também é borda.
|
|
116
|
+
*
|
|
117
|
+
* Uma afirmação falsa sobre o repositório DELE é exatamente o que esta parte existe para impedir, e
|
|
118
|
+
* é a razão de a fonte 2 não estar aqui. O pré-requisito está nomeado: o scan precisa reportar cada
|
|
119
|
+
* ocorrência, e não a primeira de cada literal por linha - o que muda a granularidade de tudo que
|
|
120
|
+
* consome `findings`, e por isso é uma frente própria, não uma linha a mais neste arquivo.
|
|
121
|
+
*/
|
package/package.json
CHANGED