synthesisui 0.16.471 → 0.16.473

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.
@@ -48,6 +48,8 @@ const IGNORED = [
48
48
  ".ledger-sent",
49
49
  /** O que a última medição viu, para a próxima dizer o que mudou - descreve ESTE clone. */
50
50
  ".last-sync.json",
51
+ /** O que o sync destacado mandou, esperando o fim do turno seguinte dizer - ver `what-grew.ts` (28/09). */
52
+ ".last-growth.json",
51
53
  "not-expressed.md",
52
54
  /**
53
55
  * O RELATÓRIO QUE O `inspect` ESCREVE - a leitura inteira com forma, para ele abrir no navegador.
@@ -11,6 +11,7 @@ import { governs, ungovernedIn } from "../governed.js";
11
11
  import { readMode } from "../mode.js";
12
12
  import { namesRemoved, previousText, rulesTouchedBy, } from "../rule-touched.js";
13
13
  import { loadSystem } from "./doctor.js";
14
+ import { takeGrowthNote } from "../what-grew.js";
14
15
  const pass = () => ({ continue: true });
15
16
  const speak = (context) => ({
16
17
  continue: true,
@@ -664,6 +665,10 @@ async function endOfTurn(root, input) {
664
665
  const { said } = changed.length > 0
665
666
  ? await checkChanged(root, changed, mode, "turn")
666
667
  : { said: [] };
668
+ /** O que o sync destacado do turno anterior mandou para o rascunho - dito uma vez (re-rodada, 28/09). */
669
+ const grown = await takeGrowthNote(root).catch(() => null);
670
+ if (grown)
671
+ said.push(grown);
667
672
  await moveTurnClock(root);
668
673
  out(said.length === 0
669
674
  ? pass()
@@ -4648,6 +4648,16 @@ quiet = false) {
4648
4648
  say(body(n));
4649
4649
  }
4650
4650
  }
4651
+ /**
4652
+ * A FRASE DO NASCIMENTO - e ela depende de o código dele nomear alguma coisa. Bateria, 28/09 (b2): num projeto sem
4653
+ * token nenhum o sistema inteiro é a semente, e "v1 is your tokens" levava ao contrário. Ver
4654
+ * `nao-espelha-o-que-nao-existe.spec.ts`.
4655
+ */
4656
+ export function birthLine(name, declared) {
4657
+ return declared === 0
4658
+ ? `${name} - your code names no token yet, so this system is our starting point, with your colours and sizes measured next to it (listed in the report).`
4659
+ : `${name} - v1 is your tokens, nothing improved yet; where your code names nothing, it keeps the starting point's value (listed in the report).`;
4660
+ }
4651
4661
  export async function runImport(opts) {
4652
4662
  const root = opts.root ?? process.cwd();
4653
4663
  /**
@@ -4983,8 +4993,10 @@ export async function runImport(opts) {
4983
4993
  census.scheme = scheme;
4984
4994
  if (!scheme && mustNotGuessPolarity(census)) {
4985
4995
  console.log("");
4986
- console.log(body("Your stylesheet does not say which face this project opens in, and nothing here declares a light or a dark scheme on its own. Nothing was guessed: the measurement is complete and the default stays yours to pick."));
4987
- console.log(body(`Name it with ${paint.strong("--scheme dark")} or ${paint.strong("--scheme light")}, or answer the question this command asks at a terminal.`));
4996
+ console.log(body(
4997
+ /** Desde a "(b)" do dono (28/09): as cores entram como a face única, e a escolha é um clique na tela. */
4998
+ "Your stylesheet does not say which face this project opens in, and nothing here declares a light or a dark scheme on its own. Nothing was guessed: your colours came in as the system's one face."));
4999
+ console.log(body(`Next, if you like: say light or dark on the system's page - one click - or run this with ${paint.strong("--scheme dark")} or ${paint.strong("--scheme light")}.`));
4988
5000
  }
4989
5001
  // The file on disk has to say what we sent, or the next person to read it is
4990
5002
  // reading a different import than the one that happened.
@@ -5139,7 +5151,7 @@ export async function runImport(opts) {
5139
5151
  * NÃO "ESPELHA EXATAMENTE" - rodadas 11 e 13, 28/09: a v1 trazia um `display` de 4rem e uma mono JetBrains que
5140
5152
  * o projeto nunca declarou, e a frase dizia o contrário. As fundações vêm da semente onde ele não nomeia nada.
5141
5153
  */
5142
- `${paint.strong(payload?.name ?? "Your system")} - v1 is your tokens, nothing improved yet; where your code names nothing, it keeps the starting point's value (listed in the report).`));
5154
+ birthLine(paint.strong(payload?.name ?? "Your system"), Object.keys(census.declared ?? {}).length)));
5143
5155
  /**
5144
5156
  * ONDE ELE NASCEU, dito pela plataforma e não pelo que foi pedido.
5145
5157
  *
@@ -50,9 +50,25 @@ export async function login(opts) {
50
50
  */
51
51
  if (!opts.force) {
52
52
  const existing = await readCredentials();
53
- if (existing && sameRegistry(existing.registry, base)) {
53
+ /**
54
+ * E DIZ QUEM - bateria, 28/09 (b11). O passo Install da galeria acabou de dizer que a CONTA importa ("the
55
+ * account that owns <slug>"), e esta linha dizia só o host. A mesma pergunta que o `status` já faz (#1630).
56
+ * Um token que a plataforma responde e não aceita mais não é "já entrou": segue para o login de verdade.
57
+ * Sem resposta nenhuma, vale o que a máquina tem - offline não é motivo para abrir um browser.
58
+ */
59
+ const who = existing && sameRegistry(existing.registry, base)
60
+ ? await fetch(`${base}/api/auth/get-session`, {
61
+ headers: { Authorization: `Bearer ${existing.token}` },
62
+ })
63
+ .then(async (r) => r.ok ? (await r.json()) : null)
64
+ .then((j) => ({ reached: true, email: j?.user?.email ?? null }))
65
+ .catch(() => ({ reached: false, email: null }))
66
+ : null;
67
+ if (who && (who.email || !who.reached)) {
54
68
  console.log("");
55
- console.log(`✓ Already signed in to ${base} on this machine.`);
69
+ console.log(who.email
70
+ ? `✓ Already signed in as ${who.email} on ${base}.`
71
+ : `✓ Already signed in to ${base} on this machine (the account could not be confirmed - the registry did not answer).`);
56
72
  console.log(" Run `synthesisui login --force` to sign in as someone else,");
57
73
  console.log(" or `synthesisui logout` to sign out of this machine.");
58
74
  return;
@@ -13,7 +13,7 @@ import { measuredScope, rememberScope } from "../measured-scope.js";
13
13
  import { fromCensus } from "../memory/observation.js";
14
14
  import { reportMeasurement } from "../memory/report.js";
15
15
  import { mergeCensus } from "../merge-census.js";
16
- import { describeGrowth, whatGrew } from "../what-grew.js";
16
+ import { describeGrowth, recordGrowthForTurn, whatGrew } from "../what-grew.js";
17
17
  import { body, paint, section, snippet } from "../output.js";
18
18
  import { repoStateOf } from "../repo-state.js";
19
19
  import { reportWhatIsLeft } from "./align.js";
@@ -617,6 +617,9 @@ export async function remeasure(args) {
617
617
  console.log(section("What your code grew"));
618
618
  for (const l of growth)
619
619
  console.log(body(l));
620
+ /** Sem terminal (o fim do turno), a lista fica para o turno seguinte dizer - ver `recordGrowthForTurn`. */
621
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
622
+ await recordGrowthForTurn(root, growth);
620
623
  if (!(await askToSendGrowth())) {
621
624
  console.log(body("Nothing was sent. Your draft is untouched - run `npx synthesisui sync` again when you want them in."));
622
625
  return null;
@@ -490,6 +490,44 @@ export function scanSource(file, source, table) {
490
490
  }
491
491
  return scanCore(file, source, table);
492
492
  }
493
+ /**
494
+ * OS SELETORES QUE DECLARAM VOCABULÁRIO - `@theme`, `:root` e a redefinição por tema (bateria, 28/09: b5 escreve
495
+ * `[data-theme="dark"] { --c-bg: #0f172a; ... }`). Dentro deles uma `--x: valor` é a definição, não um uso.
496
+ */
497
+ const TOKEN_SELECTOR_PART = /^(@theme\b.*|:root(\[[^\]]*\])?|html(\[[^\]]*\]|\.[\w-]+)?|\[data-(theme|mode|scheme|color-scheme)(=[^\]]*)?\]|\.(dark|light))$/;
498
+ /**
499
+ * O SELETOR INTEIRO precisa ser a raiz do tema - `html.dark .shell { --shell-fill: … }` é uma variável de UM
500
+ * componente redefinida por tema, e essa continua sendo acusada (o corpus `css-global-custom-property`).
501
+ */
502
+ function isTokenSelector(selector) {
503
+ const parts = selector.split(",").map((p) => p.trim());
504
+ return parts.length > 0 && parts.every((p) => TOKEN_SELECTOR_PART.test(p));
505
+ }
506
+ /**
507
+ * ONDE ESTÃO OS VALORES QUE ESTA LINHA ATRIBUI A UMA VARIÁVEL, dentro de um bloco de vocabulário - bateria, 28/09.
508
+ *
509
+ * A guarda de `scanCore` lia `--x:` só no COMEÇO da linha, e `:root { --q-stone: #44403c; ... }` numa linha só
510
+ * voltava como quatro "replace now" (b8). Trocar `#44403c` por `var(--q-stone)` na própria declaração faz o token
511
+ * apontar para si mesmo: o navegador descarta o valor e toda cor do app some. O seletor que governa cada
512
+ * declaração é o do último `{` antes dela na linha; sem `{` antes, vale o bloco multilinha em que a linha está.
513
+ */
514
+ function declarationSpans(line, inBlock) {
515
+ const out = [];
516
+ for (const m of line.matchAll(DECLARES_ALL)) {
517
+ const start = m.index ?? 0;
518
+ const open = line.lastIndexOf("{", start + 1);
519
+ const governed = open < 0
520
+ ? inBlock
521
+ : line.lastIndexOf("}", start) < open &&
522
+ isTokenSelector(line.slice(line.lastIndexOf("}", open) + 1, open).trim());
523
+ if (!governed)
524
+ continue;
525
+ const colon = m[0].indexOf(":");
526
+ const valueAt = start + m[0].indexOf(m[2], colon);
527
+ out.push([valueAt, valueAt + m[2].length]);
528
+ }
529
+ return out;
530
+ }
493
531
  /** `--color-ocean-500: #059aed;` - uma declaração de custom property no CSS dele. */
494
532
  /** Cada declaração `--x: valor;` da linha, quantas houver - ver o laço em `scanCore`. */
495
533
  const DECLARES_ALL = /(?:^|[\s{;])(--[a-z0-9-]+)\s*:\s*([^;{}]+);/gi;
@@ -558,7 +596,7 @@ function scanCore(file, source, table) {
558
596
  const indent = line ? raw.indexOf(line) : 0;
559
597
  const at = i + 1;
560
598
  const depthBefore = braceDepth;
561
- const opensTokenBlock = /^(@theme\b|:root\b)/.test(line) && line.includes("{");
599
+ const opensTokenBlock = line.includes("{") && isTokenSelector(line.slice(0, line.indexOf("{")).trim());
562
600
  if (opensTokenBlock && tokenBlockDepth === 0)
563
601
  tokenBlockDepth = depthBefore + 1;
564
602
  const inTokenBlock = tokenBlockDepth > 0 && (opensTokenBlock || depthBefore >= tokenBlockDepth);
@@ -639,6 +677,8 @@ function scanCore(file, source, table) {
639
677
  // written by hand - flagging it would be telling someone to tokenize a
640
678
  // variable. And the same literal twice in one declaration (a two-stop
641
679
  // shadow) is one decision, so it is reported once.
680
+ const declared = declarationSpans(line, inTokenBlock && !opensTokenBlock);
681
+ const inDeclaration = (at) => declared.some(([a, b]) => at >= a && at < b);
642
682
  const spans = fallbackSpans(line, table);
643
683
  const inFallback = (at) => spans.some(([a, b]) => at >= a && at <= b);
644
684
  /** Ver `markupTextSpans`: HTML montado como texto sai do alcance da folha do app. */
@@ -653,6 +693,9 @@ function scanCore(file, source, table) {
653
693
  const push = (kind, literal, col = -1) => {
654
694
  if (literal.includes("$") || literal.includes("{"))
655
695
  return;
696
+ /** A definição do token não é drift - ver `declarationSpans`. */
697
+ if (col >= 0 && inDeclaration(col))
698
+ return;
656
699
  if (col >= 0 && inFallback(col)) {
657
700
  setAside("a token's own fallback");
658
701
  return;
package/dist/fonts.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { subsetsToEmit, weightsToEmit, } from "./next-font-weights.js";
2
- // Fallbacks genéricos do CSS - não são webfonts.
2
+ // Fallbacks genéricos do CSS e as famílias de sistema - nenhuma delas é webfont para baixar.
3
3
  const GENERIC_FAMILIES = new Set([
4
4
  "sans-serif",
5
5
  "serif",
@@ -12,6 +12,24 @@ const GENERIC_FAMILIES = new Set([
12
12
  "fantasy",
13
13
  "inherit",
14
14
  "initial",
15
+ /**
16
+ * AS FAMÍLIAS QUE JÁ ESTÃO EM TODA MÁQUINA - bateria, 28/09. Georgia, Arial e Verdana não se baixam de lugar
17
+ * nenhum, e pedi-las ao Google Fonts gerava um `<link>` para o que ele não tem, logo abaixo da frase que dizia isso.
18
+ */
19
+ "arial",
20
+ "helvetica",
21
+ "helvetica neue",
22
+ "georgia",
23
+ "verdana",
24
+ "tahoma",
25
+ "trebuchet ms",
26
+ "times new roman",
27
+ "times",
28
+ "courier new",
29
+ "courier",
30
+ "segoe ui",
31
+ "-apple-system",
32
+ "blinkmacsystemfont",
15
33
  ]);
16
34
  /**
17
35
  * A PRIMEIRA FAMÍLIA DE UMA PILHA - `"Geist Mono", ui-monospace, monospace` é `Geist Mono`.
@@ -295,7 +295,16 @@
295
295
  * escrita como a função dele (`color("brand")`), nunca `$semantic-brand` (rodada 14 do refinamento). Um
296
296
  * componente escrito antes num projeto desses está sem estilo.
297
297
  */
298
- export const MATERIALISER_SINCE = "0.16.471";
298
+ /**
299
+ * 0.16.471 -> 0.16.472 em 28/09, e o passo 1 dá **SIM**: o `.gitignore` da pasta passa a cobrir `.last-growth.json`, o que
300
+ * o sync destacado deixa para o fim do turno dizer (re-rodada depois do #1631). Uma pasta escrita antes deixaria esse
301
+ * arquivo local entrar num commit.
302
+ */
303
+ /**
304
+ * 0.16.472 -> 0.16.473 em 28/09, e o passo 1 dá **SIM** (a bateria): as famílias de sistema (Georgia, Arial, Verdana)
305
+ * deixam de virar `<link>` do Google Fonts, e a webfont ao lado delas ganha o `fonts.ts` que antes não saía.
306
+ */
307
+ export const MATERIALISER_SINCE = "0.16.473";
299
308
  /**
300
309
  * A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
301
310
  *
@@ -467,7 +476,16 @@ export const COUNTED_DIFFERENTLY = "this run counts a value as named only when Y
467
476
  * (`$palette`, `color("brand")`) - rodada 14 do refinamento. Um hook anterior acusa como valor à mão toda cor
468
477
  * que o projeto guarda num mapa.
469
478
  */
470
- export const CHECKER_SINCE = "0.16.471";
479
+ /**
480
+ * 0.16.471 -> 0.16.472 em 28/09, e o passo 1 dá **SIM**: o fim do turno entrega ao agente o que o sync destacado do turno
481
+ * anterior mandou para o rascunho (re-rodada, f12). Um hook anterior deixa esses componentes entrarem calados.
482
+ */
483
+ /**
484
+ * 0.16.472 -> 0.16.473 em 28/09, e o passo 1 dá **SIM** (a bateria, b8): o hook deixa de acusar o valor que um
485
+ * `:root { --q-stone: #44403c; … }` de uma linha atribui ao token. Um hook anterior manda trocar a definição pelo
486
+ * próprio nome, e quem obedece apaga a cor.
487
+ */
488
+ export const CHECKER_SINCE = "0.16.473";
471
489
  /**
472
490
  * A ÚLTIMA VERSÃO EM QUE OS LEITORES PASSARAM A PRODUZIR UM CENSO DIFERENTE.
473
491
  *
@@ -932,7 +950,12 @@ export const CHECKER_SINCE = "0.16.471";
932
950
  * não põe no sistema a peça do framework (`StrictMode`, `next/link`) nem a raiz do Vite (`src/App.tsx`); e o que
933
951
  * mora na pasta compartilhada de componentes nunca vira "feature". Um censo lido antes guardou tudo isso errado.
934
952
  */
935
- export const READER_SINCE = "0.16.471";
953
+ /**
954
+ * 0.16.471 -> 0.16.473 em 28/09, e o passo 1 dá **SIM** (a bateria, b3, b5, b8): a varredura deixa de contar como valor à
955
+ * mão o que um `:root { --a: x; }` ou um `[data-theme="dark"] { … }` de UMA linha atribui a uma variável - a definição do
956
+ * token. Quatro apps do corpus moveram. Um censo lido antes guardou essas declarações como drift.
957
+ */
958
+ export const READER_SINCE = "0.16.473";
936
959
  /**
937
960
  * O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
938
961
  *
@@ -1,3 +1,4 @@
1
+ import { SKIP_LABEL } from "./doctor/component-gate.js";
1
2
  /**
2
3
  * O QUE ESTA LEITURA NÃO CONSEGUIU SEGURAR, escrito pela esteira e não pedido a ninguém.
3
4
  *
@@ -69,7 +70,9 @@ export function notExpressed(census) {
69
70
  " a reading to correct - and the reason below is the one to argue with.");
70
71
  out.push("");
71
72
  for (const s of skipped)
72
- out.push(` ${s.name}${s.file ? ` (${s.file})` : ""}\n ${s.because}`);
73
+ out.push(
74
+ /** `because` é opcional; sem ele, o rótulo do `why` - nunca `undefined` (bateria, 28/09). */
75
+ ` ${s.name}${s.file ? ` (${s.file})` : ""}\n ${s.because ?? SKIP_LABEL[s.why] ?? s.why}`);
73
76
  }
74
77
  out.push("");
75
78
  return `${out.join("\n")}\n`;
package/dist/what-grew.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import { join, relative } from "node:path";
2
+ import { dirname, join, relative } from "node:path";
3
+ import { readStyleSheet } from "./doctor/scss-lens.js";
3
4
  import { loadSystem, walkAll } from "./commands/doctor.js";
4
5
  import { diagnose, scanSource } from "./doctor/scan.js";
5
6
  export async function whatGrew(root, census,
@@ -26,6 +27,9 @@ before) {
26
27
  context.push(report);
27
28
  }
28
29
  }
30
+ /** Os componentes dele que um arquivo monta, cada um uma vez - fora ele mesmo. */
31
+ const own = new Set(now.filter((c) => typeof c.api?.file === "string").map((c) => c.name));
32
+ const composed = (src, self) => new Set([...src.matchAll(/<([A-Z][A-Za-z0-9]*)\b/g)].map((m) => m[1]).filter((n) => n !== self && own.has(n))).size;
29
33
  const born = [];
30
34
  for (const c of fresh) {
31
35
  const file = c.api?.file;
@@ -34,13 +38,29 @@ before) {
34
38
  born.push({ name: c.name, file, fromSystem: 0, of: 0, byHand: [] });
35
39
  continue;
36
40
  }
37
- const d = diagnose([scanSource(file, src, table)], context);
41
+ /**
42
+ * E A FOLHA QUE ELE IMPORTA - relato 15, 28/09: "Landing - no colour or spacing of its own to measure", com os
43
+ * valores no `Landing.module.css` ao lado. O componente é o `.tsx` MAIS o estilo que ele puxa.
44
+ */
45
+ const reports = [scanSource(file, src, table)];
46
+ for (const m of src.matchAll(/^\s*import\s+(?:[\w{}\s,*]+\s+from\s+)?["'](\.{1,2}\/[^"']+\.(?:css|scss|sass|less))["']/gm)) {
47
+ const sheet = join(dirname(file), m[1]);
48
+ const css = await readStyleSheet(join(root, sheet)).catch(() => "");
49
+ if (css)
50
+ reports.push(scanSource(sheet, css, table));
51
+ }
52
+ const d = diagnose(reports, context);
53
+ /** Classes utilitárias numa string qualquer do arquivo - `className="…"`, `cn("…")`, `cva("…")` (f11, 28/09). */
54
+ const utilities = !d.reach.measured &&
55
+ /["'`][^"'`\n]*(?<![\w-])(bg|text|border|p[xytrbl]?|m[xytrbl]?|gap|rounded|shadow)-[a-z0-9][^"'`\n]*["'`]/.test(src);
38
56
  born.push({
39
57
  name: c.name,
40
58
  file,
41
59
  fromSystem: d.reach.measured ? d.reach.uses : 0,
42
60
  of: d.reach.measured ? d.reach.of : 0,
43
61
  byHand: [...new Set(d.findings.map((f) => f.literal))],
62
+ ...(utilities ? { utilities: true } : {}),
63
+ ...(composed(src, c.name) >= 3 ? { composes: composed(src, c.name) } : {}),
44
64
  });
45
65
  }
46
66
  return { born, gone };
@@ -50,9 +70,50 @@ export function describeGrowth(grown) {
50
70
  const out = [];
51
71
  for (const b of grown.born)
52
72
  out.push(b.of === 0
53
- ? `${b.name} (${b.file}) - no colour or spacing of its own to measure`
73
+ ? b.utilities
74
+ ? `${b.name} (${b.file}) - styled with utility classes; \`npx synthesisui doctor\` measures those`
75
+ : `${b.name} (${b.file}) - no colour or spacing of its own to measure`
54
76
  : `${b.name} (${b.file}) - ${b.fromSystem} of ${b.of} values from your system${b.byHand.length > 0 ? `; by hand: ${b.byHand.slice(0, 4).join(", ")}${b.byHand.length > 4 ? ", …" : ""}` : ""}`);
77
+ /** A tela que mora na pasta de componentes: a linha pergunta, e quem responde decide. */
78
+ for (const [i, b] of grown.born.entries())
79
+ if (b.composes)
80
+ out[i] = `${out[i]} - it composes ${b.composes} of your components - a screen?`;
55
81
  for (const g of grown.gone)
56
82
  out.push(`${g} is no longer in your code - it leaves the draft with this send`);
57
83
  return out;
58
84
  }
85
+ /**
86
+ * O QUE O SYNC DESTACADO MANDOU, PARA O FIM DO TURNO SEGUINTE DIZER - re-rodada, f12 (28/09).
87
+ *
88
+ * Pelo agente, o `sync` roda destacado do fim do turno (`auto-sync.ts`), com a saída descartada: o bloco "What your
89
+ * code grew" não chegava a ninguém, e o componente entrava no rascunho calado - o contrário da decisão do dono. A
90
+ * lista fica ao lado, e o fim do turno seguinte a entrega ao agente UMA vez, para ele contar à pessoa.
91
+ */
92
+ const GROWTH_FILE = ".last-growth.json";
93
+ export async function recordGrowthForTurn(root, lines) {
94
+ if (lines.length === 0)
95
+ return;
96
+ const { writeFile } = await import("node:fs/promises");
97
+ await writeFile(join(root, "_synthesisui", GROWTH_FILE), JSON.stringify({ at: new Date().toISOString(), lines }), "utf8").catch(() => { });
98
+ }
99
+ export async function takeGrowthNote(root) {
100
+ const { rm } = await import("node:fs/promises");
101
+ const path = join(root, "_synthesisui", GROWTH_FILE);
102
+ const raw = await readFile(path, "utf8").catch(() => null);
103
+ if (raw == null)
104
+ return null;
105
+ await rm(path, { force: true }).catch(() => { });
106
+ try {
107
+ const { lines } = JSON.parse(raw);
108
+ if (!lines?.length)
109
+ return null;
110
+ return [
111
+ "The last sync sent to your system's draft (the person has not seen this yet - tell the person, in one line each):",
112
+ ...lines.map((l) => ` ${l}`),
113
+ "Each one can stay out of the system on its page before the draft is published.",
114
+ ].join("\n");
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.471",
3
+ "version": "0.16.473",
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": {