synthesisui 0.16.436 → 0.16.440
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/import.js +78 -9
- package/dist/doctor/fragments.js +5 -0
- package/dist/doctor/value-ledger.js +51 -4
- package/dist/global-wear.js +19 -0
- package/dist/install-marks.js +1 -1
- package/dist/report-html.js +121 -2
- package/dist/report-insight.js +1 -1
- package/dist/report-unread.js +144 -0
- package/package.json +1 -1
package/dist/commands/import.js
CHANGED
|
@@ -44,7 +44,7 @@ import { variantCallsIn } from "../doctor/variant-calls.js";
|
|
|
44
44
|
import { transcribeVariants } from "../doctor/variant-read.js";
|
|
45
45
|
import { frontierKind, packageRoot } from "../frontier-kind.js";
|
|
46
46
|
import { keyframesInSheets } from "../global-keyframes.js";
|
|
47
|
-
import { globalClassesWorn } from "../global-wear.js";
|
|
47
|
+
import { globalClassesWorn, porNecessidade } from "../global-wear.js";
|
|
48
48
|
import { withLibraryStructure } from "../library-structure.js";
|
|
49
49
|
import { architectureGaps, mergeCensus } from "../merge-census.js";
|
|
50
50
|
import { claimName } from "../name-claim.js";
|
|
@@ -57,6 +57,7 @@ import { phase, startProgress } from "../progress.js";
|
|
|
57
57
|
import { repoStateOf } from "../repo-state.js";
|
|
58
58
|
import { relatorioHtml, secoesDe } from "../report-html.js";
|
|
59
59
|
import { abertura, aneis, historiaDeCor, oQueMuda, passos, } from "../report-insight.js";
|
|
60
|
+
import { naoLido } from "../report-unread.js";
|
|
60
61
|
import { runtimeDeclaredVars, runtimeDeclaredVarsIn } from "../runtime-vars.js";
|
|
61
62
|
import { detectStack, resolveDeps, stackVersions } from "../stack.js";
|
|
62
63
|
import { desligarCaptura, ligarCaptura } from "../transcript.js";
|
|
@@ -246,7 +247,22 @@ async function harvestOwnCss(root,
|
|
|
246
247
|
* (dono, 04/08). O veredito de cada uma sai depois, em `judgeFragments`: pertencer a componente
|
|
247
248
|
* não admitido é um motivo, não um desaparecimento.
|
|
248
249
|
*/
|
|
249
|
-
into
|
|
250
|
+
into,
|
|
251
|
+
/**
|
|
252
|
+
* A RAIZ DO REPOSITÓRIO, quando a medição está escopada - e ela entra SÓ pelas folhas globais.
|
|
253
|
+
*
|
|
254
|
+
* O QUE O CLIENTE GANHA (dono, 19/09): a classe que o componente dele veste é resolvida onde ela
|
|
255
|
+
* for DEFINIDA no repositório, e não só se ela morar dentro do escopo apontado. Medido nos
|
|
256
|
+
* repositórios reais: dentro do escopo de um deles existem 19 folhas globais e 7 classes
|
|
257
|
+
* aproveitáveis, enquanto o repositório inteiro declara 815 em 24 folhas - e os nomes que o
|
|
258
|
+
* componente veste e a leitura não resolvia (`d-flex`, `col-xs-12`, `icon-round`) estavam
|
|
259
|
+
* justamente do lado de fora.
|
|
260
|
+
*
|
|
261
|
+
* SÓ AS CLASSES, e esta é a linha que o parâmetro não cruza: `css` continua sendo o do escopo,
|
|
262
|
+
* porque é dele que sai a tabela de tokens DECLARADOS. Puxar a folha do repositório inteiro para
|
|
263
|
+
* lá faria o vocabulário do sistema absorver todo CSS do projeto, que é outra promessa.
|
|
264
|
+
*/
|
|
265
|
+
alsoGlobalsFrom) {
|
|
250
266
|
/**
|
|
251
267
|
* DOIS PASSES, porque o escopo de uma folha está no GRAFO e não no nome dela.
|
|
252
268
|
*
|
|
@@ -261,6 +277,24 @@ into) {
|
|
|
261
277
|
const body = await readFile(file, "utf8").catch(() => "");
|
|
262
278
|
sheets.push({ file, body });
|
|
263
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* AS FOLHAS DO RESTO DO REPOSITÓRIO - lidas com a MESMA régua, e nunca com uma segunda.
|
|
282
|
+
* `deForaDoEscopo` marca quais são, porque elas só podem virar classe global: nem `css`, nem
|
|
283
|
+
* ledger, nem fragmento. Duas classificações da mesma folha seriam duas verdades.
|
|
284
|
+
*/
|
|
285
|
+
const deForaDoEscopo = new Set();
|
|
286
|
+
if (alsoGlobalsFrom && alsoGlobalsFrom !== root) {
|
|
287
|
+
const dentro = `${root}${sep}`;
|
|
288
|
+
for await (const file of walkAll([alsoGlobalsFrom])) {
|
|
289
|
+
if (!/\.(css|scss|sass|less)$/i.test(file))
|
|
290
|
+
continue;
|
|
291
|
+
if (file === root || file.startsWith(dentro))
|
|
292
|
+
continue;
|
|
293
|
+
const body = await readFile(file, "utf8").catch(() => "");
|
|
294
|
+
deForaDoEscopo.add(file);
|
|
295
|
+
sheets.push({ file, body });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
264
298
|
/** Todo caminho que alguma folha importa, resolvido como o sass resolveria. */
|
|
265
299
|
const imported = new Set();
|
|
266
300
|
for (const sheet of sheets)
|
|
@@ -269,8 +303,15 @@ into) {
|
|
|
269
303
|
imported.add(join(dirname(sheet.file), candidate));
|
|
270
304
|
let css = "";
|
|
271
305
|
const globals = [];
|
|
272
|
-
|
|
273
|
-
|
|
306
|
+
/** As de fora entram ANTES, para que uma classe do escopo ganhe dela no cascade do leitor. */
|
|
307
|
+
const ordenadas = [
|
|
308
|
+
...sheets.filter((s) => deForaDoEscopo.has(s.file)),
|
|
309
|
+
...sheets.filter((s) => !deForaDoEscopo.has(s.file)),
|
|
310
|
+
];
|
|
311
|
+
for (const { file, body } of ordenadas) {
|
|
312
|
+
const fora = deForaDoEscopo.has(file);
|
|
313
|
+
if (!fora)
|
|
314
|
+
css += `\n${body}`;
|
|
274
315
|
if (!body)
|
|
275
316
|
continue;
|
|
276
317
|
const partial = basename(file).startsWith("_");
|
|
@@ -280,8 +321,14 @@ into) {
|
|
|
280
321
|
? "dead"
|
|
281
322
|
: "global";
|
|
282
323
|
if (kind === "global")
|
|
283
|
-
globals.push({
|
|
284
|
-
|
|
324
|
+
globals.push({
|
|
325
|
+
/** O caminho de uma folha de fora se lê da RAIZ, senão ele sai cheio de `../`. */
|
|
326
|
+
file: relative(fora ? (alsoGlobalsFrom ?? root) : root, file),
|
|
327
|
+
body,
|
|
328
|
+
...(fora ? { outside: true } : {}),
|
|
329
|
+
});
|
|
330
|
+
/** Uma folha de fora do escopo não vira fragmento: o ledger conta o que ELE mandou medir. */
|
|
331
|
+
if (fora || !into)
|
|
285
332
|
continue;
|
|
286
333
|
into.push({
|
|
287
334
|
file: relative(root, file),
|
|
@@ -503,7 +550,7 @@ export async function takeCensus(root, opts) {
|
|
|
503
550
|
useLiveCatalogue(fetched.ok ? asCatalogueTable(fetched.index) : null);
|
|
504
551
|
/** Todo arquivo de estilo do escopo, para o ledger - ver o parâmetro `into`. */
|
|
505
552
|
const styleFiles = [];
|
|
506
|
-
const { css, globals: globalSheets } = await harvestOwnCss(root, styleFiles);
|
|
553
|
+
const { css, globals: globalSheets } = await harvestOwnCss(root, styleFiles, repoRoot);
|
|
507
554
|
const table = buildTable({ css, source: "yours" });
|
|
508
555
|
const schemes = parseSchemeBlocks(css);
|
|
509
556
|
const reports = [];
|
|
@@ -577,6 +624,16 @@ export async function takeCensus(root, opts) {
|
|
|
577
624
|
* escreva utilitário por atacado, e existe porque um censo que dobra de tamanho em silêncio é o
|
|
578
625
|
* tipo de surpresa que ninguém pede.
|
|
579
626
|
*/
|
|
627
|
+
/**
|
|
628
|
+
* O QUE VEIO DE FORA DO ESCOPO É DITO, e não colhido em silêncio (dono, 19/09).
|
|
629
|
+
*
|
|
630
|
+
* Ele apontou uma pasta e a leitura foi buscar a definição de classe no resto do repositório. Isso
|
|
631
|
+
* melhora o número dele, e um número que melhora sem explicação é um número em que ninguém confia.
|
|
632
|
+
*/
|
|
633
|
+
const deForaDoEscopo = globalSheets.filter((g) => g.outside).length;
|
|
634
|
+
if (deForaDoEscopo > 0) {
|
|
635
|
+
console.log(body(paint.faint(`${deForaDoEscopo} stylesheets outside the folder you pointed at were read for class definitions only - a class your components wear is resolved where it is defined, and nothing from there becomes a recipe.`)));
|
|
636
|
+
}
|
|
580
637
|
if (globalClasses.size > MAX_GLOBAL_CLASSES) {
|
|
581
638
|
console.log(body(paint.faint(`${globalClasses.size} classes in your global sheet, and ${globalClasses.size - MAX_GLOBAL_CLASSES} of them are not travelling with this measurement - the first ${MAX_GLOBAL_CLASSES} are.`)));
|
|
582
639
|
}
|
|
@@ -2052,6 +2109,8 @@ export async function takeCensus(root, opts) {
|
|
|
2052
2109
|
...globalClassesClaimed,
|
|
2053
2110
|
...globalClassesWorn(looks, globalClasses),
|
|
2054
2111
|
]),
|
|
2112
|
+
/** Toda classe que a folha DELE define - ver `definedGlobal` em `judgeFragments`. */
|
|
2113
|
+
definedGlobal: new Set(globalClasses.keys()),
|
|
2055
2114
|
islandRead: islandClassesRead,
|
|
2056
2115
|
refused: new Set(skips.map((s) => s.file)),
|
|
2057
2116
|
/**
|
|
@@ -2149,7 +2208,9 @@ export async function takeCensus(root, opts) {
|
|
|
2149
2208
|
for (const layer of look.rawLayers ?? [])
|
|
2150
2209
|
tokens.push(...layer.classes);
|
|
2151
2210
|
if (tokens.length > 0) {
|
|
2152
|
-
valueByComponent[lookName] = accountClasses(tokens, declaredValues, answeredTokens
|
|
2211
|
+
valueByComponent[lookName] = accountClasses(tokens, declaredValues, answeredTokens,
|
|
2212
|
+
/** A MESMA resposta do julgamento por fragmento - ver `definedGlobal`. */
|
|
2213
|
+
new Set(globalClasses.keys()));
|
|
2153
2214
|
}
|
|
2154
2215
|
}
|
|
2155
2216
|
const valuesTotal = sumAccounts(Object.values(valueByComponent));
|
|
@@ -2619,7 +2680,10 @@ export async function takeCensus(root, opts) {
|
|
|
2619
2680
|
*/
|
|
2620
2681
|
...(globalClasses.size > 0
|
|
2621
2682
|
? {
|
|
2622
|
-
globalClasses: Object.fromEntries(
|
|
2683
|
+
globalClasses: Object.fromEntries(porNecessidade(globalClasses, new Set([
|
|
2684
|
+
...globalClassesClaimed,
|
|
2685
|
+
...globalClassesWorn(looks, globalClasses),
|
|
2686
|
+
])).slice(0, MAX_GLOBAL_CLASSES)),
|
|
2623
2687
|
}
|
|
2624
2688
|
: {}),
|
|
2625
2689
|
classStyle,
|
|
@@ -3786,6 +3850,11 @@ async function mostrarInspecao(c, root, transcrito) {
|
|
|
3786
3850
|
*/
|
|
3787
3851
|
abertura: abertura(c, c.measured?.repo ?? basename(root)),
|
|
3788
3852
|
aneis: aneis(c),
|
|
3853
|
+
/**
|
|
3854
|
+
* QUAIS SAO OS NAO LIDOS, LOGO ABAIXO DO ANEL QUE OS ANUNCIA (dono, 19/09) - e partidos pela
|
|
3855
|
+
* pergunta que decide se aquilo e' problema dele: veste um componente que ele entrega, ou nao?
|
|
3856
|
+
*/
|
|
3857
|
+
naoLido: naoLido(c),
|
|
3789
3858
|
cor: historiaDeCor(c),
|
|
3790
3859
|
/**
|
|
3791
3860
|
* O COMANDO NO TOPO E NO FIM (dono, 18/09): quem decidiu adotar no meio da leitura nao
|
package/dist/doctor/fragments.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arranjoSemValor } from "./value-ledger.js";
|
|
1
2
|
/**
|
|
2
3
|
* TODO FRAGMENTO QUE CARREGA ESTILO, com arquivo e linha.
|
|
3
4
|
*
|
|
@@ -529,6 +530,7 @@ elsewhere,
|
|
|
529
530
|
*/
|
|
530
531
|
forms = []) {
|
|
531
532
|
const wornGlobal = elsewhere?.wornGlobal ?? new Set();
|
|
533
|
+
const definedGlobal = elsewhere?.definedGlobal ?? new Set();
|
|
532
534
|
const islandRead = elsewhere?.islandRead ?? new Map();
|
|
533
535
|
const refused = elsewhere?.refused ?? new Set();
|
|
534
536
|
const captured = elsewhere?.keyframes ?? new Set();
|
|
@@ -645,7 +647,10 @@ forms = []) {
|
|
|
645
647
|
const tokens = literal.split(/\s+/).filter(Boolean);
|
|
646
648
|
const readElsewhere = tokens.length > 0 &&
|
|
647
649
|
tokens.every((tk) => STRUCTURE.has(tk) ||
|
|
650
|
+
/** Arranjo sem valor de design - a MESMA resposta que a conta do anel dá. */
|
|
651
|
+
arranjoSemValor(tk) ||
|
|
648
652
|
wornGlobal.has(tk) ||
|
|
653
|
+
definedGlobal.has(tk) ||
|
|
649
654
|
islandRead.get(f.file)?.has(tk));
|
|
650
655
|
if (readElsewhere)
|
|
651
656
|
return { ...f, read: true };
|
|
@@ -95,6 +95,37 @@ const STRUCTURAL_TOKENS = new Set([
|
|
|
95
95
|
"peer",
|
|
96
96
|
"isolate",
|
|
97
97
|
]);
|
|
98
|
+
/**
|
|
99
|
+
* O ARRANJO QUE NÃO CARREGA VALOR DE DESIGN - e por isso sai do denominador (dono, 19/09).
|
|
100
|
+
*
|
|
101
|
+
* O QUE O CLIENTE GANHA: a cobertura dele para de ser descontada por `mx-auto` e `col-span-6`.
|
|
102
|
+
* Medido nos dois repositórios limpos, `mx-auto` é o utilitário que mais aparece no que sobrava
|
|
103
|
+
* como não lido - 12 ocorrências num deles, o maior item isolado da lista inteira. Centrar um
|
|
104
|
+
* bloco e dizer quantas colunas ele ocupa não são decisões de design: são arranjo, a mesma coisa
|
|
105
|
+
* que `flex-col` e `w-full`, que já saem por `STRUCTURAL_TOKENS`.
|
|
106
|
+
*
|
|
107
|
+
* O TESTE É O VALOR, e não o nome da família. Estas famílias já resolvem para propriedades que
|
|
108
|
+
* `STRUCTURAL_PROPERTIES` conhece - `gridColumn`, `gridTemplateColumns`, `order` - e o que decide
|
|
109
|
+
* é o outro lado: o valor é uma CONTAGEM (`6`, `12`) ou a palavra `auto`, nenhum dos dois sendo
|
|
110
|
+
* quantidade que alguém escolheu de uma escala. `gap-6` continua fora daqui de propósito, porque
|
|
111
|
+
* ali o número É uma escolha de escala: ele muda o ritmo da tela.
|
|
112
|
+
*
|
|
113
|
+
* A LISTA CONTINUA CONSERVADORA, pelo mesmo motivo escrito em `STRUCTURAL_TOKENS`: o que não
|
|
114
|
+
* estiver aqui cai em `unread`, que é o lado certo de errar. E ela vive NESTE arquivo, junto das
|
|
115
|
+
* outras duas, porque três fontes para "isto é arranjo?" discordariam no primeiro conserto.
|
|
116
|
+
*/
|
|
117
|
+
const ARRANJO_SEM_VALOR = [
|
|
118
|
+
/** Grade e ordem: o valor é uma contagem de trilhas, `auto`, `full` ou `none`. */
|
|
119
|
+
/^(col|row)-(span|start|end)-(\d+|auto|full)$/,
|
|
120
|
+
/^grid-(cols|rows)-(\d+|none|subgrid)$/,
|
|
121
|
+
/^order-(\d+|first|last|none)$/,
|
|
122
|
+
/** Margem `auto` é centralização - a única margem cujo valor não sai de escala nenhuma. */
|
|
123
|
+
/^-?m[xytblrse]?-auto$/,
|
|
124
|
+
];
|
|
125
|
+
/** `mx-auto` e `col-span-6` nomeiam arranjo; `gap-6` nomeia uma escolha de escala. */
|
|
126
|
+
export function arranjoSemValor(utility) {
|
|
127
|
+
return ARRANJO_SEM_VALOR.some((re) => re.test(utility));
|
|
128
|
+
}
|
|
98
129
|
/**
|
|
99
130
|
* O destino de UM token de classe. Exportado para o julgamento por valor e a triagem lerem a
|
|
100
131
|
* MESMA resposta - duas cópias desta decisão é como o item 11 nasceu.
|
|
@@ -105,9 +136,21 @@ const STRUCTURAL_TOKENS = new Set([
|
|
|
105
136
|
* disputa o que o leitor resolve sozinho. Sem o conjunto (import fresco, triagem, julgamento por
|
|
106
137
|
* fragmento) a conta é a mesma de sempre.
|
|
107
138
|
*/
|
|
108
|
-
export function destinationOf(cls, declared, answered
|
|
139
|
+
export function destinationOf(cls, declared, answered,
|
|
140
|
+
/**
|
|
141
|
+
* AS CLASSES QUE A FOLHA DELE DEFINE - e é isto que fecha a conta com o julgamento por fragmento
|
|
142
|
+
* (dono, 19/09).
|
|
143
|
+
*
|
|
144
|
+
* Um nome como `icon-round` não é utilitário de escala nenhuma: ele é uma classe que o CSS do
|
|
145
|
+
* projeto declara, com as declarações dela na mão. Chamá-lo de não lido conta como perda uma
|
|
146
|
+
* coisa que a leitura entendeu inteira. Medido num repositório real: sem esta linha o ledger
|
|
147
|
+
* reporta 645 fragmentos com valor não lido, com ela 379 - e a conta do anel, que é esta função,
|
|
148
|
+
* ficava presa no número velho enquanto o capítulo já mostrava o novo. Dois números para a mesma
|
|
149
|
+
* pergunta na mesma página é o defeito que a página acabou de deixar de ter.
|
|
150
|
+
*/
|
|
151
|
+
defined) {
|
|
109
152
|
const { utility } = parseClass(cls);
|
|
110
|
-
if (STRUCTURAL_TOKENS.has(utility))
|
|
153
|
+
if (STRUCTURAL_TOKENS.has(utility) || arranjoSemValor(utility))
|
|
111
154
|
return "structure";
|
|
112
155
|
const read = readUtility(utility, declared);
|
|
113
156
|
if (read) {
|
|
@@ -118,10 +161,14 @@ export function destinationOf(cls, declared, answered) {
|
|
|
118
161
|
/** A grafia respondida é a do código dele - com o modificador fora, como `applyAnswers` casa. */
|
|
119
162
|
if (answered && (answered.has(utility) || answered.has(cls)))
|
|
120
163
|
return "answered";
|
|
164
|
+
if (defined?.has(cls) || defined?.has(utility))
|
|
165
|
+
return "interpreted";
|
|
121
166
|
return "unread";
|
|
122
167
|
}
|
|
123
168
|
/** A conta de uma lista de tokens - a soma SEMPRE fecha com `seen`, por construção. */
|
|
124
|
-
export function accountClasses(tokens, declared, answered
|
|
169
|
+
export function accountClasses(tokens, declared, answered,
|
|
170
|
+
/** As classes que a folha dele define - ver `destinationOf`. */
|
|
171
|
+
defined) {
|
|
125
172
|
const account = {
|
|
126
173
|
seen: 0,
|
|
127
174
|
interpreted: 0,
|
|
@@ -132,7 +179,7 @@ export function accountClasses(tokens, declared, answered) {
|
|
|
132
179
|
};
|
|
133
180
|
for (const cls of tokens) {
|
|
134
181
|
account.seen += 1;
|
|
135
|
-
account[destinationOf(cls, declared, answered)] += 1;
|
|
182
|
+
account[destinationOf(cls, declared, answered, defined)] += 1;
|
|
136
183
|
}
|
|
137
184
|
return account;
|
|
138
185
|
}
|
package/dist/global-wear.js
CHANGED
|
@@ -77,3 +77,22 @@ export function globalClassesWorn(looks, globals) {
|
|
|
77
77
|
}
|
|
78
78
|
return worn;
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* QUAIS CLASSES VIAJAM QUANDO O TETO MORDE - e a ordem é a NECESSIDADE, nunca a do arquivo.
|
|
82
|
+
*
|
|
83
|
+
* O QUE O CLIENTE GANHA: o censo continua do mesmo tamanho e passa a levar as classes que os
|
|
84
|
+
* componentes dele REALMENTE vestem. Medido num repositório real: o repositório declara 815 classes
|
|
85
|
+
* globais e o teto deixa passar 300 - escolhidas por ordem de leitura de arquivo, o que fazia a
|
|
86
|
+
* classe de um componente entregue perder o lugar para a de um relatório de teste, e o valor dela
|
|
87
|
+
* aparecia como não lido do outro lado.
|
|
88
|
+
*
|
|
89
|
+
* VESTIDA PRIMEIRO, o resto na ordem em que foi lido. Não é um corte novo: é o mesmo corte
|
|
90
|
+
* escolhendo por quem precisa.
|
|
91
|
+
*/
|
|
92
|
+
export function porNecessidade(classes, vestidas) {
|
|
93
|
+
const entradas = [...classes.entries()];
|
|
94
|
+
return [
|
|
95
|
+
...entradas.filter(([nome]) => vestidas.has(nome)),
|
|
96
|
+
...entradas.filter(([nome]) => !vestidas.has(nome)),
|
|
97
|
+
];
|
|
98
|
+
}
|
package/dist/install-marks.js
CHANGED
|
@@ -815,7 +815,7 @@ export const CHECKER_SINCE = "0.16.424";
|
|
|
815
815
|
* deveriam estar lá. Quem tem censo gravado precisa de um `sync` para a fila de trabalho dele ser a
|
|
816
816
|
* real; ver o livro-razão de `corpus.spec.ts`.
|
|
817
817
|
*/
|
|
818
|
-
export const READER_SINCE = "0.16.
|
|
818
|
+
export const READER_SINCE = "0.16.440";
|
|
819
819
|
/**
|
|
820
820
|
* O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
|
|
821
821
|
*
|
package/dist/report-html.js
CHANGED
|
@@ -93,7 +93,7 @@ ${layout()}
|
|
|
93
93
|
<div class="bar-fim">
|
|
94
94
|
<p class="meta">${escapar(quando)} · CLI ${escapar(cabecalho.cli)} · nothing left this machine</p>${input.comando
|
|
95
95
|
? `
|
|
96
|
-
<
|
|
96
|
+
<a class="comando topo" href="${INSTALL_URL}" target="_blank" rel="noreferrer">${escapar(input.comando)}<i aria-hidden="true">\u2192</i></a>`
|
|
97
97
|
: ""}
|
|
98
98
|
</div>
|
|
99
99
|
</div>
|
|
@@ -115,6 +115,7 @@ ${layout()}
|
|
|
115
115
|
${numeros}
|
|
116
116
|
</div>
|
|
117
117
|
${desenharAneis(input.aneis ?? [])}
|
|
118
|
+
${desenharNaoLido(input.naoLido)}
|
|
118
119
|
${desenharCor(input.cor)}
|
|
119
120
|
</section>
|
|
120
121
|
|
|
@@ -208,6 +209,70 @@ function desenharAneis(aneis) {
|
|
|
208
209
|
${cartoes}
|
|
209
210
|
</section>`;
|
|
210
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* O CAPÍTULO QUE RESPONDE "QUAIS SÃO OS 8%", LOGO ABAIXO DO ANEL QUE OS ANUNCIA (dono, 19/09).
|
|
214
|
+
*
|
|
215
|
+
* O QUE O CLIENTE GANHA: ele para de procurar. O anel dizia que o resto estava nomeado "nos
|
|
216
|
+
* capítulos abaixo" e os capítulos contavam outra população - medido na página dele, 143 contra
|
|
217
|
+
* 1143. Aqui as duas contas se encontram, e o não lido chega partido pela única pergunta que muda
|
|
218
|
+
* o que ele faz em seguida: isto veste um componente que eu entrego, ou não?
|
|
219
|
+
*
|
|
220
|
+
* A PILHA DE FORA VEM COM AS PASTAS. Uma frase dizendo "não afeta seus componentes" pede confiança;
|
|
221
|
+
* `_local/bkp - 872` entrega a razão junto, e ele reconhece a própria pasta sem abrir arquivo.
|
|
222
|
+
*
|
|
223
|
+
* E O QUE NÃO DÁ PARA NOMEAR É DITO. Quando o anel conta mais decisões do que este capítulo mostra
|
|
224
|
+
* com arquivo e linha, a diferença aparece em uma frase - porque a promessa quebrada de nomear é
|
|
225
|
+
* exatamente o defeito que este capítulo existe para consertar.
|
|
226
|
+
*/
|
|
227
|
+
function desenharNaoLido(n) {
|
|
228
|
+
if (!n || n.total === 0)
|
|
229
|
+
return "";
|
|
230
|
+
const semTexto = n.contagem.semTexto;
|
|
231
|
+
const reconciliacao = n.contagem.anel > 0
|
|
232
|
+
? `Your components hold ${n.contagem.anel} style ${plural(n.contagem.anel, "decision")} this version did not read. Below is every unread fragment whose text we kept, split by the only question that changes what you do next: does it dress a component you ship?`
|
|
233
|
+
: `Below is every unread fragment whose text we kept, split by the only question that changes what you do next: does it dress a component you ship?`;
|
|
234
|
+
const lacuna = semTexto > 0
|
|
235
|
+
? ` <p class="callout">${escapar(`${n.total} fragments were counted in all; ${semTexto} of them were counted without their text being kept, so for those this version can tell you how many and not which - that is our gap, not a shape of yours.`)}</p>\n`
|
|
236
|
+
: "";
|
|
237
|
+
const pastas = n.pastas.length > 0
|
|
238
|
+
? ` <p class="pastas">${n.pastas
|
|
239
|
+
.map((p) => `<span><b>${escapar(p.pasta)}</b> ${p.fragmentos}</span>`)
|
|
240
|
+
.join("")}</p>\n`
|
|
241
|
+
: "";
|
|
242
|
+
return ` <section class="chapter nao-lido" id="not-read" style="--ordem:2">
|
|
243
|
+
<h2><span>What we could not read, and whether it touches you</span></h2>
|
|
244
|
+
<p class="callout">${escapar(reconciliacao)}</p>
|
|
245
|
+
${lacuna} <div class="pilhas">
|
|
246
|
+
<div class="pilha toca">
|
|
247
|
+
<h3>On components you ship</h3>
|
|
248
|
+
<p class="pilha-conta"><b>${n.dentro.fragmentos}</b> ${plural(n.dentro.fragmentos, "fragment")}</p>
|
|
249
|
+
<p class="pilha-peso">${escapar(n.dentro.fragmentos === 0 ? "Nothing here is waiting on us: every style decision on the components you ship was read." : "This is the pile that still stands between your report and 100%, and closing it is reading work on our side - nothing for you to change.")}</p>
|
|
250
|
+
${grupos(n.dentro.grupos)} </div>
|
|
251
|
+
<div class="pilha fora">
|
|
252
|
+
<h3>Outside what you ship</h3>
|
|
253
|
+
<p class="pilha-conta"><b>${n.fora.fragmentos}</b> ${plural(n.fora.fragmentos, "fragment")}</p>
|
|
254
|
+
<p class="pilha-peso">${escapar("These sit in files that dress no component in your system - a global sheet, a folder you do not ship, a component this scope does not admit. Reading them would not change the consistency of your design.")}</p>
|
|
255
|
+
${pastas}${grupos(n.fora.grupos)} </div>
|
|
256
|
+
</div>
|
|
257
|
+
</section>`;
|
|
258
|
+
}
|
|
259
|
+
/** Os grupos de uma pilha: a frase do motivo, o tamanho, e as linhas que provam. */
|
|
260
|
+
function grupos(lista) {
|
|
261
|
+
if (lista.length === 0)
|
|
262
|
+
return "";
|
|
263
|
+
return `${lista
|
|
264
|
+
.map((g) => ` <div class="motivo">
|
|
265
|
+
<p><b>${g.fragmentos}</b> in ${g.arquivos} ${plural(g.arquivos, "file")} - ${escapar(g.porque)}</p>
|
|
266
|
+
${g.exemplos
|
|
267
|
+
.map((e) => ` <code class="linha">${escapar(e.file)}${typeof e.line === "number" ? `:${e.line}` : ""} ${escapar(e.text)}</code>`)
|
|
268
|
+
.join("\n")}
|
|
269
|
+
</div>`)
|
|
270
|
+
.join("\n")}\n`;
|
|
271
|
+
}
|
|
272
|
+
/** Singular quando é um. Um relatório que escreve "1 files" perde a autoridade na primeira linha. */
|
|
273
|
+
function plural(n, palavra) {
|
|
274
|
+
return n === 1 ? palavra : `${palavra}s`;
|
|
275
|
+
}
|
|
211
276
|
/**
|
|
212
277
|
* A HISTÓRIA DAS CORES, com a paleta desenhada do lado - as cores DELA, nos valores dela.
|
|
213
278
|
*
|
|
@@ -220,7 +285,7 @@ function desenharCor(cor) {
|
|
|
220
285
|
const amostras = cor.paleta
|
|
221
286
|
.map((c) => `<i style="--amostra:${escapar(c)}" title="${escapar(c)}"></i>`)
|
|
222
287
|
.join("");
|
|
223
|
-
return ` <section class="chapter cor" style="--ordem:
|
|
288
|
+
return ` <section class="chapter cor" style="--ordem:3">
|
|
224
289
|
<h2><span>Your colours</span></h2>
|
|
225
290
|
${cor.linhas.map((l) => ` <p class="callout">${escapar(l)}</p>`).join("\n")}
|
|
226
291
|
<div class="paleta" aria-hidden="true">${amostras}</div>
|
|
@@ -764,6 +829,60 @@ footer { margin-top: 40px; color: var(--ds-color-semantic-muted); font-size: 13p
|
|
|
764
829
|
}
|
|
765
830
|
.botao:hover { opacity: 0.9; }
|
|
766
831
|
|
|
832
|
+
/* O COMANDO DO TOPO E' UM BOTAO: ele abre a pagina de instalacao, entao nao se comporta como
|
|
833
|
+
texto para selecionar - e a seta diz que leva a algum lugar antes de alguem descobrir clicando. */
|
|
834
|
+
a.comando.topo {
|
|
835
|
+
display: inline-flex;
|
|
836
|
+
align-items: center;
|
|
837
|
+
gap: 8px;
|
|
838
|
+
text-decoration: none;
|
|
839
|
+
user-select: none;
|
|
840
|
+
}
|
|
841
|
+
a.comando.topo i { font-style: normal; color: var(--ds-color-semantic-accent); }
|
|
842
|
+
a.comando.topo:hover { border-color: var(--ds-color-semantic-accent); }
|
|
843
|
+
|
|
844
|
+
/* AS DUAS PILHAS DO NAO LIDO, LADO A LADO: a comparacao e' o conteudo, entao elas nao se empilham
|
|
845
|
+
enquanto houver largura - ler "isto me afeta" e "isto nao" em sequencia perde a comparacao. */
|
|
846
|
+
.pilhas { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; margin-top: 4px; }
|
|
847
|
+
@media (max-width: 860px) { .pilhas { grid-template-columns: 1fr; } }
|
|
848
|
+
.pilha {
|
|
849
|
+
border: 1px solid var(--ds-color-semantic-border);
|
|
850
|
+
border-radius: var(--ds-radius-lg, 12px);
|
|
851
|
+
padding: 18px 20px;
|
|
852
|
+
min-width: 0;
|
|
853
|
+
}
|
|
854
|
+
.pilha.toca { border-color: var(--ds-color-semantic-accent); }
|
|
855
|
+
.pilha h3 { margin: 0; font-size: 14px; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
856
|
+
.pilha-conta { margin: 10px 0 6px; font-size: 14px; color: var(--ds-color-semantic-muted); }
|
|
857
|
+
.pilha-conta b { font-size: 26px; letter-spacing: -0.02em; color: var(--ds-color-semantic-foreground); }
|
|
858
|
+
.pilha-peso { margin: 0 0 14px; font-size: 14px; line-height: 1.55; color: var(--ds-color-semantic-muted); }
|
|
859
|
+
/* AS PASTAS: o reconhecimento vem antes da explicacao - ele ve a propria pasta e ja' sabe. */
|
|
860
|
+
.pastas { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
|
|
861
|
+
.pastas span {
|
|
862
|
+
font-family: ${mono};
|
|
863
|
+
font-size: 12px;
|
|
864
|
+
padding: 4px 10px;
|
|
865
|
+
border-radius: var(--ds-radius-sm, 8px);
|
|
866
|
+
background: var(--ds-color-semantic-canvas);
|
|
867
|
+
border: 1px solid var(--ds-color-semantic-border);
|
|
868
|
+
color: var(--ds-color-semantic-muted);
|
|
869
|
+
}
|
|
870
|
+
.pastas b { color: var(--ds-color-semantic-foreground); font-weight: 600; }
|
|
871
|
+
.motivo { margin: 0 0 14px; }
|
|
872
|
+
.motivo p { margin: 0 0 6px; font-size: 14px; line-height: 1.5; color: var(--ds-color-semantic-muted); }
|
|
873
|
+
.motivo p b { color: var(--ds-color-semantic-foreground); }
|
|
874
|
+
.linha {
|
|
875
|
+
display: block;
|
|
876
|
+
font-family: ${mono};
|
|
877
|
+
font-size: 12px;
|
|
878
|
+
line-height: 1.6;
|
|
879
|
+
padding: 3px 0 3px 12px;
|
|
880
|
+
border-left: 2px solid var(--ds-color-semantic-border);
|
|
881
|
+
color: var(--ds-color-semantic-muted);
|
|
882
|
+
white-space: pre-wrap;
|
|
883
|
+
word-break: break-word;
|
|
884
|
+
}
|
|
885
|
+
|
|
767
886
|
.lista-passos { list-style: none; margin: 0; padding: 0; display: grid; gap: 22px; }
|
|
768
887
|
.lista-passos li {
|
|
769
888
|
border-left: 2px solid var(--ds-color-semantic-border);
|
package/dist/report-insight.js
CHANGED
|
@@ -39,7 +39,7 @@ export function aneis(c) {
|
|
|
39
39
|
numero: `${pct(cl.interpreted, decisoes)}%`,
|
|
40
40
|
parte: cl.interpreted,
|
|
41
41
|
de: decisoes,
|
|
42
|
-
frase: `Of the ${decisoes} style decisions in your components, we understood ${cl.interpreted}. The remaining ${decisoes - cl.interpreted} are shapes this version does not read yet -
|
|
42
|
+
frase: `Of the ${decisoes} style decisions in your components, we understood ${cl.interpreted}. The remaining ${decisoes - cl.interpreted} are shapes this version does not read yet - the chapter right below says where each one lives, and whether it touches a component you ship.`,
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
return saida;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O QUE A LEITURA NÃO ENTENDEU, RESPONDIDO NUMA TELA SÓ (dono, 19/09).
|
|
3
|
+
*
|
|
4
|
+
* O QUE O CLIENTE GANHA. O anel diz "92%" e manda procurar o resto "nos capítulos abaixo". Medido
|
|
5
|
+
* no relatório dele: o anel conta 143 decisões não lidas e o capítulo que deveria nomeá-las conta
|
|
6
|
+
* 1143 fragmentos - populações diferentes, e nenhuma frase ligando as duas. Quem quer saber quais
|
|
7
|
+
* são os 8% abre dezesseis capítulos e não acha. Aqui ele lê uma vez: quais, por quê, e se aquilo
|
|
8
|
+
* toca o código que ele entrega.
|
|
9
|
+
*
|
|
10
|
+
* A CLASSIFICAÇÃO É DERIVADA, NUNCA FIXADA. O que separa as duas pilhas é o que O PROJETO DELE
|
|
11
|
+
* declara: um fragmento está DENTRO quando o arquivo dele é o arquivo de um componente que o censo
|
|
12
|
+
* admite (`looks[X].api.file`), e FORA quando não é - folha global, pasta de backup, componente que
|
|
13
|
+
* o escopo não admite. Nenhum nome de pasta, de framework ou de convenção entra nesta decisão:
|
|
14
|
+
* num projeto que ninguém aqui viu, a lista de componentes é a dele, e a régua acompanha.
|
|
15
|
+
*
|
|
16
|
+
* POR QUE A SEPARAÇÃO É O PRODUTO, e não um detalhe de apresentação. "8% não lido" lê-se como
|
|
17
|
+
* dívida enquanto ninguém diz onde aquilo mora. A pilha de FORA não muda a consistência do design
|
|
18
|
+
* dele - são declarações em arquivos que não vestem componente nenhum -, e dizê-lo transforma o
|
|
19
|
+
* mesmo número em "pronto". A pilha de DENTRO é a única que ainda separa o relatório dos 100%, e é
|
|
20
|
+
* ela que vira trabalho de leitura do nosso lado.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* OS DOIS MOTIVOS QUE JÁ DIZEM "FORA" SEM PRECISAR DO ARQUIVO.
|
|
24
|
+
*
|
|
25
|
+
* Um componente que o escopo não admite e uma folha que build nenhum compila estão fora do que ele
|
|
26
|
+
* entrega POR DEFINIÇÃO - e nos dois casos o arquivo pode até ser de componente, o que faria a
|
|
27
|
+
* regra de caminho classificá-los errado. O motivo é mais específico que o caminho, então ele ganha.
|
|
28
|
+
*/
|
|
29
|
+
const FORA_POR_MOTIVO = new Set([
|
|
30
|
+
"component-not-admitted",
|
|
31
|
+
"sheet-not-imported",
|
|
32
|
+
]);
|
|
33
|
+
export function naoLido(c) {
|
|
34
|
+
const grupos = c.ledger?.unread ?? [];
|
|
35
|
+
if (grupos.length === 0)
|
|
36
|
+
return null;
|
|
37
|
+
const deComponente = arquivosDeComponente(c);
|
|
38
|
+
const baldes = new Map();
|
|
39
|
+
const arquivos = { dentro: new Set(), fora: new Set() };
|
|
40
|
+
const porPasta = new Map();
|
|
41
|
+
let total = 0;
|
|
42
|
+
let comTexto = 0;
|
|
43
|
+
for (const g of grupos) {
|
|
44
|
+
const itens = (g.unreadable ?? []).filter((f) => f.file && f.text);
|
|
45
|
+
const mostrar = itens.length > 0 ? itens : (g.examples ?? []);
|
|
46
|
+
/**
|
|
47
|
+
* `uses` é a contagem do ledger e ela vale mais que o tamanho da lista: a lista tem teto, e
|
|
48
|
+
* preferir o comprimento dela encolheria o total do capítulo sem avisar ninguém.
|
|
49
|
+
*/
|
|
50
|
+
total += typeof g.uses === "number" ? g.uses : mostrar.length;
|
|
51
|
+
const porque = g.because ?? g.reason ?? "not interpreted";
|
|
52
|
+
/**
|
|
53
|
+
* DOIS MOTIVOS JÁ DIZEM "FORA" SEM PRECISAR DO ARQUIVO, e eles ganham do caminho: um componente
|
|
54
|
+
* que o escopo não admite e uma folha que build nenhum compila estão fora do que ele entrega
|
|
55
|
+
* por definição, e nos dois casos o arquivo PODE ser de componente.
|
|
56
|
+
*/
|
|
57
|
+
const foraPorMotivo = FORA_POR_MOTIVO.has(g.reason ?? "");
|
|
58
|
+
for (const f of mostrar) {
|
|
59
|
+
if (!f.file || !f.text)
|
|
60
|
+
continue;
|
|
61
|
+
comTexto += 1;
|
|
62
|
+
/**
|
|
63
|
+
* A PILHA É DECIDIDA POR FRAGMENTO, NUNCA PELO GRUPO. Medido no repositório dele: um grupo
|
|
64
|
+
* de 54 fragmentos tinha DOIS arquivos - um componente entregue e um `.jsx` numa pasta de
|
|
65
|
+
* backup -, e decidir pelo grupo levava os 54 inteiros para a pilha que toca componente. A
|
|
66
|
+
* pessoa lia "54 esperando por nós" sobre um arquivo que ela não entrega.
|
|
67
|
+
*/
|
|
68
|
+
const fora = foraPorMotivo || !deComponente.has(comEscopo(c, f.file));
|
|
69
|
+
const chave = `${fora ? "fora" : "dentro"}\u0000${porque}`;
|
|
70
|
+
let balde = baldes.get(chave);
|
|
71
|
+
if (!balde) {
|
|
72
|
+
balde = { porque, fragmentos: 0, arquivos: 0, exemplos: [], fora };
|
|
73
|
+
baldes.set(chave, balde);
|
|
74
|
+
}
|
|
75
|
+
balde.fragmentos += 1;
|
|
76
|
+
arquivos[fora ? "fora" : "dentro"].add(f.file);
|
|
77
|
+
if (fora)
|
|
78
|
+
porPasta.set(pastaDe(f.file), (porPasta.get(pastaDe(f.file)) ?? 0) + 1);
|
|
79
|
+
if (balde.exemplos.length < (fora ? 3 : 6))
|
|
80
|
+
balde.exemplos.push({
|
|
81
|
+
file: f.file,
|
|
82
|
+
...(typeof f.line === "number" ? { line: f.line } : {}),
|
|
83
|
+
text: f.text,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Quantos ARQUIVOS distintos cada grupo toca - contado do que ele realmente viu. */
|
|
88
|
+
for (const balde of baldes.values()) {
|
|
89
|
+
balde.arquivos = new Set(balde.exemplos.map((e) => e.file)).size;
|
|
90
|
+
}
|
|
91
|
+
const pilha = (fora) => {
|
|
92
|
+
const grupos = [...baldes.values()]
|
|
93
|
+
.filter((b) => b.fora === fora)
|
|
94
|
+
.map(({ fora: _, ...g }) => g)
|
|
95
|
+
.sort((a, b) => b.fragmentos - a.fragmentos);
|
|
96
|
+
return {
|
|
97
|
+
fragmentos: grupos.reduce((a, g) => a + g.fragmentos, 0),
|
|
98
|
+
arquivos: arquivos[fora ? "fora" : "dentro"].size,
|
|
99
|
+
grupos,
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
const cl = c.values?.classes;
|
|
103
|
+
const decisoes = (cl?.seen ?? 0) - (cl?.structure ?? 0);
|
|
104
|
+
return {
|
|
105
|
+
total,
|
|
106
|
+
dentro: pilha(false),
|
|
107
|
+
fora: pilha(true),
|
|
108
|
+
pastas: [...porPasta.entries()]
|
|
109
|
+
.map(([pasta, fragmentos]) => ({ pasta, fragmentos }))
|
|
110
|
+
.sort((a, b) => b.fragmentos - a.fragmentos)
|
|
111
|
+
.slice(0, 6),
|
|
112
|
+
contagem: {
|
|
113
|
+
anel: Math.max(0, decisoes - (cl?.interpreted ?? 0)),
|
|
114
|
+
nomeadas: comTexto,
|
|
115
|
+
semTexto: Math.max(0, total - comTexto),
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* OS ARQUIVOS QUE VESTEM UM COMPONENTE QUE O SISTEMA ENTREGA - e eles saem do censo, não de uma
|
|
121
|
+
* lista nossa. `looks` é o que aquele projeto declara ter; num projeto arbitrário essa é a única
|
|
122
|
+
* fonte que sabe quais arquivos importam.
|
|
123
|
+
*/
|
|
124
|
+
function arquivosDeComponente(c) {
|
|
125
|
+
const out = new Set();
|
|
126
|
+
for (const look of Object.values(c.looks ?? {}))
|
|
127
|
+
if (look?.api?.file)
|
|
128
|
+
out.add(look.api.file);
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* O CAMINHO DO FRAGMENTO É RELATIVO AO ESCOPO MEDIDO e o do componente é relativo à raiz. Comparar
|
|
133
|
+
* os dois sem o prefixo não casa NENHUM - medido no censo do cliente: 0 casam sem ele, 4 com ele.
|
|
134
|
+
*/
|
|
135
|
+
function comEscopo(c, file) {
|
|
136
|
+
return c.scope ? `${c.scope}/${file}` : file;
|
|
137
|
+
}
|
|
138
|
+
/** A pasta, com dois níveis: fundo o bastante para distinguir `_local/bkp` de `apps/landing`. */
|
|
139
|
+
function pastaDe(file) {
|
|
140
|
+
const partes = file.split("/");
|
|
141
|
+
if (partes.length <= 1)
|
|
142
|
+
return ".";
|
|
143
|
+
return partes.slice(0, Math.min(2, partes.length - 1)).join("/");
|
|
144
|
+
}
|
package/package.json
CHANGED