synthesisui 0.16.231 → 0.16.234
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 +9 -14
- package/dist/commands/doctor.js +9 -2
- package/dist/commands/use.js +2 -2
- package/dist/component-codegen.js +17 -5
- package/dist/doctor/ledger.js +12 -1
- package/dist/doctor/their-names.js +24 -3
- package/dist/doctor/tokens.js +51 -2
- package/dist/install-marks.js +18 -2
- package/dist/last-sync.js +1 -1
- package/dist/types.js +10 -1
- package/package.json +1 -1
package/dist/absorb-plan.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { deltaE, JND } from "./doctor/color-distance.js";
|
|
2
|
-
import { nearestToken, normalizeValue, } from "./doctor/tokens.js";
|
|
2
|
+
import { familySays, nearestToken, normalizeValue, } from "./doctor/tokens.js";
|
|
3
3
|
/** Onde cada tipo de valor mora na fundação. `color` é o único com dois segmentos. */
|
|
4
4
|
const HOME = {
|
|
5
5
|
color: "color",
|
|
@@ -18,23 +18,18 @@ const clean = (name) => name.replace(/^--/, "").toLowerCase();
|
|
|
18
18
|
* para um achado de espaçamento, e o caminho que sai daí é `typography.h2` sobre uma margem - o mesmo
|
|
19
19
|
* erro de categoria que o `crossFamily` foi criado para impedir do outro lado da esteira.
|
|
20
20
|
*
|
|
21
|
-
* A
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* A pista são as palavras de família do nome dele - `familySays`, a MESMA tabela que o mapa de
|
|
22
|
+
* vocabulário usa para recusar uma sugestão de categoria trocada. Duas listas para a mesma pergunta é
|
|
23
|
+
* como duas telas passam a discordar sobre o mesmo `12px`.
|
|
24
|
+
*
|
|
25
|
+
* Quando nenhum candidato concorda com a natureza do achado, COR ainda passa - um hex numa posição de
|
|
26
|
+
* cor não tem ambiguidade de família - e comprimento não passa: sem concordância não há o que
|
|
27
|
+
* separasse um token de tipo de um de espaçamento, e o valor volta sem caminho para a pessoa decidir.
|
|
25
28
|
*/
|
|
26
|
-
const KIND_WORDS = {
|
|
27
|
-
color: ["color", "colour"],
|
|
28
|
-
radius: ["radius", "rounded"],
|
|
29
|
-
spacing: ["spacing", "space", "gap"],
|
|
30
|
-
font: ["font", "type", "text"],
|
|
31
|
-
motion: ["duration", "motion", "ease", "easing", "transition"],
|
|
32
|
-
};
|
|
33
29
|
function nameOf(kind, candidates) {
|
|
34
30
|
if (!candidates || candidates.length === 0)
|
|
35
31
|
return undefined;
|
|
36
|
-
const
|
|
37
|
-
const agrees = candidates.find((n) => KIND_WORDS[kind].includes(head(n)));
|
|
32
|
+
const agrees = candidates.find((n) => familySays(kind, n));
|
|
38
33
|
if (agrees)
|
|
39
34
|
return agrees;
|
|
40
35
|
return kind === "color" ? candidates[0] : undefined;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -748,7 +748,7 @@ export async function doctor(opts) {
|
|
|
748
748
|
const asideTotal = [...aside.values()].reduce((n, v) => n + v, 0);
|
|
749
749
|
if (verbose) {
|
|
750
750
|
for (const [reason, count] of aside) {
|
|
751
|
-
console.log(body(`
|
|
751
|
+
console.log(body(`out of the count: ${plural(count, "value")} in ${reason}`));
|
|
752
752
|
}
|
|
753
753
|
}
|
|
754
754
|
else if (asideTotal > 0) {
|
|
@@ -1157,7 +1157,14 @@ export async function doctor(opts) {
|
|
|
1157
1157
|
console.log(body("Add the name to the system, or use one it has. Do not leave it."));
|
|
1158
1158
|
}
|
|
1159
1159
|
if (d.findings.length > 0) {
|
|
1160
|
-
|
|
1160
|
+
/**
|
|
1161
|
+
* "Drift" É A NOSSA PALAVRA para um valor fora do sistema, e era o TÍTULO de uma seção inteira.
|
|
1162
|
+
*
|
|
1163
|
+
* A rodada padrão perdeu o vocabulário nosso em 13/08 e o `--verbose` ficou - a guarda rodava
|
|
1164
|
+
* sobre a saída sem `--verbose`, que é o que a maioria lê. O que a seção lista é literal escrito
|
|
1165
|
+
* à mão, e é isso que o título passa a dizer.
|
|
1166
|
+
*/
|
|
1167
|
+
say(section("Values written by hand"));
|
|
1161
1168
|
const order = ["color", "radius", "spacing", "font"].filter((k) => d.counts[k] > 0);
|
|
1162
1169
|
for (const kind of order) {
|
|
1163
1170
|
say(body(`${d.counts[kind]} ${KIND_LABEL[kind]}`));
|
package/dist/commands/use.js
CHANGED
|
@@ -53,9 +53,9 @@ export async function use(slug, intent, opts) {
|
|
|
53
53
|
const hasDoctrine = await exists(join(slugDir, "doctrine.json"));
|
|
54
54
|
const readFirst = [
|
|
55
55
|
hasDoctrine
|
|
56
|
-
? "- Call `system_doctrine` first
|
|
56
|
+
? "- Call `system_doctrine` first - this system's rules and philosophy, pinned to the installed version; obey them above everything else. Without MCP, the same data is `doctrine.json` and the `philosophy` of `design-system.json`"
|
|
57
57
|
: "",
|
|
58
|
-
`- ${base}/v${version}/GUIDE.md
|
|
58
|
+
`- ${base}/v${version}/GUIDE.md - how to apply the system, the recipes and the token vocabulary`,
|
|
59
59
|
].filter(Boolean);
|
|
60
60
|
// The styling contract differs by target: Next projects in this product use
|
|
61
61
|
// Tailwind v4 backed by the DS; the "general" target is framework-agnostic CSS.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PHRASING_FORMS, } from "./types.js";
|
|
1
2
|
export const DEFAULT_CONVENTION = {
|
|
2
3
|
prefix: "ds-",
|
|
3
4
|
partSeparator: "-",
|
|
@@ -106,11 +107,22 @@ function elementFor(name, recipe) {
|
|
|
106
107
|
* UI and a hand-written disclosure, because it reads the SHAPE.
|
|
107
108
|
*/
|
|
108
109
|
if (tag === "button") {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
/**
|
|
111
|
+
* A LISTA SAI DE `PHRASING_FORMS`, e a enumeração à mão estava incompleta - conserto de 14/08.
|
|
112
|
+
*
|
|
113
|
+
* Ela esquecia `heading`, `row` e `stack`, e o custo é a marcação que a lei nomeia logo acima: uma
|
|
114
|
+
* raiz `action` com um heading no topo saía como `<button><h3>…</h3></button>`. Reproduzido pelo
|
|
115
|
+
* próprio codegen:
|
|
116
|
+
*
|
|
117
|
+
* preview.kind = "action", parts = [{ as: "heading" }] -> ComponentProps<"button">
|
|
118
|
+
*
|
|
119
|
+
* E o ramo abaixo já sabia que heading importa - ele devolve `article` quando o topo tem um -,
|
|
120
|
+
* então era código morto: o gate nunca deixava chegar até ele.
|
|
121
|
+
*
|
|
122
|
+
* O complemento de phrasing é a pergunta certa, e não uma lista de formas de bloco: são três nomes
|
|
123
|
+
* de um lado contra oito do outro, e a lista curta é a que não esquece um.
|
|
124
|
+
*/
|
|
125
|
+
const holdsBlock = (nodes) => nodes.some((n) => !PHRASING_FORMS.includes(n.as) ||
|
|
114
126
|
(n.children != null && n.children.length > 0));
|
|
115
127
|
const tree = recipe.preview?.parts;
|
|
116
128
|
if (tree && tree.length > 0 && holdsBlock(tree)) {
|
package/dist/doctor/ledger.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { appendFile, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { nameToWrite } from "./scan.js";
|
|
3
4
|
/**
|
|
4
5
|
* THE LAYER THAT REMEMBERS - governance that cannot answer "is it getting
|
|
5
6
|
* better?" is a sequence of moments, not a system.
|
|
@@ -43,13 +44,23 @@ const MATCHED_CAP = 300;
|
|
|
43
44
|
* `Diagnosis` mora em `scan.ts`, e importar de lá para cá fecharia um ciclo.
|
|
44
45
|
*/
|
|
45
46
|
export function suggestionsFrom(repeats) {
|
|
47
|
+
/**
|
|
48
|
+
* O FILTRO É "TEM NOME", e não "tem nome NOSSO" - senão a contagem do outro lado mente.
|
|
49
|
+
*
|
|
50
|
+
* A versão anterior exigia `r.token`, então as 23 sugestões que só o vocabulário DELE nomeia nunca
|
|
51
|
+
* saíam do repositório: a plataforma achava que 59 ofertas foram feitas onde foram 82. Para COR não
|
|
52
|
+
* há julgamento a perder (um hex numa posição de cor não tem ambiguidade de família), mas "0 de 843
|
|
53
|
+
* erradas" é um fato, e não ter o número é diferente de o número ser zero.
|
|
54
|
+
*/
|
|
46
55
|
return repeats
|
|
47
|
-
.filter((r) => Boolean(r
|
|
56
|
+
.filter((r) => Boolean(nameToWrite(r)))
|
|
48
57
|
.slice(0, MATCHED_CAP)
|
|
49
58
|
.map((r) => ({
|
|
50
59
|
literal: r.literal,
|
|
51
60
|
kind: r.kind,
|
|
52
61
|
token: r.token,
|
|
62
|
+
/** Não-nulo por construção: o filtro acima só deixa passar quem tem nome. */
|
|
63
|
+
shown: nameToWrite(r),
|
|
53
64
|
count: r.count,
|
|
54
65
|
}));
|
|
55
66
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FAMILY_PREFIX, familySays, normalizeValue, } from "./tokens.js";
|
|
2
2
|
/**
|
|
3
3
|
* COMO ELE CHAMA O VALOR - para a gente parar de renomear as variáveis dele.
|
|
4
4
|
*
|
|
@@ -116,7 +116,7 @@ export function theirNames(ours, theirs) {
|
|
|
116
116
|
const head = segments(name)[0] ?? "";
|
|
117
117
|
convention.set(head, (convention.get(head) ?? 0) + 1);
|
|
118
118
|
}
|
|
119
|
-
for (const [kind, prefix] of Object.entries(
|
|
119
|
+
for (const [kind, prefix] of Object.entries(FAMILY_PREFIX)) {
|
|
120
120
|
for (const [name, raw] of ours.byName) {
|
|
121
121
|
if (!name.startsWith(prefix))
|
|
122
122
|
continue;
|
|
@@ -127,7 +127,28 @@ export function theirNames(ours, theirs) {
|
|
|
127
127
|
const candidates = theirs.byValue.get(value);
|
|
128
128
|
if (!candidates || candidates.length === 0)
|
|
129
129
|
continue;
|
|
130
|
-
|
|
130
|
+
/**
|
|
131
|
+
* E A CATEGORIA DO NOME DELE TEM QUE FECHAR - senão o nosso nome fica, e ele está certo.
|
|
132
|
+
*
|
|
133
|
+
* O NOSSO prefixo carrega a família por construção; o dele não carrega nada garantido. Quando a
|
|
134
|
+
* FORMA do valor já decide (hex, `ms`, pilha de fontes) não há o que proteger e o nome dele
|
|
135
|
+
* entra. Quando o valor é um comprimento cru, `12px` pode ser `--text-caption` ou `--spacing-sm`
|
|
136
|
+
* no vocabulário dele, e escolher errado escreve um token de tipo num `border-radius`.
|
|
137
|
+
*
|
|
138
|
+
* Medido no repositório real em 14/08, antes desta linha: 290 sugestões com a categoria trocada,
|
|
139
|
+
* 66 delas gravadas por um `--fix --write`, e a pior na PRIMEIRA página do relatório -
|
|
140
|
+
* `0.25em → --radius-xs` em 116 arquivos. Ver `FAMILY_WORDS`.
|
|
141
|
+
*
|
|
142
|
+
* Recusar aqui não perde informação: sem alias, `nameToWrite` cai no NOSSO token, que é da
|
|
143
|
+
* família certa porque foi o prefixo dela que o trouxe a este laço.
|
|
144
|
+
*/
|
|
145
|
+
const formDecides = Object.values(UNAMBIGUOUS).some((f) => f.test(value));
|
|
146
|
+
const usable = formDecides
|
|
147
|
+
? candidates
|
|
148
|
+
: candidates.filter((n) => familySays(kind, n));
|
|
149
|
+
if (usable.length === 0)
|
|
150
|
+
continue;
|
|
151
|
+
out.set(key, pick(usable, name, convention));
|
|
131
152
|
}
|
|
132
153
|
}
|
|
133
154
|
for (const [value, candidates] of theirs.byValue) {
|
package/dist/doctor/tokens.js
CHANGED
|
@@ -506,13 +506,62 @@ export function nearestToken(table, literal) {
|
|
|
506
506
|
*/
|
|
507
507
|
/** Exportado para `their-names.ts`, que precisa da MESMA divisão de família para validar a natureza
|
|
508
508
|
* de um nome dele - duas tabelas de família seriam duas leis. */
|
|
509
|
-
|
|
509
|
+
/**
|
|
510
|
+
* O ESPELHO de `FAMILY_PREFIX` e `FAMILY_WORDS` em `libs/ds-contracts/src/token-ref.ts`.
|
|
511
|
+
*
|
|
512
|
+
* O CLI é publicado standalone (`dependencies: {}`) e não pode importar os contratos, então as duas
|
|
513
|
+
* cópias são inevitáveis - `family-vocabulary.spec.ts` compara as duas com o arquivo do contrato, como
|
|
514
|
+
* o gêmeo do `frontier-kind` já faz. O nome é o MESMO dos dois lados de propósito: `DS_FAMILY` aqui e
|
|
515
|
+
* `FAMILY` lá era a mesma tabela com dois nomes, e ninguém que lesse um dos dois saberia do outro.
|
|
516
|
+
*/
|
|
517
|
+
export const FAMILY_PREFIX = {
|
|
510
518
|
color: "--ds-color-",
|
|
511
519
|
radius: "--ds-radius-",
|
|
512
520
|
spacing: "--ds-spacing-",
|
|
513
521
|
font: "--ds-typography-",
|
|
514
522
|
motion: "--ds-motion-",
|
|
515
523
|
};
|
|
524
|
+
/**
|
|
525
|
+
* AS PALAVRAS COM QUE O MUNDO NOMEIA CADA FAMÍLIA - e é isto que impede um erro de categoria.
|
|
526
|
+
*
|
|
527
|
+
* O NOSSO prefixo carrega a família (`--ds-spacing-md` é espaçamento por construção). O DELE não
|
|
528
|
+
* carrega nada garantido, e nada verificava: `--text-caption` e `--spacing-sm` podem segurar o mesmo
|
|
529
|
+
* `12px`, e sugerir o primeiro para um `border-radius` é escrever um token de tipo numa posição de
|
|
530
|
+
* raio. É exatamente o erro que o `crossFamily` foi criado para impedir do NOSSO lado, e ele passou
|
|
531
|
+
* a acontecer pelo lado dele quando o mapa de vocabulário nasceu.
|
|
532
|
+
*
|
|
533
|
+
* Medido no repositório real em 14/08, antes desta trava:
|
|
534
|
+
*
|
|
535
|
+
* 2680 achados com nome dele
|
|
536
|
+
* 1635 categoria concorda
|
|
537
|
+
* 290 categoria DISCORDA <- sugestão errada na tela
|
|
538
|
+
* 66 dessas o `--fix --write` GRAVARIA (o resto é `em`, que já tem trava)
|
|
539
|
+
* 755 nome sem palavra de categoria - e são todos cor (748) e fonte (7)
|
|
540
|
+
*
|
|
541
|
+
* A pior estava na primeira página: `0.25em → --radius-xs` em 116 arquivos, na lista das mais
|
|
542
|
+
* repetidas. E `4px → --radius-xs` num padding é uma escrita silenciosa, sem `em` para barrá-la.
|
|
543
|
+
*
|
|
544
|
+
* `text` está na lista de tipo porque `--text-h2` é a forma dominante no mundo real, e `transition`
|
|
545
|
+
* na de motion pelo mesmo motivo.
|
|
546
|
+
*/
|
|
547
|
+
export const FAMILY_WORDS = {
|
|
548
|
+
color: ["color", "colour"],
|
|
549
|
+
radius: ["radius", "rounded", "corner"],
|
|
550
|
+
spacing: ["spacing", "space", "gap", "inset"],
|
|
551
|
+
font: ["font", "type", "text"],
|
|
552
|
+
motion: ["duration", "motion", "ease", "easing", "transition"],
|
|
553
|
+
};
|
|
554
|
+
/**
|
|
555
|
+
* O NOME DELE DIZ QUE É DESTA FAMÍLIA - por QUALQUER segmento, não só pelo primeiro.
|
|
556
|
+
*
|
|
557
|
+
* `--dashboard-radius-lg` é um raio e o primeiro segmento é o namespace dele. Olhar só o começo
|
|
558
|
+
* recusaria um nome certo por causa de um prefixo de produto, que é a metade oposta do mesmo erro.
|
|
559
|
+
*/
|
|
560
|
+
export const familySays = (kind, name) => {
|
|
561
|
+
const words = FAMILY_WORDS[kind] ?? [];
|
|
562
|
+
const segments = name.replace(/^--/, "").toLowerCase().split("-");
|
|
563
|
+
return segments.some((seg) => words.includes(seg));
|
|
564
|
+
};
|
|
516
565
|
/**
|
|
517
566
|
* O token que carrega este valor, e SE ELE É DA MESMA FAMÍLIA.
|
|
518
567
|
*
|
|
@@ -535,7 +584,7 @@ export function tokenMatch(table, literal, kind) {
|
|
|
535
584
|
const hit = table.byValue.get(normalizeValue(literal, table.rootPx));
|
|
536
585
|
if (!hit || hit.length === 0)
|
|
537
586
|
return null;
|
|
538
|
-
const prefix = kind ?
|
|
587
|
+
const prefix = kind ? FAMILY_PREFIX[kind] : undefined;
|
|
539
588
|
const family = prefix ? hit.filter((n) => n.startsWith(prefix)) : [];
|
|
540
589
|
const pool = family.length > 0 ? family : hit;
|
|
541
590
|
// Semantic roles name intent; primitives name a shelf. Prefer intent.
|
package/dist/install-marks.js
CHANGED
|
@@ -71,7 +71,16 @@
|
|
|
71
71
|
* a prop, e 104 das 207 condições sem expressão nenhuma. Um `upgrade` anterior a esta versão reescreve
|
|
72
72
|
* os componentes dele com a perda intacta.
|
|
73
73
|
*/
|
|
74
|
-
|
|
74
|
+
/**
|
|
75
|
+
* 0.16.220 -> 0.16.233 em 14/08: o componente materializado muda de bytes. A regra de contenção HTML
|
|
76
|
+
* era enumerada à mão em `elementFor` e a enumeração esquecia `heading`, `row` e `stack` - uma raiz de
|
|
77
|
+
* papel `action` com um heading no topo saía como `<button><h3>…</h3></button>`, que é marcação
|
|
78
|
+
* inválida embarcada num copy-paste. Agora sai `<article>`.
|
|
79
|
+
*
|
|
80
|
+
* Zero ocorrências no sistema real medido (todos os headings dele estão aninhados, e o `children > 0`
|
|
81
|
+
* já os pegava), então para ele o `upgrade` é no-op. A marca é sobre o caso geral.
|
|
82
|
+
*/
|
|
83
|
+
export const MATERIALISER_SINCE = "0.16.233";
|
|
75
84
|
/**
|
|
76
85
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
77
86
|
*
|
|
@@ -123,7 +132,14 @@ export const MATERIALISER_SINCE = "0.16.220";
|
|
|
123
132
|
* e diz que o sistema não nomeia a fonte que o css dela nomeia. Medido no repositório real: as trocas
|
|
124
133
|
* com nome disponível foram de 1624 para 1631.
|
|
125
134
|
*/
|
|
126
|
-
|
|
135
|
+
/**
|
|
136
|
+
* 0.16.231 -> 0.16.232 em 14/08, e este é o pior que esta marca já carregou: o pinado não erra por
|
|
137
|
+
* omissão, ele erra o CONSELHO. O mapa de vocabulário escolhia entre os nomes DELE sem conferir a
|
|
138
|
+
* categoria, então um hook pinado antes disto olha um `border-radius: 12px` recém-escrito e manda usar
|
|
139
|
+
* `var(--text-caption)` - um token de tipo numa posição de raio. Medido no repositório real: 290
|
|
140
|
+
* sugestões com a categoria trocada, 66 delas graváveis por um `--fix --write`.
|
|
141
|
+
*/
|
|
142
|
+
export const CHECKER_SINCE = "0.16.232";
|
|
127
143
|
/**
|
|
128
144
|
* A ÚLTIMA VERSÃO EM QUE OS LEITORES PASSARAM A PRODUZIR UM CENSO DIFERENTE.
|
|
129
145
|
*
|
package/dist/last-sync.js
CHANGED
|
@@ -124,7 +124,7 @@ export function describeDelta(now, before) {
|
|
|
124
124
|
? `, and the reader moved ${before.cli} → ${now.cli}, so some of this is us reading better rather than your code changing`
|
|
125
125
|
: "";
|
|
126
126
|
if (parts.length > 0)
|
|
127
|
-
return `${parts.join(" · ")}
|
|
127
|
+
return `${parts.join(" · ")} - since your last sync${when ? `, ${when}` : ""}${reader}`;
|
|
128
128
|
/**
|
|
129
129
|
* NADA MUDOU É DIFERENTE DE NÃO DÁ PARA SABER. Um carimbo de uma versão anterior a esta não tem
|
|
130
130
|
* as impressões, então afirmar que nada mudou seria afirmar sobre o que não foi medido - e é
|
package/dist/types.js
CHANGED
|
@@ -3,4 +3,13 @@
|
|
|
3
3
|
* import `@synthesisui-hub/ds-contracts` (it only consumes the endpoint JSON).
|
|
4
4
|
* We type only what the CLI reads to generate GUIDE.md and the .lock.
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* AS TRÊS FORMAS QUE SÃO PHRASING CONTENT - e é a lista CURTA de propósito.
|
|
8
|
+
*
|
|
9
|
+
* `<button><Upload/>Upload</button>` é HTML válido: o modelo de conteúdo de um botão é phrasing, e um
|
|
10
|
+
* glifo e uma corrida de palavras são phrasing. Tudo o mais é bloco, e perguntar pelo complemento é o
|
|
11
|
+
* que impede a lista de esquecer um nome - foi assim que `heading`, `row` e `stack` ficaram fora da
|
|
12
|
+
* enumeração à mão do `elementFor` até 14/08, e uma raiz `action` com heading no topo saía como
|
|
13
|
+
* `<button><h3>…</h3></button>`.
|
|
14
|
+
*/
|
|
15
|
+
export const PHRASING_FORMS = ["icon", "text", "image"];
|
package/package.json
CHANGED