synthesisui 0.16.122 → 0.16.124

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.
@@ -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.122",
3
+ "version": "0.16.124",
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": {