synthesisui 0.16.139 → 0.16.141

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.
@@ -65,9 +65,24 @@ const FRONTIERS = new Set(["component", "external"]);
65
65
  * its own box, because its styles already sit on the component's base.
66
66
  */
67
67
  const STRUCTURAL = /^(root|wrapper|base|el|outer)$/i;
68
- /** A pathological transcription of somebody's DOM, bounded. Six levels reaches
69
- * any real component; past that it is layout divs all the way down. */
70
- const MAX_DEPTH = 6;
68
+ /**
69
+ * A transcrição patológica do DOM de alguém, com teto - e o teto foi medido errado.
70
+ *
71
+ * "Seis níveis alcançam qualquer componente real; além disso são divs de layout até o fim" foi
72
+ * calibrado numa biblioteca de 36 componentes. Um app de 275 desmente: a leitura do
73
+ * `apps/web-dashboard` tem 64 nós além do 6, e a distribuição mostra que a cauda é fina e é
74
+ * CONTEÚDO (05/08):
75
+ *
76
+ * nível 0 1 2 3 4 5 6 7 8 9
77
+ * nós 923 935 871 609 405 231 139 50 12 2
78
+ *
79
+ * O que estava acontecendo com esses 64: `walk` retornava ali, então tudo abaixo do sexto nível era
80
+ * descartado em silêncio - a estrutura que o cliente escreveu, perdida sem uma linha de aviso.
81
+ *
82
+ * 10 cobre a população inteira com folga e mantém o teto contra recursão patológica, que é a razão
83
+ * de ele existir. Um número medido em vez de estimado.
84
+ */
85
+ const MAX_DEPTH = 10;
71
86
  /**
72
87
  * The same ceiling the sketch measured, for the same reason.
73
88
  *
@@ -2254,7 +2254,7 @@ function rootBehaviour(tag, sketch) {
2254
2254
  ? { rootProps: first.behaviourProps }
2255
2255
  : {};
2256
2256
  }
2257
- async function resolveReadParts(census, root) {
2257
+ export async function resolveReadParts(census, root) {
2258
2258
  /**
2259
2259
  * A READING IS OPTIONAL HERE. THE SKETCH IS NOT.
2260
2260
  *
@@ -3,8 +3,9 @@ import { join, resolve } from "node:path";
3
3
  import { readToken, resolveRegistry } from "../config.js";
4
4
  import { readEvents } from "../doctor/ledger.js";
5
5
  import { checkableName, closeRequest, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
6
- import { body, section } from "../output.js";
6
+ import { body, paint, section } from "../output.js";
7
7
  import { repoStateOf } from "../repo-state.js";
8
+ import { resolveReadParts, takeCensus } from "./import.js";
8
9
  /**
9
10
  * What each decision means ON THIS MACHINE - the card decides, the sync
10
11
  * enacts. The web never touches the local file; this line is printed right
@@ -83,6 +84,19 @@ export async function sync(opts) {
83
84
  console.log(body("No installed system here, so there is nowhere to sync to."));
84
85
  return;
85
86
  }
87
+ /**
88
+ * A MEDIÇÃO NOVA, ANTES DO REGISTRO - e é isto que faz um conserto de leitura alcançar alguém.
89
+ *
90
+ * Um leitor novo do CLI precisa OLHAR o repo: o que a plataforma guarda é o censo, e o fonte fica
91
+ * aqui por decisão de privacidade. Essa passada é irredutível. O que era redutível é a cerimônia -
92
+ * até hoje "re-importar" significava a skill inteira, seis perguntas, e um agente reescrevendo 261
93
+ * anatomias que já estavam gravadas (dono, 05/08).
94
+ *
95
+ * Então: a plataforma devolve o que já foi decidido (o escopo e a leitura), o CLI mede com o leitor
96
+ * ATUAL, funde a leitura no censo fresco, AVISA o que seria reescrito, e envia. Sem entrevista.
97
+ */
98
+ if (!opts.recordOnly)
99
+ await remeasure({ root, base, token, slug, cli: opts.cli });
86
100
  // Verification first, so the snapshot never reports "open" about something
87
101
  // the installed css already delivers - a request closes because a machine
88
102
  // CHECKED, not because someone remembered to say so (dono, 29/07).
@@ -152,3 +166,133 @@ export async function sync(opts) {
152
166
  console.log(body(`The record lives on your system's overview:`));
153
167
  console.log(body(` ${base}/dashboard/mine/${slug}`));
154
168
  }
169
+ /**
170
+ * MEDE DE NOVO, AVISA, E ENVIA - o corpo do `sync` que faz um leitor novo chegar num sistema.
171
+ *
172
+ * A ordem é a lei aqui, e cada passo existe por um caso real:
173
+ *
174
+ * 1. a plataforma devolve o ESCOPO e a LEITURA já gravados. Sem isto, o `sync` teria que
175
+ * perguntar de novo o que já foi decidido, e um agente reescreveria 261 anatomias.
176
+ * 2. mede com o leitor ATUAL - é o único passo que precisa estar nesta máquina, porque o fonte
177
+ * dele não sai daqui.
178
+ * 3. funde a leitura gravada no censo fresco. A anatomia é do agente; a medição é nossa.
179
+ * 4. AVISA antes de enviar o que seria reescrito, e pergunta. Avisar depois de escrever não é
180
+ * avisar.
181
+ * 5. envia para a rota que atualiza o RASCUNHO pelo slug - nada publicado se move.
182
+ */
183
+ async function remeasure(args) {
184
+ const { root, base, token, slug } = args;
185
+ const head = await fetch(`${base}/api/registry/ds/${slug}/reading`, {
186
+ headers: { Authorization: `Bearer ${token}` },
187
+ }).catch(() => null);
188
+ if (!head?.ok) {
189
+ console.log(section("Sync"));
190
+ console.log(body(head
191
+ ? `The registry answered ${head.status} for this system's reading, so nothing was re-measured. The record below still travels.`
192
+ : "Could not reach the registry, so nothing was re-measured. The record below still travels."));
193
+ return;
194
+ }
195
+ const stored = (await head.json());
196
+ /**
197
+ * SEM CENSO GRAVADO NÃO HÁ O QUE REMEDIR CONTRA. Um sistema que nasceu da galeria não tem escopo
198
+ * nem leitura, e medir o repo dele produziria um censo de um projeto que ninguém mapeou para ele.
199
+ */
200
+ if (!stored.hasCensus) {
201
+ console.log(section("Sync"));
202
+ console.log(body(`"${slug}" did not come from an import, so there is no measurement to refresh. Import it first: npx synthesisui import`));
203
+ return;
204
+ }
205
+ console.log(section("Measuring your repository again"));
206
+ console.log(body(paint.faint(`${stored.scope ? `scope ${stored.scope}` : "the whole repo"} · the reading you already authored travels unchanged`)));
207
+ const census = await takeCensus(stored.scope ? join(root, stored.scope) : root, {
208
+ ...(stored.scope ? { scopeLabel: stored.scope } : {}),
209
+ ...(args.cli ? { cli: args.cli } : {}),
210
+ /** O `--usage` é EVIDÊNCIA e leva rótulo: sem ele o censo não sabe de que app o número veio. */
211
+ usage: (stored.usage ?? []).map((u) => ({
212
+ path: join(root, u),
213
+ label: u,
214
+ })),
215
+ }).catch(() => null);
216
+ if (!census) {
217
+ console.log(body("The measurement failed, so nothing was sent."));
218
+ return;
219
+ }
220
+ /** A LEITURA GRAVADA ENTRA NO CENSO FRESCO - ver a rota `reading`. */
221
+ if (stored.reading)
222
+ census.reading = stored.reading;
223
+ await resolveReadParts(census, root).catch(() => { });
224
+ /**
225
+ * O AVISO, ANTES DE ENVIAR. `edited` são as receitas que uma pessoa escreveu na tela; o cruzamento
226
+ * com o que acabou de ser medido é uma interseção de conjuntos, e é por isso que ele mora aqui em
227
+ * vez de virar um gêmeo da regra que vive na plataforma.
228
+ */
229
+ const slugOf = (name) => name
230
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
231
+ .toLowerCase()
232
+ .replace(/[^a-z0-9]+/g, "-")
233
+ .replace(/^-|-$/g, "");
234
+ const arriving = new Set(Object.keys(census.looks ?? {}).map(slugOf));
235
+ /**
236
+ * SÓ O QUE ESTE ENVIO TOCA. Uma receita que a pessoa editou e que a medição não traz fica onde
237
+ * está, e listá-la seria assustar sem motivo - o erro mais fácil de cometer neste aviso.
238
+ */
239
+ const hit = (stored.draft?.edited ?? []).filter((e) => arriving.has(e.component));
240
+ if (hit.length > 0) {
241
+ console.log("");
242
+ console.log(section("You have an active draft"));
243
+ console.log(body(stored.draft?.warning ??
244
+ `${hit.length} of the ${stored.draft?.components ?? "?"} components in it were written on the screen, and this send would rewrite them.`));
245
+ console.log(body(paint.faint(hit
246
+ .slice(0, 6)
247
+ .map((e) => `${e.component}${e.at ? ` (${e.at.slice(0, 10)})` : ""}`)
248
+ .join(" · "))));
249
+ if (!(await askToOverwrite())) {
250
+ console.log(body("Nothing was sent. Your draft is untouched."));
251
+ return;
252
+ }
253
+ }
254
+ const res = await fetch(`${base}/api/registry/ds/${slug}/census`, {
255
+ method: "POST",
256
+ headers: {
257
+ "content-type": "application/json",
258
+ Authorization: `Bearer ${token}`,
259
+ },
260
+ body: JSON.stringify({ census }),
261
+ }).catch(() => null);
262
+ if (!res?.ok) {
263
+ console.log(body(res
264
+ ? `The registry answered ${res.status}. Nothing was written.`
265
+ : "Could not reach the registry. Nothing was written."));
266
+ return;
267
+ }
268
+ const out = (await res.json());
269
+ console.log("");
270
+ console.log(section("Sent"));
271
+ console.log(body(`${out.written ?? 0} of ${out.total ?? 0} components written.`));
272
+ for (const note of out.notes ?? [])
273
+ console.log(body(paint.faint(note)));
274
+ if (out.url)
275
+ console.log(body(out.url));
276
+ }
277
+ /**
278
+ * Pergunta uma vez, e só num terminal de verdade.
279
+ *
280
+ * Sem TTY a resposta é NÃO: uma invocação por script que sobrescreve trabalho de alguém porque
281
+ * ninguém estava lá para dizer não é o pior default possível. `--yes` é o caminho explícito.
282
+ */
283
+ async function askToOverwrite() {
284
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
285
+ return false;
286
+ const { createInterface } = await import("node:readline/promises");
287
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
288
+ try {
289
+ const answer = await rl.question(" send anyway? [y/N]: ");
290
+ return /^y(es)?$/i.test(answer.trim());
291
+ }
292
+ catch {
293
+ return false;
294
+ }
295
+ finally {
296
+ rl.close();
297
+ }
298
+ }
package/dist/index.js CHANGED
@@ -54,8 +54,10 @@ Usage - governance (deterministic, FREE):
54
54
  something no reader would change
55
55
  synthesisui request [component|token] the queue of what your agent needed and the system
56
56
  refused to invent (--done <id> closes one)
57
- synthesisui sync send the local record - checks, fixes, open requests -
58
- to your system's dashboard (by hand, never automatic)
57
+ synthesisui sync re-measure this repo with the current reader and send it,
58
+ reusing the reading you already authored, plus the local
59
+ record - checks, fixes, open requests (--record-only skips
60
+ the measurement, --yes skips the overwrite question)
59
61
  synthesisui hook the check itself; installed by connect, run by your editor
60
62
  synthesisui mcp the system as tools; installed by connect, run by your agent
61
63
 
@@ -422,8 +424,21 @@ async function main() {
422
424
  await clean({ dir, force: flags.force === true });
423
425
  break;
424
426
  case "sync":
425
- // A versão que reporta o estado do repo - ver `repoStateOf`.
426
- await sync({ dir, registry, cli: CLI_VERSION });
427
+ /**
428
+ * A versão que reporta o estado do repo - ver `repoStateOf` - e que MEDE: desde 05/08 o `sync`
429
+ * remede o repo com o leitor atual e reenvia, reusando a leitura já gravada. É o único comando
430
+ * do lado do cliente para fazer um conserto de leitura chegar.
431
+ *
432
+ * `--record-only` manda só o registro, e `--yes` não pergunta antes de sobrescrever receita
433
+ * escrita na tela. O default é medir e perguntar.
434
+ */
435
+ await sync({
436
+ dir,
437
+ registry,
438
+ cli: CLI_VERSION,
439
+ recordOnly: args.includes("--record-only"),
440
+ yes: args.includes("--yes"),
441
+ });
427
442
  break;
428
443
  case "request": {
429
444
  // `synthesisui request` lists; `request component --name x --for "..."`
@@ -1189,6 +1189,21 @@ three biggest items so they know whether it is worth opening now.
1189
1189
  found in their code, a token that is not ramp-shaped, a name the document refused. Read them
1190
1190
  and pass on the ones that matter.
1191
1191
 
1192
+ **And the notes now judge YOUR reading, which nothing did before.** \`validate_recipes\` looks at
1193
+ the \`anatomy\` inside a recipe; the anatomy you author travels in \`reading.components[X].anatomy\`,
1194
+ and until 05/08 no one looked at it - so a note may name findings the batch validator never saw.
1195
+ Two kinds, and both are yours to act on:
1196
+
1197
+ - **structure that does not survive the reader.** Nine of these landed on a real import, every one
1198
+ the same shape: a \`button\` holding a \`stack\`. Only \`row\` and \`stack\` arrange, so the children are
1199
+ hoisted out and become siblings - the nesting they wrote is gone. Fix it in the reading and send
1200
+ again; the note tells you the component and the path.
1201
+ - **references pointing at components the system does not contain.** 215 of 660 on that same
1202
+ import. This one is usually NOT a mistake of yours: their component composes something the gate
1203
+ left out, or something outside the scope that was measured. Say the number to the person and name
1204
+ the top few, because a specimen there draws the reference and nothing else - and if the missing
1205
+ piece is central to their system, the answer is a wider \`--scope\`, not a rewritten reading.
1206
+
1192
1207
  ### 6. Close the loop in their repo
1193
1208
 
1194
1209
  The system is only worth something once it governs the code it came from:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.139",
3
+ "version": "0.16.141",
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": {