synthesisui 0.16.97 → 0.16.99

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.
@@ -123,7 +123,14 @@ function frontierClasses(node, sketch) {
123
123
  export function resolveAnatomy(read, declared, deps,
124
124
  /** Their name → the slug it reaches in this system. See `RefResolver`. */
125
125
  resolve,
126
- /** What the component returns, when the skill said and it is not a plain tag. */
126
+ /**
127
+ * What the component returns, when the skill said and it is not a plain tag.
128
+ *
129
+ * A STRING or an object: `{ name, at }` / `{ name, classes }` carries what the
130
+ * PARENT paints on the root at the call site. `MetricCard` returns
131
+ * `<Card className="flex-1 dark:bg-darkgray-300">` and that override had
132
+ * nowhere to travel (not-expressed.md, dono, 01/08).
133
+ */
127
134
  root,
128
135
  /** This component's sketch, so a node can name its classes by INDEX instead
129
136
  * of retyping them - see `AnatomyRead.at`. */
@@ -290,15 +297,39 @@ sketch) {
290
297
  * through the crosswalk exactly like a child frontier does.
291
298
  */
292
299
  let rootOut;
293
- const rootRaw = String(root ?? "").trim();
294
- if (rootRaw && /^[A-Z]/.test(rootRaw)) {
300
+ const rootObj = typeof root === "object" && root ? root : null;
301
+ const rootRaw = String((rootObj ? rootObj.name : root) ?? "").trim();
302
+ // The parent's own override on the root it returns, by index or as written.
303
+ const rootPainted = rootObj
304
+ ? (frontierClasses(rootObj, sketch).classes ??
305
+ "")
306
+ : "";
307
+ const paints = rootPainted ? { classes: rootPainted } : {};
308
+ /**
309
+ * A LIBRARY ROOT WHOSE NAME IS NOT DOTTED.
310
+ *
311
+ * Three components return a Base UI primitive imported under a single word -
312
+ * `Pill` and `SelectionCard` return `Toggle`, `RadioCardGroup` returns
313
+ * `RadioGroup` - and `"root": "Toggle"` would point at THEIR own Toggle (a
314
+ * switch), inventing a relation that does not exist. There was no way to say
315
+ * "a library component with a bare name", so `root` was left out on all three
316
+ * (not-expressed.md, dono, 01/08).
317
+ *
318
+ * `from` says it: the PACKAGE disambiguates what the name cannot. It wins over
319
+ * the name, because a caller that sends both is telling us where it came from.
320
+ */
321
+ const rootFrom = String(rootObj?.from ?? "").trim();
322
+ if (rootFrom) {
323
+ rootOut = { from: rootFrom, ...paints };
324
+ }
325
+ else if (rootRaw && /^[A-Z]/.test(rootRaw)) {
295
326
  if (rootRaw.includes(".")) {
296
- rootOut = { from: rootRaw };
327
+ rootOut = { from: rootRaw, ...paints };
297
328
  }
298
329
  else {
299
330
  const slug = resolve?.(rootRaw) ?? safePartName(rootRaw);
300
331
  if (slug)
301
- rootOut = { ref: slug, name: rootRaw };
332
+ rootOut = { ref: slug, name: rootRaw, ...paints };
302
333
  }
303
334
  }
304
335
  if (coerced > 0) {
@@ -393,19 +393,83 @@ async function resolveAtNodes(root, entries) {
393
393
  // better than refusing to validate at all.
394
394
  return entries;
395
395
  }
396
- /** Their spelling → the sketch, matched the way the contract kebabs a name. */
397
- const sketchFor = (name) => {
398
- if (looks[name]?.sketch)
399
- return looks[name].sketch;
396
+ /** Their spelling → the look, matched the way the contract kebabs a name. */
397
+ const lookFor = (name) => {
398
+ if (looks[name])
399
+ return looks[name];
400
400
  const slug = name.replace(/[^a-z0-9]/gi, "").toLowerCase();
401
401
  for (const [theirs, look] of Object.entries(looks)) {
402
402
  if (theirs.replace(/[^a-z0-9]/gi, "").toLowerCase() === slug)
403
- return look.sketch;
403
+ return look;
404
404
  }
405
405
  return undefined;
406
406
  };
407
407
  return entries.map((entry) => {
408
- const sketch = sketchFor(String(entry.name ?? ""));
408
+ const look = lookFor(String(entry.name ?? ""));
409
+ const sketch = look?.sketch;
410
+ /**
411
+ * THE STYLES THE IMPORT WILL MERGE - so the floor measured is the floor the
412
+ * component arrives with.
413
+ *
414
+ * The reading sends an ANATOMY: a tree of `at` indices, deliberately without
415
+ * the classes, because retyping them is the loss the index exists to prevent.
416
+ * The styles are the census's measured `base` and `layers`, merged at import.
417
+ * So the validator was judging a recipe whose look had not arrived yet, and
418
+ * all 35 components came back BELOW THE FLOOR with the same four lines - a
419
+ * verdict about a payload nobody will ever send (not-expressed.md, dono,
420
+ * 01/08).
421
+ *
422
+ * Merged UNDER what the payload carries: a base the reader authored on
423
+ * purpose still wins over the measurement.
424
+ */
425
+ const recipe0 = entry.recipe;
426
+ const withLook = recipe0
427
+ ? {
428
+ ...recipe0,
429
+ ...(look?.base && Object.keys(look.base).length > 0
430
+ ? { base: { ...look.base, ...(recipe0.base ?? {}) } }
431
+ : {}),
432
+ ...(look?.layers?.length
433
+ ? {
434
+ layers: [
435
+ ...look.layers,
436
+ ...(recipe0.layers ?? []),
437
+ ],
438
+ }
439
+ : {}),
440
+ /**
441
+ * THE PARTS, which is where a provider-rooted component keeps
442
+ * EVERYTHING.
443
+ *
444
+ * `Tooltip` returns `<BaseTooltip.Provider>` - a context provider that
445
+ * renders no element and carries no style - and its whole look (fill,
446
+ * radius, padding, shadow) is on the popup PART. The merge carried
447
+ * `base` and `layers` and stopped there, so the validator judged a
448
+ * component with no declarations anywhere and said BELOW THE FLOOR
449
+ * about one that draws fine (not-expressed.md, dono, 01/08).
450
+ */
451
+ ...(look?.parts && Object.keys(look.parts).length > 0
452
+ ? {
453
+ parts: {
454
+ ...look.parts,
455
+ ...(recipe0.parts ?? {}),
456
+ },
457
+ }
458
+ : {}),
459
+ /**
460
+ * THE ROOT TAG TRAVELS, so the validator can name the KIND.
461
+ *
462
+ * It falls back to `surface` when the payload declares none, and every
463
+ * one of 35 components got the surface checklist - including a Button,
464
+ * two fields, a control and two pills (not-expressed.md, dono, 01/08).
465
+ * The kind is arithmetic from the name and the root tag; the tag is
466
+ * here and the arithmetic lives on the side that owns the contract, so
467
+ * this sends the evidence rather than a second implementation of it.
468
+ */
469
+ ...(look?.rootTag ? { rootTag: look.rootTag } : {}),
470
+ }
471
+ : recipe0;
472
+ entry = { ...entry, recipe: withLook };
409
473
  if (!sketch)
410
474
  return entry;
411
475
  const walk = (nodes) => {
@@ -82,8 +82,24 @@ function staticCallClasses(body) {
82
82
  argStart = i + 1;
83
83
  }
84
84
  }
85
+ /**
86
+ * A COMMENT BEFORE THE STRING IS STILL THE STRING.
87
+ *
88
+ * Real code annotates these lists - `// Light mode`, `// Shadow with subtle
89
+ * brand tint` - and the argument then reads as `// Light mode\n"bg-darkgray-800
90
+ * …"`, which is not a bare literal, so it was dropped. The Tooltip kept only
91
+ * its last two arguments (the two with no comment above them) and lost its
92
+ * fill, radius, padding and shadow - and it only came to light because the
93
+ * floor check flagged it (not-expressed.md, dono, 01/08). Measured: SIX of 36
94
+ * components in that library annotate a `cn()` this way.
95
+ */
85
96
  const classes = args
86
- .map((a) => a.trim())
97
+ .map((a) => a
98
+ // block comments first, so a `/* … */` spanning the line cannot leave
99
+ // a stray `*/` behind for the line rule to keep
100
+ .replace(/\/\*[\s\S]*?\*\//g, "")
101
+ .replace(/(^|\n)\s*\/\/[^\n]*/g, "$1")
102
+ .trim())
87
103
  .filter((a) => /^(["'`]).*\1$/.test(a) && !a.includes("${"))
88
104
  .map((a) => a.slice(1, -1).trim())
89
105
  .filter(Boolean);
@@ -1135,6 +1135,44 @@ export function transcribe(classes, declared) {
1135
1135
  * Absent or unfound, the old behaviour stands exactly - which keeps every
1136
1136
  * single-component file reading byte-identically.
1137
1137
  */
1138
+ /**
1139
+ * AN EXPORT THAT IS AN ALIAS - follow it to the function it wraps.
1140
+ *
1141
+ * `export const Chat = Object.assign(React.forwardRef(ChatContainerComponent), …)`
1142
+ * declares the NAME the census knows and none of the markup: the styles live in
1143
+ * `ChatContainerComponent`, declared above. Measured on a real library, EIGHT of
1144
+ * 36 exports are this shape - seven `forwardRef(Fn)` and one
1145
+ * `Object.assign(forwardRef(Fn))` - and Chat arrived with an empty look, which
1146
+ * forced the one hand-read of the whole run (not-expressed.md, dono, 01/08).
1147
+ *
1148
+ * They used to work by accident: with no body brace to find, the reader fell
1149
+ * back to the first `return (` in the file, which for a one-component file is
1150
+ * the right one. Removing that fallback - correctly, because it let a helper
1151
+ * borrow a sibling's return - would have broken all eight. So the alias is
1152
+ * followed on purpose instead: the first capitalised identifier inside the call
1153
+ * is the function this name IS.
1154
+ */
1155
+ function aliasTarget(source, name) {
1156
+ const decl = new RegExp(`(?:const|let|var)\\s+${name}\\s*(?::[^=]*)?=\\s*([^;\\n]{0,200})`).exec(source);
1157
+ if (!decl)
1158
+ return null;
1159
+ const rhs = decl[1];
1160
+ // Only a WRAPPER call aliases: `forwardRef`, `memo`, `Object.assign`,
1161
+ // `styled(...)`. A plain arrow is the component itself and has no target.
1162
+ if (!/^(?:React\.)?(?:forwardRef|memo|Object\.assign|styled)\b/.test(rhs))
1163
+ return null;
1164
+ for (const m of rhs.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g)) {
1165
+ const candidate = m[1];
1166
+ if (/^(React|Object|Component)$/.test(candidate))
1167
+ continue;
1168
+ if (candidate === name)
1169
+ continue;
1170
+ // It has to be declared in this file, or it is a type argument or an import.
1171
+ if (new RegExp(`(?:function|const|let|var)\\s+${candidate}\\b`).test(source))
1172
+ return candidate;
1173
+ }
1174
+ return null;
1175
+ }
1138
1176
  function returnAt(source, name) {
1139
1177
  if (name) {
1140
1178
  const decl = new RegExp(`(?:function|const|let|var)\\s+${name}\\b|${name}\\s*[:=]\\s*(?:function|\\()`).exec(source);
@@ -1150,6 +1188,16 @@ function returnAt(source, name) {
1150
1188
  const arrow = /=>\s*\(/.exec(source.slice(decl.index, decl.index + 400));
1151
1189
  if (arrow)
1152
1190
  return decl.index + arrow.index;
1191
+ /**
1192
+ * AN ALIAS POINTS AT THE FUNCTION THAT HAS THE MARKUP - follow it once.
1193
+ * `const Chat = Object.assign(forwardRef(ChatContainerComponent), …)`.
1194
+ */
1195
+ const target = aliasTarget(source, name);
1196
+ if (target) {
1197
+ const viaAlias = ownReturn(source, source.search(new RegExp(`(?:function|const|let|var)\\s+${target}\\b`)));
1198
+ if (viaAlias !== -1)
1199
+ return viaAlias;
1200
+ }
1153
1201
  /**
1154
1202
  * FOUND THE NAME AND IT RETURNS NO JSX IN PARENTHESES - so say nothing.
1155
1203
  *
@@ -1200,13 +1248,36 @@ export function definitionSpan(source, name) {
1200
1248
  if (!decl)
1201
1249
  return null;
1202
1250
  const brace = bodyBrace(source, decl.index);
1203
- if (brace === -1)
1251
+ if (brace === -1) {
1252
+ // The export is an alias; the body belongs to what it wraps.
1253
+ const target = aliasTarget(source, name);
1254
+ if (target)
1255
+ return definitionSpan(source, target);
1204
1256
  return null;
1257
+ }
1205
1258
  const end = matchingBrace(source, brace);
1206
1259
  return { from: decl.index, to: end === -1 ? source.length : end + 1 };
1207
1260
  }
1208
1261
  /** The `{` that opens a function body declared at `declAt`, or -1. */
1209
1262
  function bodyBrace(source, declAt) {
1263
+ /**
1264
+ * A BODY INLINE INSIDE A WRAPPER: `forwardRef<A, B>((props, ref) => { … })`.
1265
+ *
1266
+ * Toggle is written this way and the plain walk below cannot find it - the
1267
+ * wrapper's own parenthesis never closes before the body starts, so it looked
1268
+ * for a `{` that is not there and the component read empty. There is no named
1269
+ * function to alias to either: the body IS the argument.
1270
+ */
1271
+ const head = source.slice(declAt, declAt + 400);
1272
+ const wrapper = /=\s*(?:React\.)?(?:forwardRef|memo|styled\([^)]*\))\s*(?:<[^>(]*>)?\s*\(/.exec(head);
1273
+ if (wrapper) {
1274
+ const arrow = /(?:=>|function\s*[A-Za-z0-9_]*\s*\([^)]*\))\s*\{/.exec(source.slice(declAt + wrapper.index));
1275
+ if (arrow) {
1276
+ const at = declAt + wrapper.index + arrow.index + arrow[0].length - 1;
1277
+ if (source[at] === "{")
1278
+ return at;
1279
+ }
1280
+ }
1210
1281
  let i = declAt;
1211
1282
  let paren = 0;
1212
1283
  let opened = false;
@@ -614,10 +614,32 @@ depth appears anyway.
614
614
  "Modal": {
615
615
  "root": "BaseDialog.Root",
616
616
  "anatomy": [ … ]
617
+ },
618
+ "MetricCard": {
619
+ "root": { "name": "Card", "at": 1 },
620
+ "anatomy": [ … ]
617
621
  }
618
622
  }
619
623
  \`\`\`
620
624
 
625
+ **A library root whose name is not dotted needs its PACKAGE.** \`Pill\` returns Base UI's
626
+ \`Toggle\`, and \`"root": "Toggle"\` would point at THEIR own Toggle - a switch - inventing a
627
+ relation that does not exist. Send where it came from instead:
628
+
629
+ \`\`\`json
630
+ "Pill": { "root": { "from": "@base-ui/react/toggle" }, "anatomy": [ … ] }
631
+ \`\`\`
632
+
633
+ \`Radio.Root\` needs none of this: the dot already says it is somebody's namespace. Leaving the
634
+ root out was the old workaround and it cost the surface its name (not-expressed.md, dono, 01/08).
635
+
636
+ **The root takes what the PARENT paints on it.** When the returned root carries an override at
637
+ the call site - \`MetricCard\` returns \`<Card className="flex-1 dark:bg-darkgray-300">\` - send the
638
+ object form and point \`at\` the sketch node: \`{ "name": "Card", "at": 1 }\`. The string form still
639
+ works and means "no override". Sending the root as an anatomy node instead would make it a leaf
640
+ and orphan the content inside it, which is why it is a field on the root rather than a node
641
+ (not-expressed.md, dono, 01/08).
642
+
621
643
  **Say what the component RETURNS when it is not a plain tag.** Measured on a real library
622
644
  (dono, 01/08): 14 of 23 components return one of their own - \`ArticleCard\` returns a \`<Card>\`,
623
645
  \`Button\` and \`Text\` return a \`<Component>\` - and 17 of 23 have no style of their own at all,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.97",
3
+ "version": "0.16.99",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {