synthesisui 0.16.105 → 0.16.107
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/anatomy-from-sketch.js +12 -2
- package/dist/anatomy-read.js +75 -9
- package/dist/commands/import.js +30 -3
- package/dist/doctor/sketch.js +11 -1
- package/dist/doctor/transcribe.js +1 -1
- package/dist/skill-import.js +20 -1
- package/package.json +1 -1
|
@@ -468,10 +468,20 @@ versionOf) {
|
|
|
468
468
|
* first one's classes became `base`, so the rest are emitted as siblings
|
|
469
469
|
* instead of being dropped with the fragment.
|
|
470
470
|
*/
|
|
471
|
-
|
|
471
|
+
/**
|
|
472
|
+
* THE COMPONENT'S OWN ROOT, which is not always the first node.
|
|
473
|
+
*
|
|
474
|
+
* A file that declares a closure before the render it uses puts that closure's
|
|
475
|
+
* markup first: three of a real library's charts open with a `CustomTooltip`, so
|
|
476
|
+
* starting at node 0 made the component's anatomy the TOOLTIP's, with the real
|
|
477
|
+
* root arriving as a sibling (dono, 03/08). The reader stamps which node the
|
|
478
|
+
* component returns - see `SketchNode.root`.
|
|
479
|
+
*/
|
|
480
|
+
const stamped = sketch.findIndex((node) => node?.root);
|
|
481
|
+
const top = stamped >= 0 ? stamped : roots.length > 0 ? roots[0] : 0;
|
|
472
482
|
const read = [
|
|
473
483
|
...(kids[top] ?? []).flatMap((child) => build(child, 0)),
|
|
474
|
-
...roots.
|
|
484
|
+
...roots.filter((at) => at !== top).flatMap((sibling) => build(sibling, 0)),
|
|
475
485
|
];
|
|
476
486
|
/**
|
|
477
487
|
* THE ROOT'S OWN `{children}` - the component whose entire content is the
|
package/dist/anatomy-read.js
CHANGED
|
@@ -44,8 +44,18 @@ const FORMS = new Set([
|
|
|
44
44
|
"external",
|
|
45
45
|
"slot",
|
|
46
46
|
]);
|
|
47
|
-
/** The two that arrange, and the only two that take children. */
|
|
47
|
+
/** The two that arrange, and the only two that take children of any kind. */
|
|
48
48
|
const CONTAINERS = new Set(["row", "stack"]);
|
|
49
|
+
/**
|
|
50
|
+
* PHRASING CONTENT - what HTML lets an inline host hold.
|
|
51
|
+
*
|
|
52
|
+
* A glyph and a run of words. This is the list the containment law is actually
|
|
53
|
+
* about: a button may hold these and may not hold a heading, a panel or a second
|
|
54
|
+
* interactive node.
|
|
55
|
+
*/
|
|
56
|
+
const PHRASING = new Set(["icon", "text", "image"]);
|
|
57
|
+
/** Forms that are inline hosts: they hold phrasing content and nothing else. */
|
|
58
|
+
const PHRASING_HOST = new Set(["button", "text", "heading"]);
|
|
49
59
|
/** The two frontiers: no name, no styles - what is there is not ours to hold. */
|
|
50
60
|
const FRONTIERS = new Set(["component", "external"]);
|
|
51
61
|
/**
|
|
@@ -145,6 +155,14 @@ root,
|
|
|
145
155
|
/** This component's sketch, so a node can name its classes by INDEX instead
|
|
146
156
|
* of retyping them - see `AnatomyRead.at`. */
|
|
147
157
|
sketch) {
|
|
158
|
+
/**
|
|
159
|
+
* THE PACKAGE THE RETURNED ELEMENT CAME FROM, measured rather than spelled.
|
|
160
|
+
* Node 0 of the sketch IS what the component returns, and the reader stamped its
|
|
161
|
+
* import specifier there - see `SketchNode.from`.
|
|
162
|
+
*/
|
|
163
|
+
const rootPackage = (sketch ?? []).find((node) => node?.root)?.from ??
|
|
164
|
+
sketch?.[0]?.from ??
|
|
165
|
+
null;
|
|
148
166
|
const parts = {};
|
|
149
167
|
const composes = [];
|
|
150
168
|
const external = [];
|
|
@@ -294,9 +312,25 @@ sketch) {
|
|
|
294
312
|
if (expects)
|
|
295
313
|
node_.expects = expects.slice(0, 80);
|
|
296
314
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
315
|
+
/**
|
|
316
|
+
* AN ACTION HOLDS ITS GLYPH, because that is valid HTML.
|
|
317
|
+
*
|
|
318
|
+
* `<button><Upload/>Upload</button>` is legal: a button's content model is
|
|
319
|
+
* PHRASING content, and an icon and a span are phrasing. The containment law
|
|
320
|
+
* forbids a BLOCK inside a button - a heading, a panel, another interactive
|
|
321
|
+
* node - and this was applying it to everything, so a real ImageUploader's two
|
|
322
|
+
* actions had their `Upload` and `Sparkles` glyphs hoisted out to become
|
|
323
|
+
* siblings: the icon left the button it belongs to (dono, 03/08).
|
|
324
|
+
*
|
|
325
|
+
* The reader upstream had already flattened them itself to obey the rule as
|
|
326
|
+
* written, which is why the census showed zero of these and the loss was
|
|
327
|
+
* invisible from here.
|
|
328
|
+
*/
|
|
329
|
+
const phrasingKids = Array.isArray(node.children) &&
|
|
330
|
+
node.children.length > 0 &&
|
|
331
|
+
node.children.every((child) => PHRASING.has(String(child?.as ?? "")));
|
|
332
|
+
if ((CONTAINERS.has(as) || (PHRASING_HOST.has(as) && phrasingKids)) &&
|
|
333
|
+
Array.isArray(node.children)) {
|
|
300
334
|
const children = walk(node.children, depth + 1);
|
|
301
335
|
if (children.length > 0)
|
|
302
336
|
node_.children = children;
|
|
@@ -342,20 +376,52 @@ sketch) {
|
|
|
342
376
|
* `from` says it: the PACKAGE disambiguates what the name cannot. It wins over
|
|
343
377
|
* the name, because a caller that sends both is telling us where it came from.
|
|
344
378
|
*/
|
|
345
|
-
|
|
379
|
+
/**
|
|
380
|
+
* THE PACKAGE, NEVER THE DOT.
|
|
381
|
+
*
|
|
382
|
+
* This read `rootRaw.includes(".")` as "somebody else's namespace" - the exact
|
|
383
|
+
* rule the platform's own root reader stopped using, left behind here. So a
|
|
384
|
+
* reading that says `root: { from: "Collapsible.Root" }` stamped a TAG where a
|
|
385
|
+
* package belongs, and since this field wins over `rootTag` downstream, ten of
|
|
386
|
+
* their thirty-five components were badged `3RD-PARTY · COLLAPSIBLE.ROOT` while
|
|
387
|
+
* the census sat on the answer: `@base-ui/react/collapsible`, headless, ours to
|
|
388
|
+
* draw (dono, 03/08).
|
|
389
|
+
*
|
|
390
|
+
* The sketch stamps the package on node 0, so the answer is one lookup away and
|
|
391
|
+
* a caller that spells a tag no longer decides.
|
|
392
|
+
*/
|
|
393
|
+
const sent = String(rootObj?.from ?? "").trim();
|
|
394
|
+
const looksLikeTag = sent !== "" && /^[A-Z]/.test(sent);
|
|
395
|
+
const rootFrom = looksLikeTag ? (rootPackage ?? "") : sent;
|
|
346
396
|
if (rootFrom) {
|
|
347
397
|
rootOut = { from: rootFrom, ...paints };
|
|
348
398
|
}
|
|
349
399
|
else if (rootRaw && /^[A-Z]/.test(rootRaw)) {
|
|
350
|
-
if (rootRaw.includes(".")) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
400
|
+
if (!rootRaw.includes(".")) {
|
|
401
|
+
/**
|
|
402
|
+
* A BARE CAPITALISED NAME goes through the crosswalk, and kebab-casing is the
|
|
403
|
+
* fallback for a system we generated ourselves - the platform then checks the
|
|
404
|
+
* slug is a component it actually has, so a wrong guess degrades to nothing
|
|
405
|
+
* rather than to a hole.
|
|
406
|
+
*/
|
|
354
407
|
const slug = resolve?.(rootRaw) ?? safePartName(rootRaw);
|
|
355
408
|
if (slug)
|
|
356
409
|
rootOut = { ref: slug, name: rootRaw, ...paints };
|
|
357
410
|
}
|
|
411
|
+
else if (rootPackage) {
|
|
412
|
+
rootOut = { from: rootPackage, ...paints };
|
|
413
|
+
}
|
|
358
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* A NAMESPACED NAME WITH NO PACKAGE ANYWHERE SAYS NOTHING.
|
|
417
|
+
*
|
|
418
|
+
* The dot is not being read as "somebody else's" here - that judgement belongs to
|
|
419
|
+
* the package and nothing else. It is read as what it structurally is: a member of
|
|
420
|
+
* a namespace, which cannot be one of their own top-level components, so the
|
|
421
|
+
* crosswalk has nothing to match and kebab-casing `BaseDialog.Root` into
|
|
422
|
+
* `base-dialog-root` would invent a component nobody has. Silence is the honest
|
|
423
|
+
* answer, and the package - when the sketch stamped one - is the loud one.
|
|
424
|
+
*/
|
|
359
425
|
if (coerced > 0) {
|
|
360
426
|
notes.push(`${coerced} node${coerced === 1 ? "" : "s"} named a form that does not exist - kept, drawn as what ${coerced === 1 ? "it holds" : "they hold"}`);
|
|
361
427
|
}
|
package/dist/commands/import.js
CHANGED
|
@@ -1774,13 +1774,40 @@ async function resolveReadParts(census, root) {
|
|
|
1774
1774
|
...(libraries.length > 0 ? { external: libraries } : {}),
|
|
1775
1775
|
...(resolved.root ? { root: resolved.root } : {}),
|
|
1776
1776
|
};
|
|
1777
|
+
/**
|
|
1778
|
+
* ONE READING WRITES ONE GENERATION.
|
|
1779
|
+
*
|
|
1780
|
+
* The tree was REPLACED and the parts were MERGED, so every pass left its part
|
|
1781
|
+
* names behind under the next pass's tree. Two commands is all it takes: `--dry`
|
|
1782
|
+
* derives an anatomy and writes `box`, `box-2`, `text`; the send resolves the
|
|
1783
|
+
* reader's own and writes `trigger`, `panel`, `status-dot` - and the first
|
|
1784
|
+
* generation stays, styled and referenced by nothing.
|
|
1785
|
+
*
|
|
1786
|
+
* Measured on the owner's census (dono, 03/08): 143 orphan parts carrying real
|
|
1787
|
+
* declarations across 26 of 36 components - `CollapsibleCard` alone had 26 parts
|
|
1788
|
+
* and 13 in its tree, which is the "11 parts carry 33 declarations the anatomy
|
|
1789
|
+
* never puts on an element" the Canvas reports. The banner was right and the
|
|
1790
|
+
* cause was ours.
|
|
1791
|
+
*
|
|
1792
|
+
* A part from an EARLIER source survives only if the new tree names it - which
|
|
1793
|
+
* keeps a CSS-module part the anatomy wears, and drops the stale generation.
|
|
1794
|
+
*/
|
|
1795
|
+
const worn = new Set();
|
|
1796
|
+
const collect = (nodes) => {
|
|
1797
|
+
for (const node of nodes) {
|
|
1798
|
+
if (node.part)
|
|
1799
|
+
worn.add(node.part);
|
|
1800
|
+
if (Array.isArray(node.children))
|
|
1801
|
+
collect(node.children);
|
|
1802
|
+
}
|
|
1803
|
+
};
|
|
1804
|
+
collect(resolved.tree);
|
|
1805
|
+
const inherited = Object.fromEntries(Object.entries(look?.parts ?? {}).filter(([part]) => worn.has(part)));
|
|
1777
1806
|
looks[component] = look
|
|
1778
1807
|
? {
|
|
1779
1808
|
...look,
|
|
1780
1809
|
...carried,
|
|
1781
|
-
...(hasParts
|
|
1782
|
-
? { parts: { ...(look.parts ?? {}), ...resolved.parts } }
|
|
1783
|
-
: {}),
|
|
1810
|
+
...(hasParts ? { parts: { ...inherited, ...resolved.parts } } : {}),
|
|
1784
1811
|
}
|
|
1785
1812
|
: {
|
|
1786
1813
|
base: {},
|
package/dist/doctor/sketch.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { scanTags } from "./css-modules.js";
|
|
22
22
|
import { importMap, packageOfTag } from "./imports.js";
|
|
23
|
-
import { definitionSpan } from "./transcribe.js";
|
|
23
|
+
import { definitionSpan, returnAt } from "./transcribe.js";
|
|
24
24
|
/** `{children}` / `{props.children}` - the caller's content, spelled either way. */
|
|
25
25
|
const CHILDREN = /\{\s*(?:props\.)?children\s*\}/;
|
|
26
26
|
/**
|
|
@@ -182,6 +182,13 @@ export function sketchOf(source, name) {
|
|
|
182
182
|
const span = name ? definitionSpan(source, name) : null;
|
|
183
183
|
if (span)
|
|
184
184
|
source = source.slice(span.from, span.to);
|
|
185
|
+
/**
|
|
186
|
+
* WHERE THE COMPONENT'S OWN RENDER STARTS, in the same slice the nodes are
|
|
187
|
+
* numbered against - so the index the sketch stamps and the index a reading sends
|
|
188
|
+
* are the same number.
|
|
189
|
+
*/
|
|
190
|
+
const ownReturn = returnAt(source, name);
|
|
191
|
+
const rootAt = ownReturn === -1 ? -1 : source.indexOf("<", ownReturn);
|
|
185
192
|
const events = scanTags(source);
|
|
186
193
|
const out = [];
|
|
187
194
|
let depth = 0;
|
|
@@ -194,6 +201,9 @@ export function sketchOf(source, name) {
|
|
|
194
201
|
if (out.length >= MAX_NODES)
|
|
195
202
|
break;
|
|
196
203
|
const node = { tag: event.tag, depth };
|
|
204
|
+
// The first element at or after the component's own `return (` IS its root.
|
|
205
|
+
if (rootAt !== -1 && event.at === rootAt)
|
|
206
|
+
node.root = true;
|
|
197
207
|
// What a capitalised tag IS, from the file's own imports - see `SketchNode.from`.
|
|
198
208
|
if (/^[A-Z]/.test(event.tag)) {
|
|
199
209
|
const pkg = packageOfTag(event.tag, imports);
|
|
@@ -1302,7 +1302,7 @@ function aliasTarget(source, name) {
|
|
|
1302
1302
|
}
|
|
1303
1303
|
return null;
|
|
1304
1304
|
}
|
|
1305
|
-
function returnAt(source, name) {
|
|
1305
|
+
export function returnAt(source, name) {
|
|
1306
1306
|
if (name) {
|
|
1307
1307
|
const decl = new RegExp(`(?:function|const|let|var)\\s+${name}\\b|${name}\\s*[:=]\\s*(?:function|\\()`).exec(source);
|
|
1308
1308
|
if (decl) {
|
package/dist/skill-import.js
CHANGED
|
@@ -681,7 +681,26 @@ external a library → needs "from": the package name
|
|
|
681
681
|
slot the CALLER's content → takes "expects": what goes there, in their words
|
|
682
682
|
\`\`\`
|
|
683
683
|
|
|
684
|
-
|
|
684
|
+
\`row\` and \`stack\` ARRANGE their children. A \`component\` or \`external\` node HOLDS them -
|
|
685
|
+
what their code nests inside a component they compose is markup THEY wrote, wearing their
|
|
686
|
+
classes, and it belongs in the tree:
|
|
687
|
+
|
|
688
|
+
\`\`\`json
|
|
689
|
+
{ "as": "component", "ref": "Card", "at": 3, "children": [
|
|
690
|
+
{ "as": "row", "name": "header", "at": 4 },
|
|
691
|
+
{ "as": "external", "from": "recharts", "at": 8 } ] }
|
|
692
|
+
\`\`\`
|
|
693
|
+
|
|
694
|
+
A \`button\` holds a glyph and its words - \`<button><Upload/>Upload</button>\` is valid HTML,
|
|
695
|
+
because a button's content model is *phrasing* and an icon is phrasing. So an \`icon\` and a
|
|
696
|
+
\`text\` may sit inside one:
|
|
697
|
+
|
|
698
|
+
\`\`\`json
|
|
699
|
+
{ "as": "button", "name": "upload", "at": 9, "children": [{ "as": "icon", "name": "upload-icon", "at": 10 }] }
|
|
700
|
+
\`\`\`
|
|
701
|
+
|
|
702
|
+
Everything else is a leaf, and a leaf that holds elements has them HOISTED OUT to become
|
|
703
|
+
its siblings - which destroys the nesting you read. If something arranges, say \`row\`/\`stack\`.
|
|
685
704
|
|
|
686
705
|
### \`{children}\` is a node, not an absence
|
|
687
706
|
|
package/package.json
CHANGED