synthesisui 0.16.119 → 0.16.120

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.
Files changed (2) hide show
  1. package/dist/guide.js +143 -1
  2. package/package.json +1 -1
package/dist/guide.js CHANGED
@@ -33,6 +33,90 @@ function customFontFamilies(families) {
33
33
  }
34
34
  return out;
35
35
  }
36
+ /**
37
+ * THE STRUCTURE, WITH WHAT EACH PART IS FOR AND WHEN IT EXISTS.
38
+ *
39
+ * A flat list of class names is enough to STYLE and not enough to BUILD: an agent in
40
+ * another project reading fifteen of them cannot tell which is the thing you press, which
41
+ * one holds the revealed content, or which two are not always there. The recipe carries
42
+ * both facts per node and the guide was printing neither (dono, 04/08).
43
+ */
44
+ function anatomyLines(cname, nodes, depth = 1) {
45
+ const out = [];
46
+ for (const node of nodes ?? []) {
47
+ if (depth > 6)
48
+ break;
49
+ const pad = " ".repeat(depth);
50
+ const name = node.part
51
+ ? `\`.ds-${kebab(cname)}-${kebab(node.part)}\``
52
+ : node.refName
53
+ ? `\`<${node.refName}>\``
54
+ : node.ref
55
+ ? `your \`${node.refName ?? node.ref}\``
56
+ : node.as;
57
+ const facts = [node.as];
58
+ if (node.role && node.role !== node.as)
59
+ facts.push(node.role);
60
+ if (node.refName && node.from)
61
+ facts.push(`from \`${node.from}\``);
62
+ if (node.expects)
63
+ facts.push(`holds ${node.expects}`);
64
+ /**
65
+ * WHEN IT EXISTS, in the recipe's own words rather than in theirs - see
66
+ * `PreviewNode.only`. Their `isLoading` means nothing in another project; "only in a
67
+ * state this component has" is actionable anywhere.
68
+ */
69
+ const only = node.only;
70
+ if (only && "axis" in only)
71
+ facts.push(`only when \`data-${kebab(only.axis)}="${only.option}"\``);
72
+ else if (only && "state" in only)
73
+ facts.push("only in that state");
74
+ else if (only && "caller" in only)
75
+ facts.push("only when you pass it");
76
+ out.push(`${pad}- ${name} - ${facts.join(" · ")}`);
77
+ out.push(...anatomyLines(cname, node.children, depth + 1));
78
+ }
79
+ return out;
80
+ }
81
+ /**
82
+ * WHAT THE COMPONENT DOES, said without naming a package.
83
+ *
84
+ * The recipe says `@base-ui/react/collapsible`. In a project that uses Radix - or nothing -
85
+ * that is evidence and not an instruction. What travels is the SHAPE of the interaction,
86
+ * and it is derivable from two facts the recipe already carries: the behaviour belongs to
87
+ * a headless library, and one part has the `action` role while another holds the content.
88
+ *
89
+ * Derived, never invented: with no `action` part and no headless library there is nothing
90
+ * to say, and this says nothing.
91
+ */
92
+ function behaviourLine(cname, recipe) {
93
+ const headless = (recipe.libraries ?? []).some((l) => l.kind === "headless");
94
+ if (!headless)
95
+ return null;
96
+ const flat = [];
97
+ const walk = (nodes) => {
98
+ for (const node of nodes ?? []) {
99
+ flat.push(node);
100
+ walk(node.children);
101
+ }
102
+ };
103
+ walk(recipe.preview?.parts);
104
+ const action = flat.find((n) => n.role === "action");
105
+ if (!action)
106
+ return null;
107
+ /**
108
+ * A part that exists only in a STATE, or a slot, is the region an action reveals - which
109
+ * is what separates a disclosure from a button that merely has a name.
110
+ */
111
+ const revealed = flat.find((n) => n.as === "slot" || (n.only && "state" in n.only));
112
+ const press = action.part
113
+ ? `\`.ds-${kebab(cname)}-${kebab(action.part)}\``
114
+ : "the action";
115
+ if (!revealed) {
116
+ return `the interaction: ${press} is the pressable one - wire it yourself or with any headless primitive, and give it the ARIA the pattern calls for.`;
117
+ }
118
+ return `the interaction: ${press} is pressed and reveals the region below it. Whatever drives that - a headless library or your own state - the pressable element announces \`aria-expanded\` and points at the region with \`aria-controls\`; the region carries the matching \`id\`. This recipe styles both and drives neither.`;
119
+ }
36
120
  /** One entry per component: class, variant data-*, states, and multi-part anatomy. */
37
121
  function componentEntry(cname, recipe) {
38
122
  const cls = `.ds-${kebab(cname)}`;
@@ -50,7 +134,65 @@ function componentEntry(cname, recipe) {
50
134
  });
51
135
  partsText = `\n parts: ${items.join(", ")}`;
52
136
  }
53
- return `- **${cname}** (\`${cls}\`)${variantsText}\n ${recipe.description}${partsText}${statesText}`;
137
+ /**
138
+ * EVERYTHING ELSE THE RECIPE ALREADY KNOWS, and this entry was throwing away.
139
+ *
140
+ * Measured on a real system: the recipe holds 15 fields and the guide read 4 of them, so
141
+ * an agent in another project got a list of class names and had to invent the rest -
142
+ * which is the difference between styling this component and building one that behaves
143
+ * like it (dono, 04/08).
144
+ */
145
+ const facts = [];
146
+ if (recipe.tier)
147
+ facts.push(`a ${recipe.tier}`);
148
+ if (recipe.api?.name) {
149
+ facts.push(recipe.api.from
150
+ ? `their own \`${recipe.api.name}\`, imported from \`${recipe.api.from}\``
151
+ : `their own \`${recipe.api.name}\``);
152
+ }
153
+ /**
154
+ * WHO OWNS THE STATE, which is the first question anybody rebuilding it asks. A recipe
155
+ * that manages itself needs no props to work; one the caller drives needs the pair.
156
+ */
157
+ const runtime = recipe.runtime;
158
+ if (runtime?.stateOwner) {
159
+ const owner = runtime.stateOwner === "self"
160
+ ? "manages its own state"
161
+ : runtime.stateOwner === "caller"
162
+ ? "the caller owns its state"
163
+ : "works controlled or uncontrolled";
164
+ const pairs = (runtime.controlled ?? [])
165
+ .map((c) => `\`${c.prop}\` + \`${c.event}\``)
166
+ .join(", ");
167
+ facts.push(pairs ? `${owner} (${pairs})` : owner);
168
+ }
169
+ /**
170
+ * WHOSE BEHAVIOUR IT IS, and what that means for somebody rebuilding it. `headless` is
171
+ * the one that changes the answer: the pixels are in this recipe and the interaction is
172
+ * a library's, so ANY headless primitive of that shape will do - it does not have to be
173
+ * the one they used.
174
+ */
175
+ for (const lib of recipe.libraries ?? []) {
176
+ if (lib.kind === "headless")
177
+ facts.push(`behaviour from \`${lib.from}\` - a headless library: every pixel is in this recipe, the interaction is not. Any equivalent primitive works`);
178
+ else if (lib.kind === "opaque")
179
+ facts.push(`\`${lib.from}\` draws part of it, and that part is not in this recipe`);
180
+ }
181
+ const defaults = Object.entries(recipe.defaults ?? {})
182
+ .map(([axis, option]) => `\`data-${kebab(axis)}="${option}"\``)
183
+ .join(", ");
184
+ const defaultsText = defaults ? `\n at rest: ${defaults}` : "";
185
+ const factsText = facts.length ? `\n ${facts.join("\n ")}` : "";
186
+ /** Its own laws, computed from its own usage - and nothing read them (dono, 04/08). */
187
+ const laws = (recipe.usage ?? []).map((law) => `\n - ${law}`).join("");
188
+ const lawsText = laws ? `\n it obeys:${laws}` : "";
189
+ const behaviour = behaviourLine(cname, recipe);
190
+ const behaviourText = behaviour ? `\n ${behaviour}` : "";
191
+ const anatomy = anatomyLines(cname, recipe.preview?.parts);
192
+ const anatomyText = anatomy.length
193
+ ? `\n anatomy, in this nesting:\n${anatomy.join("\n")}`
194
+ : "";
195
+ return `- **${cname}** (\`${cls}\`)${variantsText}\n ${recipe.description}${factsText}${defaultsText}${partsText}${statesText}${behaviourText}${anatomyText}${lawsText}`;
54
196
  }
55
197
  /**
56
198
  * Builds GUIDE.md - instructions *for the agent* on how to build components
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.119",
3
+ "version": "0.16.120",
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": {