synthesisui 0.16.268 → 0.16.270
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/claude-md.js +28 -1
- package/dist/commands/add.js +21 -0
- package/dist/commands/connect.js +22 -3
- package/dist/commands/import.js +24 -1
- package/dist/commands/mcp.js +66 -1
- package/dist/component-codegen.js +10 -2
- package/dist/fonts.js +28 -8
- package/dist/global-keyframes.js +46 -0
- package/dist/guide.js +23 -18
- package/dist/install-marks.js +29 -2
- package/package.json +1 -1
package/dist/claude-md.js
CHANGED
|
@@ -312,8 +312,35 @@ async function readInterfaceLanguage(projectRoot) {
|
|
|
312
312
|
return null;
|
|
313
313
|
}
|
|
314
314
|
async function renderRegion(projectRoot, installed) {
|
|
315
|
+
/**
|
|
316
|
+
* AINDA NÃO EXISTE SISTEMA, E O BLOCO PASSA A DIZER ISSO - antes ele saía vazio.
|
|
317
|
+
*
|
|
318
|
+
* Medido no repo do dono (21/08): 52 bytes, só o par de marcadores. A informação existia por três
|
|
319
|
+
* canais e os três dependem de execução - as skills e o MCP exigem REABRIR a sessão, e o
|
|
320
|
+
* `SessionStart` exige `npx ... align` rodar. O CLAUDE.md é o único que não depende de nada, e era
|
|
321
|
+
* justamente o calado: se o SessionStart falhar, o agente abre sem saber que há algo a fazer aqui.
|
|
322
|
+
*
|
|
323
|
+
* UMA LINHA, NÃO UM MANUAL, e nada sobre token ou componente: um bloco que falasse de
|
|
324
|
+
* `--ds-color-*` num repo sem sistema mandaria o agente escrever refs que não resolvem. O erro
|
|
325
|
+
* oposto é pior que o silêncio.
|
|
326
|
+
*/
|
|
315
327
|
if (installed.length === 0) {
|
|
316
|
-
return
|
|
328
|
+
return [
|
|
329
|
+
START,
|
|
330
|
+
"## Design system",
|
|
331
|
+
"",
|
|
332
|
+
"This project is wired to synthesisui and has **no design system yet** - so there is",
|
|
333
|
+
"no token vocabulary to follow, and nothing here to obey.",
|
|
334
|
+
"",
|
|
335
|
+
"To turn this repository into one, run `/sui-import-ds` and I will read what is",
|
|
336
|
+
"already in the code: the colours, the type, the shapes and the components. Nothing",
|
|
337
|
+
"is invented, and nothing is copied out.",
|
|
338
|
+
"",
|
|
339
|
+
"The `synthesisui` MCP server is registered, so ASK it instead of guessing - it",
|
|
340
|
+
"answers what exists and what does not. This block fills itself in once a system is",
|
|
341
|
+
"installed.",
|
|
342
|
+
END,
|
|
343
|
+
].join("\n");
|
|
317
344
|
}
|
|
318
345
|
/**
|
|
319
346
|
* A CAPACIDADE, MEDIDA UMA VEZ POR ARQUIVO - ver `recallAvailable`.
|
package/dist/commands/add.js
CHANGED
|
@@ -41,6 +41,27 @@ const IGNORED = [
|
|
|
41
41
|
const IGNORE_HEADER = "# Managed by synthesisui. The identity and the CSS are committed so a fresh\n" +
|
|
42
42
|
"# clone is governed; the measurement and the local record are not, because\n" +
|
|
43
43
|
"# both are rewritten in full on every run and would conflict on every merge.\n";
|
|
44
|
+
/**
|
|
45
|
+
* A CASA DO REGISTRO LOCAL, ABERTA ANTES DE ALGUÉM ESCREVER NELA.
|
|
46
|
+
*
|
|
47
|
+
* Medido no repo do dono em 21/08, depois de apagar tudo e rodar só o `connect`: `.claude/`,
|
|
48
|
+
* `.mcp.json` e `CLAUDE.md` nasceram, e `_synthesisui/` não. `appendEvent` grava com `appendFile` e
|
|
49
|
+
* nunca cria diretório - sem a pasta é `ENOENT`, e o `catch` dele engole em silêncio. Então TODO
|
|
50
|
+
* evento entre o `connect` e o primeiro `import` se perdia: o `run` do próprio connect, e cada check
|
|
51
|
+
* do hook depois de cada escrita.
|
|
52
|
+
*
|
|
53
|
+
* POR QUE AQUI E NÃO NO `appendEvent`: fazer o gravador criar a pasta faria um `npx synthesisui list`
|
|
54
|
+
* de curiosidade plantar `_synthesisui/` no repo de quem só estava olhando - e aquele `catch` existe
|
|
55
|
+
* exatamente para esse caso. O `connect` é o comando que INSTALA, e é o único lugar onde abrir a casa
|
|
56
|
+
* é uma decisão que a pessoa tomou.
|
|
57
|
+
*
|
|
58
|
+
* A casa nasce com o `.gitignore`, senão o primeiro `ledger.jsonl` aparece no `git status` dele antes
|
|
59
|
+
* de o `add` rodar - a diferença entre um registro local e um arquivo que ele commita por engano.
|
|
60
|
+
*/
|
|
61
|
+
export async function ensureGovernanceHome(projectRoot) {
|
|
62
|
+
await mkdir(join(projectRoot, "_synthesisui"), { recursive: true });
|
|
63
|
+
await writeGovernanceIgnore(projectRoot);
|
|
64
|
+
}
|
|
44
65
|
async function writeGovernanceIgnore(projectRoot) {
|
|
45
66
|
const path = join(projectRoot, "_synthesisui", ".gitignore");
|
|
46
67
|
const existing = await readFile(path, "utf8").catch(() => null);
|
package/dist/commands/connect.js
CHANGED
|
@@ -7,7 +7,7 @@ import { body, paint, section, snippet } from "../output.js";
|
|
|
7
7
|
import { readShellAnswer, rememberShellNo } from "../shell-answer.js";
|
|
8
8
|
import { existingRc, hasHook, pinnedInHook, rcPathFor, shellFrom, shellSnippet, withHook, } from "../shell-hook.js";
|
|
9
9
|
import { SKILLS } from "../skills.js";
|
|
10
|
-
import { add } from "./add.js";
|
|
10
|
+
import { add, ensureGovernanceHome } from "./add.js";
|
|
11
11
|
import { reportWhatIsLeft } from "./align.js";
|
|
12
12
|
import { ci } from "./ci.js";
|
|
13
13
|
import { MCP_TOOL_COUNT } from "./mcp.js";
|
|
@@ -209,6 +209,14 @@ export async function connect(opts) {
|
|
|
209
209
|
* `syncClaudeMd` abaixo tem que ser o último a falar sobre ele.
|
|
210
210
|
*/
|
|
211
211
|
const refreshed = await refreshInstall(root, opts.version, opts.registry).catch(() => null);
|
|
212
|
+
/**
|
|
213
|
+
* A CASA DO REGISTRO LOCAL, ANTES DA PRIMEIRA ESCRITA - ver `ensureGovernanceHome`.
|
|
214
|
+
*
|
|
215
|
+
* Aqui e não depois: o `wireAgent` já é um comando que roda, e o `run` desta própria execução é
|
|
216
|
+
* gravado no fim pelo wrapper do `index.ts`. Sem a pasta ele cai num `catch` silencioso, junto com
|
|
217
|
+
* todo check do hook até o primeiro `import` - medido no repo do dono em 21/08.
|
|
218
|
+
*/
|
|
219
|
+
await ensureGovernanceHome(root);
|
|
212
220
|
const wired = await wireAgent(root, opts.version, want);
|
|
213
221
|
// The block reads the settings we just wrote, so it must be regenerated
|
|
214
222
|
// after them, not before.
|
|
@@ -299,9 +307,20 @@ export async function connect(opts) {
|
|
|
299
307
|
const moved = contract.changed.includes(home);
|
|
300
308
|
console.log(body(`${moved ? "✓" : "·"} ${home.padEnd(22)} ${moved
|
|
301
309
|
? home === "CLAUDE.md"
|
|
302
|
-
?
|
|
310
|
+
? /**
|
|
311
|
+
* A FRASE DIZ O QUE O BLOCO CARREGA, e ela descrevia um conteúdo que não existia.
|
|
312
|
+
*
|
|
313
|
+
* Num repo sem sistema o bloco saía vazio e esta linha dizia "rewritten for what is
|
|
314
|
+
* installed" - nada estava instalado, e nada foi escrito. Agora o bloco diz o que
|
|
315
|
+
* fazer, e a linha diz qual dos dois ele é (dono, 21/08).
|
|
316
|
+
*/
|
|
317
|
+
contract.count === 0
|
|
318
|
+
? "how to turn this repo into your system"
|
|
319
|
+
: "rewritten for what is installed"
|
|
303
320
|
: "the same rules, where this agent reads them"
|
|
304
|
-
:
|
|
321
|
+
: contract.count === 0
|
|
322
|
+
? "already says how to start"
|
|
323
|
+
: "already says what is installed"}`));
|
|
305
324
|
}
|
|
306
325
|
/**
|
|
307
326
|
* The fourth layer, and the one that had no installer at all: the import
|
package/dist/commands/import.js
CHANGED
|
@@ -35,6 +35,7 @@ import { buildTable } from "../doctor/tokens.js";
|
|
|
35
35
|
import { definitionSpan, parseClass, readInlineStyle, rootClasses, rootTag, transcribe, } from "../doctor/transcribe.js";
|
|
36
36
|
import { transcribeVariants } from "../doctor/variant-read.js";
|
|
37
37
|
import { frontierKind, packageRoot } from "../frontier-kind.js";
|
|
38
|
+
import { keyframesInSheets } from "../global-keyframes.js";
|
|
38
39
|
import { withLibraryStructure } from "../library-structure.js";
|
|
39
40
|
import { mergeCensus } from "../merge-census.js";
|
|
40
41
|
import { claimName } from "../name-claim.js";
|
|
@@ -435,6 +436,18 @@ export async function takeCensus(root, opts) {
|
|
|
435
436
|
* porque é isso que o juiz do ledger precisa para chamar a classe e a regra de LIDAS.
|
|
436
437
|
*/
|
|
437
438
|
const globalClasses = readGlobalClasses(globalSheets, declaredValues);
|
|
439
|
+
/**
|
|
440
|
+
* OS KEYFRAMES DA FOLHA DELE, colhidos onde as folhas globais já estão na mão.
|
|
441
|
+
*
|
|
442
|
+
* Eram 35 dos 84 tokens declarados do codelevel - 42% do vocabulário - e o censo saía com
|
|
443
|
+
* `keyframes: undefined`, porque o leitor só olhava CSS Module e `<style>` island. Ver
|
|
444
|
+
* `global-keyframes.ts` para o porquê e para o que o valor de cada `--animate-*` carrega.
|
|
445
|
+
*
|
|
446
|
+
* Aqui e não dentro de `harvestOwnCss` porque aquela função é o laço de COLHEITA, e esta é uma
|
|
447
|
+
* leitura sobre o que ela colheu - a mesma divisão que `readGlobalClasses` já respeita, uma linha
|
|
448
|
+
* acima.
|
|
449
|
+
*/
|
|
450
|
+
const globalKeyframes = keyframesInSheets(globalSheets);
|
|
438
451
|
const globalClassesClaimed = new Set();
|
|
439
452
|
/** Por arquivo, as classes cuja regra a `<style>` island já entregou - ver o juiz. */
|
|
440
453
|
const islandClassesRead = new Map();
|
|
@@ -1952,7 +1965,17 @@ export async function takeCensus(root, opts) {
|
|
|
1952
1965
|
: {}),
|
|
1953
1966
|
},
|
|
1954
1967
|
declared: Object.fromEntries(table.byName),
|
|
1955
|
-
|
|
1968
|
+
/**
|
|
1969
|
+
* A FOLHA DELE ENTRA POR ÚLTIMO, e a ordem é a resposta a quem manda.
|
|
1970
|
+
*
|
|
1971
|
+
* `keyframes` já traz o que veio de CSS Module e de `<style>` island - definições de COMPONENTE.
|
|
1972
|
+
* A folha global é a base do app, então ela preenche o que falta e não sobrescreve o que um
|
|
1973
|
+
* componente declarou para si. Sem esta linha o codelevel saía com `keyframes: undefined` e 35 dos
|
|
1974
|
+
* 84 tokens dele - 42% do vocabulário - não chegavam a lugar nenhum.
|
|
1975
|
+
*/
|
|
1976
|
+
...(Object.keys({ ...globalKeyframes, ...keyframes }).length > 0
|
|
1977
|
+
? { keyframes: { ...globalKeyframes, ...keyframes } }
|
|
1978
|
+
: {}),
|
|
1956
1979
|
...(animations.size > 0 ? { animations: [...animations].sort() } : {}),
|
|
1957
1980
|
...(brokenRefs.length > 0 ? { brokenRefs } : {}),
|
|
1958
1981
|
/**
|
package/dist/commands/mcp.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readdir, readFile } from "node:fs/promises";
|
|
|
3
3
|
import { join, relative, resolve } from "node:path";
|
|
4
4
|
import { provenanceAction, toolSlug, withSource, } from "../agent-provenance.js";
|
|
5
5
|
import { pinnedHookVersion } from "../agent-wiring.js";
|
|
6
|
+
import { elementClass, kebabAxis } from "../component-codegen.js";
|
|
6
7
|
import { composePlan, FAMILIES, isFamily, noFamilyAnswer, renderPlan, } from "../compose-context.js";
|
|
7
8
|
import { readToken, resolveRegistry } from "../config.js";
|
|
8
9
|
import { readEvents } from "../doctor/ledger.js";
|
|
@@ -11,6 +12,7 @@ import { diagnose, nameToWrite, scanSource } from "../doctor/scan.js";
|
|
|
11
12
|
import { nearestToken, normalizeValue, tokenFor } from "../doctor/tokens.js";
|
|
12
13
|
import { fromCensus } from "../memory/observation.js";
|
|
13
14
|
import { handleRecall, handleRemember, MEMORY_TOOLS } from "../memory/tools.js";
|
|
15
|
+
import { readInstalledConvention } from "../project-facts.js";
|
|
14
16
|
import { repoStateOf } from "../repo-state.js";
|
|
15
17
|
import { detectStack } from "../stack.js";
|
|
16
18
|
import { component } from "./component.js";
|
|
@@ -974,7 +976,7 @@ query) {
|
|
|
974
976
|
}
|
|
975
977
|
}
|
|
976
978
|
async function describeComponent(root, name) {
|
|
977
|
-
const { documents, requires } = await loadSystem(root);
|
|
979
|
+
const { documents, requires, table } = await loadSystem(root);
|
|
978
980
|
let recipe;
|
|
979
981
|
for (const doc of documents) {
|
|
980
982
|
const d = doc;
|
|
@@ -1118,6 +1120,69 @@ async function describeComponent(root, name) {
|
|
|
1118
1120
|
if (composes.length > 0) {
|
|
1119
1121
|
out.push("", `Built out of: ${composes.join(", ")}. Change one of those in one place rather than reproducing it here, and call describe_component on it before you do.`);
|
|
1120
1122
|
}
|
|
1123
|
+
/**
|
|
1124
|
+
* HOW TO DRESS IT - and it was the half this answer never carried.
|
|
1125
|
+
*
|
|
1126
|
+
* Measured on the owner's own census (21/08): his Button declares three axes -
|
|
1127
|
+
* `variant` with eight options, `size` with four, `glow` with two - plus
|
|
1128
|
+
* defaults, 32 layers and 78 properties. The whole reply for a component like
|
|
1129
|
+
* that was its name, its description and "no rules govern this yet". An agent
|
|
1130
|
+
* reading it composes without knowing `gold` exists, and nothing anywhere
|
|
1131
|
+
* says an option was missed.
|
|
1132
|
+
*
|
|
1133
|
+
* The material was already here: `recipe.variants` is the SAME field the
|
|
1134
|
+
* `GUIDE.md` reads to print `data-variant="primary|gold|…"`. Two surfaces
|
|
1135
|
+
* answering one question, and only the file on disk answered it.
|
|
1136
|
+
*
|
|
1137
|
+
* The class comes from `readInstalledConvention`, never from a literal `ds-`:
|
|
1138
|
+
* an imported system may carry its own prefix in `meta.classNames`, and
|
|
1139
|
+
* telling the agent to write a class that does not exist is worse than
|
|
1140
|
+
* telling it nothing.
|
|
1141
|
+
*/
|
|
1142
|
+
const axes = Object.entries(recipe.variants ?? {});
|
|
1143
|
+
if (axes.length > 0) {
|
|
1144
|
+
const convention = await readInstalledConvention(root, String(table.slug ?? ""));
|
|
1145
|
+
const cls = elementClass(name, convention);
|
|
1146
|
+
out.push("", `How to dress it: \`${cls}\`, plus one data attribute per axis.`);
|
|
1147
|
+
for (const [axis, options] of axes) {
|
|
1148
|
+
const names = Object.keys(options ?? {});
|
|
1149
|
+
if (names.length === 0)
|
|
1150
|
+
continue;
|
|
1151
|
+
const resting = recipe.defaults?.[axis];
|
|
1152
|
+
/**
|
|
1153
|
+
* O DEFAULT É O QUE ELE NÃO PRECISA ESCREVER, e dizer isso encurta o
|
|
1154
|
+
* código que o agente produz em vez de só informá-lo.
|
|
1155
|
+
*/
|
|
1156
|
+
const rest = resting
|
|
1157
|
+
? ` - at rest it is \`${resting}\`, so you can leave the attribute out`
|
|
1158
|
+
: "";
|
|
1159
|
+
out.push(` data-${kebabAxis(axis)}="${names.join("|")}"${rest}`);
|
|
1160
|
+
}
|
|
1161
|
+
const states = Object.keys(recipe.states ?? {});
|
|
1162
|
+
if (states.length > 0) {
|
|
1163
|
+
out.push(` states the recipe already carries: ${states.join(", ")} - they are CSS, so do not write them again in JS.`);
|
|
1164
|
+
}
|
|
1165
|
+
out.push(` Or call add_component { "name": "${name}" } and get it as typed code, one prop per axis.`);
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* AS INSTRUÇÕES DE RENDER QUE A LEITURA MEDIU - e até aqui elas morriam no
|
|
1169
|
+
* terminal.
|
|
1170
|
+
*
|
|
1171
|
+
* O CLI mede `renderNotes` por componente e imprime uma vez, no import. São
|
|
1172
|
+
* 25 delas em 14 dos 59 componentes do dono, e uma diz: *"`ring` gates a look
|
|
1173
|
+
* and is a boolean rather than an axis - `data-ring` has to be set for it to
|
|
1174
|
+
* apply"*. Sem essa linha o agente escreve markup correto e o look nunca
|
|
1175
|
+
* aparece, que é o pior modo de falhar - nada reclama.
|
|
1176
|
+
*
|
|
1177
|
+
* O princípio, e ele vale além destas notas: **interpretada ou não por nós,
|
|
1178
|
+
* a instrução vai para quem constrói**. Uma nota que a esteira não soube
|
|
1179
|
+
* transformar em regra continua sendo a coisa mais útil que existe sobre
|
|
1180
|
+
* aquele componente.
|
|
1181
|
+
*/
|
|
1182
|
+
const renderNotes = recipe.renderNotes ?? [];
|
|
1183
|
+
if (renderNotes.length > 0) {
|
|
1184
|
+
out.push("", "Read off your own code, and it decides whether the look appears:", ...renderNotes.map((n) => ` ${n}`));
|
|
1185
|
+
}
|
|
1121
1186
|
/**
|
|
1122
1187
|
* WHAT IT NEEDS INSTALLED - the whole reason this tool earns its place.
|
|
1123
1188
|
*
|
|
@@ -4,8 +4,16 @@ export const DEFAULT_CONVENTION = {
|
|
|
4
4
|
partSeparator: "-",
|
|
5
5
|
};
|
|
6
6
|
const kebab = (v) => v.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* `metric-card` → `ds-metric-card`, or `metric-card`, or `sui-metric-card`.
|
|
9
|
+
*
|
|
10
|
+
* EXPORTADA porque o `describe_component` passou a dizer ao agente qual classe
|
|
11
|
+
* escrever, e uma segunda implementação do mesmo nome é como as duas começam a
|
|
12
|
+
* divergir: o MCP mandaria escrever uma classe que este codegen não emite.
|
|
13
|
+
*/
|
|
14
|
+
export const elementClass = (name, c) => `${c.prefix}${kebab(name)}`;
|
|
15
|
+
/** O mesmo kebab que decide a classe e o `data-*`, para quem precisa só do eixo. */
|
|
16
|
+
export const kebabAxis = (v) => kebab(v);
|
|
9
17
|
/** `metric-card` + `title` → `ds-metric-card-title`, or `metric-card__title`. */
|
|
10
18
|
const partClassName = (name, part, c) => `${elementClass(name, c)}${c.partSeparator}${kebab(part)}`;
|
|
11
19
|
const pascal = (name) => name
|
package/dist/fonts.js
CHANGED
|
@@ -12,12 +12,31 @@ const GENERIC_FAMILIES = new Set([
|
|
|
12
12
|
"inherit",
|
|
13
13
|
"initial",
|
|
14
14
|
]);
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* TODA VAGA DE FONTE DO DOCUMENTO, dedup e sem os genéricos.
|
|
17
|
+
*
|
|
18
|
+
* Enumerava `[display, body, mono]` literalmente, e o documento passou a aceitar as famílias que o
|
|
19
|
+
* código DELE declara além das nossas três - `--font-serif` do codelevel é "Instrument Serif", 25 usos,
|
|
20
|
+
* reservada por ele para "editorial moments". O token existia, o compilador emitia
|
|
21
|
+
* `--ds-typography-families-serif`, e ninguém baixava a fonte: o texto saía no fallback do navegador e
|
|
22
|
+
* nada avisava. Um token que aponta para uma fonte que ninguém carregou é pior que um token ausente -
|
|
23
|
+
* o ausente pelo menos aparece na conta.
|
|
24
|
+
*
|
|
25
|
+
* A ORDEM É DETERMINÍSTICA: as três vagas primeiro, as dele em ordem alfabética. Isto vira href num
|
|
26
|
+
* arquivo gerado, e um conjunto que muda de ordem entre rodadas produz diff onde nada mudou.
|
|
27
|
+
*/
|
|
28
|
+
export const familySlots = (families) => {
|
|
29
|
+
const ours = ["display", "body", "mono"].filter((k) => k in families);
|
|
30
|
+
const theirs = Object.keys(families)
|
|
31
|
+
.filter((k) => !ours.includes(k))
|
|
32
|
+
.sort();
|
|
33
|
+
return [...ours, ...theirs];
|
|
34
|
+
};
|
|
16
35
|
export function customFontFamilies(families) {
|
|
17
36
|
const seen = new Set();
|
|
18
37
|
const names = [];
|
|
19
|
-
for (const
|
|
20
|
-
const name =
|
|
38
|
+
for (const slot of familySlots(families)) {
|
|
39
|
+
const name = families[slot]?.trim();
|
|
21
40
|
if (!name)
|
|
22
41
|
continue;
|
|
23
42
|
const key = name.toLowerCase();
|
|
@@ -47,7 +66,8 @@ export function googleFontsHref(families) {
|
|
|
47
66
|
* fallback - it works everywhere but swaps visibly on cold loads.
|
|
48
67
|
*/
|
|
49
68
|
export function nextFontSnippet(families, slug, appDir = "app") {
|
|
50
|
-
|
|
69
|
+
/** Toda vaga que o documento tem, não só as nossas três - ver `customFontFamilies`. */
|
|
70
|
+
const roles = familySlots(families).filter((role) => {
|
|
51
71
|
const name = families[role]?.trim();
|
|
52
72
|
return name && !GENERIC_FAMILIES.has(name.toLowerCase());
|
|
53
73
|
});
|
|
@@ -58,7 +78,7 @@ export function nextFontSnippet(families, slug, appDir = "app") {
|
|
|
58
78
|
const importNames = [];
|
|
59
79
|
const consts = [];
|
|
60
80
|
for (const role of roles) {
|
|
61
|
-
const name = families[role].trim();
|
|
81
|
+
const name = (families[role] ?? "").trim();
|
|
62
82
|
if (!seen.has(name)) {
|
|
63
83
|
seen.set(name, role);
|
|
64
84
|
importNames.push(importName(name));
|
|
@@ -81,13 +101,13 @@ export function nextFontSnippet(families, slug, appDir = "app") {
|
|
|
81
101
|
...consts,
|
|
82
102
|
];
|
|
83
103
|
const roleVar = (role) => {
|
|
84
|
-
const name = families[role].trim();
|
|
104
|
+
const name = (families[role] ?? "").trim();
|
|
85
105
|
return `--font-ds-${seen.get(name)}`;
|
|
86
106
|
};
|
|
87
107
|
const layout = [
|
|
88
108
|
`// ${appDir}/layout.tsx`,
|
|
89
|
-
`import { ${[...new Set(roles.map((r) => seen.get(families[r].trim())))].join(", ")} } from "./fonts";`,
|
|
90
|
-
`<body data-ds="${slug}" className={\`${[...new Set(roles.map((r) => `\${${seen.get(families[r].trim())}.variable}`))].join(" ")}\`}>`,
|
|
109
|
+
`import { ${[...new Set(roles.map((r) => seen.get((families[r] ?? "").trim())))].join(", ")} } from "./fonts";`,
|
|
110
|
+
`<body data-ds="${slug}" className={\`${[...new Set(roles.map((r) => `\${${seen.get((families[r] ?? "").trim())}.variable}`))].join(" ")}\`}>`,
|
|
91
111
|
];
|
|
92
112
|
const css = [
|
|
93
113
|
`/* ${appDir}/globals.css - AFTER the tokens.css import */`,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { keyframeOffsets, readModuleCss } from "./doctor/css-modules.js";
|
|
2
|
+
/**
|
|
3
|
+
* OS KEYFRAMES QUE A FOLHA DELE DECLARA - e eram 42% do vocabulário do codelevel.
|
|
4
|
+
*
|
|
5
|
+
* O leitor colhia keyframes de duas fontes: o CSS Module de um componente e o `<style>` island. Não da
|
|
6
|
+
* folha GLOBAL, que é exatamente onde um projeto Tailwind v4 com `@theme` os escreve. Medido no censo
|
|
7
|
+
* do dono (21/08): 35 dos 84 tokens declarados são `--animate-*`, e o censo saía com
|
|
8
|
+
* `keyframes: undefined`.
|
|
9
|
+
*
|
|
10
|
+
* O valor de cada atalho carrega tudo o que importa, e nada disso chegava:
|
|
11
|
+
*
|
|
12
|
+
* --animate-fade-slide-in: fade-slide-in 1s var(--ease-out-soft) both
|
|
13
|
+
* ^ o keyframe ^ dur ^ o easing DELE ^ fill
|
|
14
|
+
*
|
|
15
|
+
* E a consequência não era só perder token. `unknownAnimations` crescia: o documento recusava definir
|
|
16
|
+
* quatro animações que os componentes dele nomeiam - a nota dizia "a plugin's own" - quando a definição
|
|
17
|
+
* estava na folha dele, a um parser de distância.
|
|
18
|
+
*
|
|
19
|
+
* NADA AQUI É PARSER NOVO. `readModuleCss` já extrai `@keyframes` de qualquer CSS e `keyframeOffsets`
|
|
20
|
+
* já vira o corpo cru na forma que o documento quer (offset → declarações). Só ninguém os chamava para
|
|
21
|
+
* a folha global. É o mesmo defeito de forma que apareceu seis vezes nesta rodada: a varredura
|
|
22
|
+
* enumerando as fontes que já conhecia.
|
|
23
|
+
*
|
|
24
|
+
* MATERIALISER - um cliente pinado recebe diferente? Não: isto muda o CENSO, e o que cai na pasta dele
|
|
25
|
+
* vem do documento que o servidor compila depois. Ele só vê a diferença após um re-import, e é por isso
|
|
26
|
+
* que esta é a única entrega da rodada que exige um.
|
|
27
|
+
*/
|
|
28
|
+
export function keyframesInSheets(sheets) {
|
|
29
|
+
const out = {};
|
|
30
|
+
for (const sheet of sheets) {
|
|
31
|
+
if (!sheet.body.includes("@keyframes"))
|
|
32
|
+
continue;
|
|
33
|
+
const read = readModuleCss(sheet.body);
|
|
34
|
+
for (const [name, body] of Object.entries(read.keyframes)) {
|
|
35
|
+
/** A PRIMEIRA declaração vence, como em todo leitor daqui: duas folhas com o mesmo nome é
|
|
36
|
+
* theming por área, e escolher a última faria a ordem de caminhada decidir o resultado. */
|
|
37
|
+
if (out[name])
|
|
38
|
+
continue;
|
|
39
|
+
const offsets = keyframeOffsets(body);
|
|
40
|
+
/** Um nome sem corpo legível viraria uma referência que não anima nada. */
|
|
41
|
+
if (Object.keys(offsets).length > 0)
|
|
42
|
+
out[name] = offsets;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
package/dist/guide.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* UMA IMPLEMENTAÇÃO SÓ, e este arquivo tinha a terceira.
|
|
3
|
+
*
|
|
4
|
+
* `customFontFamilies` existia aqui, em `fonts.ts` e em `ds-font-link.tsx` - três cópias de um
|
|
5
|
+
* julgamento, e as três enumeravam `[display, body, mono]`. Quando o documento passou a aceitar as
|
|
6
|
+
* famílias que o código dele declara, as três deixaram de carregar a fonte ao mesmo tempo. Este
|
|
7
|
+
* arquivo já importava `nextFontSnippet` do mesmo módulo; a cópia não tinha razão de existir.
|
|
8
|
+
*/
|
|
9
|
+
import { customFontFamilies, familySlots, nextFontSnippet } from "./fonts.js";
|
|
2
10
|
import { animationShorthand, classifyKeyframe, describeKeyframe, isFullRotation, } from "./motion.js";
|
|
3
11
|
const kebab = (v) => v.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
4
12
|
/**
|
|
@@ -25,21 +33,6 @@ const GENERIC_FAMILIES = new Set([
|
|
|
25
33
|
"initial",
|
|
26
34
|
]);
|
|
27
35
|
/** Famílias custom do documento (display/body/mono), deduplicadas, sem genéricos. */
|
|
28
|
-
function customFontFamilies(families) {
|
|
29
|
-
const seen = new Set();
|
|
30
|
-
const out = [];
|
|
31
|
-
for (const family of [families.display, families.body, families.mono]) {
|
|
32
|
-
const name = family?.trim();
|
|
33
|
-
if (!name)
|
|
34
|
-
continue;
|
|
35
|
-
const key = name.toLowerCase();
|
|
36
|
-
if (GENERIC_FAMILIES.has(key) || seen.has(key))
|
|
37
|
-
continue;
|
|
38
|
-
seen.add(key);
|
|
39
|
-
out.push(name);
|
|
40
|
-
}
|
|
41
|
-
return out;
|
|
42
|
-
}
|
|
43
36
|
/**
|
|
44
37
|
* THE STRUCTURE, WITH WHAT EACH PART IS FOR AND WHEN IT EXISTS.
|
|
45
38
|
*
|
|
@@ -269,6 +262,14 @@ export function buildGuide(payload) {
|
|
|
269
262
|
const { document: doc, slug, name, version } = payload;
|
|
270
263
|
const { meta, foundations, motion, components } = doc;
|
|
271
264
|
const semanticRoles = Object.keys(foundations.color.semantic);
|
|
265
|
+
/**
|
|
266
|
+
* AS CORES QUE ELE NOMEIA POR PROPÓSITO, no arquivo que o agente lê primeiro.
|
|
267
|
+
*
|
|
268
|
+
* O compilador passou a emitir `--ds-color-tier-gold` e o `find_token` já as acha (ele lê o
|
|
269
|
+
* `tokens.css`), mas o GUIDE é onde o agente descobre o vocabulário ANTES de perguntar - e ele não
|
|
270
|
+
* as mencionava. São 19 no censo do dono, e são a parte que faz o sistema dele ser dele.
|
|
271
|
+
*/
|
|
272
|
+
const namedColours = Object.keys(foundations.color.named ?? {});
|
|
272
273
|
const seriesKeys = Object.keys(foundations.color.series ?? {});
|
|
273
274
|
const fontFamilies = customFontFamilies(foundations.typography.families);
|
|
274
275
|
const fontsHref = fontFamilies.length > 0
|
|
@@ -680,13 +681,17 @@ ${hasTailwind
|
|
|
680
681
|
- **Always use semantic tokens**, never raw values nor primitives directly.
|
|
681
682
|
Color: \`var(--ds-color-semantic-<role>)\`${hasTailwind ? " (utility: `bg-<role>`/`text-<role>`)" : ""}. The roles are: ${list(semanticRoles)}.
|
|
682
683
|
- Primitives (\`--ds-color-<palette>-<step>\`) exist but should **not** be referenced directly -
|
|
683
|
-
they feed the semantic roles.${
|
|
684
|
+
they feed the semantic roles.${namedColours.length > 0
|
|
685
|
+
? `\n- **Yours by name** → \`var(--ds-color-<name>)\`: ${namedColours.length} colour${namedColours.length === 1 ? "" : "s"} your code names by PURPOSE rather than by step, so no scale could hold ${namedColours.length === 1 ? "it" : "them"} - ${list(namedColours.slice(0, 8))}${namedColours.length > 8 ? ", …" : ""}. These are yours: reach for them when the purpose matches, and prefer a semantic role when it does not.`
|
|
686
|
+
: ""}${seriesKeys.length > 0
|
|
684
687
|
? `\n- Data-viz → \`var(--ds-color-series-<n>)\`${hasTailwind ? " (utility: `bg-series-<n>`/`text-series-<n>`/`fill-series-<n>`)" : ""}: categorical chart/series colors, ${seriesKeys.length} of them (${list(seriesKeys)}). Use them in order for multi-series charts; they re-paint with the system.`
|
|
685
688
|
: ""}
|
|
686
689
|
- Spacing → \`var(--ds-spacing-<key>)\`: ${list(Object.keys(foundations.spacing))}.
|
|
687
690
|
- Radius → \`var(--ds-radius-<key>)\`: ${list(Object.keys(foundations.radius))}.
|
|
688
691
|
- Shadow → \`var(--ds-shadow-<key>)\`: ${list(Object.keys(foundations.shadow))}.
|
|
689
|
-
- Typography: families
|
|
692
|
+
- Typography: families ${familySlots(foundations.typography.families)
|
|
693
|
+
.map((slot) => `\`--ds-typography-families-${slot}\` (${foundations.typography.families[slot]})`)
|
|
694
|
+
.join(", ")};
|
|
690
695
|
weights${hasTailwind ? " (utility: `font-<key>`)" : ""}: ${list(weights)};
|
|
691
696
|
scale \`--ds-typography-scale-<key>-font-size\`${hasTailwind ? " (utility: `text-<key>`)" : ""}: ${list(Object.keys(foundations.typography.scale))}.
|
|
692
697
|
- Motion: durations \`--ds-motion-durations-<key>\` (${list(Object.keys(motion.durations))}) and
|
package/dist/install-marks.js
CHANGED
|
@@ -115,7 +115,20 @@
|
|
|
115
115
|
* O índice de componentes é BYTE-IDÊNTICO nos dois caminhos - mesma fonte canônica, mesmos nomes,
|
|
116
116
|
* mesma quantidade (`claude-md.f0.spec.ts`). O corte troca uma frase, nunca o inventário.
|
|
117
117
|
*/
|
|
118
|
-
|
|
118
|
+
/**
|
|
119
|
+
* 0.16.269 -> 0.16.270 em 21/08, e a correção é de uma ARMADILHA que vai repetir.
|
|
120
|
+
*
|
|
121
|
+
* A marca foi escrita como "0.16.269" porque era o que o `package.json` dizia naquele instante. Só que
|
|
122
|
+
* o dono PUBLICOU o 0.16.269 antes de o código entrar - o pacote no npm saiu 17:46 e o PR foi mergeado
|
|
123
|
+
* 18:53. Então a marca apontava para uma versão que existe e NÃO tem a mudança, e o efeito era o pior
|
|
124
|
+
* possível: `installedBehind("0.16.269", "0.16.270", "0.16.269")` é FALSO, então o cliente que instalou
|
|
125
|
+
* aquele 0.16.269 nunca seria avisado.
|
|
126
|
+
*
|
|
127
|
+
* A REGRA, para não repetir: a marca aponta para a versão que vai ser PUBLICADA COM a mudança, e essa
|
|
128
|
+
* é sempre o bump deste PR - nunca o número que o `package.json` já carrega, porque alguém pode
|
|
129
|
+
* publicar no meio.
|
|
130
|
+
*/
|
|
131
|
+
export const MATERIALISER_SINCE = "0.16.270";
|
|
119
132
|
/**
|
|
120
133
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
121
134
|
*
|
|
@@ -217,7 +230,21 @@ export const CHECKER_SINCE = "0.16.250";
|
|
|
217
230
|
* (era 53% no melhor caminho; o derivado dava 38%). Um censo medido antes disto não
|
|
218
231
|
* tem versão nenhuma - e o mapa de blueprints por versão não tem o que consultar.
|
|
219
232
|
*/
|
|
220
|
-
|
|
233
|
+
/**
|
|
234
|
+
* `0.16.261` -> `0.16.269` em 21/08: os keyframes que a folha GLOBAL dele declara passaram a ser
|
|
235
|
+
* lidos - ver `global-keyframes.ts`.
|
|
236
|
+
*
|
|
237
|
+
* O portão desta marca é o corpus dourado, e ele NÃO cobrou: o censo dos treze apps saiu idêntico,
|
|
238
|
+
* porque nenhuma das treze fixtures escreve `@keyframes` em folha global. A marca sobe por decisão,
|
|
239
|
+
* não por vermelho - e o número que justifica está no repo real: 35 dos 84 tokens declarados do
|
|
240
|
+
* codelevel são `--animate-*`, o censo dele saía com `keyframes: undefined`, e agora sai com 35.
|
|
241
|
+
*
|
|
242
|
+
* Sem subir, o `align` nunca diria a quem já importou que vale remedir - que é a única coisa que esta
|
|
243
|
+
* marca existe para fazer.
|
|
244
|
+
*/
|
|
245
|
+
/** 0.16.269 -> 0.16.270 pelo mesmo motivo do MATERIALISER acima: o 0.16.269 foi publicado sem o
|
|
246
|
+
* leitor de keyframes globais, e uma marca que aponta para ele cala o aviso para quem o instalou. */
|
|
247
|
+
export const READER_SINCE = "0.16.270";
|
|
221
248
|
/**
|
|
222
249
|
* O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
|
|
223
250
|
*
|
package/package.json
CHANGED