synthesisui 0.16.94 → 0.16.95

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.
@@ -23,7 +23,7 @@ import { buildTable } from "../doctor/tokens.js";
23
23
  import { readInlineStyle, rootClasses, rootTag, transcribe, } from "../doctor/transcribe.js";
24
24
  import { transcribeVariants } from "../doctor/variant-read.js";
25
25
  import { body, paint, section } from "../output.js";
26
- import { startProgress } from "../progress.js";
26
+ import { phase, startProgress } from "../progress.js";
27
27
  import { detectStack, resolveDeps } from "../stack.js";
28
28
  import { walk, walkAll } from "./doctor.js";
29
29
  /**
@@ -1797,9 +1797,17 @@ export async function runImport(opts) {
1797
1797
  * Resolved here rather than at measure time because the reading arrives
1798
1798
  * AFTER the census was taken - this is the first moment both halves exist.
1799
1799
  */
1800
+ phase(2, 3, "Resolving the reading against the census");
1800
1801
  await resolveReadParts(census, root);
1801
1802
  }
1802
1803
  else {
1804
+ /**
1805
+ * THE MACRO STEP. Three lines for a run, not thirty: a person watching wants
1806
+ * to know which phase they are in and how far into it, and everything else -
1807
+ * every file opened, every candidate ruled out - is the machine talking to
1808
+ * itself (dono, 01/08).
1809
+ */
1810
+ phase(1, 3, "Measuring the repository");
1803
1811
  console.log(section("Reading your project"));
1804
1812
  census = await takeCensus(readFrom, {
1805
1813
  ...(usage.length > 0 ? { usage } : {}),
@@ -1882,6 +1890,7 @@ export async function runImport(opts) {
1882
1890
  console.log("");
1883
1891
  return;
1884
1892
  }
1893
+ phase(3, 3, "Sending v1 and the v2 proposal");
1885
1894
  const res = await fetch(`${base}/api/onboarding/import`, {
1886
1895
  method: "POST",
1887
1896
  headers: {
@@ -117,6 +117,28 @@ const TOOLS = [
117
117
  required: ["recipe"],
118
118
  },
119
119
  },
120
+ {
121
+ name: "validate_recipes",
122
+ description: "The SAME question as validate_recipe, asked once for a whole reading. Send every recipe you are about to write and get one report back: which would arrive whole, and for the rest what would not survive, by name. Validating 35 components one at a time is 35 round trips and none of them thinking - this is one. Prefer it whenever you have more than one recipe in hand.",
123
+ inputSchema: {
124
+ type: "object",
125
+ properties: {
126
+ recipes: {
127
+ type: "array",
128
+ description: "Every candidate, in the document's own shape: [{ name, recipe }].",
129
+ items: {
130
+ type: "object",
131
+ properties: {
132
+ name: { type: "string" },
133
+ recipe: { type: "object" },
134
+ },
135
+ required: ["recipe"],
136
+ },
137
+ },
138
+ },
139
+ required: ["recipes"],
140
+ },
141
+ },
120
142
  {
121
143
  name: "request_component",
122
144
  description: "File a component request when nothing in the catalogue covers what you need. This is the OTHER HALF of the refusal rule: you already say which entry you considered and why it did not fit - said in chat, that reasoning evaporates; filed here, it becomes the queue the system's author works from. File it at the moment you build the workaround, while the reasoning is still yours.",
@@ -333,6 +355,63 @@ async function validateRecipe(name, recipe) {
333
355
  lines.push("", "Write this into `_synthesisui/not-expressed.md` - what we cannot hold is the list", "the system's author works from, and said only in chat it evaporates.");
334
356
  return lines.join("\n");
335
357
  }
358
+ /**
359
+ * A WHOLE READING, VALIDATED IN ONE QUESTION.
360
+ *
361
+ * `validate_recipe` is right and it is one round trip per component: a reader
362
+ * with 35 anatomies in hand spent 35 turns asking the same question, and that
363
+ * was the largest single block of wall-clock in a real import with none of it
364
+ * thinking (dono, 01/08). Every answer is independent, so batching costs nothing
365
+ * in fidelity - the server runs the same judgment per recipe.
366
+ *
367
+ * The report leads with the count that decides what to do next, then names only
368
+ * what would NOT survive. A list of 30 "this one is fine" lines is a wall the
369
+ * reader has to skim to find the two that matter.
370
+ */
371
+ async function validateRecipes(entries) {
372
+ if (entries.length === 0)
373
+ return "Send at least one recipe: { recipes: [{ name, recipe }] }.";
374
+ const answer = await askCatalogue("validate", { recipes: entries });
375
+ if (!answer.ok)
376
+ return answer.because;
377
+ const body = answer.body;
378
+ /**
379
+ * A SERVER THAT DOES NOT KNOW THE BATCH still has to answer usefully - a
380
+ * published CLI meeting an older host must degrade to the singular rather than
381
+ * print a shape error. One round trip per recipe is slower and it is correct.
382
+ */
383
+ if (!body.batch || !Array.isArray(body.results)) {
384
+ const one = [];
385
+ for (const e of entries)
386
+ one.push(await validateRecipe(String(e.name ?? "this component"), e.recipe));
387
+ return `This host does not answer batches, so each was asked on its own.\n\n${one.join("\n\n")}`;
388
+ }
389
+ const results = body.results;
390
+ const bad = results.filter((r) => r.ok !== true);
391
+ const lines = [
392
+ `${results.length} recipes validated - ${results.length - bad.length} would arrive whole.`,
393
+ ];
394
+ if (bad.length === 0) {
395
+ lines.push("", "Nothing to fix. Send them.");
396
+ return lines.join("\n");
397
+ }
398
+ lines.push("", `${bad.length} would NOT arrive whole:`);
399
+ for (const r of bad) {
400
+ lines.push("", `## ${r.name ?? "this component"}`);
401
+ for (const x of r.rejected ?? [])
402
+ lines.push(` REFUSED ${x.at}: ${x.why}`);
403
+ for (const l of r.lost ?? [])
404
+ lines.push(` DROPPED BY THE COMPILER ${l}`);
405
+ for (const m of r.missing ?? [])
406
+ lines.push(` BELOW THE FLOOR ${m}`);
407
+ if (r.deferred)
408
+ lines.push(` floor deferred: ${r.deferred}`);
409
+ for (const c of r.checklist ?? [])
410
+ lines.push(` - go look for: ${c}`);
411
+ }
412
+ lines.push("", "Write these into `_synthesisui/not-expressed.md` - what we cannot hold is the", "list the system's author works from, and said only in chat it evaporates.");
413
+ return lines.join("\n");
414
+ }
336
415
  /**
337
416
  * The catalogue's door, with the session the device login already granted.
338
417
  *
@@ -585,6 +664,10 @@ async function callTool(root, name, args) {
585
664
  return text(await recipeVocabulary());
586
665
  case "validate_recipe":
587
666
  return text(await validateRecipe(String(args.name ?? "this component"), args.recipe));
667
+ case "validate_recipes":
668
+ return text(await validateRecipes(Array.isArray(args.recipes)
669
+ ? args.recipes
670
+ : []));
588
671
  case "request_component": {
589
672
  const r = await fileRequest(root, {
590
673
  kind: "component",
@@ -19,8 +19,23 @@
19
19
  * same in an atomic library, a feature-foldered app, or a single flat directory.
20
20
  */
21
21
  import { scanTags } from "./css-modules.js";
22
- /** Past this a component is a page in disguise, and the gate already said no. */
23
- const MAX_NODES = 60;
22
+ /**
23
+ * Past this a component is a page in disguise, and the gate already said no.
24
+ *
25
+ * 60 was a guess and it cost a real read: measured across the owner's library,
26
+ * exactly ONE component overflows it - `TextEditor`, 67 nodes - and the seven it
27
+ * lost included `EditorContent`, the editable region that is the whole point of
28
+ * the component. So the skill opened TextEditor.tsx by hand at line 353, which
29
+ * is the "a file read whose content the census already carries is a pipeline
30
+ * optimization failure" rule breaking on our own cap (not-expressed.md, dono,
31
+ * 01/08).
32
+ *
33
+ * At 150 nothing in that library truncates (largest after TextEditor: 43), and
34
+ * 200 buys nothing over 150 - so 150 is the measured ceiling, not a rounder
35
+ * guess. The gate upstream is what keeps a page out; this only keeps a big
36
+ * COMPONENT whole.
37
+ */
38
+ const MAX_NODES = 150;
24
39
  /** `className="..."` or className={"..."} / {`...`} with no interpolation. */
25
40
  const CLASS_ATTR = /className=\{?["'`]([^"'`{}$]*)["'`]\}?/;
26
41
  /**
package/dist/progress.js CHANGED
@@ -17,6 +17,22 @@ const BAR_WIDTH = 24;
17
17
  /** Every this-many items, the non-TTY path prints one line. Enough to see movement in
18
18
  * a piped log without turning it into the log. */
19
19
  const MILESTONE_EVERY = 25;
20
+ /**
21
+ * THE MACRO STEP - "2/3 Validating recipes", on its own line, once.
22
+ *
23
+ * A run that prints every grep it ran spends the person's attention on
24
+ * mechanics, and one that prints nothing for minutes is indistinguishable from
25
+ * one that hung (dono, 01/08). Three lines for three phases is the whole
26
+ * contract: the bar underneath says how far into THIS phase it is, and nothing
27
+ * else gets a line.
28
+ *
29
+ * Written to stderr on purpose. A phase banner is progress, not output: a caller
30
+ * piping stdout to a file - or the skill reading a census off it - gets the data
31
+ * clean, and a person at the terminal still sees both.
32
+ */
33
+ export function phase(n, of, title, write = (s) => process.stderr.write(s)) {
34
+ write(`\n${n}/${of} ${title}\n`);
35
+ }
20
36
  /** A bar that reads as a bar at any width. Two glyphs, no colour: this line is
21
37
  * overwritten dozens of times a second and anything fancier flickers. */
22
38
  function bar(done, total) {
@@ -64,7 +64,9 @@ cannot expire on a host that never issued it. That is how a full read ended in a
64
64
  twice (dono, 31/07).
65
65
 
66
66
  So if the file says \`"registry": "http://localhost:3000"\`, every command in this skill carries
67
- \`--registry http://localhost:3000\`. If it says production, pass nothing. The CLI now refuses a
67
+ \`--registry http://localhost:3000\`. If it says production, pass nothing. **Read that file ONCE**
68
+ and carry the answer for the whole run - re-reading it before each command is a round trip that
69
+ answers a question already answered. The CLI now refuses a
68
70
  mismatch instead of letting the server reject it, but the point is not to reach that.
69
71
 
70
72
  **Do this silently.** Which host you are pointed at and which flags follow from it is plumbing,
@@ -143,7 +145,196 @@ The census still lands at \`<root>/_synthesisui/census.json\` and records both w
143
145
  and where the evidence came from, so you always send the same path and there is never a second
144
146
  file to pick between. Nothing is sent by \`--dry\`. Read the file.
145
147
 
146
- ### 2. Read what arithmetic cannot
148
+ ### 2. Walk them through the decisions - ALL of them, before any reading
149
+
150
+ **Every question this skill asks is answerable from the census you just took.** Source, name,
151
+ primary, theme, canvas, our-kit-or-theirs, architecture - the numbers behind all seven are in
152
+ the file on their disk, and none of them needs the anatomy.
153
+
154
+ They used to be asked AFTER the reading, and that put six hundred lines of work between the
155
+ person's first answer and their second (dono, 01/08). A person who answers seven questions in
156
+ ninety seconds and then watches a bar has had a good four minutes; the same person interrupted
157
+ twice in the middle of a silence has had a bad twenty.
158
+
159
+ So: ask everything here, in one pass, and **do not come back**. If something you learn during
160
+ the reading contradicts an answer, you do not reopen the question - you say so in the report at
161
+ the end and let them change it there.
162
+
163
+ Seven decisions are theirs. **Ask them as separate questions with selectable options** - use your
164
+ question tool, one call per decision, so they pick instead of reading a wall and composing a
165
+ reply. A single block containing everything is a report, and a report gets read, not answered.
166
+
167
+ Every question carries **your recommendation first, marked as such**, and the evidence for it in
168
+ one line. They are choosing, not auditing you.
169
+
170
+ **2a. Source** - the folder you measure the system out of.
171
+
172
+ Options are the candidates the CLI already ranked, each with what it would produce, plus the
173
+ whole repo, plus *somewhere else* for a path they type:
174
+
175
+ \`\`\`
176
+ Where should the system come from?
177
+
178
+ packages/ui (recommended) 114 files · 47% of its values already named ·
179
+ 92 tokens of their own · every app imports it
180
+ the whole repo 2797 files · 36% · the average of one dark app
181
+ and two light ones, which is a diagnosis and
182
+ not a system
183
+ apps/web-dashboard the heaviest consumer, if you want one app's
184
+ vocabulary rather than the shared one
185
+ somewhere else a path you name
186
+ \`\`\`
187
+
188
+ If they name a path you cannot use, say which of the two it is and offer the list again:
189
+
190
+ - **it does not exist**, or holds no files you can read
191
+ - **it holds no design values** - no colours, no radii, no tokens. A folder of server code or
192
+ config is not a design system, and measuring it produces a system of nothing.
193
+
194
+ **2b. Name** - suggest theirs, and let Enter take it:
195
+
196
+ \`\`\`
197
+ What should it be called? (SignalUI)
198
+ \`\`\`
199
+
200
+ Say where the suggestion came from - \`Introduction.mdx\`, the package name, the folder. A name
201
+ they recognise is worth more than a clever one. The slug is derived once and never changes, so
202
+ this is the moment.
203
+
204
+ **2c. Primary** - and explain the role before offering the choice, because *primary* is our word
205
+ and not necessarily theirs:
206
+
207
+ \`\`\`
208
+ Primary is the colour that carries action - buttons, links, focus, the thing
209
+ you want pressed. Everything else in the system is measured for contrast
210
+ against it.
211
+
212
+ #059aed ocean-500 (recommended) your ColorPalette.mdx calls Ocean the
213
+ "Primary branding scale"
214
+ #1a4ed8 royal-blue-500 your docs label this one secondary
215
+ #ec4899 vivid-pink-500 half of your signature AI gradient
216
+ #4A90E2 blue-500 the most-painted blue in the folder,
217
+ but it sits after your own
218
+ /* End - Signal UI */ marker
219
+ \`\`\`
220
+
221
+ Always name the hex AND the token, and always give the reason a value is *not* recommended when
222
+ frequency would have picked it. That last row is the whole point of asking.
223
+
224
+ **2d. Default theme** - which face it opens in:
225
+
226
+ \`\`\`
227
+ dark (recommended) data-theme="dark" in the root layout, next-themes
228
+ defaultTheme="dark", enableSystem={false}
229
+ light the other one, also real here
230
+ \`\`\`
231
+
232
+ **Two is all there is** - a SynthesisUI system carries one palette and one alternate, and the
233
+ schema is \`"light" | "dark"\`. So this is not "which themes do you support", it is "which one do
234
+ docs and installs open in". If both are real, both get built from their own tokens; this picks
235
+ the face.
236
+
237
+ **2e. Canvas** - confirm, do not offer a list. The page is measured, not chosen:
238
+
239
+ \`\`\`
240
+ canvas #222326 darkgray-500 - what DashboardLayout paints on <main>
241
+ (your sidebar sits a step deeper at darkgray-700)
242
+ \`\`\`
243
+
244
+ Say it and let them object. Offering five near-blacks to pick between is asking someone to
245
+ re-decide something their code already decided.
246
+
247
+ **2f. Our kit, or theirs alone** - ask this ONLY when they already have a component library,
248
+ which is when \`--scope\` pointed at one and the CLI said so. An app with no components of its own
249
+ has nothing to weigh and should never see the question.
250
+
251
+ \`\`\`
252
+ Every system born here comes with 45 components of ours - button, card, input,
253
+ table, the rest of a working toolkit. You already have 37.
254
+
255
+ add ours to yours (recommended) 68 components, and the 23 governed by your
256
+ own rules are the 23 you wrote
257
+ mine alone 37 components, all of them yours. Our
258
+ layouts that need a component you do not
259
+ have stay out too. Add any of ours later
260
+ from the library, one at a time
261
+ \`\`\`
262
+
263
+ Send it as \`"standards": "none"\` in the reading when they choose the second, and leave the field
264
+ out when they choose the first. **Recommend adding ours**, and say why in one line: a component of
265
+ ours costs nothing until something composes it, and having a \`table\` the day somebody needs a
266
+ table is worth more than a shorter list.
267
+
268
+ Two things to be straight about, because both are true and neither is obvious:
269
+
270
+ - **the foundations come from the seed either way.** Their palette, their scales, their type - all
271
+ theirs - but the seven type slots, the easing and the neutral floor arrive from ours, because
272
+ the alternative to a type scale is no components at all rather than their components.
273
+ - **our layouts are written in our recipes.** A landing that composes \`button\`, \`card\` and
274
+ \`badge\` needs those to exist. Choosing *mine alone* keeps the layouts whose recipes they
275
+ happen to have and drops the rest - a layout pointing at a recipe nobody has renders as holes.
276
+
277
+ **2g. Which architecture has priority** - ask only when the CLI reported more than
278
+ one, which it does under "How this project is organised".
279
+
280
+ A project having two shapes is not a project with a mistake: a library organised
281
+ atomically inside a monorepo whose apps are organised by feature is two true answers.
282
+ Say that before you ask, or the question reads as an accusation.
283
+
284
+ \`\`\`
285
+ Two shapes, and neither is wrong:
286
+
287
+ atomic under packages/ui (recommended) 114 files - atoms/molecules/organisms,
288
+ and this is where the system comes from
289
+ feature under apps/web-dashboard 900 files - features/, where the app
290
+ consumes it
291
+ \`\`\`
292
+
293
+ The one they pick becomes a rule that reaches \`CLAUDE.md\`, so **every prompt after this
294
+ knows where a new component goes** - not what the folders are called, but which rung or
295
+ which feature a new file belongs to. Recommend the one the system came from.
296
+
297
+ **2h. Fonts** - do not ask at all. Report and move on:
298
+
299
+ \`\`\`
300
+ fonts Figtree for both display and body - the only family declared
301
+ (--font-sans, and next/font Figtree in all three root layouts)
302
+ \`\`\`
303
+
304
+ If a project genuinely declares two families, say which is which and why you paired them that
305
+ way. If it declares one, there is nothing to decide.
306
+
307
+ **A note on colour swatches.** You cannot paint a hex in this conversation, so the token name
308
+ carries the weight - \`ocean-500\` tells someone more than a square would. When they run
309
+ \`npx synthesisui import\` themselves in a terminal, the CLI paints them.
310
+
311
+ ### 3. Read what arithmetic cannot - and do it in ONE uninterrupted run
312
+
313
+ Every answer is in hand, so nothing below needs the person. **Do not ask, do not confirm, do
314
+ not narrate the mechanics.** Three lines on screen for the whole phase:
315
+
316
+ \`\`\`
317
+ 1/3 Measuring the repository... (step 1, already done)
318
+ 2/3 Naming the anatomy and validating... (this step)
319
+ 3/3 Sending v1 and v2... (step 4)
320
+ \`\`\`
321
+
322
+ **Narrate findings, never mechanics.** A grep, a glob, a file you opened and ruled out, a draft
323
+ you rewrote - none of those is a finding, and a run that prints them reads as a machine talking
324
+ to itself. *"Their docs call Ocean the primary branding scale"* is a finding and earns its line.
325
+
326
+ **The census answers first, always.** Before opening any file, check whether \`signals\`, \`sketch\`,
327
+ \`declared\` or \`coverage\` already hold it - they usually do, and a file read whose content the
328
+ census carries is the failure this pipeline keeps paying for. The sketch now runs to 150 nodes,
329
+ so a big component arrives WHOLE: \`TextEditor\` used to lose its editable region at node 60 and
330
+ that single truncation is what forced a hand-read of the file (dono, 01/08).
331
+
332
+ **Validate in ONE call, at the end.** Build every recipe in memory first, then send them all to
333
+ \`validate_recipes\` - one round trip for the whole reading instead of one per component. A reader
334
+ with 35 anatomies spent 35 turns asking the same question, and none of that was thinking. Only
335
+ after the batch answers do you fix what it named and, if you fixed anything, ask once more.
336
+
337
+
147
338
 
148
339
  Open the project and answer the questions below. Then add a \`reading\` object to
149
340
  \`_synthesisui/census.json\`:
@@ -591,6 +782,16 @@ a recipe before these tools existed, and four separate readings were lost in sil
591
782
  \`dark:\` inside a variant, a state the compiler cannot spell, a part name with a dot in it,
592
783
  a layer with an empty style. Each of those was correct work that arrived as nothing.
593
784
 
785
+ **In ONE call.** Build every recipe in memory, then send the whole reading to
786
+ \`validate_recipes\` - one round trip for 35 components instead of 35. \`validate_recipe\`
787
+ (singular) is for the one recipe you rewrote after the batch named it; reaching for it in a
788
+ loop is the shape this optimisation exists to kill (dono, 01/08).
789
+
790
+ \`\`\`
791
+ validate_recipes({ recipes: [{ name, recipe }, …] }) the whole reading, one question
792
+ validate_recipe(name, recipe) the one you just fixed
793
+ \`\`\`
794
+
594
795
  \`validate_recipe\` never answers yes or no. It answers with what would not survive:
595
796
 
596
797
  \`\`\`
@@ -748,7 +949,16 @@ scale and lost the sentence (dono, 01/08).
748
949
 
749
950
  When their docs (a \`Geometry.mdx\`, a design-tokens page, comments beside the \`@theme\`)
750
951
  state what a step is FOR, send it as system-wide rules - \`applies: []\`, \`fact: true\`, the
751
- doc as evidence:
952
+ doc as evidence.
953
+
954
+ **One doc pass, and only for what the census cannot hold.** A scale's INTENT is prose, so it
955
+ genuinely needs a human sentence somebody wrote - but a scale's VALUES, its token names, the
956
+ theme, the class convention and the architecture are all measured and sitting in \`declared\`,
957
+ \`signals\` and \`conventions\`. Opening a docs page to confirm a number the census already
958
+ carries costs a turn and answers nothing. Read the docs ONCE, for the sentences; never in a
959
+ loop, and never to check arithmetic.
960
+
961
+ Send it like this:
752
962
 
753
963
  \`\`\`
754
964
  this system's spacing has exactly three steps - sm for close relationships inside a
@@ -786,157 +996,9 @@ The older flat form - \`"parts": [{ "name": "label", "classes": "..." }]\` - sti
786
996
  styles correctly. It just carries no shape, so the preview lays the parts out in a row and the
787
997
  spec view has no nesting to draw.
788
998
 
789
- ### 3. Walk them through the decisions, one at a time
790
-
791
- Six decisions are theirs. **Ask them as separate questions with selectable options** - use your
792
- question tool, one call per decision, so they pick instead of reading a wall and composing a
793
- reply. A single block containing everything is a report, and a report gets read, not answered.
794
-
795
- Every question carries **your recommendation first, marked as such**, and the evidence for it in
796
- one line. They are choosing, not auditing you.
797
-
798
- **3a. Source** - the folder you measure the system out of.
799
-
800
- Options are the candidates the CLI already ranked, each with what it would produce, plus the
801
- whole repo, plus *somewhere else* for a path they type:
802
-
803
- \`\`\`
804
- Where should the system come from?
805
-
806
- packages/ui (recommended) 114 files · 47% of its values already named ·
807
- 92 tokens of their own · every app imports it
808
- the whole repo 2797 files · 36% · the average of one dark app
809
- and two light ones, which is a diagnosis and
810
- not a system
811
- apps/web-dashboard the heaviest consumer, if you want one app's
812
- vocabulary rather than the shared one
813
- somewhere else a path you name
814
- \`\`\`
815
-
816
- If they name a path you cannot use, say which of the two it is and offer the list again:
817
-
818
- - **it does not exist**, or holds no files you can read
819
- - **it holds no design values** - no colours, no radii, no tokens. A folder of server code or
820
- config is not a design system, and measuring it produces a system of nothing.
821
-
822
- **3b. Name** - suggest theirs, and let Enter take it:
823
-
824
- \`\`\`
825
- What should it be called? (SignalUI)
826
- \`\`\`
827
-
828
- Say where the suggestion came from - \`Introduction.mdx\`, the package name, the folder. A name
829
- they recognise is worth more than a clever one. The slug is derived once and never changes, so
830
- this is the moment.
831
-
832
- **3c. Primary** - and explain the role before offering the choice, because *primary* is our word
833
- and not necessarily theirs:
834
-
835
- \`\`\`
836
- Primary is the colour that carries action - buttons, links, focus, the thing
837
- you want pressed. Everything else in the system is measured for contrast
838
- against it.
839
-
840
- #059aed ocean-500 (recommended) your ColorPalette.mdx calls Ocean the
841
- "Primary branding scale"
842
- #1a4ed8 royal-blue-500 your docs label this one secondary
843
- #ec4899 vivid-pink-500 half of your signature AI gradient
844
- #4A90E2 blue-500 the most-painted blue in the folder,
845
- but it sits after your own
846
- /* End - Signal UI */ marker
847
- \`\`\`
848
-
849
- Always name the hex AND the token, and always give the reason a value is *not* recommended when
850
- frequency would have picked it. That last row is the whole point of asking.
851
-
852
- **3d. Default theme** - which face it opens in:
853
-
854
- \`\`\`
855
- dark (recommended) data-theme="dark" in the root layout, next-themes
856
- defaultTheme="dark", enableSystem={false}
857
- light the other one, also real here
858
- \`\`\`
859
-
860
- **Two is all there is** - a SynthesisUI system carries one palette and one alternate, and the
861
- schema is \`"light" | "dark"\`. So this is not "which themes do you support", it is "which one do
862
- docs and installs open in". If both are real, both get built from their own tokens; this picks
863
- the face.
864
-
865
- **3e. Canvas** - confirm, do not offer a list. The page is measured, not chosen:
866
-
867
- \`\`\`
868
- canvas #222326 darkgray-500 - what DashboardLayout paints on <main>
869
- (your sidebar sits a step deeper at darkgray-700)
870
- \`\`\`
871
-
872
- Say it and let them object. Offering five near-blacks to pick between is asking someone to
873
- re-decide something their code already decided.
874
-
875
- **3e-bis. Our kit, or theirs alone** - ask this ONLY when they already have a component library,
876
- which is when \`--scope\` pointed at one and the CLI said so. An app with no components of its own
877
- has nothing to weigh and should never see the question.
878
-
879
- \`\`\`
880
- Every system born here comes with 45 components of ours - button, card, input,
881
- table, the rest of a working toolkit. You already have 37.
882
-
883
- add ours to yours (recommended) 68 components, and the 23 governed by your
884
- own rules are the 23 you wrote
885
- mine alone 37 components, all of them yours. Our
886
- layouts that need a component you do not
887
- have stay out too. Add any of ours later
888
- from the library, one at a time
889
- \`\`\`
890
-
891
- Send it as \`"standards": "none"\` in the reading when they choose the second, and leave the field
892
- out when they choose the first. **Recommend adding ours**, and say why in one line: a component of
893
- ours costs nothing until something composes it, and having a \`table\` the day somebody needs a
894
- table is worth more than a shorter list.
895
-
896
- Two things to be straight about, because both are true and neither is obvious:
897
-
898
- - **the foundations come from the seed either way.** Their palette, their scales, their type - all
899
- theirs - but the seven type slots, the easing and the neutral floor arrive from ours, because
900
- the alternative to a type scale is no components at all rather than their components.
901
- - **our layouts are written in our recipes.** A landing that composes \`button\`, \`card\` and
902
- \`badge\` needs those to exist. Choosing *mine alone* keeps the layouts whose recipes they
903
- happen to have and drops the rest - a layout pointing at a recipe nobody has renders as holes.
904
-
905
- **3e-ter. Which architecture has priority** - ask only when the CLI reported more than
906
- one, which it does under "How this project is organised".
907
-
908
- A project having two shapes is not a project with a mistake: a library organised
909
- atomically inside a monorepo whose apps are organised by feature is two true answers.
910
- Say that before you ask, or the question reads as an accusation.
911
-
912
- \`\`\`
913
- Two shapes, and neither is wrong:
914
-
915
- atomic under packages/ui (recommended) 114 files - atoms/molecules/organisms,
916
- and this is where the system comes from
917
- feature under apps/web-dashboard 900 files - features/, where the app
918
- consumes it
919
- \`\`\`
920
-
921
- The one they pick becomes a rule that reaches \`CLAUDE.md\`, so **every prompt after this
922
- knows where a new component goes** - not what the folders are called, but which rung or
923
- which feature a new file belongs to. Recommend the one the system came from.
924
-
925
- **3f. Fonts** - do not ask at all. Report and move on:
926
-
927
- \`\`\`
928
- fonts Figtree for both display and body - the only family declared
929
- (--font-sans, and next/font Figtree in all three root layouts)
930
- \`\`\`
931
-
932
- If a project genuinely declares two families, say which is which and why you paired them that
933
- way. If it declares one, there is nothing to decide.
934
-
935
- **A note on colour swatches.** You cannot paint a hex in this conversation, so the token name
936
- carries the weight - \`ocean-500\` tells someone more than a square would. When they run
937
- \`npx synthesisui import\` themselves in a terminal, the CLI paints them.
999
+ ### 4. Send it
938
1000
 
939
- Then, and only after all of them are answered:
1001
+ Everything is answered and everything is validated:
940
1002
 
941
1003
  \`\`\`
942
1004
  npx synthesisui import --census _synthesisui/census.json --name "<the name>" [--registry <from step 0>]
@@ -947,13 +1009,13 @@ npx synthesisui import --census _synthesisui/census.json --name "<the name>" [--
947
1009
  Pass every answer explicitly - \`--name\`, \`--scope\` and \`--usage\` if they changed either, and the reading rewritten
948
1010
  with their primary and their theme. The CLI cannot ask anything when you are the one running it:
949
1011
  it has no terminal, so its own prompts are skipped by design. **Whatever you did not ask, nobody
950
- asked.** Skip 3a and the first time they learn where their system came from is when they open
1012
+ asked.** Skip 2a and the first time they learn where their system came from is when they open
951
1013
  it.
952
1014
 
953
1015
  If it refuses, it says which of the three it is: no session, an expired one, or a token issued
954
1016
  by a different host. All are recoverable from the census on disk; none need a re-measure.
955
1017
 
956
- ### 4. Tell them exactly what they have, and what is theirs to decide
1018
+ ### 5. Tell them exactly what they have, and what is theirs to decide
957
1019
 
958
1020
  The import publishes **v1** as a faithful baseline and opens a **v2 draft** holding the
959
1021
  proposal. Say all of this in your own words - do not just print the link:
@@ -984,7 +1046,7 @@ three biggest items so they know whether it is worth opening now.
984
1046
  found in their code, a token that is not ramp-shaped, a name the document refused. Read them
985
1047
  and pass on the ones that matter.
986
1048
 
987
- ### 5. Close the loop in their repo
1049
+ ### 6. Close the loop in their repo
988
1050
 
989
1051
  The system is only worth something once it governs the code it came from:
990
1052
 
@@ -1022,6 +1084,14 @@ The user should end up with a system that reads like theirs and not like ours:
1022
1084
  - **Do not guess \`themes\` to be helpful.** Omitting it falls back to counting rungs, which is
1023
1085
  a guess the product labels as a guess. A wrong confident answer does not get labelled.
1024
1086
  - **Do not apologise for the empty contracts.** They are the design. Explain them.
1087
+ - **Do not come back with a question once step 3 has started.** Everything answerable was
1088
+ answered in step 2. An interruption in the middle of a silent phase costs the person the
1089
+ whole phase, because they now have to hold what you were doing in their head.
1090
+ - **Do not narrate the mechanics.** No grep, no glob, no "let me check whether X exists", no
1091
+ draft you rewrote. Findings earn a line; the machine talking to itself does not.
1092
+ - **Do not open a file to confirm something the census already carries.** \`signals\`, \`sketch\`,
1093
+ \`declared\`, \`conventions\` and \`coverage\` are the answer to almost every "let me just check".
1094
+ - **Do not validate in a loop.** One \`validate_recipes\` for the whole reading.
1025
1095
 
1026
1096
  ## Not yet true
1027
1097
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.94",
3
+ "version": "0.16.95",
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": {