synthesisui 0.16.400 → 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/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
|
/**
|
|
@@ -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