synthesisui 0.16.29 → 0.16.30

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.
@@ -4,7 +4,7 @@ import { findDivergences } from "../doctor/coherence.js";
4
4
  import { findFrozenBindings } from "../doctor/frozen.js";
5
5
  import { appendEvent, readEvents, summarize } from "../doctor/ledger.js";
6
6
  import { bindingsFromDocument, countComponents, findOverrides, } from "../doctor/overrides.js";
7
- import { readRequests } from "../doctor/requests.js";
7
+ import { checkableName, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
8
8
  import { diagnose, scanSource, siblingTokens, } from "../doctor/scan.js";
9
9
  import { findSelfConflicts, forbiddenProps, isReset, propMatchesLabel, } from "../doctor/self-conflict.js";
10
10
  import { buildTable, EMPTY_TABLE, nearestToken, } from "../doctor/tokens.js";
@@ -575,12 +575,24 @@ export async function doctor(opts) {
575
575
  * that actually hit the gap.
576
576
  */
577
577
  if (hasSystem && scopes.length === 0) {
578
+ // Verification first: whether an ask is still missing is a FACT about
579
+ // the installed css, so the queue re-checks before it reports. A request
580
+ // the build now delivers closes itself and says so - nobody marks done
581
+ // what a machine can see is done (dono, 29/07).
582
+ const verified = await verifyAndCloseRequests(root);
583
+ if (verified.length > 0) {
584
+ console.log("");
585
+ console.log(section("Requests verified"));
586
+ for (const r of verified) {
587
+ console.log(body(` ✓ ${r.id} ${checkableName(r) ?? r.name} - the installed system covers it now. Closed.`));
588
+ }
589
+ }
578
590
  const requests = await readRequests(root);
579
591
  if (requests.length > 0) {
580
592
  console.log("");
581
593
  console.log(section("What your agent asked for"));
582
594
  for (const r of requests.slice(0, 8)) {
583
- console.log(body(` ${r.id} ${r.kind === "token" ? `token ${r.value} as ${r.name}` : `component "${r.name}"`}`));
595
+ console.log(body(` ${r.id} ${r.kind === "token" ? `token ${r.value} as ${r.name}` : `component "${r.name}"`}${r.area === "platform" ? " [platform]" : ""}`));
584
596
  console.log(body(` for: ${r.purpose}`));
585
597
  if (r.considered)
586
598
  console.log(body(` considered: ${r.considered}`));
@@ -590,6 +602,9 @@ export async function doctor(opts) {
590
602
  }
591
603
  console.log("");
592
604
  console.log(body("Author it in the studio, or close it: synthesisui request --done <id>"));
605
+ if (requests.some((r) => r.area === "platform")) {
606
+ console.log(body("[platform] items are the synthesisui team's - no action needed; they close themselves when an update delivers."));
607
+ }
593
608
  }
594
609
  }
595
610
  /**
@@ -220,19 +220,54 @@ async function callTool(root, name, args) {
220
220
  return text(`Filed as ${r.id}. It shows up in \`synthesisui doctor\` until a person authors it or closes it - keep building with your workaround, and say in your summary that the request exists.`);
221
221
  }
222
222
  case "request_token": {
223
+ const name = String(args.name ?? "");
224
+ // TRIAGE AT FILING: if the system's own contract already promises this
225
+ // name (a keyframe exists, so `animate-<it>` should too), the gap is
226
+ // DELIVERY - ours, not the author's. It files routed to the platform,
227
+ // the owner is never asked to act, and it closes itself the moment a
228
+ // doctor/sync finds the promise kept in the installed css.
229
+ const area = (await isPromisedByContract(root, name))
230
+ ? "platform"
231
+ : undefined;
223
232
  const r = await fileRequest(root, {
224
233
  kind: "token",
225
- name: String(args.name ?? ""),
234
+ name,
226
235
  purpose: String(args.purpose ?? ""),
227
236
  value: String(args.value ?? ""),
228
237
  file: args.file ? String(args.file) : undefined,
238
+ ...(area ? { area } : null),
229
239
  });
230
- return text(`Filed as ${r.id}. Do not add the token yourself - the request shows up in \`synthesisui doctor\` for a person to decide.`);
240
+ return text(area
241
+ ? `Filed as ${r.id} and routed to the synthesisui platform team - the system's contract already promises this and the shipped css does not deliver it. No one needs to act: it closes itself when an update lands. Keep the quiet base meanwhile.`
242
+ : `Filed as ${r.id}. Do not add the token yourself - the request shows up in \`synthesisui doctor\` for a person to decide.`);
231
243
  }
232
244
  default:
233
245
  return text(`No tool named ${name}.`, true);
234
246
  }
235
247
  }
248
+ /**
249
+ * Does the installed system's own contract promise this token name? The one
250
+ * case a machine can be sure of today: `animate-<x>` where `<x>` is a
251
+ * keyframe the document declares - the vocabulary section is generated FROM
252
+ * those keyframes, so asking for one means the promise exists and the
253
+ * artifact failed it (the exact shape of the first field-filed request,
254
+ * hvdoyc, 29/07).
255
+ */
256
+ async function isPromisedByContract(root, name) {
257
+ const m = /animate-([a-z0-9-]+)/.exec(name);
258
+ if (!m)
259
+ return false;
260
+ const kebab = (v) => v.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
261
+ const { documents } = await loadSystem(root);
262
+ for (const raw of documents) {
263
+ const doc = raw;
264
+ for (const k of Object.keys(doc.motion?.keyframes ?? {})) {
265
+ if (kebab(k) === m[1])
266
+ return true;
267
+ }
268
+ }
269
+ return false;
270
+ }
236
271
  /**
237
272
  * One request in, one response out - or null for a notification, which MUST
238
273
  * NOT be answered. Replying to `notifications/initialized` is the classic way
@@ -2,7 +2,7 @@ import { readdir, readFile } from "node:fs/promises";
2
2
  import { join, resolve } from "node:path";
3
3
  import { readToken, resolveRegistry } from "../config.js";
4
4
  import { readEvents } from "../doctor/ledger.js";
5
- import { closeRequest, readRequests } from "../doctor/requests.js";
5
+ import { checkableName, closeRequest, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
6
6
  import { body, section } from "../output.js";
7
7
  /**
8
8
  * What each decision means ON THIS MACHINE - the card decides, the sync
@@ -82,6 +82,10 @@ export async function sync(opts) {
82
82
  console.log(body("No installed system here, so there is nowhere to sync to."));
83
83
  return;
84
84
  }
85
+ // Verification first, so the snapshot never reports "open" about something
86
+ // the installed css already delivers - a request closes because a machine
87
+ // CHECKED, not because someone remembered to say so (dono, 29/07).
88
+ const verified = await verifyAndCloseRequests(root);
85
89
  const events = await readEvents(root);
86
90
  const requests = await readRequests(root);
87
91
  if (events.length === 0 && requests.length === 0) {
@@ -111,6 +115,14 @@ export async function sync(opts) {
111
115
  const out = (await res.json());
112
116
  console.log(section("Sync"));
113
117
  console.log(body(`${out.eventsReceived} checks sent, ${out.eventsNew} new. ${out.requestsNow} open request${out.requestsNow === 1 ? "" : "s"}.`));
118
+ // Closed by CHECKING, not by word: the installed css now delivers these.
119
+ if (verified.length > 0) {
120
+ console.log("");
121
+ console.log(body("Verified against the installed css, closed here:"));
122
+ for (const r of verified) {
123
+ console.log(body(` ✓ ${r.id} ${checkableName(r) ?? r.name} - delivered. Nothing for anyone to do.`));
124
+ }
125
+ }
114
126
  // Decisions travel BACK: the owner answered on the card, and this - the
115
127
  // person's own sync, on their machine - is what closes the local file.
116
128
  // Nobody hunts an id: deciding on the card and running sync is the whole
@@ -1,4 +1,4 @@
1
- import { appendFile, readFile, writeFile } from "node:fs/promises";
1
+ import { appendFile, readdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  /**
4
4
  * RECUSA → PEDIDO: the refusal becomes the request queue.
@@ -72,3 +72,89 @@ export async function closeRequest(root, id) {
72
72
  await writeFile(path(root), keep.length ? `${keep.map((r) => JSON.stringify(r)).join("\n")}\n` : "", "utf8");
73
73
  return true;
74
74
  }
75
+ /**
76
+ * REQUESTS CLOSE BY VERIFICATION, NOT BY WORD (dono, 29/07: "não deveria ser
77
+ * automático e verificado no deploy?").
78
+ *
79
+ * A token request names what was missing. Whether it is still missing is a
80
+ * FACT about the installed css, so the queue re-checks it on every doctor and
81
+ * sync: once `--animate-rise` (or `--ds-<name>`) exists in what is actually
82
+ * installed, the request closes itself and says so. Trust-based closing
83
+ * ("Mark authored") remains only for what a machine cannot check.
84
+ *
85
+ * Only the conservative direction is automated: a false "still open" costs a
86
+ * glance; a false "satisfied" silently buries a real gap. Component requests
87
+ * and prose-named tokens stay manual.
88
+ */
89
+ /** The name a token request is checkable by: its first kebab-case run.
90
+ * Agents write prose around it ("animate-rise (and animate-fade / ...)"). */
91
+ export function checkableName(req) {
92
+ if (req.kind !== "token")
93
+ return null;
94
+ const m = /[a-z][a-z0-9]*(?:-[a-z0-9]+)+/.exec(req.name.trim());
95
+ return m ? m[0] : null;
96
+ }
97
+ /** Requests whose ask the installed css now demonstrably delivers. */
98
+ export function satisfiedRequests(requests, installedCss) {
99
+ return requests.filter((r) => {
100
+ const name = checkableName(r);
101
+ if (!name)
102
+ return false;
103
+ const candidates = name.startsWith("animate-")
104
+ ? [`--${name}`]
105
+ : [`--ds-${name}`, `--${name}`];
106
+ return candidates.some((c) => installedCss.includes(`${c}:`));
107
+ });
108
+ }
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
+ export async function readInstalledCss(root) {
116
+ const dsDir = join(root, "_synthesisui", "ds");
117
+ let css = "";
118
+ let slugs = [];
119
+ try {
120
+ slugs = (await readdir(dsDir, { withFileTypes: true }))
121
+ .filter((e) => e.isDirectory())
122
+ .map((e) => e.name);
123
+ }
124
+ catch {
125
+ return "";
126
+ }
127
+ for (const s of slugs) {
128
+ const raw = await readFile(join(dsDir, s, ".lock"), "utf8").catch(() => "");
129
+ let version;
130
+ try {
131
+ version = JSON.parse(raw).version;
132
+ }
133
+ catch {
134
+ // no lock, no pinned folder to read
135
+ }
136
+ if (!version)
137
+ continue;
138
+ for (const f of ["tokens.css", "theme.css"]) {
139
+ css += `\n${await readFile(join(dsDir, s, `v${version}`, f), "utf8").catch(() => "")}`;
140
+ }
141
+ }
142
+ return css;
143
+ }
144
+ /**
145
+ * The whole gesture: read, verify against the installed css, close what is
146
+ * now delivered, and report each closure. Shared by doctor and sync so the
147
+ * queue never says "open" about something the build already ships.
148
+ */
149
+ export async function verifyAndCloseRequests(root) {
150
+ const requests = await readRequests(root);
151
+ if (requests.length === 0)
152
+ return [];
153
+ const css = await readInstalledCss(root);
154
+ if (!css)
155
+ return [];
156
+ const satisfied = satisfiedRequests(requests, css);
157
+ for (const r of satisfied)
158
+ await closeRequest(root, r.id);
159
+ return satisfied;
160
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.29",
3
+ "version": "0.16.30",
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": {