synthesisui 0.16.147 → 0.16.148

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.
@@ -1271,12 +1271,18 @@ export async function doctor(opts) {
1271
1271
  if (opts.fix) {
1272
1272
  const read = await readerFor(root, d.findings.filter((f) => f.token).map((f) => f.file));
1273
1273
  const { result, next } = planFix(d, read);
1274
- if (!opts.dry)
1274
+ const writing = opts.write === true;
1275
+ if (writing)
1275
1276
  await writeFix(root, next);
1276
- console.log(section(opts.dry ? "What --fix would do" : "Fixed what had an answer"));
1277
- for (const line of describeFix(result, opts.dry === true))
1277
+ console.log(section(writing ? "Fixed what had an answer" : "What --fix would do"));
1278
+ for (const line of describeFix(result, !writing))
1278
1279
  console.log(body(line));
1279
- if (!opts.dry && result.applied.length > 0) {
1280
+ if (!writing && result.applied.length > 0) {
1281
+ console.log("");
1282
+ console.log(body("Nothing was written. When the list above is what you want:"));
1283
+ console.log(paint.blue(snippet(["npx synthesisui@latest doctor --fix --write"])));
1284
+ }
1285
+ if (writing && result.applied.length > 0) {
1280
1286
  console.log("");
1281
1287
  console.log(body(paint.dim("Read the diff before committing - it is your code, and this replaced literals with the tokens your own system declares.")));
1282
1288
  }
@@ -65,6 +65,23 @@ export function planFix(d, read) {
65
65
  });
66
66
  continue;
67
67
  }
68
+ /**
69
+ * COINCIDÊNCIA NÃO SE APLICA - ver `crossFamily` em `scan.ts`.
70
+ *
71
+ * Um `2rem` que só existe como token de tipografia é uma resposta para um `font-size` e um erro
72
+ * de categoria para um `border-radius`. O `--fix` trocou `rounded-[2rem]` por
73
+ * `var(--ds-typography-scale-h1-font-size)` no repo do dono (05/08) - um canto que se move no dia
74
+ * em que alguém edita a escala de tipo. Medido lá: 14 de 129, e 0 de 76 na biblioteca dele.
75
+ */
76
+ if (f.crossFamily) {
77
+ skipped.push({
78
+ file: f.file,
79
+ line: f.line,
80
+ literal: f.literal,
81
+ because: "cross-family",
82
+ });
83
+ continue;
84
+ }
68
85
  if (!lines.has(f.file)) {
69
86
  const raw = read(f.file);
70
87
  if (raw === null) {
@@ -134,6 +151,7 @@ export async function readerFor(root, files) {
134
151
  export function describeFix(result, dry) {
135
152
  const { applied, skipped } = result;
136
153
  const moved = skipped.filter((s) => s.because === "moved").length;
154
+ const coincidence = skipped.filter((s) => s.because === "cross-family").length;
137
155
  const unread = skipped.filter((s) => s.because === "unreadable").length;
138
156
  const decisions = skipped.filter((s) => s.because === "no-token").length;
139
157
  const lines = [];
@@ -153,6 +171,8 @@ export function describeFix(result, dry) {
153
171
  lines.push(` var(${token}) · ${n} time${n === 1 ? "" : "s"}`);
154
172
  if (moved > 0)
155
173
  lines.push(`${moved} line${moved === 1 ? "" : "s"} changed since the scan and ${moved === 1 ? "was" : "were"} left alone - run it again.`);
174
+ if (coincidence > 0)
175
+ lines.push(`${coincidence} left alone because the only token with that value belongs to another family - a length that happens to equal a font size is a coincidence, not an answer.`);
156
176
  if (unread > 0)
157
177
  lines.push(`${unread} file${unread === 1 ? "" : "s"} could not be read.`);
158
178
  if (decisions > 0)
@@ -9,7 +9,7 @@
9
9
  * Text in, findings out. No filesystem, no AST, no network - a diagnosis that
10
10
  * takes eight seconds and needs a build step is a diagnosis nobody runs.
11
11
  */
12
- import { tokenFor } from "./tokens.js";
12
+ import { tokenMatch } from "./tokens.js";
13
13
  /**
14
14
  * `next/og` renders JSX to a PNG on the server. There is no document, so there
15
15
  * is no `var(--ds-*)` to read: every colour in such a file MUST be a literal.
@@ -335,13 +335,22 @@ function scanCore(file, source, table) {
335
335
  if (seen.has(key))
336
336
  return;
337
337
  seen.add(key);
338
+ // The kind is already known here, and without passing it the lookup
339
+ // answers a `gap` with a radius token.
340
+ const match = tokenMatch(table, literal, kind);
338
341
  findings.push({
339
342
  kind,
340
343
  line: at,
341
344
  literal,
342
- // The kind is already known here, and without passing it the lookup
343
- // answers a `gap` with a radius token.
344
- token: tokenFor(table, literal, kind),
345
+ token: match?.token ?? null,
346
+ /**
347
+ * A COINCIDÊNCIA VIAJA COM O ACHADO - ver `tokenMatch`.
348
+ *
349
+ * Sem esta marca, quem só tem `token: string` não consegue distinguir "o seu sistema nomeia
350
+ * este raio" de "existe um token de TIPOGRAFIA com o mesmo 2rem". A primeira é uma troca de
351
+ * uma linha; a segunda é um erro de categoria, e o `--fix` fez essa troca no repo do dono.
352
+ */
353
+ ...(match && !match.family ? { crossFamily: true } : {}),
345
354
  excerpt: clip(line),
346
355
  });
347
356
  };
@@ -445,17 +445,36 @@ const FAMILY = {
445
445
  font: "--ds-typography-",
446
446
  motion: "--ds-motion-",
447
447
  };
448
- export function tokenFor(table, literal, kind) {
448
+ /**
449
+ * O token que carrega este valor, e SE ELE É DA MESMA FAMÍLIA.
450
+ *
451
+ * A queda para o conjunto inteiro sempre existiu aqui, com um comentário admitindo que "deixa de ser
452
+ * recomendação". Como INFORMAÇÃO num relatório isso é honesto: um `2rem` que só existe como
453
+ * `--ds-typography-scale-h1-font-size` é um fato sobre o sistema deles.
454
+ *
455
+ * Como AÇÃO é um erro de categoria, e ele aconteceu: o `--fix` trocou `rounded-[2rem]` por
456
+ * `var(--ds-typography-scale-h1-font-size)` e `margin: 2rem` pela mesma coisa, no repo do dono
457
+ * (05/08). Um canto que se move no dia em que alguém edita a escala de tipo.
458
+ *
459
+ * Medido no `apps/web-dashboard` dele: 14 de 129 achados com token (11%) são coincidência entre
460
+ * famílias, e no `packages/ui` são 0 de 76 - então não é raro nem universal, é o tipo de coisa que
461
+ * só aparece num app grande.
462
+ *
463
+ * Então a função passa a dizer QUAL DOS DOIS é: quem informa usa os dois, quem escreve usa só
464
+ * `family`.
465
+ */
466
+ export function tokenMatch(table, literal, kind) {
449
467
  const hit = table.byValue.get(normalizeValue(literal));
450
468
  if (!hit || hit.length === 0)
451
469
  return null;
452
- // Narrow to the family first, and only fall back to the whole set when the
453
- // system has no token of that kind holding this value - a fallback is still
454
- // better than silence, it just stops being a recommendation.
455
470
  const prefix = kind ? FAMILY[kind] : undefined;
456
471
  const family = prefix ? hit.filter((n) => n.startsWith(prefix)) : [];
457
472
  const pool = family.length > 0 ? family : hit;
458
473
  // Semantic roles name intent; primitives name a shelf. Prefer intent.
459
474
  const semantic = pool.find((n) => n.includes("-semantic-"));
460
- return semantic ?? pool[0];
475
+ return { token: semantic ?? pool[0], family: family.length > 0 };
476
+ }
477
+ /** O nome só, para quem já tratava um `string | null` - a forma antiga, preservada. */
478
+ export function tokenFor(table, literal, kind) {
479
+ return tokenMatch(table, literal, kind)?.token ?? null;
461
480
  }
package/dist/index.js CHANGED
@@ -60,8 +60,8 @@ Usage - governance (deterministic, FREE):
60
60
  reusing the reading you already authored, plus the local
61
61
  record - checks, fixes, open requests (--record-only skips
62
62
  the measurement, --yes skips the overwrite question)
63
- synthesisui doctor --fix [--dry] replace every hand-written value your system already
64
- names with the token, in place
63
+ synthesisui doctor --fix [--write] show every hand-written value your system already names,
64
+ and the token for it. --write applies them in place
65
65
  synthesisui status where this project stands: installed version, last check,
66
66
  the ratchet, open requests - local first, no login needed
67
67
  synthesisui ci [--write] the two steps that put drift in your PR - annotations on
@@ -273,7 +273,11 @@ async function main() {
273
273
  : {}),
274
274
  ...(flags.baseline === true ? { compareBaseline: true } : {}),
275
275
  ...(flags.fix === true ? { fix: true } : {}),
276
- ...(flags.dry === true ? { dry: true } : {}),
276
+ /**
277
+ * `--fix` MOSTRA; `--fix --write` escreve. `--dry` continua aceito e é um no-op, porque um
278
+ * script escrito hoje não pode virar uma escrita amanhã por causa de uma flag que sumiu.
279
+ */
280
+ ...(flags.write === true ? { write: true } : {}),
277
281
  ...(flags["write-baseline"] === true ? { writeBaseline: true } : {}),
278
282
  cli: CLI_VERSION,
279
283
  });
@@ -84,26 +84,39 @@ Then wire it, once, with the command that is already idempotent:
84
84
  npx synthesisui@latest connect
85
85
  \`\`\`
86
86
 
87
- ## 2. AUTHORISATION - ONE QUESTION, THREE BOXES
87
+ ## 2. AUTHORISATION - ONE QUESTION, AND IT IS A SINGLE CHOICE
88
88
 
89
89
  Ask it HERE, before the import, because it is the only way the end can be four lines instead of a
90
90
  negotiation.
91
91
 
92
- \`\`\`
93
- After I import, may I apply the deterministic fixes? Nothing here is AI - it is arithmetic,
94
- and your untouched version stays frozen either way.
92
+ **ASK IT AS A SINGLE CHOICE, NEVER A MULTI-SELECT.** The first real run of this skill made the reason
93
+ obvious (dono, 05/08): a multi-select renders as three checkboxes plus a fourth, \`[ ] Type something\`,
94
+ that the question tool always appends - and free text next to three closed deterministic transforms
95
+ has nothing to act on. Whatever somebody types there matches no transform that exists, so the option
96
+ is either ignored or it lies. Three named options and the escape stays meaningful.
95
97
 
96
- [x] tokens & contrast values that repeat become one named token; text that fails
97
- contrast gets its INK changed, never its background
98
- [x] spacing lengths off your own scale converge onto the step you named
99
- [x] typography sizes and weights land on your own scale
98
+ \`\`\`
99
+ May I apply the deterministic fixes after importing? Nothing here is AI - it is arithmetic,
100
+ and the version you have now stays frozen either way.
101
+
102
+ All three (recommended)
103
+ tokens & contrast - values that repeat become one named token, and text that fails
104
+ contrast gets its INK changed, never its background
105
+ spacing - lengths off your own scale converge onto the step you named
106
+ typography - sizes and weights land on your own scale
107
+ Let me pick
108
+ then, and only then, ask which ones - there the multi-select is the right shape,
109
+ because picking IS the question
110
+ None
111
+ the system arrives exactly as your code has it, and the fixes wait on a button
100
112
  \`\`\`
101
113
 
102
- All three checked by default. Two things must be said in the same breath, because a default that
103
- edits is only defensible with both:
114
+ Two things must be said in the same breath with the recommendation, because a default that edits is
115
+ only defensible with both:
104
116
 
105
- - **v1 is frozen before anything is applied.** It is a published version, it never moves, and
106
- restoring it is one click.
117
+ - **The version they have now is frozen before anything is applied.** It becomes a published version,
118
+ it never moves, and restoring it is one click. Say the NUMBER the route answers with, never "v1":
119
+ a system publishes its baseline at birth, so an import writes into v2 and the freeze produces v3.
107
120
  - **Value, never form.** Your axes, options, parts and layers are declarations - we do not touch
108
121
  them. \`archetypeFidelity\` is excluded from this run on purpose: its fix once deleted the \`variant\`
109
122
  and \`size\` a real \`Input\` declared (dono, 02/08).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.147",
3
+ "version": "0.16.148",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {