synthesisui 0.16.235 → 0.16.238
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.
- package/dist/commands/add.js +8 -10
- package/dist/commands/component.js +13 -6
- package/dist/commands/generate.js +9 -5
- package/dist/commands/mcp.js +92 -6
- package/dist/commands/refit.js +8 -5
- package/dist/guide.js +4 -3
- package/dist/install-marks.js +1 -1
- package/dist/skill-adapt.js +10 -342
- package/dist/skill-import.js +10 -1311
- package/dist/skill-init.js +10 -320
- package/package.json +1 -1
package/dist/skill-import.js
CHANGED
|
@@ -1,1316 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* O STUB DA SKILL - o corpo é SERVIDO, não embarcado (R1, dono 16/08).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* O playbook inteiro morava aqui (65 KB) e, por consequência, no tarball
|
|
5
|
+
* público do npm e no repo de cada cliente em texto plano. Ele mudou para a
|
|
6
|
+
* plataforma (apps/web/src/lib/ds/playbooks.ts, gêmeo por spec do
|
|
7
|
+
* `.claude/skills/sui-import-ds/SKILL.md`) e é servido pelo catalogue autenticado -
|
|
8
|
+
* a mesma doutrina de sempre: SERVED, NOT SHIPPED. O agente busca o sumário
|
|
9
|
+
* e depois SÓ o capítulo do passo em que está (a dieta de contexto).
|
|
7
10
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* turns a test red instead of shipping a skill that describes a pipeline the
|
|
11
|
-
* CLI no longer has.
|
|
11
|
+
* O frontmatter fica INTEIRO no stub: é a description que faz a skill ser
|
|
12
|
+
* invocada, e ela não é segredo - a esteira é.
|
|
12
13
|
*/
|
|
13
|
-
export const IMPORT_SKILL = `---
|
|
14
|
-
name: sui-import-ds
|
|
15
|
-
description: Turn a codebase the user ALREADY has into a SynthesisUI design system. Use when someone points at an existing repo, app or component library and asks to import it, adopt it, bring it in, or "make a design system from this" (e.g. "/sui-import-ds", "importa o meu packages/ui", "turn this app into a design system"). Drives the full pipeline - census → your reading → import → v2 proposal - and answers the questions arithmetic cannot.
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
# Import Design System
|
|
19
|
-
|
|
20
|
-
Read a project the user already built and give it back to them as a system that governs it.
|
|
21
|
-
|
|
22
|
-
The census is **arithmetic** and it is not yours to redo. Every colour ranked, every ΔE
|
|
23
|
-
measured, every component crosswalked - the CLI does all of it, deterministically, for free,
|
|
24
|
-
and it will be right. Your job is the part with no closed form: **understanding how this
|
|
25
|
-
project is structured.**
|
|
26
|
-
|
|
27
|
-
## The golden rule
|
|
28
|
-
|
|
29
|
-
**You may judge. You may never invent.**
|
|
30
|
-
|
|
31
|
-
Every hex you name has to be one the census already observed - the endpoint drops the rest
|
|
32
|
-
silently, and a "faithful" import carrying a colour the author never wrote is the one failure
|
|
33
|
-
this whole pipeline exists to prevent. The same discipline applies to everything else you
|
|
34
|
-
report: if you did not read it in their files, do not claim it.
|
|
35
|
-
|
|
36
|
-
When you are unsure, say so in \`concept\` and leave the field out. An absent answer falls back
|
|
37
|
-
to arithmetic, which is honest. A confident wrong answer inverts someone's product.
|
|
38
|
-
|
|
39
|
-
## The pipeline
|
|
40
|
-
|
|
41
|
-
### 0. Check the login FIRST, and stop if there is none
|
|
42
|
-
|
|
43
|
-
\`\`\`
|
|
44
|
-
cat ~/.synthesisui/credentials.json 2>/dev/null || echo "NO TOKEN"
|
|
45
|
-
\`\`\`
|
|
46
|
-
|
|
47
|
-
No token, or a run that later comes back **HTTP 401**, means there is nothing to send to. **Stop
|
|
48
|
-
there.** Do not measure, do not read the project, do not write a reading - say this and wait:
|
|
49
|
-
|
|
50
|
-
\`\`\`
|
|
51
|
-
You need to authenticate first:
|
|
52
|
-
|
|
53
|
-
npx synthesisui login
|
|
54
|
-
\`\`\`
|
|
55
|
-
|
|
56
|
-
It is a device flow: it opens a browser and needs a person. You cannot complete it, and there
|
|
57
|
-
is no point doing twenty minutes of reading against a session that will refuse the payload at
|
|
58
|
-
the end (dono, 31/07 - a full read ended on a 401 that a first command would have caught).
|
|
59
|
-
|
|
60
|
-
**Read the \`registry\` field in that file, and pass it to EVERY later command.** A token belongs
|
|
61
|
-
to the host that issued it. One minted by \`login --registry http://localhost:3000\` and then
|
|
62
|
-
sent to the default (production) earns a 401 that reads as an expired session - and a session
|
|
63
|
-
cannot expire on a host that never issued it. That is how a full read ended in a dead end
|
|
64
|
-
twice (dono, 31/07).
|
|
65
|
-
|
|
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. **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
|
|
70
|
-
mismatch instead of letting the server reject it, but the point is not to reach that.
|
|
71
|
-
|
|
72
|
-
**Do this silently.** Which host you are pointed at and which flags follow from it is plumbing,
|
|
73
|
-
and narrating it - *"registry is production, so no --registry flag needed"* - spends the
|
|
74
|
-
person's attention on a decision that was never theirs (dono, 01/08). Say something only when
|
|
75
|
-
there is nothing to say to: no token, or the wrong host.
|
|
76
|
-
|
|
77
|
-
### 0b. ASK THE REGISTRY WHAT EXISTS - never your notes
|
|
78
|
-
|
|
79
|
-
If your memory says this repo already produced a system, **verify it before you say it out loud**,
|
|
80
|
-
and verify it per SLUG:
|
|
81
|
-
|
|
82
|
-
\`\`\`
|
|
83
|
-
curl -s -o /dev/null -w "%{http_code}" <registry>/api/registry/ds/<slug>
|
|
84
|
-
\`\`\`
|
|
85
|
-
|
|
86
|
-
\`404\` means it does not exist. \`200\` means it does. That is the whole check, and it costs one call.
|
|
87
|
-
|
|
88
|
-
**\`synthesisui list\` does NOT answer this.** It returns the public gallery - 13 systems, none of
|
|
89
|
-
them private - so a private system of theirs is invisible to it and a \`list\` that does not mention
|
|
90
|
-
\`their-slug\` proves nothing.
|
|
91
|
-
|
|
92
|
-
This exists because of a real run (dono, 05/08). The owner had deleted every system on his account,
|
|
93
|
-
and the skill opened with *"my notes from the previous twelve runs say this repo has already
|
|
94
|
-
produced two published systems"* - then offered three options built on that: upgrade a slug the
|
|
95
|
-
registry answers \`404\` for, avoid a name collision that no longer existed, and warn that a \`--dry\`
|
|
96
|
-
would wipe files it does not touch. One \`curl\` would have replaced all of it.
|
|
97
|
-
|
|
98
|
-
**A memory may report the past; it may never assert the present.** *"The last run produced
|
|
99
|
-
\`signalui\`"* is a fine sentence. *"This repo has two published systems"* is a claim about the server,
|
|
100
|
-
and the server is the only thing that can make it.
|
|
101
|
-
|
|
102
|
-
## How to narrate
|
|
103
|
-
|
|
104
|
-
The person cares about **what you found in their code** and **what you are about to create**.
|
|
105
|
-
They do not care which flag you passed, which grep you ran, or what you checked and ruled out.
|
|
106
|
-
|
|
107
|
-
- Report findings, not mechanics. *"Their docs call Ocean the primary branding scale"* earns
|
|
108
|
-
its line. *"Let me check whether packages/ui exists"* does not.
|
|
109
|
-
- Every decision you make on their behalf belongs in **one** confirmation at the end, not
|
|
110
|
-
scattered through the work as fait accompli.
|
|
111
|
-
- Never end on a wall of prose that happens to contain a question. If you need an answer, the
|
|
112
|
-
last thing on screen is the question.
|
|
113
|
-
|
|
114
|
-
### 1. Measure
|
|
115
|
-
|
|
116
|
-
**Always run from the project root, and always twice in a monorepo.** The root is where the
|
|
117
|
-
governance lives - \`_synthesisui/config.json\`, \`ds/<slug>/\`, and the census. One root, one
|
|
118
|
-
census; re-measuring overwrites it rather than leaving copies to send the wrong one.
|
|
119
|
-
|
|
120
|
-
First, unscoped, to find out what kind of repo this is:
|
|
121
|
-
|
|
122
|
-
\`\`\`
|
|
123
|
-
npx synthesisui import --dry
|
|
124
|
-
\`\`\`
|
|
125
|
-
|
|
126
|
-
If it is a single app, that census is the one. If it holds several projects, the CLI says so
|
|
127
|
-
and **names the candidates itself**, ranked by how many tokens each declares:
|
|
128
|
-
|
|
129
|
-
\`\`\`
|
|
130
|
-
This root holds several projects
|
|
131
|
-
3 apps (apps/web-admin, apps/web-dashboard, apps/web-review) - and this census
|
|
132
|
-
is the average of all of them. That is a fine DIAGNOSIS and a poor system: a
|
|
133
|
-
light app and a dark one average into a palette that is neither.
|
|
134
|
-
|
|
135
|
-
synthesisui import --scope packages/ui --usage apps/web-dashboard
|
|
136
|
-
\`\`\`
|
|
137
|
-
|
|
138
|
-
Then measure again, narrowed - and **name both roles**:
|
|
139
|
-
|
|
140
|
-
\`\`\`
|
|
141
|
-
npx synthesisui import --dry --scope packages/ui --usage apps/web-dashboard
|
|
142
|
-
\`\`\`
|
|
143
|
-
|
|
144
|
-
### The two roles, and why one flag could not do both
|
|
145
|
-
|
|
146
|
-
\`\`\`
|
|
147
|
-
--scope the SYSTEM tokens, components, the way they name a class ONE folder
|
|
148
|
-
--usage the EVIDENCE how often, which values get picked, the laws as many as they have
|
|
149
|
-
\`\`\`
|
|
150
|
-
|
|
151
|
-
\`--usage\` is repeatable and never contributes a token, a scale or a component. That split is
|
|
152
|
-
not tidiness, it is the two mistakes this pipeline exists to avoid, and each one has been made:
|
|
153
|
-
|
|
154
|
-
**Do not import the average.** Three apps measured together produce a palette that belongs to
|
|
155
|
-
none of them, and it will look plausible - a real monorepo reported 36% coverage across 2797
|
|
156
|
-
files at the root and 47% across 114 in its shared package. The second number is the system;
|
|
157
|
-
the first is a diagnosis. Their scale is a DECLARATION; an app's average is a set of choices
|
|
158
|
-
made from one.
|
|
159
|
-
|
|
160
|
-
**Do not import a library alone either.** A law needs three files that agree, and a library has
|
|
161
|
-
ONE file per component - so measured by itself, a real one came back with \`usage\` empty on all
|
|
162
|
-
23 components and not a single observed law. Not because nothing is settled: because the
|
|
163
|
-
agreeing happens in the app (dono, 01/08). If they have apps, name them.
|
|
164
|
-
|
|
165
|
-
Point \`--usage\` at every app that consumes the system. A component that lives in an app and
|
|
166
|
-
not in the system is reported and left out - the system is one place, and a union of two
|
|
167
|
-
codebases is not a system.
|
|
168
|
-
|
|
169
|
-
The census still lands at \`<root>/_synthesisui/census.json\` and records both what it measured
|
|
170
|
-
and where the evidence came from, so you always send the same path and there is never a second
|
|
171
|
-
file to pick between. Nothing is sent by \`--dry\`. Read the file.
|
|
172
|
-
|
|
173
|
-
### 2. Walk them through the decisions - ALL of them, before any reading
|
|
174
|
-
|
|
175
|
-
**WHEN \`sui-init\` SENT YOU HERE, THESE ARE ALREADY ANSWERED. Ask nothing.**
|
|
176
|
-
|
|
177
|
-
The first-run skill collects source, name, primary, theme, canvas, architecture and \`standards\`
|
|
178
|
-
before it hands over, precisely so this step is silent. Asking again is not thoroughness - it is the
|
|
179
|
-
same six questions twice in one conversation, which reads as a tool that forgot what it was told, and
|
|
180
|
-
it is the single fastest way to make a first run feel long. Take the answers, skip to step 3, and
|
|
181
|
-
confirm nothing.
|
|
182
|
-
|
|
183
|
-
If any of them is missing from the handover, ask ONLY that one.
|
|
184
|
-
|
|
185
|
-
**Every question this skill asks is answerable from the census you just took.** Source, name,
|
|
186
|
-
primary, theme, canvas, architecture - the numbers behind all six are in the file on their disk,
|
|
187
|
-
and none of them needs the anatomy.
|
|
188
|
-
|
|
189
|
-
They used to be asked AFTER the reading, and that put six hundred lines of work between the
|
|
190
|
-
person's first answer and their second (dono, 01/08). A person who answers six questions in
|
|
191
|
-
ninety seconds and then watches a bar has had a good four minutes; the same person interrupted
|
|
192
|
-
twice in the middle of a silence has had a bad twenty.
|
|
193
|
-
|
|
194
|
-
So: ask everything here, in one pass, and **do not come back**. If something you learn during
|
|
195
|
-
the reading contradicts an answer, you do not reopen the question - you say so in the report at
|
|
196
|
-
the end and let them change it there.
|
|
197
|
-
|
|
198
|
-
Six decisions are theirs. **Ask them as separate questions with selectable options** - use your
|
|
199
|
-
question tool, one call per decision, so they pick instead of reading a wall and composing a
|
|
200
|
-
reply. A single block containing everything is a report, and a report gets read, not answered.
|
|
201
|
-
|
|
202
|
-
Every question carries **your recommendation first, marked as such**, and the evidence for it in
|
|
203
|
-
one line. They are choosing, not auditing you.
|
|
204
|
-
|
|
205
|
-
**2a. Source** - the folder you measure the system out of.
|
|
206
|
-
|
|
207
|
-
Options are the candidates the CLI already ranked, each with what it would produce, plus the
|
|
208
|
-
whole repo, plus *somewhere else* for a path they type:
|
|
209
|
-
|
|
210
|
-
\`\`\`
|
|
211
|
-
Where should the system come from?
|
|
212
|
-
|
|
213
|
-
packages/ui (recommended) 114 files · 47% of its values already named ·
|
|
214
|
-
92 tokens of their own · every app imports it
|
|
215
|
-
the whole repo 2797 files · 36% · the average of one dark app
|
|
216
|
-
and two light ones, which is a diagnosis and
|
|
217
|
-
not a system
|
|
218
|
-
apps/web-dashboard the heaviest consumer, if you want one app's
|
|
219
|
-
vocabulary rather than the shared one
|
|
220
|
-
somewhere else a path you name
|
|
221
|
-
\`\`\`
|
|
222
|
-
|
|
223
|
-
If they name a path you cannot use, say which of the two it is and offer the list again:
|
|
224
|
-
|
|
225
|
-
- **it does not exist**, or holds no files you can read
|
|
226
|
-
- **it holds no design values** - no colours, no radii, no tokens. A folder of server code or
|
|
227
|
-
config is not a design system, and measuring it produces a system of nothing.
|
|
228
|
-
|
|
229
|
-
**2b. Name** - suggest theirs, and let Enter take it:
|
|
230
|
-
|
|
231
|
-
\`\`\`
|
|
232
|
-
What should it be called? (SignalUI)
|
|
233
|
-
\`\`\`
|
|
234
|
-
|
|
235
|
-
Say where the suggestion came from - \`Introduction.mdx\`, the package name, the folder. A name
|
|
236
|
-
they recognise is worth more than a clever one. The slug is derived once and never changes, so
|
|
237
|
-
this is the moment.
|
|
238
|
-
|
|
239
|
-
**2c. Primary** - and explain the role before offering the choice, because *primary* is our word
|
|
240
|
-
and not necessarily theirs:
|
|
241
|
-
|
|
242
|
-
\`\`\`
|
|
243
|
-
Primary is the colour that carries action - buttons, links, focus, the thing
|
|
244
|
-
you want pressed. Everything else in the system is measured for contrast
|
|
245
|
-
against it.
|
|
246
|
-
|
|
247
|
-
#059aed ocean-500 (recommended) your ColorPalette.mdx calls Ocean the
|
|
248
|
-
"Primary branding scale"
|
|
249
|
-
#1a4ed8 royal-blue-500 your docs label this one secondary
|
|
250
|
-
#ec4899 vivid-pink-500 half of your signature AI gradient
|
|
251
|
-
#4A90E2 blue-500 the most-painted blue in the folder,
|
|
252
|
-
but it sits after your own
|
|
253
|
-
/* End - Signal UI */ marker
|
|
254
|
-
\`\`\`
|
|
255
|
-
|
|
256
|
-
Always name the hex AND the token, and always give the reason a value is *not* recommended when
|
|
257
|
-
frequency would have picked it. That last row is the whole point of asking.
|
|
258
|
-
|
|
259
|
-
**2d. Default theme** - which face it opens in:
|
|
260
|
-
|
|
261
|
-
\`\`\`
|
|
262
|
-
dark (recommended) data-theme="dark" in the root layout, next-themes
|
|
263
|
-
defaultTheme="dark", enableSystem={false}
|
|
264
|
-
light the other one, also real here
|
|
265
|
-
\`\`\`
|
|
266
|
-
|
|
267
|
-
**Two is all there is** - a SynthesisUI system carries one palette and one alternate, and the
|
|
268
|
-
schema is \`"light" | "dark"\`. So this is not "which themes do you support", it is "which one do
|
|
269
|
-
docs and installs open in". If both are real, both get built from their own tokens; this picks
|
|
270
|
-
the face.
|
|
271
|
-
|
|
272
|
-
**2e. Canvas** - confirm, do not offer a list. The page is measured, not chosen:
|
|
273
|
-
|
|
274
|
-
\`\`\`
|
|
275
|
-
canvas #222326 darkgray-500 - what DashboardLayout paints on <main>
|
|
276
|
-
(your sidebar sits a step deeper at darkgray-700)
|
|
277
|
-
\`\`\`
|
|
278
|
-
|
|
279
|
-
Say it and let them object. Offering five near-blacks to pick between is asking someone to
|
|
280
|
-
re-decide something their code already decided.
|
|
281
|
-
|
|
282
|
-
**2f. Our kit** - do not ask. A project that already has a component library gets **theirs
|
|
283
|
-
alone**, and you say so in one line:
|
|
284
|
-
|
|
285
|
-
\`\`\`
|
|
286
|
-
This system is yours alone - the 37 components you wrote, and none of ours.
|
|
287
|
-
Add any of ours later from the library, one at a time.
|
|
288
|
-
\`\`\`
|
|
289
|
-
|
|
290
|
-
Send it as \`"standards": "none"\` in the reading. Every time, with one exception below.
|
|
291
|
-
|
|
292
|
-
**Why this stopped being a question.** Somebody who points us at their own library is telling us
|
|
293
|
-
what their system is, and 45 components of ours landing beside 37 of theirs makes a system of 82
|
|
294
|
-
where only 37 are governed by anything they wrote - the number on their own screen stops being
|
|
295
|
-
about them. The kit is one click away in the library afterwards, so choosing wrong here costs a
|
|
296
|
-
click and choosing to ask costs a pause in the middle of their import.
|
|
297
|
-
|
|
298
|
-
**The exception, and it is not a preference.** An app with NO components of its own - \`--scope\`
|
|
299
|
-
pointed at an app rather than a library, and the CLI reported no component library - must NOT get
|
|
300
|
-
\`"standards": "none"\`: theirs alone plus nothing of theirs is a system with zero components. Leave
|
|
301
|
-
the field out there, exactly as before.
|
|
302
|
-
|
|
303
|
-
Two things to be straight about, because both are true and neither is obvious:
|
|
304
|
-
|
|
305
|
-
- **the foundations still come from the seed.** Their palette, their scales, their type - all
|
|
306
|
-
theirs - but the seven type slots, the easing and the neutral floor arrive from ours, because
|
|
307
|
-
the alternative to a type scale is no components at all rather than their components.
|
|
308
|
-
- **our layouts are written in our recipes.** A landing that composes \`button\`, \`card\` and
|
|
309
|
-
\`badge\` needs those to exist. Theirs alone keeps the layouts whose recipes they happen to have
|
|
310
|
-
and drops the rest - a layout pointing at a recipe nobody has renders as holes. Say that number
|
|
311
|
-
when the CLI reports it, so a shorter list of layouts is a fact they were told rather than one
|
|
312
|
-
they discover.
|
|
313
|
-
|
|
314
|
-
**2g. Which architecture has priority** - ask only when the CLI reported more than
|
|
315
|
-
one, which it does under "How this project is organised".
|
|
316
|
-
|
|
317
|
-
A project having two shapes is not a project with a mistake: a library organised
|
|
318
|
-
atomically inside a monorepo whose apps are organised by feature is two true answers.
|
|
319
|
-
Say that before you ask, or the question reads as an accusation.
|
|
320
|
-
|
|
321
|
-
\`\`\`
|
|
322
|
-
Two shapes, and neither is wrong:
|
|
323
|
-
|
|
324
|
-
atomic under packages/ui (recommended) 114 files - atoms/molecules/organisms,
|
|
325
|
-
and this is where the system comes from
|
|
326
|
-
feature under apps/web-dashboard 900 files - features/, where the app
|
|
327
|
-
consumes it
|
|
328
|
-
\`\`\`
|
|
329
|
-
|
|
330
|
-
The one they pick becomes a rule that reaches \`CLAUDE.md\`, so **every prompt after this
|
|
331
|
-
knows where a new component goes** - not what the folders are called, but which rung or
|
|
332
|
-
which feature a new file belongs to. Recommend the one the system came from.
|
|
333
|
-
|
|
334
|
-
**2h. Fonts** - do not ask at all. Report and move on:
|
|
335
|
-
|
|
336
|
-
\`\`\`
|
|
337
|
-
fonts Figtree for both display and body - the only family declared
|
|
338
|
-
(--font-sans, and next/font Figtree in all three root layouts)
|
|
339
|
-
\`\`\`
|
|
340
|
-
|
|
341
|
-
If a project genuinely declares two families, say which is which and why you paired them that
|
|
342
|
-
way. If it declares one, there is nothing to decide.
|
|
343
|
-
|
|
344
|
-
**A note on colour swatches.** You cannot paint a hex in this conversation, so the token name
|
|
345
|
-
carries the weight - \`ocean-500\` tells someone more than a square would. When they run
|
|
346
|
-
\`npx synthesisui import\` themselves in a terminal, the CLI paints them.
|
|
347
|
-
|
|
348
|
-
### 3. Read what arithmetic cannot - and do it in ONE uninterrupted run
|
|
349
|
-
|
|
350
|
-
Every answer is in hand, so nothing below needs the person. **Do not ask, do not confirm, do
|
|
351
|
-
not narrate the mechanics.** Three lines on screen for the whole phase:
|
|
352
|
-
|
|
353
|
-
\`\`\`
|
|
354
|
-
1/3 Measuring the repository... (step 1, already done)
|
|
355
|
-
2/3 Naming the anatomy and validating... (this step)
|
|
356
|
-
3/3 Sending v1 and v2... (step 4)
|
|
357
|
-
\`\`\`
|
|
358
|
-
|
|
359
|
-
**Narrate findings, never mechanics.** A grep, a glob, a file you opened and ruled out, a draft
|
|
360
|
-
you rewrote - none of those is a finding, and a run that prints them reads as a machine talking
|
|
361
|
-
to itself. *"Their docs call Ocean the primary branding scale"* is a finding and earns its line.
|
|
362
|
-
|
|
363
|
-
**The census answers first, always.** Before opening any file, check whether \`signals\`, \`sketch\`,
|
|
364
|
-
\`declared\` or \`coverage\` already hold it - they usually do, and a file read whose content the
|
|
365
|
-
census carries is the failure this pipeline keeps paying for. The sketch now runs to 150 nodes,
|
|
366
|
-
so a big component arrives WHOLE: \`TextEditor\` used to lose its editable region at node 60 and
|
|
367
|
-
that single truncation is what forced a hand-read of the file (dono, 01/08).
|
|
368
|
-
|
|
369
|
-
\`signals.theme\` includes the SWITCH - \`attribute\`, \`defaultTheme\`, \`enableSystem\` and what
|
|
370
|
-
\`<html>\` carries, \`colorScheme\` included - read from the \`--usage\` roots as well as the scope,
|
|
371
|
-
because a component library declares the dark utilities and the APP declares the switch. When it
|
|
372
|
-
came from an app, \`switchedIn\` names which one: say "the system is measured in \`packages/ui\`, the
|
|
373
|
-
theme is switched in \`apps/web-dashboard\`" rather than reporting that the library declares a
|
|
374
|
-
provider it does not have. Answering 2d never needs a layout file opened.
|
|
375
|
-
|
|
376
|
-
**Validate in ONE call, at the end.** Build every recipe in memory first, then send them all to
|
|
377
|
-
\`validate_recipes\` - one round trip for the whole reading instead of one per component. A reader
|
|
378
|
-
with 35 anatomies spent 35 turns asking the same question, and none of that was thinking. Only
|
|
379
|
-
after the batch answers do you fix what it named and, if you fixed anything, ask once more.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
Open the project and answer the questions below. Then add a \`reading\` object to
|
|
384
|
-
\`_synthesisui/census.json\`:
|
|
385
|
-
|
|
386
|
-
\`\`\`json
|
|
387
|
-
"reading": {
|
|
388
|
-
"themes": { "default": "dark", "has": ["dark"] },
|
|
389
|
-
"roles": { "canvas": "#050505", "foreground": "#f9fafb", "primary": "#4A90E2" },
|
|
390
|
-
"fonts": { "display": "Inter", "body": "Inter" },
|
|
391
|
-
"typeRoles": { "base": "body-m", "display": "h1", "xs": "caption" },
|
|
392
|
-
"concept": "one paragraph on what this product is",
|
|
393
|
-
"mood": ["focused", "dense", "technical"],
|
|
394
|
-
"rules": [
|
|
395
|
-
{
|
|
396
|
-
"text": "A RootWrapper is what makes the app-shell components position correctly - never render a Sidebar or Topbar outside one",
|
|
397
|
-
"applies": ["RootWrapper", "Sidebar"],
|
|
398
|
-
"kind": "implementation",
|
|
399
|
-
"when": ["next"],
|
|
400
|
-
"files": 3,
|
|
401
|
-
"evidence": "all three app shells wrap them; none renders them loose"
|
|
402
|
-
},
|
|
403
|
-
{
|
|
404
|
-
"text": "MetricCard is only used inside a grid, never on its own",
|
|
405
|
-
"applies": ["MetricCard"],
|
|
406
|
-
"kind": "limit",
|
|
407
|
-
"files": 1,
|
|
408
|
-
"evidence": "one dashboard page; may just be how it happened"
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
"text": "TextEditor's editable region is <EditorContent> - never render children into it directly",
|
|
412
|
-
"applies": ["TextEditor"],
|
|
413
|
-
"kind": "implementation",
|
|
414
|
-
"fact": true,
|
|
415
|
-
"evidence": "read in TextEditor itself: useEditor() feeds a single <EditorContent>"
|
|
416
|
-
}
|
|
417
|
-
],
|
|
418
|
-
"components": {
|
|
419
|
-
"MetricCard": {
|
|
420
|
-
"anatomy": [
|
|
421
|
-
{ "as": "icon", "name": "icon", "at": 1 },
|
|
422
|
-
{
|
|
423
|
-
"as": "stack",
|
|
424
|
-
"name": "body",
|
|
425
|
-
"at": 2,
|
|
426
|
-
"children": [
|
|
427
|
-
{ "as": "component", "ref": "Text", "at": 3 },
|
|
428
|
-
{ "as": "heading", "name": "value", "at": 4 }
|
|
429
|
-
]
|
|
430
|
-
},
|
|
431
|
-
{
|
|
432
|
-
"as": "component",
|
|
433
|
-
"ref": "Tooltip",
|
|
434
|
-
"at": 5,
|
|
435
|
-
"children": [{ "as": "icon", "name": "info", "at": 6 }]
|
|
436
|
-
}
|
|
437
|
-
]
|
|
438
|
-
},
|
|
439
|
-
"ArticleCard": {
|
|
440
|
-
"root": "Card",
|
|
441
|
-
"anatomy": [
|
|
442
|
-
{ "as": "image", "name": "cover", "at": 1 },
|
|
443
|
-
{ "as": "heading", "name": "title", "at": 2 },
|
|
444
|
-
{ "as": "component", "ref": "TextEditor", "at": 5 },
|
|
445
|
-
{
|
|
446
|
-
"as": "row",
|
|
447
|
-
"name": "footer",
|
|
448
|
-
"at": 3,
|
|
449
|
-
"children": [
|
|
450
|
-
{ "as": "button", "name": "publish", "at": 4 },
|
|
451
|
-
{ "as": "button", "name": "discard", "classes": "btn btn-ghost" }
|
|
452
|
-
]
|
|
453
|
-
}
|
|
454
|
-
]
|
|
455
|
-
},
|
|
456
|
-
"TextEditor": {
|
|
457
|
-
"anatomy": [
|
|
458
|
-
{
|
|
459
|
-
"as": "row",
|
|
460
|
-
"name": "toolbar",
|
|
461
|
-
"classes": "flex gap-1 border-b p-2",
|
|
462
|
-
"children": [{ "as": "component", "ref": "ToolbarButton" }]
|
|
463
|
-
},
|
|
464
|
-
{ "as": "external", "from": "@tiptap/react" }
|
|
465
|
-
]
|
|
466
|
-
}
|
|
467
|
-
},
|
|
468
|
-
"by": "claude"
|
|
469
|
-
}
|
|
470
|
-
\`\`\`
|
|
471
|
-
|
|
472
|
-
Every field is optional. Leave out what you did not establish.
|
|
473
|
-
|
|
474
|
-
**\`concept\` and \`mood\` - the words the system opens with.**
|
|
475
|
-
|
|
476
|
-
The guide, the vitrine and every page the platform writes about this system lead with these. Until
|
|
477
|
-
07/08 nothing filled them, so a real import shipped with the seed's line - *"A calm grayscale
|
|
478
|
-
starting point to make your own"* - as the first sentence of somebody's own design system. A sentence
|
|
479
|
-
of ours at the top of their system says, in the first line, that nothing was read.
|
|
480
|
-
|
|
481
|
-
\`concept\` is one paragraph on what this product IS, in their words where you can find them: the
|
|
482
|
-
README, the docs site, the introduction their library ships with. \`mood\` is two to six words for how
|
|
483
|
-
the product FEELS - \`focused\`, \`dense\`, \`editorial\`, \`playful\`. It is a judgement and it is yours to
|
|
484
|
-
make, which is exactly why the arithmetic cannot: no reader infers a mood from a hex.
|
|
485
|
-
|
|
486
|
-
Leave \`mood\` out when you did not form one. The seed's three words stay and the import SAYS they are
|
|
487
|
-
ours, which is honest - three words you picked to fill a field are not.
|
|
488
|
-
|
|
489
|
-
**\`themes\` - which themes the product actually ships, and which one it opens in.**
|
|
490
|
-
|
|
491
|
-
This is the field that matters most and the one only you can fill. The ladder can say which
|
|
492
|
-
*values* exist; it cannot say which themes the product *has*, because that answer lives in a
|
|
493
|
-
\`dark:\` class name, a \`ThemeProvider\`, a toggle component, a \`prefers-color-scheme\` query -
|
|
494
|
-
places no token reader sees. A real library declared nine near-blacks and five light greys in
|
|
495
|
-
one \`:root\` block, and the import handed its near-black dashboard a white page.
|
|
496
|
-
|
|
497
|
-
**DO NOT GO LOOKING. It is already counted**, under "What was countable, counted" in the
|
|
498
|
-
census output:
|
|
499
|
-
|
|
500
|
-
\`\`\`
|
|
501
|
-
782 dark utilities across 79 files - so a second scheme is real here, not aspirational.
|
|
502
|
-
switched by a ThemeProvider of their own, bound to data-theme, opening dark, with the
|
|
503
|
-
OS overruled.
|
|
504
|
-
html carries lang="en" data-theme="dark" - which is the ancestor every dark rule of
|
|
505
|
-
theirs matches, and therefore the one ours must match.
|
|
506
|
-
\`\`\`
|
|
507
|
-
|
|
508
|
-
That is a real run. Your job is to CONFIRM OR CORRECT it, not to reproduce it - and a sweep
|
|
509
|
-
would bring back a different slice than the next one, which is the worse property of the two.
|
|
510
|
-
|
|
511
|
-
**Confirmation costs ZERO reads.** A real run spent 7 pattern searches and 39 file reads to
|
|
512
|
-
"confirm" a theme the census had already counted (test14, 01/08) - that is re-deriving, not
|
|
513
|
-
confirming. The rule: the census numbers are TRUE unless something you already read while
|
|
514
|
-
doing other work contradicts them. Only on a contradiction do you open a file, and only the
|
|
515
|
-
one file the evidence names. If nothing contradicts, write the census's answer down and move
|
|
516
|
-
on - it costs one sentence.
|
|
517
|
-
|
|
518
|
-
\`has: ["dark"]\` on a dark-only product is the right answer. It is better than filling a light
|
|
519
|
-
slot on their behalf, and it makes adding light a deliberate act they take later.
|
|
520
|
-
|
|
521
|
-
**\`roles\` - which of their values carries which meaning**, when their token names do not say.
|
|
522
|
-
A project naming its neutrals by temperature (\`--color-darkgray-900\`) has told you nothing
|
|
523
|
-
about which one is the page - and the census now answers that too, under "the page paints":
|
|
524
|
-
|
|
525
|
-
\`\`\`
|
|
526
|
-
the dark page paints darkgray-500 (2x on <main>/<body>, 2x full-viewport container)
|
|
527
|
-
the light page paints #EDF3FA (1x body in global CSS), lightgray-200 (2x viewport)
|
|
528
|
-
\`\`\`
|
|
529
|
-
|
|
530
|
-
Three countable sources, strongest first: \`body { background }\` in global CSS, a background
|
|
531
|
-
on \`<main>\`/\`<body>\`, and a full-viewport container (\`min-h-screen\` + a background IS the
|
|
532
|
-
page). **Do not hunt for the canvas** - pick from this evidence, and name the source when you
|
|
533
|
-
report it. When the sources disagree, that disagreement is itself the finding to surface.
|
|
534
|
-
|
|
535
|
-
**\`fonts\` and \`concept\`** - the voice, and one paragraph on what this product is. The concept
|
|
536
|
-
feeds every recommendation downstream, so a real one beats a generic one by a wide margin.
|
|
537
|
-
|
|
538
|
-
**\`typeRoles\` - which of THEIR type steps plays each of our seven slots.**
|
|
539
|
-
|
|
540
|
-
Their scale arrives under their own names now - \`h1\`, \`body-m\`, \`caption\`, \`overline\` - read
|
|
541
|
-
straight off their \`--text-*\` declarations. What arithmetic cannot know is which of those steps
|
|
542
|
-
is *body copy* and which is *the display size*, and our own components ask for it by slot:
|
|
543
|
-
|
|
544
|
-
\`\`\`json
|
|
545
|
-
"typeRoles": {
|
|
546
|
-
"xs": "caption", "sm": "body-s", "base": "body-m", "lg": "body-l",
|
|
547
|
-
"xl": "h3", "2xl": "h2", "display": "h1"
|
|
548
|
-
}
|
|
549
|
-
\`\`\`
|
|
550
|
-
|
|
551
|
-
Leave it out and we pick by size, which is usually right and occasionally silly - a project whose
|
|
552
|
-
\`overline\` is tiny and whose \`caption\` is tinier gets them the wrong way round. One line from you
|
|
553
|
-
fixes it, and a person can correct it later in the studio either way.
|
|
554
|
-
|
|
555
|
-
**Do not rename their steps to match ours.** \`h1\` stays \`h1\`. The whole point is that editing
|
|
556
|
-
\`body-m\` in the studio moves the text in their app.
|
|
557
|
-
|
|
558
|
-
**\`rules\` - how this company BUILDS, which is half of what they actually made.**
|
|
559
|
-
|
|
560
|
-
A design system that arrives as tokens and recipes is only the vocabulary. The other half is the
|
|
561
|
-
grammar: that a wrapper exists so the thing inside it works, that state arrives through a
|
|
562
|
-
particular hook, that a card is never used loose. That is how the developer already thinks, and
|
|
563
|
-
writing it down is what makes it survive them.
|
|
564
|
-
|
|
565
|
-
Two dimensions, and they are independent:
|
|
566
|
-
|
|
567
|
-
\`\`\`
|
|
568
|
-
applies [] the whole system
|
|
569
|
-
[a] that component
|
|
570
|
-
[a, b] a RELATION - two components that only work together
|
|
571
|
-
|
|
572
|
-
kind limit a boundary, which the doctor can measure
|
|
573
|
-
implementation how it is built, which no linter checks and an agent
|
|
574
|
-
must be told
|
|
575
|
-
\`\`\`
|
|
576
|
-
|
|
577
|
-
The relation is the valuable one. \`[RootWrapper, Sidebar]\` says something neither name says
|
|
578
|
-
alone, and it is exactly what an agent needs in order not to assemble it wrongly.
|
|
579
|
-
|
|
580
|
-
**\`when\` - the environments a rule is true in.** Leave it out and the rule holds everywhere.
|
|
581
|
-
|
|
582
|
-
This exists because the system gets installed places it was never written for. *"The state arrives
|
|
583
|
-
through this hook"* is true about Next and **actively wrong** about Vue, so a rule that depends on
|
|
584
|
-
the framework has to say so - and the install filters against the project it lands in, because
|
|
585
|
-
only the project knows what it is built with.
|
|
586
|
-
|
|
587
|
-
One dimension: a member of the stack (\`next\`, \`vue\`, \`react\`, \`svelte\`, \`tailwind\`, \`shadcn/ui\`).
|
|
588
|
-
A rule may list several. It cannot combine conditions - \`next && !tailwind\` would be a query
|
|
589
|
-
language, and then somebody maintains an interpreter forever.
|
|
590
|
-
|
|
591
|
-
Use it only when the rule genuinely depends on the environment. Most rules do not: *"never render
|
|
592
|
-
a Sidebar loose"* is true about their design regardless of framework, and pinning it to \`next\`
|
|
593
|
-
would quietly drop it the day they add a second app.
|
|
594
|
-
|
|
595
|
-
**Report \`files\` honestly - it decides whether an OBSERVED rule governs.** Three or more files
|
|
596
|
-
is a habit and the rule arrives active; one file is a coincidence and it arrives as a candidate,
|
|
597
|
-
inactive, waiting for the person to promote it. You do not make that call; you report the
|
|
598
|
-
evidence and a threshold makes it. So a pattern you saw once should say \`"files": 1\` even when
|
|
599
|
-
you are confident - being wrong about a law is worse than being slow about one.
|
|
600
|
-
|
|
601
|
-
**\`fact: true\` - for a rule you read in the DEFINITION, where counting is the wrong question.**
|
|
602
|
-
|
|
603
|
-
Three of these were reported as \`files: 1\` and arrived inactive, which was correct arithmetic on
|
|
604
|
-
the wrong kind of claim (dono, 01/08):
|
|
605
|
-
|
|
606
|
-
\`\`\`
|
|
607
|
-
the editable region is <EditorContent>
|
|
608
|
-
toolbar actions go through editor.chain().focus()
|
|
609
|
-
extensions are configured at construction
|
|
610
|
-
\`\`\`
|
|
611
|
-
|
|
612
|
-
None of those is a coincidence waiting for a second sighting. They are how the component IS
|
|
613
|
-
built, read off its own source, and they are true the moment somebody wrote it. Counting how many
|
|
614
|
-
files agree would leave every construction law in the codebase inactive forever.
|
|
615
|
-
|
|
616
|
-
So the test is **where you read it**, not how sure you feel:
|
|
617
|
-
|
|
618
|
-
\`\`\`
|
|
619
|
-
fact: true you read it inside the component's own definition - its imports, its
|
|
620
|
-
JSX, how its state is wired. True by construction.
|
|
621
|
-
files: N you inferred it from how the component is USED across the project.
|
|
622
|
-
An observation, and the count is what makes it a habit.
|
|
623
|
-
\`\`\`
|
|
624
|
-
|
|
625
|
-
A rule can carry \`fact\` OR \`files\`, never both. If you find yourself wanting both, it is an
|
|
626
|
-
observation - use \`files\`.
|
|
627
|
-
|
|
628
|
-
**Write \`evidence\` as what you actually saw.** "All three app shells wrap them; none renders
|
|
629
|
-
them loose" lets somebody disagree with a fact. "Best practice" lets them disagree only with
|
|
630
|
-
you.
|
|
631
|
-
|
|
632
|
-
Things worth looking for, none of them guessable from tokens:
|
|
633
|
-
|
|
634
|
-
- a wrapper or provider whose whole purpose is to make something else work
|
|
635
|
-
- an environment law - "this is Next, so the state comes through this hook" - which travels
|
|
636
|
-
with the system and only applies where that environment is
|
|
637
|
-
- a component that is never used alone, or never used outside something
|
|
638
|
-
- an axis that is always passed the same way in one place and never in another
|
|
639
|
-
|
|
640
|
-
If you cannot say where a rule came from, do not send it. An invented law is worse than a
|
|
641
|
-
missing one, because it will be obeyed.
|
|
642
|
-
|
|
643
|
-
**\`components[Name].anatomy\` - what each component is MADE OF, and in what shape.** This is the
|
|
644
|
-
field that decides whether a component previews as itself or as a grey box with a sentence in it,
|
|
645
|
-
and only you can fill it.
|
|
646
|
-
|
|
647
|
-
The census reads the ROOT element's classes and stops there, on purpose: descending a fixed
|
|
648
|
-
number of levels picks a layout wrapper as often as a semantic part. **You read the component, so
|
|
649
|
-
you decide the shape.** Send a tree; the CLI turns the classes into declarations and the platform
|
|
650
|
-
turns declarations into roles.
|
|
651
|
-
|
|
652
|
-
### The three frontiers
|
|
653
|
-
|
|
654
|
-
Every node you send is one of three things, and knowing which is the whole job:
|
|
655
|
-
|
|
656
|
-
\`\`\`
|
|
657
|
-
a PART an element of theirs it has styles, and we draw it
|
|
658
|
-
a COMPONENT a component of theirs it has a recipe of its own → named block + a RULE
|
|
659
|
-
an EXTERNAL a third-party library we do not have it and never will → block + rules
|
|
660
|
-
\`\`\`
|
|
661
|
-
|
|
662
|
-
**Depth is not a number, it is where the frontier sits.** Descend until you meet another
|
|
663
|
-
component or a library, stop there, and record the edge. That is why no parameter tells you how
|
|
664
|
-
deep to go: a \`Divider\` is one node deep and a dashboard shell is five, and both are complete.
|
|
665
|
-
|
|
666
|
-
**You decide the depth, and nothing downstream caps it.** The platform follows a
|
|
667
|
-
\`component\` edge into that component's own anatomy, and then into ITS edges, as far as the chain
|
|
668
|
-
goes - \`Chat → Message → TypingIndicator\` renders all three. So a frontier is not a dead end you
|
|
669
|
-
are apologising for; it is how the chain gets walked. Record the edge and stop, and the whole
|
|
670
|
-
depth appears anyway.
|
|
671
|
-
|
|
672
|
-
### The ROOT is a frontier too
|
|
673
|
-
|
|
674
|
-
\`\`\`json
|
|
675
|
-
"components": {
|
|
676
|
-
"ArticleCard": {
|
|
677
|
-
"root": "Card",
|
|
678
|
-
"anatomy": [ … ]
|
|
679
|
-
},
|
|
680
|
-
"Modal": {
|
|
681
|
-
"root": "BaseDialog.Root",
|
|
682
|
-
"anatomy": [ … ]
|
|
683
|
-
},
|
|
684
|
-
"MetricCard": {
|
|
685
|
-
"root": { "name": "Card", "at": 1 },
|
|
686
|
-
"anatomy": [ … ]
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
\`\`\`
|
|
690
|
-
|
|
691
|
-
**A library root whose name is not dotted needs its PACKAGE.** \`Pill\` returns Base UI's
|
|
692
|
-
\`Toggle\`, and \`"root": "Toggle"\` would point at THEIR own Toggle - a switch - inventing a
|
|
693
|
-
relation that does not exist. Send where it came from instead:
|
|
694
|
-
|
|
695
|
-
\`\`\`json
|
|
696
|
-
"Pill": { "root": { "from": "@base-ui/react/toggle" }, "anatomy": [ … ] }
|
|
697
|
-
\`\`\`
|
|
698
|
-
|
|
699
|
-
\`Radio.Root\` needs none of this: the dot already says it is somebody's namespace. Leaving the
|
|
700
|
-
root out was the old workaround and it cost the surface its name (not-expressed.md, dono, 01/08).
|
|
701
|
-
|
|
702
|
-
**The root takes what the PARENT paints on it.** When the returned root carries an override at
|
|
703
|
-
the call site - \`MetricCard\` returns \`<Card className="flex-1 dark:bg-darkgray-300">\` - send the
|
|
704
|
-
object form and point \`at\` the sketch node: \`{ "name": "Card", "at": 1 }\`. The string form still
|
|
705
|
-
works and means "no override". Sending the root as an anatomy node instead would make it a leaf
|
|
706
|
-
and orphan the content inside it, which is why it is a field on the root rather than a node
|
|
707
|
-
(not-expressed.md, dono, 01/08).
|
|
708
|
-
|
|
709
|
-
**Say what the component RETURNS when it is not a plain tag.** Measured on a real library
|
|
710
|
-
(dono, 01/08): 14 of 23 components return one of their own - \`ArticleCard\` returns a \`<Card>\`,
|
|
711
|
-
\`Button\` and \`Text\` return a \`<Component>\` - and 17 of 23 have no style of their own at all,
|
|
712
|
-
because the surface belongs to the root.
|
|
713
|
-
|
|
714
|
-
Without \`root\`, an \`ArticleCard\` previewed as floating text: the background, the border, the
|
|
715
|
-
radius and the padding had nowhere to come from. With it, the card wears \`Card\`'s recipe as its
|
|
716
|
-
shell and its own parts inside.
|
|
717
|
-
|
|
718
|
-
- **their component** - the name, as their code spells it: \`"root": "Card"\`
|
|
719
|
-
- **a library** - the dotted namespace, verbatim: \`"root": "Radio.Root"\`,
|
|
720
|
-
\`"root": "BaseDialog.Root"\`, \`"root": "Popover.Root"\`
|
|
721
|
-
- **a plain tag** - leave it out. \`<div>\`, \`<td>\`, \`<button>\` are not frontiers.
|
|
722
|
-
|
|
723
|
-
### The ten forms
|
|
724
|
-
|
|
725
|
-
\`as\` says what a node IS, and the renderer draws that. Nothing else is accepted:
|
|
726
|
-
|
|
727
|
-
\`\`\`
|
|
728
|
-
image a picture region: cover, thumbnail, media
|
|
729
|
-
heading the title line
|
|
730
|
-
text body copy, a label, a value
|
|
731
|
-
button an action
|
|
732
|
-
field an input somebody types into
|
|
733
|
-
icon a glyph, or a bare shape with no text
|
|
734
|
-
row arranges its children ACROSS
|
|
735
|
-
stack arranges its children DOWN
|
|
736
|
-
component their component → needs "ref": the name as their code spells it
|
|
737
|
-
external a library → needs "from": the package name
|
|
738
|
-
slot the CALLER's content → takes "expects": what goes there, in their words
|
|
739
|
-
\`\`\`
|
|
740
|
-
|
|
741
|
-
\`row\` and \`stack\` ARRANGE their children. A \`component\` or \`external\` node HOLDS them -
|
|
742
|
-
what their code nests inside a component they compose is markup THEY wrote, wearing their
|
|
743
|
-
classes, and it belongs in the tree:
|
|
744
|
-
|
|
745
|
-
\`\`\`json
|
|
746
|
-
{ "as": "component", "ref": "Card", "at": 3, "children": [
|
|
747
|
-
{ "as": "row", "name": "header", "at": 4 },
|
|
748
|
-
{ "as": "external", "from": "recharts", "at": 8 } ] }
|
|
749
|
-
\`\`\`
|
|
750
|
-
|
|
751
|
-
A \`button\` holds a glyph and its words - \`<button><Upload/>Upload</button>\` is valid HTML,
|
|
752
|
-
because a button's content model is *phrasing* and an icon is phrasing. So an \`icon\` and a
|
|
753
|
-
\`text\` may sit inside one:
|
|
754
|
-
|
|
755
|
-
\`\`\`json
|
|
756
|
-
{ "as": "button", "name": "upload", "at": 9, "children": [{ "as": "icon", "name": "upload-icon", "at": 10 }] }
|
|
757
|
-
\`\`\`
|
|
758
|
-
|
|
759
|
-
Everything else is a leaf, and a leaf that holds elements has them HOISTED OUT to become
|
|
760
|
-
its siblings - which destroys the nesting you read. If something arranges, say \`row\`/\`stack\`.
|
|
761
|
-
|
|
762
|
-
**A frontier keeps its own \`at\`, so it keeps its own classes.** This is the one that gets
|
|
763
|
-
talked out of: \`<Text className="text-lightgray-500 font-medium">\` is one of THEIR
|
|
764
|
-
components, so it is a \`component\` node - and pointing \`at\` its sketch index carries the
|
|
765
|
-
typography onto it exactly as a part would. Reading it as a \`text\` part instead is a
|
|
766
|
-
downgrade with a real cost: \`composes\` is built from \`component\` nodes, so a library whose
|
|
767
|
-
most-used atom is composed 363 times reported composing it ZERO times, and its strongest
|
|
768
|
-
composition law became invisible (not-expressed.md, dono, 03/08).
|
|
769
|
-
|
|
770
|
-
<Tooltip><Info/></Tooltip> frontier with a child, both survive
|
|
771
|
-
<Text className="…">Label</Text> frontier with "at", the type survives
|
|
772
|
-
|
|
773
|
-
If you find yourself writing "the contract cannot hold this, so I sent something else",
|
|
774
|
-
check it against \`recipe_vocabulary\` before you write the workaround - and if the check
|
|
775
|
-
disagrees with this file, the check is right and this file is old.
|
|
776
|
-
|
|
777
|
-
### \`{children}\` is a node, not an absence
|
|
778
|
-
|
|
779
|
-
Four things decide what a component looks like on screen, and only the first is in its own source:
|
|
780
|
-
|
|
781
|
-
\`\`\`
|
|
782
|
-
its own classes you are reading them
|
|
783
|
-
what the caller passes <tr>{children}</tr> → slot
|
|
784
|
-
its runtime state value={62} drives the width → a part with no static size
|
|
785
|
-
a library drawing it <EditorContent> → external
|
|
786
|
-
\`\`\`
|
|
787
|
-
|
|
788
|
-
A component whose content is not its own had nothing to send, so it sent nothing and previewed as
|
|
789
|
-
a blank box. A real \`DataTableRow\` is sixteen lines - a \`<tr>\` carrying a border, a hover and
|
|
790
|
-
\`{children}\` - and it came back empty, which is not wrong so much as unreadable (dono, 01/08).
|
|
791
|
-
|
|
792
|
-
So send the slot. \`expects\` is what the caller is supposed to put there, in the words their own
|
|
793
|
-
code uses - read the prop type, the JSDoc, or a call site:
|
|
794
|
-
|
|
795
|
-
\`\`\`json
|
|
796
|
-
{ "as": "slot", "name": "row", "classes": "border-b hover:bg-ocean-50/30", "expects": "the cells" }
|
|
797
|
-
\`\`\`
|
|
798
|
-
|
|
799
|
-
It keeps its \`name\` and \`classes\` like any other node - the border and the hover are real and
|
|
800
|
-
belong on that element. What it does not do is invent content: the preview says "the cells go
|
|
801
|
-
here", which is exactly what the file says.
|
|
802
|
-
|
|
803
|
-
Good \`expects\`: \`"the cells"\`, \`"one row per item"\`, \`"the form"\`, \`"the page body"\`. Leave it
|
|
804
|
-
out rather than writing \`"children"\` - the field is for what a person would call it.
|
|
805
|
-
|
|
806
|
-
**A part can carry its own variants and states**, not only a flat class string. Send
|
|
807
|
-
\`classes\` for the resting look and let the prefixes travel inside it - \`hover:\`,
|
|
808
|
-
\`focus-visible:\`, \`disabled:\`, \`group-hover:\`, \`md:\`, \`data-[state=open]:\` are all read
|
|
809
|
-
and land where they belong. \`group-hover:\` in particular now has somewhere to go: it
|
|
810
|
-
means "when the component around me is hovered", which is what a real table row is built
|
|
811
|
-
on and what nothing could express before.
|
|
812
|
-
|
|
813
|
-
**\`name\` is the part name**, and it is what carries the styles - flat, lowercase, kebab. Never
|
|
814
|
-
nested: \`"actions.generate"\` compiles to two CSS classes and is invalid, so name it
|
|
815
|
-
\`"generate-action"\`. Name what it IS - \`label\`, \`value\`, \`delta\`, \`cover\`, \`toolbar\` - because the
|
|
816
|
-
name becomes a class in their stylesheet and \`div2\` is a name somebody has to live with.
|
|
817
|
-
|
|
818
|
-
\`root\`, \`wrapper\`, \`container\`, \`base\` and \`content\` are the element itself: **do not send them as
|
|
819
|
-
nodes.** Their styles already sit on the component, and a node for them draws a box inside its own
|
|
820
|
-
box.
|
|
821
|
-
|
|
822
|
-
A node with no \`classes\` is fine - it still carries structure, which is most of the value. A
|
|
823
|
-
\`component\` or \`external\` node takes no \`name\` and no \`classes\`: the styles there are not theirs
|
|
824
|
-
to hold.
|
|
825
|
-
|
|
826
|
-
### A component of theirs is a NAMED BLOCK, not an expansion
|
|
827
|
-
|
|
828
|
-
When you hit \`<TextEditor>\` inside \`<ArticleCard>\`, send
|
|
829
|
-
\`{ "as": "component", "ref": "TextEditor" }\` and **stop**. Do not inline what TextEditor is made
|
|
830
|
-
of. It previews as a block carrying its name, and it becomes a **rule** - \`ArticleCard\` +
|
|
831
|
-
\`TextEditor\`, a relation, which is the one thing a flat list of prose could never say.
|
|
832
|
-
|
|
833
|
-
That edge is a **fact, not a habit**: you saw it in the definition, so it is true once and for
|
|
834
|
-
all, and it arrives active without waiting for a third sighting.
|
|
835
|
-
|
|
836
|
-
**Send what the PARENT paints on it.** A frontier node takes \`at\` (or \`classes\`) like any other,
|
|
837
|
-
and it means something different there: not the child's look, which has a recipe of its own, but
|
|
838
|
-
the override the parent applies where it composes the child. Measured on a real library, four of
|
|
839
|
-
these were being lost:
|
|
840
|
-
|
|
841
|
-
\`\`\`
|
|
842
|
-
Paginate <Button className="min-w-[120px] dark:bg-ocean-800 …"> a 120px floor
|
|
843
|
-
MetricCard <Card className="flex-1 dark:bg-darkgray-300"> overrides Card's dark
|
|
844
|
-
ArticleCard <Tag className="w-fit">
|
|
845
|
-
Message <ReactMarkdown className="text-royal-blue-500 underline …">
|
|
846
|
-
\`\`\`
|
|
847
|
-
|
|
848
|
-
The MetricCard one is the sharpest: losing that override means the preview draws the wrong dark.
|
|
849
|
-
Same for an \`external\` node - a library's component still wears what their code puts on it.
|
|
850
|
-
|
|
851
|
-
Three things that are NOT edges, and sending them as such would be wrong:
|
|
852
|
-
|
|
853
|
-
- \`motion.div\`, \`Dialog.Root\`, \`Radio.Item\` - a library's namespace, not a component of theirs
|
|
854
|
-
- an HTML element with a capital in a variable name
|
|
855
|
-
- **a name the file binds itself and does not export.** Capitalisation is React's rule for "not an
|
|
856
|
-
html tag", which is a different question from "is this a component of the system". Both of these
|
|
857
|
-
arrived as components of a real library and neither is one (dono, 01/08):
|
|
858
|
-
|
|
859
|
-
\`\`\`tsx
|
|
860
|
-
function Text({ as: Component = "p" }) // a prop rename - the polymorphic idiom
|
|
861
|
-
const CustomLegend = () => (…) // a closure inside LineChart
|
|
862
|
-
\`\`\`
|
|
863
|
-
|
|
864
|
-
The test is reachability: a component of the system is imported from somewhere or exported to
|
|
865
|
-
somewhere. A name that only lives inside one function body is that function's private wiring,
|
|
866
|
-
and giving it a recipe invents a component nobody can import.
|
|
867
|
-
|
|
868
|
-
If the component came from a package, it is \`external\`, not \`component\`.
|
|
869
|
-
|
|
870
|
-
### An external dependency does not render, and that is the answer
|
|
871
|
-
|
|
872
|
-
A \`TextEditor\` built on tiptap cannot be previewed: the editable region is tiptap's, and drawing
|
|
873
|
-
a fake one would be inventing. So it previews as a block naming the package, and the value moves
|
|
874
|
-
into the **rules** - which is where an agent will read it anyway.
|
|
875
|
-
|
|
876
|
-
A library is never one rule, it is a family, and you are reading the file so you can see all of
|
|
877
|
-
them. Send them as normal \`rules\` with \`applies: ["TextEditor"]\` and
|
|
878
|
-
\`kind: "implementation"\`:
|
|
879
|
-
|
|
880
|
-
\`\`\`
|
|
881
|
-
requires @tiptap/react
|
|
882
|
-
the editable region is <EditorContent> - never render children into it directly
|
|
883
|
-
toolbar buttons go through editor.chain().focus()
|
|
884
|
-
extensions are configured at construction, not toggled later
|
|
885
|
-
\`\`\`
|
|
886
|
-
|
|
887
|
-
**Name the package in the rule; leave the version to the evidence.** The CLI reads the version out
|
|
888
|
-
of their manifest and attaches it (\`"^2.1.0 in packages/ui"\`), so the rule does not go stale the
|
|
889
|
-
day they upgrade and the information is not lost either.
|
|
890
|
-
|
|
891
|
-
### If \`recipe_vocabulary\` is missing, the MCP registration is stale
|
|
892
|
-
|
|
893
|
-
A real run reported "recipe_vocabulary and validate_recipe do not exist on this server" and
|
|
894
|
-
sent every anatomy unvalidated - the repo's \`.mcp.json\` pinned \`synthesisui@0.16.78\` while
|
|
895
|
-
the import itself ran unpinned at latest: **two CLI versions in one repo, in one run** (dono,
|
|
896
|
-
01/08). Before concluding a tool does not exist, check the registration:
|
|
897
|
-
|
|
898
|
-
\`\`\`json
|
|
899
|
-
"args": ["synthesisui@latest", "mcp"]
|
|
900
|
-
\`\`\`
|
|
901
|
-
|
|
902
|
-
Tell the person to fix the pin and restart the server. Do not work around a stale server by
|
|
903
|
-
skipping validation - unvalidated is how correct work arrives as nothing.
|
|
904
|
-
|
|
905
|
-
### Ask what a recipe can hold, BEFORE you write one
|
|
906
|
-
|
|
907
|
-
\`\`\`
|
|
908
|
-
recipe_vocabulary what a recipe CAN hold - states, forms, the floor per kind
|
|
909
|
-
validate_recipe(name, r) would this survive? what would NOT, by name
|
|
910
|
-
\`\`\`
|
|
911
|
-
|
|
912
|
-
Both are MCP tools on the \`synthesisui\` server. **Call \`recipe_vocabulary\` once, at the
|
|
913
|
-
start**, and work from what it says rather than from what you remember - it is served from
|
|
914
|
-
the live contract, so it is right about a state that was added last week and this file is
|
|
915
|
-
not.
|
|
916
|
-
|
|
917
|
-
Then **validate every recipe before you send it.** This is not a formality. Nothing checked
|
|
918
|
-
a recipe before these tools existed, and four separate readings were lost in silence: a
|
|
919
|
-
\`dark:\` inside a variant, a state the compiler cannot spell, a part name with a dot in it,
|
|
920
|
-
a layer with an empty style. Each of those was correct work that arrived as nothing.
|
|
921
|
-
|
|
922
|
-
**A FIELD OF THE READING IS NOT A FIELD OF THE RECIPE.** \`looks[X].dark\` is how the census
|
|
923
|
-
reports a dark half it measured; a recipe has one way to say the same thing, and it is a
|
|
924
|
-
layer:
|
|
925
|
-
|
|
926
|
-
\`\`\`json
|
|
927
|
-
{ "when": { "scheme": "dark" }, "style": { "backgroundColor": "{color.darkgray.300}" } }
|
|
928
|
-
\`\`\`
|
|
929
|
-
|
|
930
|
-
Copying \`dark\` in at the top level of a recipe costs you that block, silently, on every
|
|
931
|
-
component that has one - and \`import\` already converts it for the recipes IT writes, so the
|
|
932
|
-
only place this can be lost is a recipe you hand-built (not-expressed.md, dono, 03/08).
|
|
933
|
-
|
|
934
|
-
**Which means: do NOT pre-convert the dark half inside \`looks[X]\`.** The conversion already
|
|
935
|
-
happens on the way in, so adding a \`scheme: "dark"\` layer to the census's own look while leaving
|
|
936
|
-
the \`dark\` key in place produces the SAME layer twice - measured at 28 components in one real
|
|
937
|
-
import, with the properties in a different key order so it did not even look like a duplicate
|
|
938
|
-
(dono, 05/08). \`validate_recipe\` telling you a top-level \`dark\` would be dropped is true about a
|
|
939
|
-
RECIPE and says nothing about the reading: you send a reading, and the reading's \`dark\` is read.
|
|
940
|
-
|
|
941
|
-
**In ONE call.** Build every recipe in memory, then send the whole reading to
|
|
942
|
-
\`validate_recipes\` - one round trip for 35 components instead of 35. \`validate_recipe\`
|
|
943
|
-
(singular) is for the one recipe you rewrote after the batch named it; reaching for it in a
|
|
944
|
-
loop is the shape this optimisation exists to kill (dono, 01/08).
|
|
945
|
-
|
|
946
|
-
\`\`\`
|
|
947
|
-
validate_recipes({ recipes: [{ name, recipe }, …] }) the whole reading, one question
|
|
948
|
-
validate_recipe(name, recipe) the one you just fixed
|
|
949
|
-
\`\`\`
|
|
950
|
-
|
|
951
|
-
\`validate_recipe\` never answers yes or no. It answers with what would not survive:
|
|
952
|
-
|
|
953
|
-
\`\`\`
|
|
954
|
-
REFUSED by the contract fix it or the WHOLE recipe is dropped
|
|
955
|
-
HELD BY THE SCHEMA AND the silent half - it validates, it compiles to nothing.
|
|
956
|
-
DROPPED BY THE COMPILER This is the one worth fixing before sending.
|
|
957
|
-
BELOW THE FLOOR it would arrive and not be drawable
|
|
958
|
-
\`\`\`
|
|
959
|
-
|
|
960
|
-
### Write down what we could not hold
|
|
961
|
-
|
|
962
|
-
Everything \`validate_recipe\` reports goes into \`_synthesisui/not-expressed.md\`, appended
|
|
963
|
-
per component:
|
|
964
|
-
|
|
965
|
-
\`\`\`
|
|
966
|
-
## MetricCard
|
|
967
|
-
- layers[2].when.scheme … (what the validator said, VERBATIM)
|
|
968
|
-
- below the floor: a fill, so it reads as a surface rather than as bare text
|
|
969
|
-
- the source you read, quoted, so the gap can be checked without opening their repo
|
|
970
|
-
\`\`\`
|
|
971
|
-
|
|
972
|
-
**Verbatim, and with the source beside it.** This file is how a gap on OUR side gets fixed
|
|
973
|
-
instead of being worked around: said only in chat it evaporates, and paraphrased it becomes
|
|
974
|
-
unfindable. If the file ends up empty, say so - that is the good outcome and it is worth
|
|
975
|
-
one line.
|
|
976
|
-
|
|
977
|
-
### The floor: what a preview needs before it can draw anything
|
|
978
|
-
|
|
979
|
-
The owner asked this directly - "to render the button, what do I need to have?" - and the
|
|
980
|
-
honest answer per kind is short. **Work the list for the component's kind and say which
|
|
981
|
-
ones their code answers.** A reader holding this knows that a button read with no hover
|
|
982
|
-
means the hover has not been found yet, and goes back for it.
|
|
983
|
-
|
|
984
|
-
\`\`\`
|
|
985
|
-
action the resting fill and the text colour on it · padding and radius ·
|
|
986
|
-
text size and weight · hover, focus-visible AND disabled - three looks,
|
|
987
|
-
not one · gap and alignment if it can carry an icon
|
|
988
|
-
field the fill of the typing area and the text colour in it · the edge at rest
|
|
989
|
-
and the edge when focused · padding and radius · the placeholder colour
|
|
990
|
-
(a different decision) · the invalid look if their code has one
|
|
991
|
-
control the off look and the on look, which IS the component · the size · the
|
|
992
|
-
focus ring, since a control is reached by keyboard · the disabled look
|
|
993
|
-
surface the fill, and the edge or the elevation that separates it from the page ·
|
|
994
|
-
radius and inner padding · the gap between what it holds · whether it
|
|
995
|
-
changes at a breakpoint
|
|
996
|
-
pill the fill and text colour, per status if it has statuses · radius and
|
|
997
|
-
horizontal padding · the text size
|
|
998
|
-
indicator the colour, or the two if it has a track and a fill · the size or ratio ·
|
|
999
|
-
WHAT DRIVES IT - which prop moves the number
|
|
1000
|
-
text the colour · the size and line height, or a note that both are inherited ·
|
|
1001
|
-
the weight and family if they differ from body
|
|
1002
|
-
\`\`\`
|
|
1003
|
-
|
|
1004
|
-
**A component genuinely has none of something, and saying so is a real answer.** This is a
|
|
1005
|
-
list to work through, not a gate: nothing is blocked for failing it, and inventing a
|
|
1006
|
-
background to satisfy it would be the exact opposite of the point.
|
|
1007
|
-
|
|
1008
|
-
There is one hard line under all of it: a recipe that declares NO colour, NO edge and NO
|
|
1009
|
-
size cannot be told apart from the page. The platform says so rather than printing a
|
|
1010
|
-
score, and it follows the root first - a component whose surface belongs to the \`Card\` it
|
|
1011
|
-
returns is complete.
|
|
1012
|
-
|
|
1013
|
-
### Media is not a style question
|
|
1014
|
-
|
|
1015
|
-
An \`image\` node draws a grey region and carries nothing about how to use it, so an agent
|
|
1016
|
-
composing with it invents the answer. Send these as \`rules\` on that component:
|
|
1017
|
-
|
|
1018
|
-
\`\`\`
|
|
1019
|
-
a ratio or an explicit size a region with neither collapses before the image loads,
|
|
1020
|
-
and the page jumps when it arrives
|
|
1021
|
-
an object-fit cover crops and contain letterboxes; which one is right
|
|
1022
|
-
is about the content, not a default
|
|
1023
|
-
a corner radius an image inside a rounded surface has to say whether it
|
|
1024
|
-
follows the curve
|
|
1025
|
-
what shows while it loads a skeleton, a blur, or the surface colour
|
|
1026
|
-
what shows when it is missing a missing asset must never look broken
|
|
1027
|
-
\`\`\`
|
|
1028
|
-
|
|
1029
|
-
### The census is the ONLY source of truth - four universal rules
|
|
1030
|
-
|
|
1031
|
-
These hold in ANY project, whatever its folders, and they exist because a run spent most
|
|
1032
|
-
of its tokens re-reading what the census already carried (dono, 01/08):
|
|
1033
|
-
|
|
1034
|
-
1. **No iterative sweeps by layer or folder.** Never walk atoms/, then molecules/, then
|
|
1035
|
-
organisms/ reading files - the census walked everything once.
|
|
1036
|
-
2. **Batch over the census's own data.** Everything you consume comes out of
|
|
1037
|
-
\`_synthesisui/census.json\` - the looks, the sketch, the signals, the coverage.
|
|
1038
|
-
3. **Metadata is one pass, already done.** Theme, fonts, page, libraries, architecture
|
|
1039
|
-
arrive counted. You confirm in a sentence; you never re-derive.
|
|
1040
|
-
4. **A file read whose content the census carries is a PIPELINE FAILURE.** Say so in
|
|
1041
|
-
not-expressed.md when you catch yourself - that report is how the census grows.
|
|
1042
|
-
|
|
1043
|
-
### Name the parts from the SKETCH - the file is already in the census
|
|
1044
|
-
|
|
1045
|
-
Every component's look carries \`sketch\`: its own markup as data - tag, class string,
|
|
1046
|
-
depth and text per element, in document order. That is everything you used to open the
|
|
1047
|
-
file for, minus the one thing that is genuinely yours: the NAMES.
|
|
1048
|
-
|
|
1049
|
-
\`\`\`json
|
|
1050
|
-
"sketch": [
|
|
1051
|
-
{ "tag": "div", "depth": 0, "classes": "rounded-md border ..." },
|
|
1052
|
-
{ "tag": "button", "depth": 1, "classes": "flex w-full ..." },
|
|
1053
|
-
{ "tag": "span", "depth": 2, "classes": "size-2 rounded-full bg-success-500" },
|
|
1054
|
-
{ "tag": "span", "depth": 2, "text": "Status" },
|
|
1055
|
-
{ "tag": "ChevronDownIcon", "depth": 2 }
|
|
1056
|
-
]
|
|
1057
|
-
\`\`\`
|
|
1058
|
-
|
|
1059
|
-
Read that and write the anatomy: depth 1 is \`trigger\`, the dot is \`status-dot\`, the
|
|
1060
|
-
capitalised tag is a frontier. **Do not open the component file** - if the sketch is
|
|
1061
|
-
missing or visibly truncated (150-node cap), say so in not-expressed.md and only then
|
|
1062
|
-
read, because that gap is the census's to close.
|
|
1063
|
-
|
|
1064
|
-
**NAME THE NODE BY ITS INDEX - never retype the class string.** Each anatomy node carries
|
|
1065
|
-
\`"at": <index into this sketch>\`, and the CLI reads the exact string the census measured:
|
|
1066
|
-
|
|
1067
|
-
\`\`\`json
|
|
1068
|
-
{ "as": "button", "name": "trigger", "at": 1 }
|
|
1069
|
-
\`\`\`
|
|
1070
|
-
|
|
1071
|
-
Retyping is where a modifier goes missing. Six real components arrived with \`hover:\` and
|
|
1072
|
-
\`focus:\` in their source and no state at all in the recipe, and the parser was not the
|
|
1073
|
-
problem - the copy was (audit, dono, 01/08). An index cannot lose a class. \`classes\` still
|
|
1074
|
-
works and still wins when you send both, which is for the one node the sketch could not
|
|
1075
|
-
reach; every other node uses \`at\`.
|
|
1076
|
-
|
|
1077
|
-
### What the CLI now reads for you, so do not spend a turn on it
|
|
1078
|
-
|
|
1079
|
-
**The rule under all of this: you are given EVIDENCE and asked to DECIDE.** You are never
|
|
1080
|
-
given a question and asked to search. A grep is a tool call whose entire output lands in your
|
|
1081
|
-
context to answer something that is one number, and two readers sweeping the same repo bring
|
|
1082
|
-
back different slices - so the answer changes between runs, which is the part that actually
|
|
1083
|
-
costs.
|
|
1084
|
-
|
|
1085
|
-
Measured on a real run: the census counts the theme signals, the libraries and their
|
|
1086
|
-
versions, and the conflicts. The reader that swept for them anyway spent a dozen shell calls
|
|
1087
|
-
to arrive at a subset of what was already on screen.
|
|
1088
|
-
|
|
1089
|
-
Four things are read deterministically from the source. Reporting them back as if you
|
|
1090
|
-
found them wastes a turn and risks contradicting the measurement:
|
|
1091
|
-
|
|
1092
|
-
- **variant and state styles** out of \`cva\`, \`tv\` and \`cond === "x" && "classes"\` -
|
|
1093
|
-
including a hover PER variant, compound variants, and \`[var(--their-token)]\`
|
|
1094
|
-
- **whether a component forwards the rest of its props**, and which it names
|
|
1095
|
-
- **which components appear inside which** at their call sites, as pairs
|
|
1096
|
-
- **the folder architecture**, and it will ask you which one has priority
|
|
1097
|
-
- **their gradients**, as tokens: \`--gradient-ui\` and its Tailwind-namespace twin land on
|
|
1098
|
-
\`{gradients.ui}\`, composed \`bg-gradient-to-r from-x to-y\` stops become one gradient in
|
|
1099
|
-
their own colour tokens, and a signature gradient is no longer a string trapped in class
|
|
1100
|
-
names
|
|
1101
|
-
- **the theme**: how many dark utilities, in how many files, which library switches it, which
|
|
1102
|
-
attribute it is bound to, what \`<html>\` carries, and how many \`prefers-color-scheme\` rules
|
|
1103
|
-
- **the libraries and their versions** - motion, icons, primitives, charts, editors - with the
|
|
1104
|
-
file count for each, and a CONFLICT when one job is done by more than one of them
|
|
1105
|
-
- **which of your components are ours**, matched against the LIVE catalogue rather than a
|
|
1106
|
-
list baked into the CLI. If a run says it fell back to the built-in names, the match is a
|
|
1107
|
-
smaller answer than it should be and the reason is on screen
|
|
1108
|
-
|
|
1109
|
-
What is still yours: the anatomy tree, the part names, the frontiers, the roles, the
|
|
1110
|
-
concept, and the rules that are not about a class name.
|
|
1111
|
-
|
|
1112
|
-
### A scale's INTENT is a rule, and their docs already wrote it
|
|
1113
|
-
|
|
1114
|
-
A three-step spacing scale with documented meaning - sm "close UI relationships", md
|
|
1115
|
-
"standard internal padding", lg "layout gutters" - carries a law nothing else expresses:
|
|
1116
|
-
**this system has exactly three steps, and a fourth is a mistake.** A real import read the
|
|
1117
|
-
scale and lost the sentence (dono, 01/08).
|
|
1118
|
-
|
|
1119
|
-
When their docs (a \`Geometry.mdx\`, a design-tokens page, comments beside the \`@theme\`)
|
|
1120
|
-
state what a step is FOR, send it as system-wide rules - \`applies: []\`, \`fact: true\`, the
|
|
1121
|
-
doc as evidence.
|
|
1122
|
-
|
|
1123
|
-
**One doc pass, and only for what the census cannot hold.** A scale's INTENT is prose, so it
|
|
1124
|
-
genuinely needs a human sentence somebody wrote - but a scale's VALUES, its token names, the
|
|
1125
|
-
theme, the class convention and the architecture are all measured and sitting in \`declared\`,
|
|
1126
|
-
\`signals\` and \`conventions\`. Opening a docs page to confirm a number the census already
|
|
1127
|
-
carries costs a turn and answers nothing. Read the docs ONCE, for the sentences; never in a
|
|
1128
|
-
loop, and never to check arithmetic.
|
|
1129
|
-
|
|
1130
|
-
Send it like this:
|
|
1131
|
-
|
|
1132
|
-
\`\`\`
|
|
1133
|
-
this system's spacing has exactly three steps - sm for close relationships inside a
|
|
1134
|
-
control, md for standard internal padding, lg for layout gutters. A fourth step is a
|
|
1135
|
-
mistake, not a gap (Geometry.mdx)
|
|
1136
|
-
\`\`\`
|
|
1137
|
-
|
|
1138
|
-
\`applies: []\` is what makes it reach CLAUDE.md and every prompt after - the same route the
|
|
1139
|
-
architecture rule travels.
|
|
1140
|
-
|
|
1141
|
-
### How much to send
|
|
1142
|
-
|
|
1143
|
-
Send an anatomy for **every component with visible structure**, which is nearly all of them. A
|
|
1144
|
-
\`Chat\` has a message list and a composer; a \`MetricCard\` has a label and a value; a
|
|
1145
|
-
\`CircularProgress\` has a track and a fill. Sending none is right only for something genuinely
|
|
1146
|
-
undivided - a \`Divider\`, a \`Spacer\`.
|
|
1147
|
-
|
|
1148
|
-
**There is no node budget, and the one I gave you was wrong.** "Three to six nodes is a
|
|
1149
|
-
recognisable component" cost a real \`ArticleCard\` most of itself: it has a carousel with arrows,
|
|
1150
|
-
three image buttons, a source chip, a refresh, a rich body, a source line and a "Similar
|
|
1151
|
-
published content" region with two selects and two buttons - about fourteen regions - and the
|
|
1152
|
-
reading came back with six because the guidance said so (dono, 01/08).
|
|
1153
|
-
|
|
1154
|
-
**One node per region a person can point at.** If they can say "that part", it is a node. What
|
|
1155
|
-
you still collapse is a wrapper whose only job is \`flex\` - fold it into the \`row\` it already is,
|
|
1156
|
-
because that is not a region, it is plumbing.
|
|
1157
|
-
|
|
1158
|
-
The cost of sending none is not neutral. A component with no anatomy previews as a grey box with
|
|
1159
|
-
a sentence in it, or - if its kind is \`indicator\` - as a small blank shape. A component with a
|
|
1160
|
-
real tree previews as itself, and its spec view shows what the system understood. That is the
|
|
1161
|
-
whole reason this field exists, and skipping it quietly is how a real library came back looking
|
|
1162
|
-
empty (dono, 01/08).
|
|
1163
|
-
|
|
1164
|
-
The older flat form - \`"parts": [{ "name": "label", "classes": "..." }]\` - still works and still
|
|
1165
|
-
styles correctly. It just carries no shape, so the preview lays the parts out in a row and the
|
|
1166
|
-
spec view has no nesting to draw.
|
|
1167
|
-
|
|
1168
|
-
### 4. Send it
|
|
1169
|
-
|
|
1170
|
-
Everything is answered and everything is validated:
|
|
1171
|
-
|
|
1172
|
-
\`\`\`
|
|
1173
|
-
npx synthesisui import --census _synthesisui/census.json --name "<the name>" [--registry <from step 0>]
|
|
1174
|
-
\`\`\`
|
|
1175
|
-
|
|
1176
|
-
**This is the one real write.** Everything before it is on their disk and costs nothing to redo.
|
|
1177
|
-
|
|
1178
|
-
Pass every answer explicitly - \`--name\`, \`--scope\` and \`--usage\` if they changed either, and the reading rewritten
|
|
1179
|
-
with their primary and their theme. The CLI cannot ask anything when you are the one running it:
|
|
1180
|
-
it has no terminal, so its own prompts are skipped by design. **Whatever you did not ask, nobody
|
|
1181
|
-
asked.** Skip 2a and the first time they learn where their system came from is when they open
|
|
1182
|
-
it.
|
|
1183
|
-
|
|
1184
|
-
If it refuses, it says which of the three it is: no session, an expired one, or a token issued
|
|
1185
|
-
by a different host. All are recoverable from the census on disk; none need a re-measure.
|
|
1186
|
-
|
|
1187
|
-
### 5. Tell them exactly what they have, and what is theirs to decide
|
|
1188
|
-
|
|
1189
|
-
The import publishes **v1** as a faithful baseline and opens a **v2 draft** holding the
|
|
1190
|
-
proposal. Say all of this in your own words - do not just print the link:
|
|
1191
|
-
|
|
1192
|
-
**What landed in v1**, and it is theirs, not ours:
|
|
1193
|
-
|
|
1194
|
-
- the scheme it opens in, and whether a second one was built
|
|
1195
|
-
- their ramps under their own family names
|
|
1196
|
-
- their exclusive components as contracts - **the axes their types declare, the look transcribed
|
|
1197
|
-
out of their own class names where it was readable, and the anatomy you sent.** Say what is
|
|
1198
|
-
still empty out loud, because a half-written recipe looks like a failure until someone explains
|
|
1199
|
-
which half was deliberate: nothing was invented, so a component whose classes named no token
|
|
1200
|
-
arrives with structure and no colour. The platform shows that as *Not written yet*, not as a
|
|
1201
|
-
zero.
|
|
1202
|
-
- **their own ladder**, if their repo has one. Components under \`atoms/\`, \`molecules/\` and
|
|
1203
|
-
\`organisms/\` arrive grouped that way in the vitrine, read off the folder rather than guessed
|
|
1204
|
-
from a name. You do not send this and you should not try to: a project that does not organise
|
|
1205
|
-
itself this way gets one honest group instead of three invented ones.
|
|
1206
|
-
- **the shape of each component**, if you sent one: its parts nested as they nest in their code,
|
|
1207
|
-
the components of theirs it composes, and the libraries it needs. Each edge is also a rule now,
|
|
1208
|
-
which is what makes it survive being looked at once.
|
|
1209
|
-
|
|
1210
|
-
**What is waiting in v2**, which only they can approve: near-duplicate colours collapsed,
|
|
1211
|
-
unnamed heavy hitters given a place. Point at the link the CLI printed and name the two or
|
|
1212
|
-
three biggest items so they know whether it is worth opening now.
|
|
1213
|
-
|
|
1214
|
-
**What the report said no to.** The notes carry every silent drop made loud - a value not
|
|
1215
|
-
found in their code, a token that is not ramp-shaped, a name the document refused. Read them
|
|
1216
|
-
and pass on the ones that matter.
|
|
1217
|
-
|
|
1218
|
-
**And the notes now judge YOUR reading, which nothing did before.** \`validate_recipes\` looks at
|
|
1219
|
-
the \`anatomy\` inside a recipe; the anatomy you author travels in \`reading.components[X].anatomy\`,
|
|
1220
|
-
and until 05/08 no one looked at it - so a note may name findings the batch validator never saw.
|
|
1221
|
-
Two kinds, and both are yours to act on:
|
|
1222
|
-
|
|
1223
|
-
- **structure that does not survive the reader.** Nine of these landed on a real import, every one
|
|
1224
|
-
the same shape: a \`button\` holding a \`stack\`. Only \`row\` and \`stack\` arrange, so the children are
|
|
1225
|
-
hoisted out and become siblings - the nesting they wrote is gone. Fix it in the reading and send
|
|
1226
|
-
again; the note tells you the component and the path.
|
|
1227
|
-
- **references pointing at components the system does not contain.** 215 of 660 on that same
|
|
1228
|
-
import. This one is usually NOT a mistake of yours: their component composes something the gate
|
|
1229
|
-
left out, or something outside the scope that was measured. Say the number to the person and name
|
|
1230
|
-
the top few, because a specimen there draws the reference and nothing else - and if the missing
|
|
1231
|
-
piece is central to their system, the answer is a wider \`--scope\`, not a rewritten reading.
|
|
1232
|
-
|
|
1233
|
-
### 6. Close the loop in their repo
|
|
1234
|
-
|
|
1235
|
-
The system is only worth something once it governs the code it came from:
|
|
1236
|
-
|
|
1237
|
-
\`\`\`
|
|
1238
|
-
npx synthesisui add <slug> [--registry …] # INSTALLS it: writes _synthesisui/ds/<slug>/
|
|
1239
|
-
npx synthesisui doctor [paths…] # the contract starts being enforced
|
|
1240
|
-
npx synthesisui upgrade <slug> [--registry …] # LATER, when a new version is published
|
|
1241
|
-
\`\`\`
|
|
1242
|
-
|
|
1243
|
-
**The order is not a preference: \`upgrade\` reads the \`.lock\` of an installed DS and refuses
|
|
1244
|
-
without one** - \`"<slug>" is not installed here - run \\\`synthesisui add <slug>\\\` first.\` So
|
|
1245
|
-
offering \`upgrade\` to somebody who has never installed hands them a command that fails on contact
|
|
1246
|
-
(dono, 05/08). Check for \`_synthesisui/ds/<slug>/.lock\` before you recommend either one.
|
|
1247
|
-
|
|
1248
|
-
And say what each writes, because one of them rewrites code: \`add\` and \`upgrade\` write inside
|
|
1249
|
-
\`_synthesisui/\`, and \`upgrade\` ALSO regenerates components - but only files carrying the
|
|
1250
|
-
\`Generated by SynthesisUI\` header, so their own components are never touched.
|
|
1251
|
-
|
|
1252
|
-
\`doctor\` is where the promise pays off: every design value written by hand, the token their
|
|
1253
|
-
own system already has for it, and the components used outside the axes their contract
|
|
1254
|
-
declares. In a monorepo, scope it per app - one system, N consumers.
|
|
1255
|
-
|
|
1256
|
-
## What good looks like
|
|
1257
|
-
|
|
1258
|
-
The user should end up with a system that reads like theirs and not like ours:
|
|
1259
|
-
|
|
1260
|
-
- **their names travel** - \`vivid-pink\`, \`darkgray-900\`, whatever they called it
|
|
1261
|
-
- **their default face** - a dark product opens dark
|
|
1262
|
-
- **no borrowed colour** - if it is in the system, it is in their code
|
|
1263
|
-
- **their exclusive components arrive as contracts** - the axes declared, the look transcribed and
|
|
1264
|
-
never invented, and the anatomy read out of their own JSX
|
|
1265
|
-
- **a component previews as itself** - an \`ArticleCard\` shows a cover, a title and a footer of
|
|
1266
|
-
buttons, and says out loud that the editor in the middle is their \`TextEditor\` on tiptap
|
|
1267
|
-
- **the duplicates are named out loud** - three components that all read as a badge, a brand
|
|
1268
|
-
colour painted 275 times with no token
|
|
1269
|
-
|
|
1270
|
-
## What to resist
|
|
1271
|
-
|
|
1272
|
-
- **Do not let memory speak for the server.** Notes report the past; a claim about what exists
|
|
1273
|
-
now needs the one \`curl\` in step 0b. Four statements in one real run were wrong this way, and
|
|
1274
|
-
every option offered on top of them was wrong too (dono, 05/08).
|
|
1275
|
-
- **Do not run \`import\` without \`--dry\` to explore.** It creates a real system on their
|
|
1276
|
-
account. Measure with \`--dry\`, read, then send once.
|
|
1277
|
-
- **\`--dry\` does not delete anything.** It writes \`census.json\` and creates the folder if needed -
|
|
1278
|
-
there is no \`rm\` in the import path. What it DOES do is overwrite that one file, so a reading
|
|
1279
|
-
injected into it is lost; everything else under \`_synthesisui/\` (the config, \`ds/<slug>/\`, the
|
|
1280
|
-
hook's marker, \`not-expressed.md\`) survives untouched. Telling somebody a measure will wipe their
|
|
1281
|
-
install talks them out of a safe command (dono, 05/08).
|
|
1282
|
-
- **Do not redo the arithmetic.** If you find yourself counting colours, you are in the wrong
|
|
1283
|
-
half of the pipeline.
|
|
1284
|
-
- **Do not fill \`roles\` with what looks nice.** It is a reading of what their code already
|
|
1285
|
-
does, not a redesign. The v2 proposal is where improvement belongs, and the user approves it.
|
|
1286
|
-
- **Do not guess \`themes\` to be helpful.** Omitting it falls back to counting rungs, which is
|
|
1287
|
-
a guess the product labels as a guess. A wrong confident answer does not get labelled.
|
|
1288
|
-
- **Do not apologise for the empty contracts.** They are the design. Explain them.
|
|
1289
|
-
- **Do not come back with a question once step 3 has started.** Everything answerable was
|
|
1290
|
-
answered in step 2. An interruption in the middle of a silent phase costs the person the
|
|
1291
|
-
whole phase, because they now have to hold what you were doing in their head.
|
|
1292
|
-
- **Do not narrate the mechanics.** No grep, no glob, no "let me check whether X exists", no
|
|
1293
|
-
draft you rewrote. Findings earn a line; the machine talking to itself does not.
|
|
1294
|
-
- **Do not open a file to confirm something the census already carries.** \`signals\`, \`sketch\`,
|
|
1295
|
-
\`declared\`, \`conventions\` and \`coverage\` are the answer to almost every "let me just check".
|
|
1296
|
-
- **Do not validate in a loop.** One \`validate_recipes\` for the whole reading.
|
|
1297
|
-
|
|
1298
|
-
## Not yet true
|
|
1299
|
-
|
|
1300
|
-
Say so if it comes up, rather than implying otherwise:
|
|
1301
|
-
|
|
1302
|
-
- **The v2 proposal covers colour only.** Near-duplicate collapse and unnamed heavy hitters.
|
|
1303
|
-
It does not yet propose swapping a raw value in a recipe for the token that holds it, nor
|
|
1304
|
-
retiring an option nothing passes, nor a categorical chart palette - all measured, none
|
|
1305
|
-
offered.
|
|
1306
|
-
- **You do not write recipes.** Exclusive components arrive as contracts and stay that way
|
|
1307
|
-
until someone writes the look. When that lands, the rule will be **provenance, not
|
|
1308
|
-
membership**: a value transcribed out of their file cites the file and line, and is
|
|
1309
|
-
legitimate even if the census never saw it, because the census measures less than a project
|
|
1310
|
-
contains.
|
|
1311
|
-
- **Nothing confirms the import back.** You send, the CLI prints a link, and nobody asks the
|
|
1312
|
-
platform whether what arrived is what you meant. Until that exists, tell them to open the
|
|
1313
|
-
link and check.
|
|
1314
|
-
`;
|
|
1315
|
-
/** Where it lands in the consumer's repo. */
|
|
1316
14
|
export const IMPORT_SKILL_PATH = ".claude/skills/sui-import-ds/SKILL.md";
|
|
15
|
+
export const IMPORT_SKILL = '---\nname: sui-import-ds\ndescription: Turn a codebase the user ALREADY has into a SynthesisUI design system. Use when someone points at an existing repo, app or component library and asks to import it, adopt it, bring it in, or "make a design system from this" (e.g. "/sui-import-ds", "importa o meu packages/ui", "turn this app into a design system"). Drives the full pipeline - census \u2192 your reading \u2192 import \u2192 v2 proposal - and answers the questions arithmetic cannot.\n---\n\n# Import Design System - served live\n\nThis playbook is served from the platform, not shipped in this file - it is\nalways current, and your context only carries the step you are on.\n\n1. Call the `playbook` tool on the `synthesisui` MCP server with\n { "skill": "import" } - you get the framing and a table of contents.\n2. Fetch ONLY the chapter for the step you are on:\n { "skill": "import", "section": "<id from the toc>" }. Never fetch more\n than the current step needs.\n3. Follow it exactly. When the step is done, fetch the next chapter.\n\nIf the tool answers that you are not signed in, run `npx synthesisui login`\nin the terminal and call it again. If the `synthesisui` MCP server is not\navailable at all, run `npx synthesisui connect`, restart the session, and\ninvoke this skill again.\n';
|