synthesisui 0.16.72 → 0.16.74
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/import.js +53 -1
- package/dist/doctor/transcribe.js +17 -0
- package/dist/skill-import.js +96 -0
- package/package.json +1 -1
package/dist/commands/import.js
CHANGED
|
@@ -11,7 +11,7 @@ import { describeConvention, describeRemainder, detectConventions, } from "../do
|
|
|
11
11
|
import { diagnose, scanSource } from "../doctor/scan.js";
|
|
12
12
|
import { parseSchemeBlocks } from "../doctor/scheme-blocks.js";
|
|
13
13
|
import { buildTable } from "../doctor/tokens.js";
|
|
14
|
-
import { rootClasses, rootTag, transcribe, } from "../doctor/transcribe.js";
|
|
14
|
+
import { rootClasses, rootTag, transcribe, transcribeParts, } from "../doctor/transcribe.js";
|
|
15
15
|
import { body, paint, section } from "../output.js";
|
|
16
16
|
import { walk, walkAll } from "./doctor.js";
|
|
17
17
|
/**
|
|
@@ -958,6 +958,46 @@ function sayReach(c) {
|
|
|
958
958
|
console.log(body(`Your neutrals only reach the ${has} end, so this system gets one scheme. A ${missing} mode would need surfaces you do not declare.`));
|
|
959
959
|
}
|
|
960
960
|
}
|
|
961
|
+
/**
|
|
962
|
+
* Fold the skill's named parts into the transcription, in place.
|
|
963
|
+
*
|
|
964
|
+
* Their declared tokens come from the census itself, so a run that only sends a
|
|
965
|
+
* file it was handed still resolves a `bg-ocean-500` by their own name.
|
|
966
|
+
*/
|
|
967
|
+
function resolveReadParts(census) {
|
|
968
|
+
const read = census.reading?.components;
|
|
969
|
+
if (!read)
|
|
970
|
+
return;
|
|
971
|
+
const declared = new Map(Object.entries(census.declared));
|
|
972
|
+
const looks = census.looks ?? (census.looks = {});
|
|
973
|
+
let named = 0;
|
|
974
|
+
for (const [component, entry] of Object.entries(read)) {
|
|
975
|
+
const parts = entry?.parts;
|
|
976
|
+
if (!Array.isArray(parts) || parts.length === 0)
|
|
977
|
+
continue;
|
|
978
|
+
const resolved = transcribeParts(parts, declared);
|
|
979
|
+
if (Object.keys(resolved).length === 0)
|
|
980
|
+
continue;
|
|
981
|
+
const look = looks[component];
|
|
982
|
+
looks[component] = look
|
|
983
|
+
? { ...look, parts: { ...(look.parts ?? {}), ...resolved } }
|
|
984
|
+
: {
|
|
985
|
+
base: {},
|
|
986
|
+
dark: {},
|
|
987
|
+
states: {},
|
|
988
|
+
skipped: [],
|
|
989
|
+
literals: [],
|
|
990
|
+
fromToken: 0,
|
|
991
|
+
fromLiteral: 0,
|
|
992
|
+
parts: resolved,
|
|
993
|
+
};
|
|
994
|
+
named += Object.keys(resolved).length;
|
|
995
|
+
}
|
|
996
|
+
if (named > 0) {
|
|
997
|
+
console.log("");
|
|
998
|
+
console.log(body(`${named} part${named === 1 ? "" : "s"} your reading named across ${Object.keys(read).length} component${Object.keys(read).length === 1 ? "" : "s"} - each previews as what it IS rather than as a text box`));
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
961
1001
|
export async function runImport(opts) {
|
|
962
1002
|
const root = opts.root ?? process.cwd();
|
|
963
1003
|
/**
|
|
@@ -993,6 +1033,18 @@ export async function runImport(opts) {
|
|
|
993
1033
|
console.log(body("That file is not a census this version can send."));
|
|
994
1034
|
return;
|
|
995
1035
|
}
|
|
1036
|
+
/**
|
|
1037
|
+
* THE SKILL NAMED PARTS; THE CLI KNOWS WHAT THEIR CLASSES DO.
|
|
1038
|
+
*
|
|
1039
|
+
* A clean split of who knows what: only the skill can say that a span holds
|
|
1040
|
+
* the title, and only this side can say that `text-ocean-500` is
|
|
1041
|
+
* `{color.ocean.500}`. The platform then turns declarations into roles,
|
|
1042
|
+
* because only it has the two palettes.
|
|
1043
|
+
*
|
|
1044
|
+
* Resolved here rather than at measure time because the reading arrives
|
|
1045
|
+
* AFTER the census was taken - this is the first moment both halves exist.
|
|
1046
|
+
*/
|
|
1047
|
+
resolveReadParts(census);
|
|
996
1048
|
}
|
|
997
1049
|
else {
|
|
998
1050
|
console.log(section("Reading your project"));
|
|
@@ -365,3 +365,20 @@ export function rootClasses(source) {
|
|
|
365
365
|
.flatMap((m) => m[1].split(/\s+/))
|
|
366
366
|
.filter((c) => c.length > 0 && !c.includes("${"));
|
|
367
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Transcribe the parts a skill named, using the same reader the root used.
|
|
370
|
+
*
|
|
371
|
+
* `declared` comes from the census itself on the `--census` path, so a run that
|
|
372
|
+
* only sends a file it was handed still resolves their tokens by name.
|
|
373
|
+
*/
|
|
374
|
+
export function transcribeParts(parts, declared) {
|
|
375
|
+
const out = {};
|
|
376
|
+
for (const part of parts) {
|
|
377
|
+
const name = part.name?.trim();
|
|
378
|
+
if (!name || typeof part.classes !== "string")
|
|
379
|
+
continue;
|
|
380
|
+
const t = transcribe(part.classes.split(/\s+/).filter(Boolean), declared);
|
|
381
|
+
out[name] = { base: t.base, dark: t.dark, states: t.states };
|
|
382
|
+
}
|
|
383
|
+
return out;
|
|
384
|
+
}
|
package/dist/skill-import.js
CHANGED
|
@@ -134,6 +134,31 @@ Open the project and answer the questions below. Then add a \`reading\` object t
|
|
|
134
134
|
"roles": { "canvas": "#050505", "foreground": "#f9fafb", "primary": "#4A90E2" },
|
|
135
135
|
"fonts": { "display": "Inter", "body": "Inter" },
|
|
136
136
|
"concept": "one paragraph on what this product is",
|
|
137
|
+
"rules": [
|
|
138
|
+
{
|
|
139
|
+
"text": "A RootWrapper is what makes the app-shell components position correctly - never render a Sidebar or Topbar outside one",
|
|
140
|
+
"applies": ["RootWrapper", "Sidebar"],
|
|
141
|
+
"kind": "implementation",
|
|
142
|
+
"files": 3,
|
|
143
|
+
"evidence": "all three app shells wrap them; none renders them loose"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"text": "MetricCard is only used inside a grid, never on its own",
|
|
147
|
+
"applies": ["MetricCard"],
|
|
148
|
+
"kind": "limit",
|
|
149
|
+
"files": 1,
|
|
150
|
+
"evidence": "one dashboard page; may just be how it happened"
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
"components": {
|
|
154
|
+
"MetricCard": {
|
|
155
|
+
"parts": [
|
|
156
|
+
{ "name": "icon", "classes": "size-8 text-ocean-500" },
|
|
157
|
+
{ "name": "label", "classes": "text-xs uppercase text-lightgray-500" },
|
|
158
|
+
{ "name": "value", "classes": "text-2xl font-bold" }
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
},
|
|
137
162
|
"by": "claude"
|
|
138
163
|
}
|
|
139
164
|
\`\`\`
|
|
@@ -166,6 +191,77 @@ about which one is the page. Read a screen and see.
|
|
|
166
191
|
**\`fonts\` and \`concept\`** - the voice, and one paragraph on what this product is. The concept
|
|
167
192
|
feeds every recommendation downstream, so a real one beats a generic one by a wide margin.
|
|
168
193
|
|
|
194
|
+
**\`rules\` - how this company BUILDS, which is half of what they actually made.**
|
|
195
|
+
|
|
196
|
+
A design system that arrives as tokens and recipes is only the vocabulary. The other half is the
|
|
197
|
+
grammar: that a wrapper exists so the thing inside it works, that state arrives through a
|
|
198
|
+
particular hook, that a card is never used loose. That is how the developer already thinks, and
|
|
199
|
+
writing it down is what makes it survive them.
|
|
200
|
+
|
|
201
|
+
Two dimensions, and they are independent:
|
|
202
|
+
|
|
203
|
+
\`\`\`
|
|
204
|
+
applies [] the whole system
|
|
205
|
+
[a] that component
|
|
206
|
+
[a, b] a RELATION - two components that only work together
|
|
207
|
+
|
|
208
|
+
kind limit a boundary, which the doctor can measure
|
|
209
|
+
implementation how it is built, which no linter checks and an agent
|
|
210
|
+
must be told
|
|
211
|
+
\`\`\`
|
|
212
|
+
|
|
213
|
+
The relation is the valuable one. \`[RootWrapper, Sidebar]\` says something neither name says
|
|
214
|
+
alone, and it is exactly what an agent needs in order not to assemble it wrongly.
|
|
215
|
+
|
|
216
|
+
**Report \`files\` honestly - it decides whether the rule governs.** Three or more files is a
|
|
217
|
+
habit and the rule arrives active; one file is a coincidence and it arrives as a candidate,
|
|
218
|
+
inactive, waiting for the person to promote it. You do not make that call; you report the
|
|
219
|
+
evidence and a threshold makes it. So a pattern you saw once should say \`"files": 1\` even when
|
|
220
|
+
you are confident - being wrong about a law is worse than being slow about one.
|
|
221
|
+
|
|
222
|
+
**Write \`evidence\` as what you actually saw.** "All three app shells wrap them; none renders
|
|
223
|
+
them loose" lets somebody disagree with a fact. "Best practice" lets them disagree only with
|
|
224
|
+
you.
|
|
225
|
+
|
|
226
|
+
Things worth looking for, none of them guessable from tokens:
|
|
227
|
+
|
|
228
|
+
- a wrapper or provider whose whole purpose is to make something else work
|
|
229
|
+
- an environment law - "this is Next, so the state comes through this hook" - which travels
|
|
230
|
+
with the system and only applies where that environment is
|
|
231
|
+
- a component that is never used alone, or never used outside something
|
|
232
|
+
- an axis that is always passed the same way in one place and never in another
|
|
233
|
+
|
|
234
|
+
If you cannot say where a rule came from, do not send it. An invented law is worse than a
|
|
235
|
+
missing one, because it will be obeyed.
|
|
236
|
+
|
|
237
|
+
**\`components[Name].parts\` - what each component is MADE OF.** This is the field that decides
|
|
238
|
+
whether a component previews as itself or as a grey box with a sentence in it, and only you can
|
|
239
|
+
fill it.
|
|
240
|
+
|
|
241
|
+
The census reads the ROOT element's classes and stops there, on purpose: descending a fixed
|
|
242
|
+
number of levels picks a layout wrapper as often as a semantic part. **You decide how far down a
|
|
243
|
+
part lives**, because you read the component. Send a name and the classes on it; the CLI turns
|
|
244
|
+
those classes into declarations and the platform turns declarations into roles.
|
|
245
|
+
|
|
246
|
+
**The name is load-bearing, not a label.** The renderer infers a part's role from it:
|
|
247
|
+
|
|
248
|
+
- \`root\`, \`wrapper\`, \`container\`, \`base\`, \`content\` - the element itself, and **skipped**. Do not
|
|
249
|
+
send these; their styles already sit on the component.
|
|
250
|
+
- anything matching \`icon\`, \`dot\`, \`indicator\`, \`bar\`, \`avatar\`, \`media\`, \`swatch\` - renders as a
|
|
251
|
+
bare span, sized and coloured by its own styles
|
|
252
|
+
- anything matching \`button\`, \`action\`, \`cta\` - renders as a real button carrying the label
|
|
253
|
+
- everything else - carries text
|
|
254
|
+
|
|
255
|
+
So a part named \`div2\` previews as a text node reading "Div2". Name what it IS: \`label\`, \`value\`,
|
|
256
|
+
\`delta\`, \`icon\`, \`action\`, \`title\`, \`meta\`.
|
|
257
|
+
|
|
258
|
+
Send them **in the order they appear**, because that is the order they render. Three or four
|
|
259
|
+
named parts is a recognisable component; twelve is a transcription of their DOM, and nobody
|
|
260
|
+
needs the layout divs.
|
|
261
|
+
|
|
262
|
+
If a component genuinely has no parts - a \`Divider\`, a \`Spinner\` - send none. An empty list is
|
|
263
|
+
a real answer.
|
|
264
|
+
|
|
169
265
|
### 3. Walk them through the decisions, one at a time
|
|
170
266
|
|
|
171
267
|
Four decisions are theirs. **Ask them as separate questions with selectable options** - use your
|
package/package.json
CHANGED