rafters 0.3.0 → 0.4.0

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.
package/dist/index.js CHANGED
@@ -82,7 +82,7 @@ import { Command } from "commander";
82
82
  // src/commands/add.ts
83
83
  import { existsSync as existsSync3 } from "fs";
84
84
  import { access, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
85
- import { basename, dirname, join as join6 } from "path";
85
+ import { basename, dirname, join as join7 } from "path";
86
86
 
87
87
  // ../../node_modules/.pnpm/apca-w3@0.1.9/node_modules/apca-w3/src/apca-w3.js
88
88
  var SA98G = {
@@ -20309,6 +20309,11 @@ var MOTION_NAMESPACE_PROPERTY = {
20309
20309
  };
20310
20310
  var MOTION_NAMESPACE_NAMES = Object.keys(MOTION_NAMESPACE_PROPERTY);
20311
20311
  var REDUCED_MOTION_ZEROED = /* @__PURE__ */ new Set(["duration", "delay"]);
20312
+ var TAILWIND_THEME_PREFIX = {
20313
+ duration: "transition-duration",
20314
+ ease: "ease",
20315
+ delay: "transition-delay"
20316
+ };
20312
20317
  var MOTION_NAMESPACE_TOKEN = new RegExp(`^rafters-(${MOTION_NAMESPACE_NAMES.join("|")})-(.+)$`);
20313
20318
  function motionNamespaceParts(name) {
20314
20319
  const match = MOTION_NAMESPACE_TOKEN.exec(name);
@@ -20565,6 +20570,11 @@ function generateThemeBlock(groups) {
20565
20570
  lines.push(bridgeLines);
20566
20571
  lines.push("");
20567
20572
  }
20573
+ const animationKeys = generateMotionAnimationKeys(groups.motion);
20574
+ if (animationKeys) {
20575
+ lines.push(animationKeys);
20576
+ lines.push("");
20577
+ }
20568
20578
  if (groups.breakpoint.length > 0) {
20569
20579
  for (const token of groups.breakpoint) {
20570
20580
  const value = tokenValueToCSS(token);
@@ -20591,10 +20601,6 @@ function generateThemeBlock(groups) {
20591
20601
  }
20592
20602
  lines.push("");
20593
20603
  }
20594
- const animationTokens = generateAnimationTokens(groups.motion);
20595
- if (animationTokens) {
20596
- lines.push(animationTokens);
20597
- }
20598
20604
  lines.push("}");
20599
20605
  return lines.join("\n");
20600
20606
  }
@@ -20685,18 +20691,6 @@ function generateKeyframes(motionTokens) {
20685
20691
  }
20686
20692
  return lines.join("\n").trim();
20687
20693
  }
20688
- function generateAnimationTokens(motionTokens) {
20689
- const animationTokens = motionTokens.filter((t) => t.name.startsWith("motion-animation-"));
20690
- if (animationTokens.length === 0) {
20691
- return "";
20692
- }
20693
- const lines = [];
20694
- for (const token of animationTokens) {
20695
- const animName = token.animationName || token.name.replace("motion-animation-", "");
20696
- lines.push(` --animate-${animName}: ${token.value};`);
20697
- }
20698
- return lines.join("\n");
20699
- }
20700
20694
  function generateDepthUtilities(depthTokens) {
20701
20695
  if (depthTokens.length === 0) return "";
20702
20696
  const lines = ["/* Depth (z-index) utilities -- words over numbers */"];
@@ -20791,8 +20785,8 @@ function generateMotionUtilities(motionTokens) {
20791
20785
  const className = token.name.replace("motion-semantic-", "motion-");
20792
20786
  lines.push(`@utility ${className} {`);
20793
20787
  lines.push(` transition-property: ${spec.properties.join(", ")};`);
20794
- lines.push(` transition-duration: var(--duration-${spec.durationTier});`);
20795
- lines.push(` transition-timing-function: var(--ease-${spec.curve});`);
20788
+ lines.push(` transition-duration: var(--rafters-duration-${spec.durationTier});`);
20789
+ lines.push(` transition-timing-function: var(--rafters-ease-${spec.curve});`);
20796
20790
  if (spec.reducedMotion) {
20797
20791
  lines.push(" @media (prefers-reduced-motion: reduce) {");
20798
20792
  lines.push(` transition-property: ${spec.reducedMotion.properties.join(", ")};`);
@@ -20848,7 +20842,7 @@ function parseCellSpec(tokenName, raw) {
20848
20842
  `tailwind exporter: motion cell token "${tokenName}" has an unrecognized duration.kind ${JSON.stringify(kind ?? null)}. Known duration.kind values: period, tier.`
20849
20843
  );
20850
20844
  }
20851
- function generateMotionCellUtilities(motionTokens) {
20845
+ function generateMotionAnimationKeys(motionTokens) {
20852
20846
  const cellTokens = motionTokens.filter((t) => t.name.startsWith("motion-cell-"));
20853
20847
  if (cellTokens.length === 0) return "";
20854
20848
  const periodMembers = /* @__PURE__ */ new Set();
@@ -20856,38 +20850,38 @@ function generateMotionCellUtilities(motionTokens) {
20856
20850
  const parts = motionNamespaceParts(token.name);
20857
20851
  if (parts?.namespace === "period") periodMembers.add(parts.member);
20858
20852
  }
20859
- const lines = [
20860
- "/* Motion cells -- one utility per animated (component, part, transition) */"
20861
- ];
20853
+ const keys = /* @__PURE__ */ new Map();
20862
20854
  for (const token of cellTokens) {
20863
20855
  if (typeof token.value !== "string") continue;
20864
20856
  const spec = parseCellSpec(token.name, token.value);
20865
- lines.push(`@utility ${token.name.replace("motion-cell-", "animate-")} {`);
20866
20857
  if (spec === null) {
20867
- lines.push(` animation: ${token.value};`);
20868
- } else if (spec.duration.kind === "period") {
20858
+ keys.set(token.name.replace("motion-cell-", ""), token.value);
20859
+ continue;
20860
+ }
20861
+ if (spec.duration.kind === "period") {
20869
20862
  const { period } = spec.duration;
20870
20863
  if (!periodMembers.has(period)) {
20871
20864
  throw new Error(
20872
20865
  `tailwind exporter: motion cell token "${token.name}" references unknown period "${period}". Known periods: ${[...periodMembers].sort().join(", ")}.`
20873
20866
  );
20874
20867
  }
20875
- lines.push(` animation-name: ${spec.keyframe};`);
20876
- lines.push(` animation-duration: var(--rafters-period-${period});`);
20877
- lines.push(" animation-iteration-count: infinite;");
20878
- } else {
20879
- lines.push(` animation-name: ${spec.keyframe};`);
20880
- lines.push(` animation-duration: var(--rafters-duration-${spec.duration.tier});`);
20881
- lines.push(` animation-timing-function: var(--rafters-ease-${spec.curve});`);
20882
- }
20883
- if (token.reducedMotionAware !== false) {
20884
- lines.push(" @media (prefers-reduced-motion: reduce) {");
20885
- lines.push(" animation-duration: 0s;");
20886
- lines.push(" }");
20868
+ keys.set(
20869
+ `${spec.keyframe}-${period}`,
20870
+ `${spec.keyframe} var(--rafters-period-${period}) infinite`
20871
+ );
20872
+ continue;
20887
20873
  }
20888
- lines.push("}");
20874
+ const { tier } = spec.duration;
20875
+ keys.set(
20876
+ `${spec.keyframe}-${tier}-${spec.curve}`,
20877
+ `${spec.keyframe} var(--rafters-duration-${tier}) var(--rafters-ease-${spec.curve})`
20878
+ );
20889
20879
  }
20890
- return lines.join("\n");
20880
+ if (keys.size === 0) return "";
20881
+ return [
20882
+ " /* Motion assignments -- one key per distinct (shape, tier, curve) */",
20883
+ ...[...keys].map(([name, value]) => ` --animate-${name}: ${value};`)
20884
+ ].join("\n");
20891
20885
  }
20892
20886
  function generateMotionNamespaceVars(motionTokens) {
20893
20887
  const lines = [];
@@ -20903,35 +20897,45 @@ function generateMotionNamespaceVars(motionTokens) {
20903
20897
  ...lines
20904
20898
  ].join("\n");
20905
20899
  }
20900
+ function generateReducedMotionLaw(motionTokens) {
20901
+ const lines = [];
20902
+ for (const token of motionTokens) {
20903
+ const parts = motionNamespaceParts(token.name);
20904
+ if (!parts || !REDUCED_MOTION_ZEROED.has(parts.namespace)) continue;
20905
+ lines.push(` --${token.name}: 0ms;`);
20906
+ }
20907
+ if (lines.length === 0) return "";
20908
+ return [
20909
+ "/* Reduced motion is law: every duration and delay is zero, loops are not. */",
20910
+ "@media (prefers-reduced-motion: reduce) {",
20911
+ " :root {",
20912
+ ...lines,
20913
+ " }",
20914
+ "}"
20915
+ ].join("\n");
20916
+ }
20906
20917
  function generateMotionBridgeVars(motionTokens) {
20907
20918
  const lines = [];
20908
20919
  for (const token of motionTokens) {
20909
- if (token.name.startsWith("motion-duration-") && token.name !== "motion-duration-base") {
20910
- const key = token.name.replace("motion-duration-", "");
20911
- lines.push(` --duration-${key}: var(--rafters-duration-${key});`);
20912
- }
20913
- if (token.name.startsWith("motion-easing-")) {
20914
- const key = token.name.replace("motion-easing-", "");
20915
- lines.push(` --ease-${key}: var(--rafters-ease-${key});`);
20916
- }
20920
+ const parts = motionNamespaceParts(token.name);
20921
+ if (!parts) continue;
20922
+ const themePrefix = TAILWIND_THEME_PREFIX[parts.namespace];
20923
+ if (!themePrefix) continue;
20924
+ lines.push(` --${themePrefix}-${parts.member}: var(--${token.name});`);
20917
20925
  }
20918
20926
  return lines.join("\n");
20919
20927
  }
20920
20928
  function generateMotionNamespaceUtilities(motionTokens) {
20921
- const lines = ["/* The five motion namespaces -- one utility per member */"];
20929
+ const lines = ["/* Motion namespaces Tailwind has no theme key for */"];
20922
20930
  let emitted = 0;
20923
20931
  for (const token of motionTokens) {
20924
20932
  const parts = motionNamespaceParts(token.name);
20925
20933
  if (!parts) continue;
20934
+ if (TAILWIND_THEME_PREFIX[parts.namespace]) continue;
20926
20935
  const property = MOTION_NAMESPACE_PROPERTY[parts.namespace];
20927
20936
  emitted++;
20928
20937
  lines.push(`@utility ${parts.namespace}-${parts.member} {`);
20929
20938
  lines.push(` ${property}: var(--${token.name});`);
20930
- if (REDUCED_MOTION_ZEROED.has(parts.namespace)) {
20931
- lines.push(" @media (prefers-reduced-motion: reduce) {");
20932
- lines.push(` ${property}: 0ms;`);
20933
- lines.push(" }");
20934
- }
20935
20939
  lines.push("}");
20936
20940
  }
20937
20941
  return emitted === 0 ? "" : lines.join("\n");
@@ -21001,6 +21005,11 @@ function tokensToTailwind(tokens, options = {}, typographyOverrides = []) {
21001
21005
  if (keyframes) {
21002
21006
  sections.push(keyframes);
21003
21007
  }
21008
+ const reducedMotionLaw = generateReducedMotionLaw(groups.motion);
21009
+ if (reducedMotionLaw) {
21010
+ sections.push("");
21011
+ sections.push(reducedMotionLaw);
21012
+ }
21004
21013
  const typographyThemeInline = generateTypographyCompositeThemeInline(
21005
21014
  groups["typography-composite"]
21006
21015
  );
@@ -21028,11 +21037,6 @@ function tokensToTailwind(tokens, options = {}, typographyOverrides = []) {
21028
21037
  sections.push("");
21029
21038
  sections.push(motionUtilities);
21030
21039
  }
21031
- const cellUtilities = generateMotionCellUtilities(groups.motion);
21032
- if (cellUtilities) {
21033
- sections.push("");
21034
- sections.push(cellUtilities);
21035
- }
21036
21040
  const overrideCSS = generateTypographyOverrideCSS(typographyOverrides);
21037
21041
  if (overrideCSS) {
21038
21042
  sections.push("");
@@ -21054,8 +21058,8 @@ async function registryToCompiled(registry2, options = {}) {
21054
21058
  ${sourceDirectives}
21055
21059
  ${themeBody}`;
21056
21060
  const { execFileSync } = await import("child_process");
21057
- const { mkdtempSync, writeFileSync: writeFileSync2, readFileSync: readFileSync4, rmSync } = await import("fs");
21058
- const { join: join15, dirname: dirname4 } = await import("path");
21061
+ const { mkdtempSync, writeFileSync: writeFileSync2, readFileSync: readFileSync5, rmSync } = await import("fs");
21062
+ const { join: join16, dirname: dirname4 } = await import("path");
21059
21063
  const { createRequire: createRequire2 } = await import("module");
21060
21064
  const require2 = createRequire2(import.meta.url);
21061
21065
  let pkgDir;
@@ -21065,10 +21069,10 @@ ${themeBody}`;
21065
21069
  } catch {
21066
21070
  throw new Error("Failed to resolve @tailwindcss/cli");
21067
21071
  }
21068
- const binPath = join15(pkgDir, "dist", "index.mjs");
21069
- const tempDir = mkdtempSync(join15(pkgDir, ".tmp-compile-"));
21070
- const tempInput = join15(tempDir, "input.css");
21071
- const tempOutput = join15(tempDir, "output.css");
21072
+ const binPath = join16(pkgDir, "dist", "index.mjs");
21073
+ const tempDir = mkdtempSync(join16(pkgDir, ".tmp-compile-"));
21074
+ const tempInput = join16(tempDir, "input.css");
21075
+ const tempOutput = join16(tempDir, "output.css");
21072
21076
  try {
21073
21077
  writeFileSync2(tempInput, input);
21074
21078
  const args = [binPath, "-i", tempInput, "-o", tempOutput];
@@ -21076,7 +21080,7 @@ ${themeBody}`;
21076
21080
  args.push("--minify");
21077
21081
  }
21078
21082
  execFileSync("node", args, { stdio: "pipe", timeout: 3e4, cwd: pkgDir });
21079
- return readFileSync4(tempOutput, "utf-8");
21083
+ return readFileSync5(tempOutput, "utf-8");
21080
21084
  } catch (error47) {
21081
21085
  const message = error47 instanceof Error ? error47.message : String(error47);
21082
21086
  throw new Error(`Failed to compile CSS: ${message}`);
@@ -21252,7 +21256,7 @@ async function registryToDocumentation(registry2, options = {}) {
21252
21256
  const themeBody = registryToTailwind(registry2, { includeImport: false });
21253
21257
  const candidates = deriveCandidates(themeBody);
21254
21258
  const { mkdtempSync, writeFileSync: writeFileSync2, rmSync } = await import("fs");
21255
- const { join: join15, dirname: dirname4 } = await import("path");
21259
+ const { join: join16, dirname: dirname4 } = await import("path");
21256
21260
  const { createRequire: createRequire2 } = await import("module");
21257
21261
  const require2 = createRequire2(import.meta.url);
21258
21262
  let pkgDir;
@@ -21264,8 +21268,8 @@ async function registryToDocumentation(registry2, options = {}) {
21264
21268
  "Failed to resolve @tailwindcss/cli -- install it to generate documentation CSS"
21265
21269
  );
21266
21270
  }
21267
- const tempDir = mkdtempSync(join15(pkgDir, ".tmp-doc-compile-"));
21268
- const candidateFile = join15(tempDir, "candidates.txt");
21271
+ const tempDir = mkdtempSync(join16(pkgDir, ".tmp-doc-compile-"));
21272
+ const candidateFile = join16(tempDir, "candidates.txt");
21269
21273
  writeFileSync2(candidateFile, candidates.join("\n"));
21270
21274
  const sourceDirectives = [
21271
21275
  `@source "${candidateFile}";`,
@@ -21274,17 +21278,17 @@ async function registryToDocumentation(registry2, options = {}) {
21274
21278
  const input = `@import "tailwindcss" source(none);
21275
21279
  ${sourceDirectives}
21276
21280
  ${themeBody}`;
21277
- const tempInput = join15(tempDir, "input.css");
21278
- const tempOutput = join15(tempDir, "output.css");
21281
+ const tempInput = join16(tempDir, "input.css");
21282
+ const tempOutput = join16(tempDir, "output.css");
21279
21283
  try {
21280
21284
  writeFileSync2(tempInput, input);
21281
21285
  const { execFileSync } = await import("child_process");
21282
- const binPath = join15(pkgDir, "dist", "index.mjs");
21286
+ const binPath = join16(pkgDir, "dist", "index.mjs");
21283
21287
  const args = [binPath, "-i", tempInput, "-o", tempOutput];
21284
21288
  if (minify) args.push("--minify");
21285
21289
  execFileSync("node", args, { stdio: "pipe", timeout: 6e4, cwd: pkgDir });
21286
- const { readFileSync: readFileSync4 } = await import("fs");
21287
- const raw = readFileSync4(tempOutput, "utf-8");
21290
+ const { readFileSync: readFileSync5 } = await import("fs");
21291
+ const raw = readFileSync5(tempOutput, "utf-8");
21288
21292
  return postProcessDocSheet(raw);
21289
21293
  } catch (error47) {
21290
21294
  const message = error47 instanceof Error ? error47.message : String(error47);
@@ -22294,6 +22298,28 @@ var DEFAULT_KEYFRAME_DEFINITIONS = {
22294
22298
  meaning: "Scale down while fading out",
22295
22299
  contexts: ["modal-exit", "popover-close"]
22296
22300
  },
22301
+ "grow-in": {
22302
+ // scaleY, not a uniform scale -- a bar growing from the baseline to its
22303
+ // full height is STRUCTURAL geometry (fixed, like the accordion
22304
+ // chevron's 180deg rotation), not a pop: no opacity, no other numeric.
22305
+ // The transform-origin anchoring the growth at the bottom (the
22306
+ // value-axis baseline) is the caller-set `transform-origin`
22307
+ // (bar-chart.behavior.ts's `transformOriginFor`), not this keyframe --
22308
+ // this declares only the scaleY 0 -> 1 extent.
22309
+ css: () => "from { transform: scaleY(0); } to { transform: scaleY(1); }",
22310
+ meaning: "Grow from zero to full height, anchored at the baseline (caller-set transform-origin)",
22311
+ contexts: ["bar-chart", "value-display"]
22312
+ },
22313
+ "grow-in-x": {
22314
+ // The horizontal-layout counterpart of grow-in: scaleX, not scaleY --
22315
+ // `layout: 'horizontal'` swaps computeBars' value axis from y to x
22316
+ // (bar-chart.behavior.ts), so the structural growth axis swaps with it.
22317
+ // Same rationale as grow-in: no opacity, no other numeric, transform-
22318
+ // origin is caller-set (transformOriginFor).
22319
+ css: () => "from { transform: scaleX(0); } to { transform: scaleX(1); }",
22320
+ meaning: "Grow from zero to full width, anchored at the baseline (caller-set transform-origin)",
22321
+ contexts: ["bar-chart", "value-display"]
22322
+ },
22297
22323
  spin: {
22298
22324
  css: () => "from { transform: rotate(0deg); } to { transform: rotate(360deg); }",
22299
22325
  meaning: "Continuous rotation",
@@ -22739,6 +22765,38 @@ var DEFAULT_MOTION_CELL_ANIMATIONS = {
22739
22765
  meaning: "The incoming tab panel as the selection moves: the calendar month-change moment on a panel instead of a grid, one tier quicker because a panel swap covers no distance.",
22740
22766
  contexts: ["tabs", "panel", "crossfade"]
22741
22767
  },
22768
+ "bar-chart-bar-enter": {
22769
+ keyframe: "grow-in",
22770
+ duration: { kind: "tier", tier: "normal" },
22771
+ curve: "enter",
22772
+ cell: { component: "bar-chart", part: "bar", transition: "enter" },
22773
+ meaning: "A bar arriving: grows from zero at the value-axis baseline (bar-chart.behavior.ts sets the transform-origin), on the arrival curve. Vertical layout, the default -- see bar-chart-bar-enter-x for the horizontal counterpart.",
22774
+ contexts: ["bar-chart", "chart", "value-display"]
22775
+ },
22776
+ "bar-chart-bar-enter-x": {
22777
+ keyframe: "grow-in-x",
22778
+ duration: { kind: "tier", tier: "normal" },
22779
+ curve: "enter",
22780
+ cell: { component: "bar-chart", part: "bar", transition: "enter-horizontal" },
22781
+ meaning: 'A bar arriving under layout: "horizontal": grows from zero at the value-axis baseline, now the left edge (bar-chart.behavior.ts sets the transform-origin), on the same arrival curve as the vertical bar-chart-bar-enter cell -- only the transform property (scaleX, not scaleY) changes with the swapped axis.',
22782
+ contexts: ["bar-chart", "chart", "value-display"]
22783
+ },
22784
+ "area-chart-area-enter": {
22785
+ keyframe: "fade-in",
22786
+ duration: { kind: "tier", tier: "moderate" },
22787
+ curve: "enter",
22788
+ cell: { component: "area-chart", part: "area", transition: "enter" },
22789
+ meaning: "A filled area arriving on mount: fade, not scale -- unlike bar-chart-bar-enter, a stacked area series has no single baseline edge to grow from (its baseline is the previous series own top curve, area-chart.behavior.ts computeAreas), so opacity is the one property every area shares whether overlaid or stacked.",
22790
+ contexts: ["area-chart", "chart", "value-display"]
22791
+ },
22792
+ "line-chart-line-enter": {
22793
+ keyframe: "fade-in",
22794
+ duration: { kind: "tier", tier: "moderate" },
22795
+ curve: "enter",
22796
+ cell: { component: "line-chart", part: "line", transition: "enter" },
22797
+ meaning: "A line series arriving on mount: fades in rather than snapping into place. The matrix assigns opacity over a stroke-dashoffset reveal for this cell -- a dashoffset keyframe needs a per-instance path-length value nothing here names, where a fade needs none.",
22798
+ contexts: ["line-chart", "chart", "value-display"]
22799
+ },
22742
22800
  // ------------------------------------------------------------- load / appearance
22743
22801
  "avatar-image-load": {
22744
22802
  keyframe: "fade-in",
@@ -27105,6 +27163,32 @@ function generateBaseSystem(config2 = {}) {
27105
27163
  }
27106
27164
  };
27107
27165
  }
27166
+ function generateNamespaces(namespaces, config2 = {}) {
27167
+ const mergedConfig = {
27168
+ ...DEFAULT_SYSTEM_CONFIG,
27169
+ ...config2
27170
+ };
27171
+ const resolvedConfig = resolveConfig(mergedConfig);
27172
+ const byNamespace = /* @__PURE__ */ new Map();
27173
+ const allTokens = [];
27174
+ const generators = createGeneratorDefs(mergedConfig.colorPaletteBases);
27175
+ const requestedGenerators = generators.filter((g2) => namespaces.includes(g2.name));
27176
+ for (const { generate: generate4 } of requestedGenerators) {
27177
+ const result = generate4(resolvedConfig);
27178
+ byNamespace.set(result.namespace, result.tokens);
27179
+ allTokens.push(...result.tokens);
27180
+ }
27181
+ return {
27182
+ byNamespace,
27183
+ allTokens,
27184
+ metadata: {
27185
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
27186
+ config: resolvedConfig,
27187
+ tokenCount: allTokens.length,
27188
+ namespaces: Array.from(byNamespace.keys())
27189
+ }
27190
+ };
27191
+ }
27108
27192
 
27109
27193
  // ../design-tokens/src/importers/adapter.ts
27110
27194
  var adapters = /* @__PURE__ */ new Map();
@@ -28701,6 +28785,21 @@ var PropFieldSchema = external_exports.discriminatedUnion("type", [
28701
28785
  requires: external_exports.object({ prop: external_exports.string() })
28702
28786
  }).optional()
28703
28787
  }),
28788
+ external_exports.object({
28789
+ type: external_exports.literal("boolean"),
28790
+ default: external_exports.boolean().optional(),
28791
+ required: external_exports.boolean().optional()
28792
+ }),
28793
+ external_exports.object({
28794
+ type: external_exports.literal("string"),
28795
+ default: external_exports.string().optional(),
28796
+ required: external_exports.boolean().optional()
28797
+ }),
28798
+ external_exports.object({
28799
+ type: external_exports.literal("number"),
28800
+ default: external_exports.number().optional(),
28801
+ required: external_exports.boolean().optional()
28802
+ }),
28704
28803
  external_exports.object({
28705
28804
  // Matches #2072's PropNode 'grammar' arm exactly, so graph.ts's
28706
28805
  // describe(<id>.props.<name>.vocab) drill has real shape data.
@@ -29493,6 +29592,11 @@ function log(event) {
29493
29592
  console.log(" Skipped import. You can run `rafters import` later.");
29494
29593
  console.log("");
29495
29594
  break;
29595
+ // Motion rebuild events
29596
+ case "motion:override-dropped":
29597
+ context.spinner?.stop();
29598
+ console.warn(` Warning: ${event.message}`);
29599
+ break;
29496
29600
  default:
29497
29601
  if (context.spinner) {
29498
29602
  context.spinner.text = eventType;
@@ -29593,6 +29697,21 @@ async function installWithPackageManager(packageManager, dependencies, options)
29593
29697
 
29594
29698
  // src/utils/install-registry-deps.ts
29595
29699
  var RAFTERS_SCOPE_PREFIX = "@rafters/";
29700
+ var PLACEHOLDER_DEPENDENCY_NAMES = /* @__PURE__ */ new Set(["none", "n/a"]);
29701
+ function isPlaceholderDependencyName(name) {
29702
+ const normalized = name.trim().toLowerCase();
29703
+ return normalized === "" || PLACEHOLDER_DEPENDENCY_NAMES.has(normalized);
29704
+ }
29705
+ var PlaceholderDependencyError = class extends Error {
29706
+ constructor(itemName, dependency) {
29707
+ super(
29708
+ `Registry item "${itemName}" declares dependency "${dependency}", which is not a real npm package name -- it looks like a placeholder left in the manifest. Refusing to install it.`
29709
+ );
29710
+ this.itemName = itemName;
29711
+ this.dependency = dependency;
29712
+ this.name = "PlaceholderDependencyError";
29713
+ }
29714
+ };
29596
29715
  var REACT_RUNTIME_PACKAGES = /* @__PURE__ */ new Set(["react", "react-dom", "@types/react", "@types/react-dom"]);
29597
29716
  function parseDependency(dep) {
29598
29717
  const trimmed = dep.trim();
@@ -29639,6 +29758,16 @@ async function installRegistryDependencies(items, targetDir, options = {}) {
29639
29758
  devInstalled: [],
29640
29759
  failed: []
29641
29760
  };
29761
+ for (const item of items) {
29762
+ for (const file2 of item.files) {
29763
+ for (const dep of file2.dependencies) {
29764
+ const { name } = parseDependency(dep);
29765
+ if (isPlaceholderDependencyName(name)) {
29766
+ throw new PlaceholderDependencyError(item.name, dep);
29767
+ }
29768
+ }
29769
+ }
29770
+ }
29642
29771
  const allDeps = new Set(items.flatMap((item) => item.files.flatMap((file2) => file2.dependencies)));
29643
29772
  if (allDeps.size === 0) {
29644
29773
  return result;
@@ -29706,17 +29835,90 @@ async function installRegistryDependencies(items, targetDir, options = {}) {
29706
29835
  return result;
29707
29836
  }
29708
29837
 
29838
+ // src/utils/motion-rebuild.ts
29839
+ import { readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
29840
+ import { join as join5 } from "path";
29841
+ function hasStoredTokens(tokensDir) {
29842
+ try {
29843
+ return readdirSync2(tokensDir).some((entry) => entry.endsWith(".rafters.json"));
29844
+ } catch {
29845
+ return false;
29846
+ }
29847
+ }
29848
+ var MOTION_CELL_PREFIX = "motion-cell-";
29849
+ var MotionCellSpecSchema = external_exports.object({
29850
+ keyframe: external_exports.string(),
29851
+ duration: external_exports.discriminatedUnion("kind", [
29852
+ external_exports.object({ kind: external_exports.literal("tier"), tier: external_exports.string() }),
29853
+ external_exports.object({ kind: external_exports.literal("period"), period: external_exports.string() })
29854
+ ]),
29855
+ curve: external_exports.string().optional()
29856
+ });
29857
+ function isCarryableMotionValue(name, value) {
29858
+ if (!name.startsWith(MOTION_CELL_PREFIX)) return true;
29859
+ if (typeof value !== "string") return false;
29860
+ let parsed;
29861
+ try {
29862
+ parsed = JSON.parse(value);
29863
+ } catch {
29864
+ return true;
29865
+ }
29866
+ if (typeof parsed !== "object" || parsed === null) return true;
29867
+ return MotionCellSpecSchema.safeParse(parsed).success;
29868
+ }
29869
+ var StoredMotionTokenSchema = external_exports.object({
29870
+ name: external_exports.string(),
29871
+ value: TokenSchema.shape.value,
29872
+ userOverride: TokenSchema.shape.userOverride
29873
+ });
29874
+ function readStoredMotionOverrides(motionFile) {
29875
+ const carried = /* @__PURE__ */ new Map();
29876
+ let raw;
29877
+ try {
29878
+ raw = JSON.parse(readFileSync3(motionFile, "utf8"));
29879
+ } catch {
29880
+ return carried;
29881
+ }
29882
+ const file2 = external_exports.object({ tokens: external_exports.array(external_exports.unknown()) }).safeParse(raw);
29883
+ if (!file2.success) return carried;
29884
+ for (const entry of file2.data.tokens) {
29885
+ const parsed = StoredMotionTokenSchema.safeParse(entry);
29886
+ if (!parsed.success) continue;
29887
+ const { name, value, userOverride } = parsed.data;
29888
+ if (!userOverride) continue;
29889
+ carried.set(name, { value, userOverride });
29890
+ }
29891
+ return carried;
29892
+ }
29893
+ function regenerateMotionNamespace(tokensDir, plugins) {
29894
+ const carried = readStoredMotionOverrides(join5(tokensDir, "motion.rafters.json"));
29895
+ const tokens = generateNamespaces(["motion"]).allTokens.map((token) => {
29896
+ const override = carried.get(token.name);
29897
+ if (!override) return token;
29898
+ if (!isCarryableMotionValue(token.name, override.value)) {
29899
+ log({
29900
+ event: "motion:override-dropped",
29901
+ token: token.name,
29902
+ message: `Stored override on "${token.name}" holds a value shape this version no longer emits; the token is rebuilt from the generator and the override is dropped.`
29903
+ });
29904
+ return token;
29905
+ }
29906
+ return { ...token, ...override };
29907
+ });
29908
+ saveRegistryToDir(tokensDir, new TokenRegistry(tokens, plugins));
29909
+ }
29910
+
29709
29911
  // src/utils/paths.ts
29710
29912
  import { realpathSync as realpathSync2 } from "fs";
29711
- import { isAbsolute as isAbsolute2, join as join5, relative, resolve as resolve2 } from "path";
29913
+ import { isAbsolute as isAbsolute2, join as join6, relative, resolve as resolve2 } from "path";
29712
29914
  function getRaftersPaths(projectRoot = process.cwd()) {
29713
- const root = join5(projectRoot, ".rafters");
29915
+ const root = join6(projectRoot, ".rafters");
29714
29916
  return {
29715
29917
  root,
29716
- config: join5(root, "config.rafters.json"),
29717
- tokens: join5(root, "tokens"),
29718
- output: join5(root, "output"),
29719
- importPending: join5(root, "import-pending.json")
29918
+ config: join6(root, "config.rafters.json"),
29919
+ tokens: join6(root, "tokens"),
29920
+ output: join6(root, "output"),
29921
+ importPending: join6(root, "import-pending.json")
29720
29922
  };
29721
29923
  }
29722
29924
  var PathEntrySchema = external_exports.union([
@@ -29778,7 +29980,7 @@ function resolveReadSet(field, cwd, fallback) {
29778
29980
  }
29779
29981
 
29780
29982
  // src/utils/reconcile.ts
29781
- import { readdirSync as readdirSync2 } from "fs";
29983
+ import { readdirSync as readdirSync3 } from "fs";
29782
29984
  var DISCOVERABLE_KINDS = ["components", "primitives", "composites"];
29783
29985
  var KIND_PATHS = {
29784
29986
  components: { field: "componentsPath", fallback: "components/ui" },
@@ -29810,7 +30012,7 @@ function readInstallRoots(cwd, config2) {
29810
30012
  const names = /* @__PURE__ */ new Set();
29811
30013
  for (const dir of resolveReadSet(pathField, cwd, fallback)) {
29812
30014
  try {
29813
- for (const entry of readdirSync2(dir)) names.add(entry);
30015
+ for (const entry of readdirSync3(dir)) names.add(entry);
29814
30016
  } catch {
29815
30017
  }
29816
30018
  }
@@ -29837,14 +30039,11 @@ function migrateConfig(raw) {
29837
30039
  var REGISTRY_PLUGINS = [scalePlugin, contrastPlugin, statePlugin, invertPlugin];
29838
30040
  async function regenerateAfterInstall(cwd, config2) {
29839
30041
  const paths = getRaftersPaths(cwd);
29840
- let registry2;
29841
- try {
29842
- registry2 = loadRegistryFromDir(paths.tokens, REGISTRY_PLUGINS);
29843
- } catch {
29844
- return;
29845
- }
29846
- if (registry2.size() === 0) return;
30042
+ if (!hasStoredTokens(paths.tokens)) return;
29847
30043
  try {
30044
+ regenerateMotionNamespace(paths.tokens, REGISTRY_PLUGINS);
30045
+ const registry2 = loadRegistryFromDir(paths.tokens, REGISTRY_PLUGINS);
30046
+ if (registry2.size() === 0) return;
29848
30047
  await regenerateOutputs(registry2, {
29849
30048
  outputDir: paths.output,
29850
30049
  exports: config2.exports,
@@ -29922,10 +30121,16 @@ function getComponentTarget(config2) {
29922
30121
  return resolveComponentTarget(config2);
29923
30122
  }
29924
30123
  var SHARED_EXTENSIONS = /* @__PURE__ */ new Set([".behavior.ts", ".classes.ts"]);
30124
+ var FRAMEWORK_EXTENSIONS = [".tsx", ".astro", ".vue", ".svelte", ".element.ts"];
29925
30125
  function isSharedFile(path) {
30126
+ if (FRAMEWORK_EXTENSIONS.some((ext) => path.endsWith(ext))) return false;
29926
30127
  for (const ext of SHARED_EXTENSIONS) {
29927
30128
  if (path.endsWith(ext)) return true;
29928
30129
  }
30130
+ if (path.startsWith("components/") && path.endsWith(".ts")) {
30131
+ const afterComponents = path.slice("components/ui/".length);
30132
+ if (afterComponents.includes("/")) return true;
30133
+ }
29929
30134
  return false;
29930
30135
  }
29931
30136
  var REACT_FALLBACK_TARGETS = /* @__PURE__ */ new Set(["astro", "vue", "svelte"]);
@@ -30026,10 +30231,10 @@ function transformPath(registryPath, config2, cwd = process.cwd()) {
30026
30231
  function substrateProjectPath(registryPath, config2, cwd = process.cwd()) {
30027
30232
  const componentsResolved = rootFor(config2?.componentsPath, cwd, "components/ui");
30028
30233
  const sourceRoot = componentsResolved.replace(/\/?components\/ui$/, "");
30029
- return sourceRoot ? join6(sourceRoot, registryPath) : registryPath;
30234
+ return sourceRoot ? join7(sourceRoot, registryPath) : registryPath;
30030
30235
  }
30031
30236
  function fileExists(cwd, relativePath) {
30032
- return existsSync3(join6(cwd, relativePath));
30237
+ return existsSync3(join7(cwd, relativePath));
30033
30238
  }
30034
30239
  function transformFileContent(content, config2, fileType = "component", cwd = process.cwd(), opts = {}) {
30035
30240
  const { substrateKinds = [], installPath } = opts;
@@ -30041,28 +30246,64 @@ function transformFileContent(content, config2, fileType = "component", cwd = pr
30041
30246
  const aliasPrimitives = stripSourceRoot(primitivesPath);
30042
30247
  const toFlatPrimitive = (_match, subpath) => `from '@/${aliasPrimitives}/${basename(subpath)}'`;
30043
30248
  transformed = transformed.replace(
30044
- /from\s+['"]\.\.\/\.\.\/primitives\/([^'"]+)['"]/g,
30249
+ /from\s+['"](?:\.\.\/)+primitives\/([^'"]+)['"]/g,
30045
30250
  toFlatPrimitive
30046
30251
  );
30047
- transformed = transformed.replace(/from\s+['"]\.\.\/primitives\/([^'"]+)['"]/g, toFlatPrimitive);
30048
- const substrateOwnDir = fileType === "substrate" && installPath ? stripSourceRoot(dirname(installPath)) : null;
30049
- const aliasSibling = fileType === "primitive" ? aliasPrimitives : substrateOwnDir ?? aliasComponents;
30050
- transformed = transformed.replace(/from\s+['"]\.\/([^'"]+)['"]/g, `from '@/${aliasSibling}/$1'`);
30252
+ const isSubsystemFile = fileType === "component" && installPath?.startsWith("components/") && installPath.slice("components/ui/".length).includes("/");
30253
+ const SHARED_SUFFIX_BASES = [".behavior", ".classes", ".types", ".constants", ".styles"];
30254
+ function extractComponentName(path) {
30255
+ if (!path.startsWith("components/")) return null;
30256
+ let base = basename(path).replace(/\.(tsx?|jsx?|astro|vue|svelte)$/, "");
30257
+ for (const suffix of SHARED_SUFFIX_BASES) {
30258
+ if (base.endsWith(suffix)) {
30259
+ base = base.slice(0, -suffix.length);
30260
+ break;
30261
+ }
30262
+ }
30263
+ return base || null;
30264
+ }
30265
+ if (isSubsystemFile) {
30266
+ } else if (fileType === "component" && installPath) {
30267
+ const componentName = extractComponentName(installPath);
30268
+ transformed = transformed.replace(
30269
+ /from\s+['"]\.\/([^'"]+)['"]/g,
30270
+ (_match, importPath) => {
30271
+ if (SHARED_SUFFIX_BASES.some((s) => importPath.endsWith(s))) {
30272
+ return `from '@/${aliasComponents}/${importPath}'`;
30273
+ }
30274
+ if (componentName) {
30275
+ return `from '@/${aliasComponents}/${componentName}/${importPath}'`;
30276
+ }
30277
+ return `from '@/${aliasComponents}/${importPath}'`;
30278
+ }
30279
+ );
30280
+ } else {
30281
+ const substrateOwnDir = fileType === "substrate" && installPath ? stripSourceRoot(dirname(installPath)) : null;
30282
+ const aliasSibling = fileType === "primitive" ? aliasPrimitives : substrateOwnDir ?? aliasComponents;
30283
+ transformed = transformed.replace(
30284
+ /from\s+['"]\.\/([^'"]+)['"]/g,
30285
+ `from '@/${aliasSibling}/$1'`
30286
+ );
30287
+ }
30051
30288
  if (substrateKinds.length > 0) {
30052
30289
  const kindAlternation = substrateKinds.join("|");
30053
30290
  transformed = transformed.replace(
30054
- new RegExp(`from\\s+['"](?:\\.\\./){1,2}(${kindAlternation})/([^'"]+)['"]`, "g"),
30291
+ new RegExp(`from\\s+['"](?:\\.\\./)+?(${kindAlternation})/([^'"]+)['"]`, "g"),
30055
30292
  "from '@/$1/$2'"
30056
30293
  );
30057
30294
  }
30058
- transformed = transformed.replace(
30059
- /from\s+['"]\.\.\/([^/'"]+)\/\1([^'"]*)['"]/g,
30060
- `from '@/${aliasComponents}/$1$2'`
30061
- );
30062
- transformed = transformed.replace(
30063
- /from\s+['"]\.\.\/([^'"]+)['"]/g,
30064
- `from '@/${aliasComponents}/$1'`
30065
- );
30295
+ if (!isSubsystemFile) {
30296
+ transformed = transformed.replace(
30297
+ /from\s+['"]\.\.\/([^/'"]+)\/\1([^'"]*)['"]/g,
30298
+ `from '@/${aliasComponents}/$1$2'`
30299
+ );
30300
+ }
30301
+ if (!isSubsystemFile) {
30302
+ transformed = transformed.replace(
30303
+ /from\s+['"]\.\.\/([^'"]+)['"]/g,
30304
+ `from '@/${aliasComponents}/$1'`
30305
+ );
30306
+ }
30066
30307
  return transformed;
30067
30308
  }
30068
30309
  async function installItem(cwd, item, options, config2, substrateKinds = []) {
@@ -30092,7 +30333,7 @@ async function installItem(cwd, item, options, config2, substrateKinds = []) {
30092
30333
  }
30093
30334
  for (const file2 of filesToInstall) {
30094
30335
  const projectPath2 = item.type === "substrate" ? substrateProjectPath(file2.path, config2, cwd) : transformPath(file2.path, config2, cwd);
30095
- const targetPath = join6(cwd, projectPath2);
30336
+ const targetPath = join7(cwd, projectPath2);
30096
30337
  if (fileExists(cwd, projectPath2)) {
30097
30338
  if (!options.overwrite) {
30098
30339
  log({
@@ -30306,13 +30547,23 @@ async function add(componentArgs, options) {
30306
30547
  target
30307
30548
  });
30308
30549
  } catch (err) {
30309
- const message = err instanceof Error ? err.message : String(err);
30310
- log({
30311
- event: "add:deps:install-failed",
30312
- message: `Failed to process dependencies: ${message}`,
30313
- dependencies: [],
30314
- suggestion: "Check package.json and try installing dependencies manually."
30315
- });
30550
+ if (err instanceof PlaceholderDependencyError) {
30551
+ log({
30552
+ event: "add:deps:install-failed",
30553
+ message: err.message,
30554
+ dependencies: [],
30555
+ suggestion: "This is a registry defect, not a local problem -- do not install it manually."
30556
+ });
30557
+ process.exitCode = 1;
30558
+ } else {
30559
+ const message = err instanceof Error ? err.message : String(err);
30560
+ log({
30561
+ event: "add:deps:install-failed",
30562
+ message: `Failed to process dependencies: ${message}`,
30563
+ dependencies: [],
30564
+ suggestion: "Check package.json and try installing dependencies manually."
30565
+ });
30566
+ }
30316
30567
  }
30317
30568
  if (depsResult.installed.length > 0 || depsResult.skipped.length > 0) {
30318
30569
  log({
@@ -30371,11 +30622,11 @@ async function add(componentArgs, options) {
30371
30622
  // src/commands/agents.ts
30372
30623
  import { existsSync as existsSync6 } from "fs";
30373
30624
  import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
30374
- import { join as join11 } from "path";
30625
+ import { join as join12 } from "path";
30375
30626
 
30376
30627
  // src/mcp/tools.ts
30377
30628
  import { readdir as readdir2, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
30378
- import { isAbsolute as isAbsolute3, join as join10 } from "path";
30629
+ import { isAbsolute as isAbsolute3, join as join11 } from "path";
30379
30630
 
30380
30631
  // ../composites/src/built-in-rules/email.ts
30381
30632
  var email3 = external_exports.string().email();
@@ -30547,7 +30798,7 @@ var import_escape_html = __toESM(require_escape_html(), 1);
30547
30798
 
30548
30799
  // ../composites/src/discovery-node.ts
30549
30800
  import { readdir, readFile as readFile4 } from "fs/promises";
30550
- import { join as join7 } from "path";
30801
+ import { join as join8 } from "path";
30551
30802
  var COMPOSITE_SUFFIX = ".composite.json";
30552
30803
  async function readDir(directory) {
30553
30804
  const entries = [];
@@ -30558,7 +30809,7 @@ async function readDir(directory) {
30558
30809
  return entries;
30559
30810
  }
30560
30811
  for (const dirent of dirents) {
30561
- const fullPath = join7(directory, dirent.name);
30812
+ const fullPath = join8(directory, dirent.name);
30562
30813
  if (dirent.isDirectory()) {
30563
30814
  entries.push(...await readDir(fullPath));
30564
30815
  continue;
@@ -30585,16 +30836,16 @@ async function discoverFromDirs(...directories) {
30585
30836
  }
30586
30837
 
30587
30838
  // src/utils/workspaces.ts
30588
- import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
30589
- import { basename as basename2, dirname as dirname3, join as join9, resolve as resolve4 } from "path";
30839
+ import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
30840
+ import { basename as basename2, dirname as dirname3, join as join10, resolve as resolve4 } from "path";
30590
30841
 
30591
30842
  // src/utils/discover.ts
30592
30843
  import { existsSync as existsSync4 } from "fs";
30593
- import { dirname as dirname2, join as join8, resolve as resolve3 } from "path";
30844
+ import { dirname as dirname2, join as join9, resolve as resolve3 } from "path";
30594
30845
  function discoverProjectRoot(startDir) {
30595
30846
  let current = resolve3(startDir);
30596
30847
  for (; ; ) {
30597
- const configPath2 = join8(current, ".rafters", "config.rafters.json");
30848
+ const configPath2 = join9(current, ".rafters", "config.rafters.json");
30598
30849
  if (existsSync4(configPath2)) {
30599
30850
  return current;
30600
30851
  }
@@ -30611,16 +30862,16 @@ function findMonorepoRoot(startDir, boundary) {
30611
30862
  let current = resolve4(startDir);
30612
30863
  const stopAt = boundary ? resolve4(boundary) : null;
30613
30864
  for (; ; ) {
30614
- const pnpmWorkspace = join9(current, "pnpm-workspace.yaml");
30865
+ const pnpmWorkspace = join10(current, "pnpm-workspace.yaml");
30615
30866
  if (existsSync5(pnpmWorkspace)) {
30616
- const patterns = parsePnpmWorkspaceYaml(readFileSync3(pnpmWorkspace, "utf-8"));
30867
+ const patterns = parsePnpmWorkspaceYaml(readFileSync4(pnpmWorkspace, "utf-8"));
30617
30868
  if (patterns.length > 0) {
30618
30869
  return { root: current, patterns };
30619
30870
  }
30620
30871
  }
30621
- const pkgJson = join9(current, "package.json");
30872
+ const pkgJson = join10(current, "package.json");
30622
30873
  if (existsSync5(pkgJson)) {
30623
- const patterns = parsePackageJsonWorkspaces(readFileSync3(pkgJson, "utf-8"));
30874
+ const patterns = parsePackageJsonWorkspaces(readFileSync4(pkgJson, "utf-8"));
30624
30875
  if (patterns.length > 0) {
30625
30876
  return { root: current, patterns };
30626
30877
  }
@@ -30674,9 +30925,9 @@ function expandPattern(monorepoRoot, pattern) {
30674
30925
  const trimmed = pattern.replace(/\/+$/, "");
30675
30926
  if (trimmed.endsWith("/*")) {
30676
30927
  const parentRel = trimmed.slice(0, -2);
30677
- const parent = join9(monorepoRoot, parentRel);
30928
+ const parent = join10(monorepoRoot, parentRel);
30678
30929
  if (!existsSync5(parent)) return [];
30679
- return readdirSync3(parent).map((entry) => join9(parent, entry)).filter((path) => {
30930
+ return readdirSync4(parent).map((entry) => join10(parent, entry)).filter((path) => {
30680
30931
  try {
30681
30932
  return statSync2(path).isDirectory();
30682
30933
  } catch {
@@ -30684,7 +30935,7 @@ function expandPattern(monorepoRoot, pattern) {
30684
30935
  }
30685
30936
  });
30686
30937
  }
30687
- const literal2 = join9(monorepoRoot, trimmed);
30938
+ const literal2 = join10(monorepoRoot, trimmed);
30688
30939
  if (existsSync5(literal2)) {
30689
30940
  try {
30690
30941
  if (statSync2(literal2).isDirectory()) return [literal2];
@@ -30708,13 +30959,13 @@ function discoverWorkspaces(startDir = process.cwd(), options = {}) {
30708
30959
  for (const dir of expandPattern(layout.root, pattern)) {
30709
30960
  if (seen.has(dir)) continue;
30710
30961
  seen.add(dir);
30711
- const config2 = join9(dir, ".rafters", "config.rafters.json");
30962
+ const config2 = join10(dir, ".rafters", "config.rafters.json");
30712
30963
  if (existsSync5(config2)) {
30713
30964
  workspaces.push({ name: basename2(dir), root: dir });
30714
30965
  }
30715
30966
  }
30716
30967
  }
30717
- const rootConfig = join9(layout.root, ".rafters", "config.rafters.json");
30968
+ const rootConfig = join10(layout.root, ".rafters", "config.rafters.json");
30718
30969
  if (existsSync5(rootConfig) && !seen.has(layout.root)) {
30719
30970
  workspaces.unshift({ name: basename2(layout.root), root: layout.root });
30720
30971
  }
@@ -31231,7 +31482,7 @@ var TOOL_DEFINITIONS = [
31231
31482
  },
31232
31483
  {
31233
31484
  name: "rafters_describe",
31234
- description: `Recursively introspect the component/composite intel graph. describe() returns the installed surface; describe(components)/describe(composites) list the kind roster; describe(button) returns a node -- intel plus type-marked, drillable children; describe(button.props.variant) drills into a prop -- an enum prop returns its literal union members; a grammar prop returns its composition rules, with the token vocabulary deliberately withheld. describe(button.*) expands all props inline in one call (no more drill-per-prop round trips); describe(button.props.color.?) probes safely (null on miss, not an error). A natural-language address (e.g. "what do I use when it needs to be above everything") routes through a separate intent door instead: deterministic keyword matching over a small, curated tag axis, returning a best-match node plus its near-miss counter-example. Below its match threshold it refuses rather than guessing, with a note pointing you at describe(components)/describe(composites) to browse; #2166 (open) is the follow-up that scores it against the intel this tool already owns. A part node carries parent, plus siblings when its parent has other parts; children are typed pointers you feed back in (the prop's own type for props, part for sub-components, edge for composesWith).`,
31485
+ description: `Recursively introspect the component/composite intel graph. describe() returns the installed surface; describe(components)/describe(composites) list the kind roster; describe(button) returns a node -- intel plus type-marked, drillable children; describe(button.props.variant) drills into a prop -- an enum prop returns its literal union members; a boolean, string, or number prop returns its kind with any default and whether it is required, and no value list, because it has no finite one; a grammar prop returns its composition rules, with the token vocabulary deliberately withheld. describe(button.*) expands all props inline in one call (no more drill-per-prop round trips); describe(button.props.color.?) probes safely (null on miss, not an error). A natural-language address (e.g. "what do I use when it needs to be above everything") routes through a separate intent door instead: deterministic keyword matching over a small, curated tag axis, returning a best-match node plus its near-miss counter-example. Below its match threshold it refuses rather than guessing, with a note pointing you at describe(components)/describe(composites) to browse; #2166 (open) is the follow-up that scores it against the intel this tool already owns. A part node carries parent, plus siblings when its parent has other parts; children are typed pointers you feed back in (the prop's own type for props, part for sub-components, edge for composesWith).`,
31235
31486
  inputSchema: {
31236
31487
  type: "object",
31237
31488
  properties: {
@@ -31480,7 +31731,7 @@ var RaftersToolHandler = class {
31480
31731
  async ensureCompositesLoaded(workspace) {
31481
31732
  if (!this.builtInCompositesLoaded) {
31482
31733
  await this.loadCompositesFromDirs(
31483
- join10(process.cwd(), "node_modules/@rafters/composites/src/typography")
31734
+ join11(process.cwd(), "node_modules/@rafters/composites/src/typography")
31484
31735
  );
31485
31736
  this.builtInCompositesLoaded = true;
31486
31737
  }
@@ -31599,7 +31850,7 @@ var RaftersToolHandler = class {
31599
31850
  */
31600
31851
  async scanInstalled(workspaceRoot, paths) {
31601
31852
  const idsUnder = async (field, fallback) => {
31602
- const roots = field ? resolveReadSet(field, workspaceRoot) : [join10(workspaceRoot, fallback)];
31853
+ const roots = field ? resolveReadSet(field, workspaceRoot) : [join11(workspaceRoot, fallback)];
31603
31854
  const ids = /* @__PURE__ */ new Set();
31604
31855
  for (const root of roots) {
31605
31856
  let entries;
@@ -31758,7 +32009,7 @@ var RaftersToolHandler = class {
31758
32009
  const paths = getRaftersPaths(workspaceRoot);
31759
32010
  const config2 = await this.readConfig(workspaceRoot);
31760
32011
  if (!config2?.compositesPath) {
31761
- return [join10(paths.root, "composites")];
32012
+ return [join11(paths.root, "composites")];
31762
32013
  }
31763
32014
  return resolveReadSet(config2.compositesPath, workspaceRoot);
31764
32015
  }
@@ -31973,7 +32224,7 @@ async function agents() {
31973
32224
  `No rafters project found: ${paths.config} is missing. Run \`rafters init\` first.`
31974
32225
  );
31975
32226
  }
31976
- const agentsPath = join11(cwd, "AGENTS.md");
32227
+ const agentsPath = join12(cwd, "AGENTS.md");
31977
32228
  const existing = existsSync6(agentsPath) ? await readFile6(agentsPath, "utf-8") : null;
31978
32229
  const merged = mergeAgentContract(existing, generateAgentContract());
31979
32230
  await writeFile4(agentsPath, merged);
@@ -31983,7 +32234,7 @@ async function agents() {
31983
32234
  import { existsSync as existsSync7 } from "fs";
31984
32235
  import { copyFile, mkdir as mkdir3, readFile as readFile7, rm, writeFile as writeFile5 } from "fs/promises";
31985
32236
  import { createRequire } from "module";
31986
- import { join as join12, relative as relative2 } from "path";
32237
+ import { join as join13, relative as relative2 } from "path";
31987
32238
  import { checkbox, confirm, select } from "@inquirer/prompts";
31988
32239
  var REGISTRY_PLUGINS2 = [scalePlugin, contrastPlugin, statePlugin, invertPlugin];
31989
32240
  async function backupCss(cssPath) {
@@ -32067,7 +32318,7 @@ function cleanSourceCssBlocks(content) {
32067
32318
  }
32068
32319
  async function stripImportedDeclarations(cwd, cssPath, importedNames) {
32069
32320
  if (importedNames.length === 0) return;
32070
- const fullPath = join12(cwd, cssPath);
32321
+ const fullPath = join13(cwd, cssPath);
32071
32322
  const content = await readFile7(fullPath, "utf-8");
32072
32323
  const pattern = new RegExp(importedNames.map((n2) => `^\\s*--${n2}[^;]*;\\s*$`).join("|"), "gm");
32073
32324
  const cleaned = cleanSourceCssBlocks(content.replace(pattern, ""));
@@ -32075,10 +32326,10 @@ async function stripImportedDeclarations(cwd, cssPath, importedNames) {
32075
32326
  await writeFile5(fullPath, collapsed);
32076
32327
  }
32077
32328
  async function updateMainCss(cwd, cssPath, themePath) {
32078
- const fullCssPath = join12(cwd, cssPath);
32329
+ const fullCssPath = join13(cwd, cssPath);
32079
32330
  const cssContent = await readFile7(fullCssPath, "utf-8");
32080
- const cssDir = join12(cwd, cssPath, "..");
32081
- const themeFullPath = join12(cwd, themePath);
32331
+ const cssDir = join13(cwd, cssPath, "..");
32332
+ const themeFullPath = join13(cwd, themePath);
32082
32333
  const relativeThemePath = relative2(cssDir, themeFullPath);
32083
32334
  if (cssContent.includes(".rafters/output/rafters.css")) {
32084
32335
  log({ event: "init:css_already_imported", cssPath });
@@ -32107,7 +32358,7 @@ function isInteractive() {
32107
32358
  }
32108
32359
  function resolveSourceCssPath(cwd, framework, detectedFramework, projectCssPath, shadcnCssPath, emit) {
32109
32360
  if (shadcnCssPath) {
32110
- if (existsSync7(join12(cwd, shadcnCssPath))) return shadcnCssPath;
32361
+ if (existsSync7(join13(cwd, shadcnCssPath))) return shadcnCssPath;
32111
32362
  const fallback = framework === detectedFramework ? projectCssPath : findCssPath(cwd, framework);
32112
32363
  emit({
32113
32364
  event: "init:shadcn_css_missing",
@@ -32242,8 +32493,11 @@ async function regenerateFromExisting(cwd, paths, source, isAgentMode2, framewor
32242
32493
  existingConfig.compositesPath = frameworkPaths.composites;
32243
32494
  existingConfig.rulesPath = frameworkPaths.rules;
32244
32495
  }
32245
- await rm(join12(paths.tokens, "elevation.rafters.json"), { force: true });
32246
- await rm(join12(paths.tokens, "fill.rafters.json"), { force: true });
32496
+ await rm(join13(paths.tokens, "elevation.rafters.json"), { force: true });
32497
+ await rm(join13(paths.tokens, "fill.rafters.json"), { force: true });
32498
+ if (hasStoredTokens(paths.tokens)) {
32499
+ regenerateMotionNamespace(paths.tokens, REGISTRY_PLUGINS2);
32500
+ }
32247
32501
  const registry2 = loadRegistryFromDir(paths.tokens, REGISTRY_PLUGINS2);
32248
32502
  if (registry2.size() === 0) {
32249
32503
  throw new Error("No tokens found. Cannot regenerate without existing tokens.");
@@ -32338,7 +32592,7 @@ async function resetToDefaults(cwd, paths, source, isAgentMode2, framework) {
32338
32592
  };
32339
32593
  await mkdir3(paths.output, { recursive: true });
32340
32594
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
32341
- const backupPath = join12(paths.output, `reset-${timestamp}.json`);
32595
+ const backupPath = join13(paths.output, `reset-${timestamp}.json`);
32342
32596
  await writeFile5(backupPath, JSON.stringify(backup, null, 2));
32343
32597
  log({
32344
32598
  event: "init:reset_backup",
@@ -32476,7 +32730,7 @@ async function init(options) {
32476
32730
  );
32477
32731
  if (sourceCssPathForBase !== null) {
32478
32732
  try {
32479
- const cssForBase = await readFile7(join12(cwd, sourceCssPathForBase), "utf-8");
32733
+ const cssForBase = await readFile7(join13(cwd, sourceCssPathForBase), "utf-8");
32480
32734
  const baseSlots = [
32481
32735
  {
32482
32736
  detect: () => adapter.detectSpacing(cssForBase).base,
@@ -32599,7 +32853,7 @@ async function init(options) {
32599
32853
  if (detectedCssPath) {
32600
32854
  let sourceCss = null;
32601
32855
  try {
32602
- sourceCss = await readFile7(join12(cwd, detectedCssPath), "utf-8");
32856
+ sourceCss = await readFile7(join13(cwd, detectedCssPath), "utf-8");
32603
32857
  } catch (err) {
32604
32858
  if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) throw err;
32605
32859
  }
@@ -32866,7 +33120,7 @@ async function init(options) {
32866
33120
 
32867
33121
  // src/commands/mcp.ts
32868
33122
  import { existsSync as existsSync8 } from "fs";
32869
- import { basename as basename3, join as join13, resolve as resolve5 } from "path";
33123
+ import { basename as basename3, join as join14, resolve as resolve5 } from "path";
32870
33124
 
32871
33125
  // src/mcp/server.ts
32872
33126
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -32919,7 +33173,7 @@ async function mcp(options) {
32919
33173
  let defaultWorkspace;
32920
33174
  if (options.projectRoot) {
32921
33175
  const explicit = resolve5(options.projectRoot);
32922
- const configPath2 = join13(explicit, ".rafters", "config.rafters.json");
33176
+ const configPath2 = join14(explicit, ".rafters", "config.rafters.json");
32923
33177
  if (!existsSync8(configPath2)) {
32924
33178
  process.stderr.write(
32925
33179
  `--project-root ${explicit} does not contain .rafters/config.rafters.json
@@ -32945,7 +33199,7 @@ import { resolve as resolve6 } from "path";
32945
33199
 
32946
33200
  // ../studio/src/api/vite-plugin.ts
32947
33201
  import { readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
32948
- import { isAbsolute as isAbsolute4, join as join14 } from "path";
33202
+ import { isAbsolute as isAbsolute4, join as join15 } from "path";
32949
33203
  var REGISTRY_PLUGINS3 = [scalePlugin, contrastPlugin, statePlugin, invertPlugin];
32950
33204
  var STUDIO_REASON_DEFAULT = "studio interactive edit";
32951
33205
  var TokenResponseSchema = external_exports.object({
@@ -32971,8 +33225,8 @@ var ColorBuildOptionsSchema = external_exports.object({
32971
33225
  states: external_exports.record(external_exports.string(), external_exports.string()).optional()
32972
33226
  });
32973
33227
  var projectPath = process.env.RAFTERS_PROJECT_PATH || process.cwd();
32974
- var outputDir = join14(projectPath, ".rafters", "output");
32975
- var configPath = join14(projectPath, ".rafters", "config.rafters.json");
33228
+ var outputDir = join15(projectPath, ".rafters", "output");
33229
+ var configPath = join15(projectPath, ".rafters", "config.rafters.json");
32976
33230
  var FontsConfigSchema = external_exports.object({
32977
33231
  path: external_exports.union([external_exports.string(), external_exports.null()]).optional(),
32978
33232
  imports: external_exports.array(external_exports.string()).optional()
@@ -33399,7 +33653,7 @@ function studioApiPlugin() {
33399
33653
  return {
33400
33654
  name: "rafters-studio-api",
33401
33655
  async configureServer(server) {
33402
- const tokensDir = join14(projectPath, ".rafters", "tokens");
33656
+ const tokensDir = join15(projectPath, ".rafters", "tokens");
33403
33657
  try {
33404
33658
  registry2 = loadRegistryFromDir(tokensDir, REGISTRY_PLUGINS3);
33405
33659
  initialized = true;
@@ -33462,11 +33716,11 @@ function studioApiPlugin() {
33462
33716
  const classDirs = watchConfig ? resolveContentSources(projectPath, watchConfig) : [];
33463
33717
  const newDirs = classDirs.filter((d2) => !trackedClassDirs.has(d2));
33464
33718
  if (newDirs.length > 0) {
33465
- server.watcher.add(newDirs.map((dir) => join14(dir, "**/*.classes.ts")));
33719
+ server.watcher.add(newDirs.map((dir) => join15(dir, "**/*.classes.ts")));
33466
33720
  for (const d2 of newDirs) trackedClassDirs.add(d2);
33467
33721
  }
33468
33722
  };
33469
- server.watcher.add([join14(tokensDir, "*.rafters.json"), configPath]);
33723
+ server.watcher.add([join15(tokensDir, "*.rafters.json"), configPath]);
33470
33724
  await syncWatchTargets();
33471
33725
  let debounce = null;
33472
33726
  const onChange = (changed) => {
@@ -96,6 +96,18 @@ declare const PropFieldSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
96
96
  prop: z.ZodString;
97
97
  }, z.core.$strip>;
98
98
  }, z.core.$strip>>;
99
+ }, z.core.$strip>, z.ZodObject<{
100
+ type: z.ZodLiteral<"boolean">;
101
+ default: z.ZodOptional<z.ZodBoolean>;
102
+ required: z.ZodOptional<z.ZodBoolean>;
103
+ }, z.core.$strip>, z.ZodObject<{
104
+ type: z.ZodLiteral<"string">;
105
+ default: z.ZodOptional<z.ZodString>;
106
+ required: z.ZodOptional<z.ZodBoolean>;
107
+ }, z.core.$strip>, z.ZodObject<{
108
+ type: z.ZodLiteral<"number">;
109
+ default: z.ZodOptional<z.ZodNumber>;
110
+ required: z.ZodOptional<z.ZodBoolean>;
99
111
  }, z.core.$strip>, z.ZodObject<{
100
112
  type: z.ZodLiteral<"grammar">;
101
113
  grammar: z.ZodArray<z.ZodString>;
@@ -126,6 +138,18 @@ declare const FacetSchema: z.ZodObject<{
126
138
  prop: z.ZodString;
127
139
  }, z.core.$strip>;
128
140
  }, z.core.$strip>>;
141
+ }, z.core.$strip>, z.ZodObject<{
142
+ type: z.ZodLiteral<"boolean">;
143
+ default: z.ZodOptional<z.ZodBoolean>;
144
+ required: z.ZodOptional<z.ZodBoolean>;
145
+ }, z.core.$strip>, z.ZodObject<{
146
+ type: z.ZodLiteral<"string">;
147
+ default: z.ZodOptional<z.ZodString>;
148
+ required: z.ZodOptional<z.ZodBoolean>;
149
+ }, z.core.$strip>, z.ZodObject<{
150
+ type: z.ZodLiteral<"number">;
151
+ default: z.ZodOptional<z.ZodNumber>;
152
+ required: z.ZodOptional<z.ZodBoolean>;
129
153
  }, z.core.$strip>, z.ZodObject<{
130
154
  type: z.ZodLiteral<"grammar">;
131
155
  grammar: z.ZodArray<z.ZodString>;
@@ -195,6 +219,18 @@ declare const RegistryItemSchema: z.ZodObject<{
195
219
  prop: z.ZodString;
196
220
  }, z.core.$strip>;
197
221
  }, z.core.$strip>>;
222
+ }, z.core.$strip>, z.ZodObject<{
223
+ type: z.ZodLiteral<"boolean">;
224
+ default: z.ZodOptional<z.ZodBoolean>;
225
+ required: z.ZodOptional<z.ZodBoolean>;
226
+ }, z.core.$strip>, z.ZodObject<{
227
+ type: z.ZodLiteral<"string">;
228
+ default: z.ZodOptional<z.ZodString>;
229
+ required: z.ZodOptional<z.ZodBoolean>;
230
+ }, z.core.$strip>, z.ZodObject<{
231
+ type: z.ZodLiteral<"number">;
232
+ default: z.ZodOptional<z.ZodNumber>;
233
+ required: z.ZodOptional<z.ZodBoolean>;
198
234
  }, z.core.$strip>, z.ZodObject<{
199
235
  type: z.ZodLiteral<"grammar">;
200
236
  grammar: z.ZodArray<z.ZodString>;
@@ -33,6 +33,21 @@ var PropFieldSchema = z.discriminatedUnion("type", [
33
33
  requires: z.object({ prop: z.string() })
34
34
  }).optional()
35
35
  }),
36
+ z.object({
37
+ type: z.literal("boolean"),
38
+ default: z.boolean().optional(),
39
+ required: z.boolean().optional()
40
+ }),
41
+ z.object({
42
+ type: z.literal("string"),
43
+ default: z.string().optional(),
44
+ required: z.boolean().optional()
45
+ }),
46
+ z.object({
47
+ type: z.literal("number"),
48
+ default: z.number().optional(),
49
+ required: z.boolean().optional()
50
+ }),
36
51
  z.object({
37
52
  // Matches #2072's PropNode 'grammar' arm exactly, so graph.ts's
38
53
  // describe(<id>.props.<name>.vocab) drill has real shape data.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rafters",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Design Intelligence CLI. Scaffold tokens, import existing shadcn/Tailwind v4 sources, add components, and serve an MCP server so AI agents read decisions instead of guessing.",
5
5
  "homepage": "https://rafters.studio",
6
6
  "license": "MIT",