synthesisui 0.16.121 → 0.16.123

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.
@@ -324,14 +324,23 @@ sketch) {
324
324
  /**
325
325
  * A MOTION ELEMENT IS THEIRS TO NAME TOO. `motion.div` is the element their panel
326
326
  * opens with, and the props that make it open have no CSS to translate to - so the
327
- * snippet emits their element and their props, verbatim. Headless for the
328
- * behaviour, motion for the animation; an opaque library is still named and
329
- * stopped at.
327
+ * snippet emits their element and their props, verbatim.
328
+ *
329
+ * AND SO IS A GLYPH, which this gate kept out and the deriver did not - so the fix
330
+ * landed on the DERIVED path and missed the AUTHORED one, which is the path he
331
+ * runs. Measured on his own live system (test44, dono, 04/08): 35 of 37 icon nodes
332
+ * arrived with no package, across 17 of 32 components, and the snippet handed him
333
+ * `<span className="ds-collapsible-card-chevron">…</span>` where his file writes
334
+ * `<ChevronUp className="size-5 …" />`. Third time this seam has taken a field.
335
+ *
336
+ * Headless for the behaviour, motion for the animation, icon for the drawing - the
337
+ * three whose every pixel is in their own class list. An opaque library draws its
338
+ * own insides and is still named and stopped at.
330
339
  */
331
340
  if (measured?.from &&
332
341
  measured.tag &&
333
342
  /^[A-Za-z]/.test(measured.tag) &&
334
- (kind === "headless" || kind === "motion")) {
343
+ (kind === "headless" || kind === "motion" || kind === "icon")) {
335
344
  node_.from = measured.from;
336
345
  node_.refName = measured.tag;
337
346
  }
@@ -508,14 +508,39 @@ export function readVariants(source) {
508
508
  * Read separately from `cva` because a file can have both, and because this shape is
509
509
  * what a codebase that never adopted a variant library uses everywhere.
510
510
  */
511
- const INLINE_COND = /([A-Za-z_$][\w$.]*)\s*(?:===|==)\s*["']([^"']+)["']\s*&&\s*(["'`])((?:(?!\3).)*)\3|([A-Za-z_$][\w$]*)\s*&&\s*(["'`])((?:(?!\6).)*)\6/g;
511
+ /**
512
+ * THE `!` HAS TO BE PART OF THE MATCH, or the two opposite branches of one `cn()` become
513
+ * the same condition.
514
+ *
515
+ * `!` is not in `[A-Za-z_$]`, so `!isLoading && "cursor-pointer"` matched starting at
516
+ * `isLoading` and the negation fell outside the match, in silence. Their collapsible card
517
+ * writes both branches:
518
+ *
519
+ * !isLoading && "cursor-pointer hover:bg-lightgray-200/50"
520
+ * isLoading && "cursor-not-allowed opacity-80"
521
+ *
522
+ * and both arrived as `{isLoading: "true"}` - so the same state carried `cursor: pointer`
523
+ * AND `cursor: not-allowed`, with whichever came last winning. A dead rule is bad; a rule
524
+ * that says the opposite of their code is worse (dono, 04/08). Measured on his library: 8
525
+ * inline conditions with a class list, 2 of them negated.
526
+ */
527
+ const INLINE_COND = /(!?)\s*([A-Za-z_$][\w$.]*)\s*(?:===|==)\s*["']([^"']+)["']\s*&&\s*(["'`])((?:(?!\4).)*)\4|(!?)\s*([A-Za-z_$][\w$]*)\s*&&\s*(["'`])((?:(?!\8).)*)\8/g;
512
528
  export function readInlineConditions(source) {
513
529
  const layers = [];
514
530
  INLINE_COND.lastIndex = 0;
515
531
  for (const m of source.matchAll(INLINE_COND)) {
516
- const prop = (m[1] ?? m[5] ?? "").split(".").pop() ?? "";
517
- const option = m[2] ?? "true";
518
- const raw = m[4] ?? m[7] ?? "";
532
+ const negated = (m[1] ?? m[6]) === "!";
533
+ const prop = (m[2] ?? m[7] ?? "").split(".").pop() ?? "";
534
+ const raw = m[5] ?? m[9] ?? "";
535
+ /**
536
+ * `!status === "loading"` is not a thing anybody writes, and negating a comparison we
537
+ * cannot invert would guess. A comparison is taken as written; only the bare boolean
538
+ * flips, and it flips to the option `false` - which is what `data-x="false"` means.
539
+ */
540
+ const compared = m[3];
541
+ if (compared != null && negated)
542
+ continue;
543
+ const option = compared ?? (negated ? "false" : "true");
519
544
  if (!prop || raw.includes("${"))
520
545
  continue;
521
546
  const classes = raw.split(/\s+/).filter(Boolean);
package/dist/guide.js CHANGED
@@ -198,6 +198,16 @@ function componentEntry(cname, recipe) {
198
198
  /** Its own laws, computed from its own usage - and nothing read them (dono, 04/08). */
199
199
  const laws = (recipe.usage ?? []).map((law) => `\n - ${law}`).join("");
200
200
  const lawsText = laws ? `\n it obeys:${laws}` : "";
201
+ /**
202
+ * WHAT IT DOES, beside what it obeys - and they are not the same reader.
203
+ *
204
+ * `usage` is law: short, imperative, checkable, injected into a Build Script. This is the
205
+ * briefing for somebody rebuilding the component in a project that has none of these prop
206
+ * names, which is the question the guide exists to answer (dono, 04/08). Measured on a
207
+ * real system: 23 of 35 components have something derived to say here.
208
+ */
209
+ const does = (recipe.behaviour ?? []).map((line) => `\n - ${line}`).join("");
210
+ const doesText = does ? `\n what it does:${does}` : "";
201
211
  const behaviour = behaviourLine(cname, recipe);
202
212
  const behaviourText = behaviour ? `\n ${behaviour}` : "";
203
213
  /**
@@ -240,7 +250,7 @@ function componentEntry(cname, recipe) {
240
250
  const anatomyText = anatomy.length
241
251
  ? `\n anatomy, in this nesting:\n${anatomy.join("\n")}`
242
252
  : "";
243
- return `- **${cname}** (\`${cls}\`)${variantsText}\n ${recipe.description}${factsText}${defaultsText}${partsText}${statesText}${behaviourText}${contractText}${anatomyText}${lawsText}`;
253
+ return `- **${cname}** (\`${cls}\`)${variantsText}\n ${recipe.description}${factsText}${defaultsText}${partsText}${statesText}${behaviourText}${contractText}${anatomyText}${doesText}${lawsText}`;
244
254
  }
245
255
  /**
246
256
  * Builds GUIDE.md - instructions *for the agent* on how to build components
@@ -402,6 +412,14 @@ It writes \`<componentsDir>/button/\` with \`button.tsx\` (+ colocated \`button.
402
412
  The boundary: tokens are global (\`tokens.css\` + the \`data-ds\` root attribute, once per app);
403
413
  everything a component owns lives in its own folder.
404
414
 
415
+ **A component's conditional classes are decided at the call site, not in the stylesheet.** A recipe
416
+ carries the look of a state as a rule keyed on a data attribute (\`[data-size="lg"]\`), and the caller
417
+ is what turns it on. \`synthesisui component\` emits this system's own \`cn()\` beside the component: it
418
+ resolves two utilities that set the SAME property in favour of the one the caller passed, which plain
419
+ Tailwind decides by emission order instead. Compose with it and an override wins; concatenate strings
420
+ and which one applies is a coin flip. Where a component needs an attribute nobody sets by default, its
421
+ own entry below says so.
422
+
405
423
  ---
406
424
  `
407
425
  : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.121",
3
+ "version": "0.16.123",
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": {