synthesisui 0.16.88 → 0.16.89

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.
@@ -288,7 +288,21 @@ async function validateRecipe(name, recipe) {
288
288
  const answer = await askCatalogue("validate", { name, recipe });
289
289
  if (!answer.ok)
290
290
  return answer.because;
291
- const v = answer.body;
291
+ const raw = answer.body;
292
+ /**
293
+ * THE WIRE IS NOT A TYPE SYSTEM. The schema-refusal branch of the API answered
294
+ * without a `checklist`, and this client read `v.checklist.length` unguarded - so the
295
+ * one recipe that most needed the validator crashed it, on every call, and a real
296
+ * import had to validate against the API by hand (test13, 01/08). Every field is
297
+ * defaulted; a shape this cannot read degrades to a sentence, never to a throw.
298
+ */
299
+ const v = {
300
+ ok: raw.ok === true,
301
+ rejected: raw.rejected ?? [],
302
+ lost: raw.lost ?? [],
303
+ missing: raw.missing ?? [],
304
+ checklist: raw.checklist ?? [],
305
+ };
292
306
  if (v.ok) {
293
307
  return `${name}: everything you sent would survive. Send it.`;
294
308
  }
@@ -532,8 +532,22 @@ function tokenRefFor(name) {
532
532
  }
533
533
  if (bare.startsWith("gradient-"))
534
534
  return `{gradients.${bare.slice(9)}}`;
535
- if (bare.startsWith("text-"))
535
+ if (bare.startsWith("text-")) {
536
+ // The companion token: `--text-body-s--line-height` names the line-height OF a
537
+ // step, and a double dash inside a ref is a grammar nothing accepts (test13).
538
+ const companion = /^text-(.+?)--(line-height|letter-spacing|font-weight)$/.exec(bare);
539
+ if (companion) {
540
+ const prop = {
541
+ "line-height": "lineHeight",
542
+ "letter-spacing": "letterSpacing",
543
+ "font-weight": "weight",
544
+ }[companion[2]];
545
+ return `{typography.scale.${companion[1]}.${prop}}`;
546
+ }
547
+ if (bare.slice(5).includes("--"))
548
+ return `var(${name})`;
536
549
  return `{typography.scale.${bare.slice(5)}.fontSize}`;
550
+ }
537
551
  if (bare.startsWith("font-"))
538
552
  return `{typography.families.${bare.slice(5)}}`;
539
553
  return `var(${name})`;
@@ -190,8 +190,37 @@ const TYPE_KEYWORD = {
190
190
  underline: { property: "textDecoration", value: "underline" },
191
191
  };
192
192
  /** States we recognise. Anything else is skipped rather than invented. */
193
- const STATE = /^(hover|focus|focus-visible|active|disabled|checked)$/;
194
- const DATA_STATE = /^data-\[([a-z-]+)\]$/;
193
+ /**
194
+ * A MODIFIER'S STATE, IN THE CONTRACT'S SPELLING.
195
+ *
196
+ * The old regex KEYED the state with the raw modifier, so `focus-visible:ring-2` arrived
197
+ * as `states["focus-visible"]` - a key the compiler cannot spell and the lint rejects.
198
+ * A real import hit it on Modal's close button and had to hand-patch the census before
199
+ * sending (test13, 01/08). The reading edge normalizes; the contract stays canonical.
200
+ */
201
+ const STATE_SPELLING = {
202
+ hover: "hover",
203
+ focus: "focus",
204
+ "focus-visible": "focusVisible",
205
+ "focus-within": "focus",
206
+ active: "active",
207
+ disabled: "disabled",
208
+ checked: "checked",
209
+ selected: "selected",
210
+ open: "open",
211
+ invalid: "invalid",
212
+ loading: "loading",
213
+ "read-only": "readOnly",
214
+ required: "required",
215
+ placeholder: "placeholder",
216
+ highlighted: "highlighted",
217
+ even: "even",
218
+ odd: "odd",
219
+ first: "first",
220
+ last: "last",
221
+ empty: "empty",
222
+ };
223
+ const DATA_STATE = /^data-\[(?:state=)?([a-z-]+)\]$/;
195
224
  /**
196
225
  * Split a class into its modifiers and the utility itself.
197
226
  *
@@ -529,6 +558,26 @@ function tokenRefFor(name) {
529
558
  if (bare.startsWith("gradient-"))
530
559
  return `{gradients.${bare.slice(9)}}`;
531
560
  if (bare.startsWith("text-")) {
561
+ /**
562
+ * THE COMPANION TOKEN. Tailwind v4 spells "the line-height OF text-body-s" as
563
+ * `--text-body-s--line-height` - a double dash inside one name. Read as a step name
564
+ * it produced `{typography.scale.body-s--line-height.fontSize}`, whose double dash
565
+ * no ref grammar accepts, and the whole recipe was REFUSED at validation (test13,
566
+ * 01/08). The suffix names the property; the middle names the step.
567
+ */
568
+ const companion = /^text-(.+?)--(line-height|letter-spacing|font-weight)$/.exec(bare);
569
+ if (companion) {
570
+ const prop = {
571
+ "line-height": "lineHeight",
572
+ "letter-spacing": "letterSpacing",
573
+ "font-weight": "weight",
574
+ }[companion[2]];
575
+ return `{typography.scale.${companion[1]}.${prop}}`;
576
+ }
577
+ // Any other double dash is a name this grammar cannot hold - the literal var()
578
+ // still resolves against their own stylesheet, and an invalid ref helps nobody.
579
+ if (bare.slice(5).includes("--"))
580
+ return `var(${name})`;
532
581
  return `{typography.scale.${bare.slice(5)}.fontSize}`;
533
582
  }
534
583
  if (bare.startsWith("font-"))
@@ -712,9 +761,10 @@ export function transcribe(classes, declared) {
712
761
  target = isDark ? out.dark : out.base;
713
762
  }
714
763
  else if (rest.length === 1 && !isDark) {
715
- const state = STATE.test(rest[0])
764
+ const raw = STATE_SPELLING[rest[0]] != null
716
765
  ? rest[0]
717
766
  : (DATA_STATE.exec(rest[0])?.[1] ?? null);
767
+ const state = raw ? (STATE_SPELLING[raw] ?? null) : null;
718
768
  if (state)
719
769
  target = out.states[state] ?? (out.states[state] = {});
720
770
  else
@@ -761,6 +811,23 @@ export function transcribe(classes, declared) {
761
811
  // First write wins, matching how a class list resolves for the properties
762
812
  // we read: later duplicates in the same list are almost always a merge
763
813
  // artefact rather than an override.
814
+ /**
815
+ * `size-4` is Tailwind for width AND height; CSS has no `size` property for
816
+ * elements, so the platform dropped the declaration whole and a real import had to
817
+ * rewrite every one by hand as `w-N h-N` (test13, 01/08). One utility, two
818
+ * declarations, on whichever target the modifiers routed to.
819
+ */
820
+ if (decl.property === "size") {
821
+ if (target.width == null)
822
+ target.width = decl.value;
823
+ if (target.height == null)
824
+ target.height = decl.value;
825
+ if (decl.token)
826
+ out.fromToken += 1;
827
+ else
828
+ out.fromLiteral += 1;
829
+ continue;
830
+ }
764
831
  if (target[decl.property] != null)
765
832
  continue;
766
833
  target[decl.property] = decl.value;
@@ -49,6 +49,7 @@ const MODIFIER_STATE = {
49
49
  checked: "checked",
50
50
  indeterminate: "checked",
51
51
  selected: "selected",
52
+ highlighted: "highlighted",
52
53
  open: "open",
53
54
  invalid: "invalid",
54
55
  required: "required",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.88",
3
+ "version": "0.16.89",
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": {