synthesisui 0.16.172 → 0.16.174

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.
@@ -137,6 +137,8 @@ export async function add(slug, opts) {
137
137
  registry: base,
138
138
  fetchedAt: new Date().toISOString(),
139
139
  ...(opts.cli ? { cli: opts.cli } : {}),
140
+ /** Ver `RegistryPayload.compiler`: é o que faz um conserto de CSS chegar a um install. */
141
+ ...(payload.compiler != null ? { compiler: payload.compiler } : {}),
140
142
  ...(scope ? { scope } : {}),
141
143
  ...(usage.length > 0 ? { usage } : {}),
142
144
  };
@@ -138,16 +138,38 @@ export async function versionBehind(root, opts = {}) {
138
138
  const token = await readToken();
139
139
  if (!token)
140
140
  return null;
141
- const res = await fetch(`${lock.registry ?? resolveRegistry()}/api/registry/ds/${lock.slug}`, { headers: { Authorization: `Bearer ${token}` } }).catch(() => null);
141
+ /**
142
+ * `?meta=1` - a versão e o compilador, e nada mais.
143
+ *
144
+ * Isto baixava o payload INTEIRO para ler um número: o documento, três CSS compilados e o GUIDE,
145
+ * a cada abertura de sessão e a cada entrada na pasta pelo terminal.
146
+ */
147
+ const res = await fetch(`${lock.registry ?? resolveRegistry()}/api/registry/ds/${lock.slug}?meta=1`, { headers: { Authorization: `Bearer ${token}` } }).catch(() => null);
142
148
  if (!res?.ok)
143
149
  return null;
144
150
  const body = (await res.json().catch(() => null));
145
- if (!body?.version || body.version <= lock.version)
151
+ if (!body?.version)
146
152
  return null;
147
- return {
148
- says: `v${body.version} of "${lock.slug}" is published and this repo is on v${lock.version}. The CSS here and the rules your agent reads are both v${lock.version} - they move together, which is why this is worth saying rather than applying.`,
149
- run: `npx synthesisui upgrade ${lock.slug}`,
150
- };
153
+ if (body.version > lock.version)
154
+ return {
155
+ says: `v${body.version} of "${lock.slug}" is published and this repo is on v${lock.version}. The CSS here and the rules your agent reads are both v${lock.version} - they move together, which is why this is worth saying rather than applying.`,
156
+ run: `npx synthesisui upgrade ${lock.slug}`,
157
+ };
158
+ /**
159
+ * A MESMA VERSÃO, COMPILADA DIFERENTE - o caso que não tinha como ser dito nem consertado.
160
+ *
161
+ * O CSS não é congelado na publicação: ele é compilado do documento a cada busca. Então um conserto
162
+ * nosso vale para uma versão JÁ publicada, e nenhum comando o buscava - `upgrade` age por versão e
163
+ * `connect` agia por CLI. Em 07/08 o bloco de esquema alternativo passou a emitir os dois
164
+ * ancestrais e não havia caminho até o disco de quem já tinha instalado.
165
+ */
166
+ if (typeof body.compiler === "number" &&
167
+ body.compiler !== (lock.compiler ?? null))
168
+ return {
169
+ says: `the css for "${lock.slug}" v${lock.version} is compiled differently now - same version, same document, a fix on our side. The files in this repo were written before it.`,
170
+ run: "npx synthesisui connect",
171
+ };
172
+ return null;
151
173
  }
152
174
  /**
153
175
  * A linha que a sessão vê.
@@ -2,6 +2,7 @@ import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import { wireAgent } from "../agent-wiring.js";
4
4
  import { blockHomes, syncClaudeMd } from "../claude-md.js";
5
+ import { resolveRegistry } from "../config.js";
5
6
  import { body, paint, section, snippet } from "../output.js";
6
7
  import { hasHook, rcPathFor, shellFrom, shellSnippet, withHook, } from "../shell-hook.js";
7
8
  import { IMPORT_SKILL, IMPORT_SKILL_PATH } from "../skill-import.js";
@@ -48,6 +49,14 @@ const LEGACY_SKILLS = ["import-design-system"];
48
49
  * SILENCIOSO QUANDO JÁ ESTÁ EM DIA, e sem rede não faz nada - um `connect` que falha por estar num
49
50
  * avião seria pior que a defasagem que ele conserta.
50
51
  */
52
+ /** O número do compilador que serviria esta versão HOJE - `null` quando não dá para saber. */
53
+ async function fetchCompiler(base, slug, version) {
54
+ const res = await fetch(`${base}/api/registry/ds/${slug}?version=${version}&meta=1`).catch(() => null);
55
+ if (!res?.ok)
56
+ return null;
57
+ const body = (await res.json().catch(() => null));
58
+ return typeof body?.compiler === "number" ? body.compiler : null;
59
+ }
51
60
  async function refreshInstall(root, cli, registry) {
52
61
  const dsDir = join(root, "_synthesisui", "ds");
53
62
  const names = await readdir(dsDir, { withFileTypes: true }).catch(() => []);
@@ -67,7 +76,22 @@ async function refreshInstall(root, cli, registry) {
67
76
  /** Um DS adotado é descrito aqui e pertencido em outro lugar - não há o que rematerializar. */
68
77
  if (!lock.slug || lock.adopted || typeof lock.version !== "number")
69
78
  continue;
70
- if (lock.cli === cli)
79
+ /**
80
+ * O COMPILADOR TAMBÉM DECIDE, e sem ele um conserto de CSS não alcançava ninguém.
81
+ *
82
+ * `tokens.css` é compilado no servidor a cada busca, então ele melhora sem que uma linha do CLI
83
+ * mude e sem que a versão do sistema ande. Aí `upgrade` (que age por versão) e este bloco (que
84
+ * agia só por CLI) passavam batido, e o conserto ficava disponível para sempre sem caminho até o
85
+ * disco de quem instalou - foi o que aconteceu em 07/08 com o seletor do esquema alternativo.
86
+ *
87
+ * `?meta=1` custa algumas centenas de bytes; falhar nele não pode custar o `connect`, então um
88
+ * erro de rede simplesmente não aciona a rematerialização.
89
+ */
90
+ /** O host que emitiu ESTE install manda - um token pertence ao host que o emitiu. */
91
+ const base = resolveRegistry(registry ?? lock.registry);
92
+ const remote = await fetchCompiler(base, lock.slug, lock.version);
93
+ const compilerMoved = remote != null && remote !== (lock.compiler ?? null);
94
+ if (lock.cli === cli && !compilerMoved)
71
95
  continue;
72
96
  const done = await add(lock.slug, {
73
97
  ...(registry ? { registry } : {}),
@@ -32,7 +32,20 @@ function idOf(kind, name, at) {
32
32
  }
33
33
  export async function fileRequest(root, req) {
34
34
  const at = req.at ?? new Date().toISOString();
35
- const full = { ...req, at, id: idOf(req.kind, req.name, at) };
35
+ /**
36
+ * O CARIMBO SÓ PODE SER FEITO AGORA: depois que alguém edita o sistema, não há como saber se o nome
37
+ * existia quando o pedido nasceu - e é essa a diferença entre "apareceu" e "sempre esteve lá".
38
+ */
39
+ const name = checkableName({ ...req, at, id: "" });
40
+ const existed = name
41
+ ? declaresName(await readInstalledCss(root), name)
42
+ : false;
43
+ const full = {
44
+ ...req,
45
+ at,
46
+ id: idOf(req.kind, req.name, at),
47
+ ...(existed ? { existed: true } : {}),
48
+ };
36
49
  try {
37
50
  await appendFile(path(root), `${JSON.stringify(full)}\n`, "utf8");
38
51
  }
@@ -94,24 +107,80 @@ export function checkableName(req) {
94
107
  const m = /[a-z][a-z0-9]*(?:-[a-z0-9]+)+/.exec(req.name.trim());
95
108
  return m ? m[0] : null;
96
109
  }
97
- /** Requests whose ask the installed css now demonstrably delivers. */
110
+ /**
111
+ * Cada `--custom-property: valor` que o css instalado declara. A ÚLTIMA vence, que é o que a cascata
112
+ * faz para declarações do mesmo peso - ler a primeira responderia sobre um bloco que o navegador
113
+ * descarta.
114
+ */
115
+ function declaredValues(css) {
116
+ const out = new Map();
117
+ const re = /(--[a-z0-9-]+)\s*:\s*([^;}]+)/gi;
118
+ let m = re.exec(css);
119
+ while (m) {
120
+ out.set(m[1].toLowerCase(), m[2].trim());
121
+ m = re.exec(css);
122
+ }
123
+ return out;
124
+ }
125
+ /**
126
+ * O valor final de uma declaração, seguindo `var()` enquanto der. Um design system nomeia um valor
127
+ * apontando um papel para uma primitiva, então o valor pedido quase nunca está escrito na linha do
128
+ * papel - parar no primeiro `var()` reprovaria justamente o caso comum.
129
+ */
130
+ function resolved(map, value, hops = 5) {
131
+ let v = value.trim();
132
+ for (let i = 0; i < hops; i += 1) {
133
+ const m = /^var\(\s*(--[a-z0-9-]+)/i.exec(v);
134
+ if (!m)
135
+ break;
136
+ const next = map.get(m[1].toLowerCase());
137
+ if (next == null)
138
+ break;
139
+ v = next.trim();
140
+ }
141
+ return v.toLowerCase().replace(/\s+/g, " ");
142
+ }
143
+ const candidatesFor = (name) => name.startsWith("animate-") ? [`--${name}`] : [`--ds-${name}`, `--${name}`];
144
+ /** Os nomes que o css instalado JÁ declara - ver `GapRequest.existed`. */
145
+ export function declaresName(installedCss, name) {
146
+ const declared = declaredValues(installedCss);
147
+ return candidatesFor(name).some((c) => declared.has(c.toLowerCase()));
148
+ }
149
+ /**
150
+ * Requests whose ask the installed css now demonstrably delivers.
151
+ *
152
+ * APARECER É PROVA; EXISTIR NÃO É - e a diferença fechou um bug real como entregue.
153
+ *
154
+ * Um pedido é "isto não tem nome". Quando o nome de fato não existia, ele passar a existir é prova
155
+ * suficiente e é o caso comum - `animate-rise` não tem um valor comparável, o pedido é pelo NOME.
156
+ *
157
+ * Mas um pedido feito SOBRE um token que já existe é sobre o VALOR dele, e aí existir responde sim
158
+ * no instante em que o pedido nasce. Em 07/08 um agente reportou `color-semantic-canvas` apontando
159
+ * para a mesma primitiva que o `foreground` - 1.00:1, texto invisível - e o `sync` seguinte imprimiu
160
+ * *"delivered. Nothing for anyone to do"* com o defeito intacto no disco do dono.
161
+ *
162
+ * Então `existed` é carimbado quando o pedido é feito, e um pedido assim só fecha quando o css
163
+ * ENTREGA o valor pedido. Sem valor comparável ele fica aberto e quem decide é a pessoa, no cartão -
164
+ * um pedido aberto custa uma linha numa tela, um fechamento falso custa o bug.
165
+ */
98
166
  export function satisfiedRequests(requests, installedCss) {
167
+ const declared = declaredValues(installedCss);
99
168
  return requests.filter((r) => {
100
169
  const name = checkableName(r);
101
170
  if (!name)
102
171
  return false;
103
- const candidates = name.startsWith("animate-")
104
- ? [`--${name}`]
105
- : [`--ds-${name}`, `--${name}`];
106
- return candidates.some((c) => installedCss.includes(`${c}:`));
172
+ const present = candidatesFor(name).filter((c) => declared.has(c.toLowerCase()));
173
+ if (present.length === 0)
174
+ return false;
175
+ if (!r.existed)
176
+ return true;
177
+ const want = r.value?.trim();
178
+ if (!want)
179
+ return false;
180
+ const asked = resolved(declared, want);
181
+ return present.some((c) => resolved(declared, declared.get(c.toLowerCase()) ?? "") === asked);
107
182
  });
108
183
  }
109
- /**
110
- * Everything the pinned install actually ships, as one string - tokens.css
111
- * AND theme.css of the locked version, for every installed system. This is
112
- * the ground verification stands on: not the doc, not the GUIDE's promises,
113
- * the css a build would really read.
114
- */
115
184
  export async function readInstalledCss(root) {
116
185
  const dsDir = join(root, "_synthesisui", "ds");
117
186
  let css = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.172",
3
+ "version": "0.16.174",
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": {