synthesisui 0.16.109 → 0.16.111

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.
@@ -436,10 +436,27 @@ versionOf) {
436
436
  },
437
437
  ];
438
438
  }
439
+ /**
440
+ * A BOX OF OURS THAT IS SOMEBODY ELSE'S ELEMENT SAYS SO.
441
+ *
442
+ * A headless primitive is ours to draw - the classes on it are the client's - and
443
+ * that is why it becomes a PART rather than a frontier. But the element itself is
444
+ * `<Collapsible.Trigger>`, and the usage snippet was emitting a plain `<div>` with
445
+ * our class on it: a person following it gets the look and none of the behaviour, no
446
+ * `aria-expanded` and no keyboard (dono, 03/08). Measured on their library: 31 parts
447
+ * across 9 components are a library element, plus 11 roots.
448
+ *
449
+ * The package AND their own spelling, because the snippet needs both - the import
450
+ * line comes from the first and the tag from the second.
451
+ */
452
+ const fromLib = kind === "headless" && node.from
453
+ ? { from: String(node.from), refName: tag }
454
+ : {};
439
455
  const built = {
440
456
  as,
441
457
  ...(name ? { name } : {}),
442
458
  ...(classes ? { at: index } : {}),
459
+ ...fromLib,
443
460
  ...(node.text ? { text: node.text } : {}),
444
461
  };
445
462
  const inside = hasKids ? descend() : [];
@@ -265,6 +265,17 @@ sketch) {
265
265
  continue;
266
266
  }
267
267
  const node_ = { as };
268
+ /**
269
+ * A PART THAT IS A LIBRARY ELEMENT CARRIES BOTH FACTS - see `AnatomyRead.from`.
270
+ * Inert for the renderer, which dispatches on `as`; read by the usage snippet,
271
+ * which has to emit `<Collapsible.Trigger>` and the import line rather than a
272
+ * `<div>` that looks right and does nothing.
273
+ */
274
+ if (typeof node.from === "string" && node.from.trim()) {
275
+ node_.from = node.from.trim();
276
+ if (typeof node.refName === "string" && node.refName.trim())
277
+ node_.refName = node.refName.trim();
278
+ }
268
279
  // The NAME is what carries the styles, so it is claimed even when nothing
269
280
  // resolved: the tree triggers on presence, and structure is most of the
270
281
  // value. A node with no name is pure structure and that is legitimate.
@@ -428,6 +428,12 @@ export async function takeCensus(root, opts) {
428
428
  }
429
429
  }
430
430
  const t = transcribe(rootClasses(src, found[0].name), declaredValues);
431
+ /**
432
+ * The transcription carries `dark` too, and spreading it put the key back after
433
+ * the conditional below decided to leave it out. Two writers for one field is
434
+ * how a decision gets undone by the line above it.
435
+ */
436
+ const { dark: _measuredDark, ...withoutDark } = t;
431
437
  /**
432
438
  * THE VARIANT STYLES, FOLDED IN.
433
439
  *
@@ -540,10 +546,20 @@ export async function takeCensus(root, opts) {
540
546
  if (size > 0 || tag) {
541
547
  looks[found[0].name] = {
542
548
  ...(sketch.length > 0 ? { sketch } : {}),
543
- ...t,
549
+ ...withoutDark,
544
550
  base,
545
551
  states,
546
- dark,
552
+ /**
553
+ * OMITTED WHEN EMPTY, so a real loss is legible.
554
+ *
555
+ * `dark` is a field of the READING, not of the contract - the platform
556
+ * converts it into a `{when:{scheme:"dark"}}` layer, which is the one way
557
+ * a recipe says dark. Emitting the key regardless made a validator report
558
+ * all 36 components as "not arriving whole" for a key with nothing in it,
559
+ * and the 7 that carry a real value were buried in the noise
560
+ * (not-expressed.md, dono, 03/08). 29 of 36 were empty.
561
+ */
562
+ ...(Object.keys(dark).length > 0 ? { dark } : {}),
547
563
  ...(Object.keys(parts).length > 0 ? { parts } : {}),
548
564
  ...(tree.length > 0 ? { tree } : {}),
549
565
  ...(tag ? { rootTag: tag } : {}),
@@ -864,7 +880,7 @@ export async function takeCensus(root, opts) {
864
880
  */
865
881
  const coverage = summarizeCoverage(shapes, componentFiles, Object.keys(looks).length, Object.values(looks).reduce((n, look) => n +
866
882
  Object.keys(look.base).length +
867
- Object.keys(look.dark).length +
883
+ Object.keys(look.dark ?? {}).length +
868
884
  Object.values(look.states).reduce((m, block) => m + Object.keys(block).length, 0) +
869
885
  // The LAYERS count: the record and ternary readers produce layers rather than a
870
886
  // base, so leaving them out reported a Tag with eight colours as three
@@ -17,8 +17,51 @@
17
17
  * this product has (dono, 01/08: "a normalização será de acordo com cada
18
18
  * projeto").
19
19
  */
20
+ import { frontierKind } from "../frontier-kind.js";
21
+ import { importMap } from "./imports.js";
20
22
  /** `var(--x)`, including inside a Tailwind arbitrary value: `bg-[var(--x)]`. */
21
23
  const VAR_REF = /var\(\s*(--[a-zA-Z0-9_-]+)/g;
24
+ /**
25
+ * VARIABLES A HEADLESS LIBRARY SETS AT RUNTIME, which no stylesheet can declare.
26
+ *
27
+ * Base UI measures the trigger and writes `--anchor-width` on the positioner element
28
+ * before paint, so `min-w-[var(--anchor-width)]` on their Select is correct and works.
29
+ * Counting it as a broken reference told the owner their own code was defective, and it
30
+ * cost the report its credibility on the twelve that ARE broken (not-expressed.md,
31
+ * dono, 03/08). Two of their thirteen were this.
32
+ *
33
+ * The list is the SHARED vocabulary of headless positioning - Base UI, Radix, Ark and
34
+ * Floating UI all name the anchor's width `--anchor-width` - plus the self-identifying
35
+ * per-library prefixes. Same reasoning as `NON_RENDERING` in `frontier-kind.ts`: a name
36
+ * is only read after the PACKAGE has answered.
37
+ */
38
+ const RUNTIME_ANCHOR = new Set([
39
+ "--anchor-width",
40
+ "--anchor-height",
41
+ "--available-width",
42
+ "--available-height",
43
+ "--transform-origin",
44
+ "--popup-width",
45
+ "--popup-height",
46
+ "--trigger-width",
47
+ "--trigger-height",
48
+ ]);
49
+ /** `--radix-popper-anchor-width` and its siblings in the other libraries. */
50
+ const RUNTIME_PREFIX = /^--(?:radix|reka|ark|kb|floating|reactour)-/;
51
+ /**
52
+ * Does this file import a library that supplies such a variable?
53
+ *
54
+ * THE PACKAGE DECIDES, never the variable name on its own: a project that declares no
55
+ * headless dependency and writes `var(--anchor-width)` really has written a reference
56
+ * to nothing, and saying so is the whole value of this scan.
57
+ */
58
+ function importsHeadless(source) {
59
+ for (const spec of Object.values(importMap(source))) {
60
+ if (frontierKind(spec) === "headless")
61
+ return true;
62
+ }
63
+ return false;
64
+ }
22
65
  /**
23
66
  * PREFIXES A DECLARATION MAY CARRY that a reference dropped.
24
67
  *
@@ -46,10 +89,13 @@ const NAMESPACES = [
46
89
  export function findBrokenRefs(sources, declared) {
47
90
  const seen = new Map();
48
91
  for (const { file, source } of sources) {
92
+ const runtime = importsHeadless(source);
49
93
  for (const m of source.matchAll(VAR_REF)) {
50
94
  const name = m[1];
51
95
  if (declared.has(name))
52
96
  continue;
97
+ if (runtime && (RUNTIME_ANCHOR.has(name) || RUNTIME_PREFIX.test(name)))
98
+ continue;
53
99
  const hit = seen.get(name) ?? { count: 0, files: new Set() };
54
100
  hit.count += 1;
55
101
  hit.files.add(file);
@@ -388,9 +388,15 @@ Open the project and answer the questions below. Then add a \`reading\` object t
388
388
  "name": "body",
389
389
  "at": 2,
390
390
  "children": [
391
- { "as": "text", "name": "label", "at": 3 },
391
+ { "as": "component", "ref": "Text", "at": 3 },
392
392
  { "as": "heading", "name": "value", "at": 4 }
393
393
  ]
394
+ },
395
+ {
396
+ "as": "component",
397
+ "ref": "Tooltip",
398
+ "at": 5,
399
+ "children": [{ "as": "icon", "name": "info", "at": 6 }]
394
400
  }
395
401
  ]
396
402
  },
@@ -399,7 +405,7 @@ Open the project and answer the questions below. Then add a \`reading\` object t
399
405
  "anatomy": [
400
406
  { "as": "image", "name": "cover", "at": 1 },
401
407
  { "as": "heading", "name": "title", "at": 2 },
402
- { "as": "component", "ref": "TextEditor" },
408
+ { "as": "component", "ref": "TextEditor", "at": 5 },
403
409
  {
404
410
  "as": "row",
405
411
  "name": "footer",
@@ -702,6 +708,21 @@ because a button's content model is *phrasing* and an icon is phrasing. So an \`
702
708
  Everything else is a leaf, and a leaf that holds elements has them HOISTED OUT to become
703
709
  its siblings - which destroys the nesting you read. If something arranges, say \`row\`/\`stack\`.
704
710
 
711
+ **A frontier keeps its own \`at\`, so it keeps its own classes.** This is the one that gets
712
+ talked out of: \`<Text className="text-lightgray-500 font-medium">\` is one of THEIR
713
+ components, so it is a \`component\` node - and pointing \`at\` its sketch index carries the
714
+ typography onto it exactly as a part would. Reading it as a \`text\` part instead is a
715
+ downgrade with a real cost: \`composes\` is built from \`component\` nodes, so a library whose
716
+ most-used atom is composed 363 times reported composing it ZERO times, and its strongest
717
+ composition law became invisible (not-expressed.md, dono, 03/08).
718
+
719
+ <Tooltip><Info/></Tooltip> frontier with a child, both survive
720
+ <Text className="…">Label</Text> frontier with "at", the type survives
721
+
722
+ If you find yourself writing "the contract cannot hold this, so I sent something else",
723
+ check it against \`recipe_vocabulary\` before you write the workaround - and if the check
724
+ disagrees with this file, the check is right and this file is old.
725
+
705
726
  ### \`{children}\` is a node, not an absence
706
727
 
707
728
  Four things decide what a component looks like on screen, and only the first is in its own source:
@@ -847,6 +868,18 @@ a recipe before these tools existed, and four separate readings were lost in sil
847
868
  \`dark:\` inside a variant, a state the compiler cannot spell, a part name with a dot in it,
848
869
  a layer with an empty style. Each of those was correct work that arrived as nothing.
849
870
 
871
+ **A FIELD OF THE READING IS NOT A FIELD OF THE RECIPE.** \`looks[X].dark\` is how the census
872
+ reports a dark half it measured; a recipe has one way to say the same thing, and it is a
873
+ layer:
874
+
875
+ \`\`\`json
876
+ { "when": { "scheme": "dark" }, "style": { "backgroundColor": "{color.darkgray.300}" } }
877
+ \`\`\`
878
+
879
+ Copying \`dark\` in at the top level of a recipe costs you that block, silently, on every
880
+ component that has one - and \`import\` already converts it for the recipes IT writes, so the
881
+ only place this can be lost is a recipe you hand-built (not-expressed.md, dono, 03/08).
882
+
850
883
  **In ONE call.** Build every recipe in memory, then send the whole reading to
851
884
  \`validate_recipes\` - one round trip for 35 components instead of 35. \`validate_recipe\`
852
885
  (singular) is for the one recipe you rewrote after the batch named it; reaching for it in a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.109",
3
+ "version": "0.16.111",
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": {