@vegastack/design 0.7.57 → 0.7.59

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.
@@ -5,7 +5,8 @@
5
5
  //
6
6
  // HOW IT WORKS (one index request + bounded item requests for installed copies):
7
7
  // 1. Resolve the @vegastack registry url + auth headers from components.json (or env).
8
- // 2. Scan the components dir for files carrying the provenance header the registry stamps:
8
+ // 2. Scan the components dir — plus `aliases.lib` / `aliases.hooks` for `@lib/…` and `@hooks/…`
9
+ // targets such as `date-time` — for files carrying the provenance header the registry stamps:
9
10
  // // @vegastack <name>@<version> sha256-<integrity>
10
11
  // That header IS the version pin recorded at copy-in time.
11
12
  // 3. Fetch the registry INDEX once (…/registry.json) — every item carries meta.version + meta.integrity.
@@ -357,10 +358,8 @@ function indexUrl(urlTemplate) {
357
358
  }
358
359
 
359
360
  // ── components dir + scan ──────────────────────────────────────────────────────────────────────
360
- function resolveComponentsDir(cwd, dirFlag, componentsJson) {
361
- if (dirFlag) return resolve(cwd, dirFlag);
362
- const ui = componentsJson?.aliases?.ui;
363
- const rel = ui ? ui.replace(/^@\//, "").replace(/^~\//, "") : "components/ui";
361
+ function resolveAliasDir(cwd, alias, fallback) {
362
+ const rel = alias ? alias.replace(/^@\//, "").replace(/^~\//, "") : fallback;
364
363
  const direct = resolve(cwd, rel); // assumes @/ = project root
365
364
  if (existsSync(direct)) return direct;
366
365
  const srcVariant = resolve(cwd, "src", rel); // common alias: @/* -> src/*
@@ -368,6 +367,35 @@ function resolveComponentsDir(cwd, dirFlag, componentsJson) {
368
367
  return direct; // documented default; "none found" message hints --dir
369
368
  }
370
369
 
370
+ function resolveComponentsDir(cwd, dirFlag, componentsJson) {
371
+ if (dirFlag) return resolve(cwd, dirFlag);
372
+ return resolveAliasDir(cwd, componentsJson?.aliases?.ui, "components/ui");
373
+ }
374
+
375
+ /**
376
+ * Where registry files land besides the components dir: `@lib/…` targets (e.g. `date-time`,
377
+ * `page-layout`) under `aliases.lib`, `@hooks/…` under `aliases.hooks`. Scanned only when
378
+ * components.json names the alias and the directory exists — so a locally edited lib file is
379
+ * drift exactly like an edited component.
380
+ */
381
+ function resolveExtraRoots(cwd, componentsJson, uiDir) {
382
+ const roots = [];
383
+ for (const kind of ["lib", "hooks"]) {
384
+ const alias = componentsJson?.aliases?.[kind];
385
+ if (!alias) continue;
386
+ const dir = resolveAliasDir(cwd, alias, kind);
387
+ if (!existsSync(dir)) continue;
388
+ const overlaps = [uiDir, ...roots.map((r) => r.dir)].some(
389
+ (other) =>
390
+ dir === other ||
391
+ dir.startsWith(other + sep) ||
392
+ other.startsWith(dir + sep),
393
+ );
394
+ if (!overlaps) roots.push({ kind, dir });
395
+ }
396
+ return roots;
397
+ }
398
+
371
399
  function walk(dir, out = [], isRoot = true) {
372
400
  let rootStat;
373
401
  try {
@@ -410,7 +438,7 @@ function walk(dir, out = [], isRoot = true) {
410
438
  // pipeline strips leading comments during its transform, so most consumer copies have NO
411
439
  // header. Headerless files are identified by filename against the registry index and compared
412
440
  // by alias-normalized CONTENT instead (see `normalizeForCompare`).
413
- function readInstalled(file, root) {
441
+ function readInstalled(file, root, kind = "ui") {
414
442
  let content;
415
443
  try {
416
444
  content = readFileSync(file, "utf8");
@@ -423,7 +451,7 @@ function readInstalled(file, root) {
423
451
  );
424
452
  const m = PROVENANCE_RE.exec(firstLine);
425
453
  const name = basename(file).replace(/\.tsx?$/, "");
426
- const relativePath = relative(root, file).split(sep).join("/");
454
+ const relativePath = `${kind}/${relative(root, file).split(sep).join("/")}`;
427
455
  return m
428
456
  ? {
429
457
  file,
@@ -464,10 +492,19 @@ function expectedVariantsForCompare(content, label, aliases, fileType) {
464
492
  return new Set([exact, stripped]);
465
493
  }
466
494
 
467
- function uiTargetPath(file) {
468
- return typeof file?.target === "string" && file.target.startsWith("@ui/")
469
- ? file.target.slice("@ui/".length)
470
- : undefined;
495
+ const TARGET_ROOTS = [
496
+ ["@ui/", "ui"],
497
+ ["@lib/", "lib"],
498
+ ["@hooks/", "hooks"],
499
+ ];
500
+
501
+ /** A registry file's install location as `<root kind>/<path>`, or undefined for other targets. */
502
+ function targetPath(file) {
503
+ if (typeof file?.target !== "string") return undefined;
504
+ for (const [prefix, kind] of TARGET_ROOTS)
505
+ if (file.target.startsWith(prefix))
506
+ return `${kind}/${file.target.slice(prefix.length)}`;
507
+ return undefined;
471
508
  }
472
509
 
473
510
  async function mapLimit(values, limit, fn) {
@@ -611,7 +648,7 @@ export async function main(argv) {
611
648
  }
612
649
  remote.set(item.name, item);
613
650
  for (const file of item.files) {
614
- const target = uiTargetPath(file);
651
+ const target = targetPath(file);
615
652
  if (!target) continue;
616
653
  if (targetOwners.has(target)) {
617
654
  console.error(
@@ -630,15 +667,19 @@ export async function main(argv) {
630
667
  // - headerless files NOT in the index are skipped (they're the consumer's own components)
631
668
  let installedFiles;
632
669
  try {
633
- installedFiles = walk(dir);
670
+ installedFiles = [
671
+ ...walk(dir).map((file) => readInstalled(file, dir, "ui")),
672
+ ...(opts.dir ? [] : resolveExtraRoots(cwd, componentsJson, dir)).flatMap(
673
+ ({ kind, dir: extra }) =>
674
+ walk(extra).map((file) => readInstalled(file, extra, kind)),
675
+ ),
676
+ ];
634
677
  } catch (err) {
635
678
  console.error(`✗ ${terminalText(err.message)}`);
636
679
  return 2;
637
680
  }
638
681
  const grouped = new Map();
639
- for (const installedFile of installedFiles
640
- .map((file) => readInstalled(file, dir))
641
- .filter(Boolean)) {
682
+ for (const installedFile of installedFiles.filter(Boolean)) {
642
683
  const itemName = installedFile.header
643
684
  ? installedFile.name
644
685
  : targetOwners.get(installedFile.relativePath);
@@ -708,7 +749,7 @@ export async function main(argv) {
708
749
  status: "missing",
709
750
  };
710
751
 
711
- const expectedTargets = (r.files ?? []).map(uiTargetPath).filter(Boolean);
752
+ const expectedTargets = (r.files ?? []).map(targetPath).filter(Boolean);
712
753
  const installedByTarget = new Map(
713
754
  group.files.map((file) => [file.relativePath, file]),
714
755
  );
@@ -735,7 +776,7 @@ export async function main(argv) {
735
776
  if (item.name !== group.name || !Array.isArray(item.files)) {
736
777
  throw new Error("registry item identity/files contract mismatch");
737
778
  }
738
- const fetchedTargets = item.files.map(uiTargetPath).filter(Boolean);
779
+ const fetchedTargets = item.files.map(targetPath).filter(Boolean);
739
780
  const sortedFetchedTargets = [...fetchedTargets].sort();
740
781
  const sortedExpectedTargets = [...expectedTargets].sort();
741
782
  if (
@@ -751,7 +792,7 @@ export async function main(argv) {
751
792
  }
752
793
  if (
753
794
  item.files.some(
754
- (entry) => uiTargetPath(entry) && typeof entry.content !== "string",
795
+ (entry) => targetPath(entry) && typeof entry.content !== "string",
755
796
  )
756
797
  ) {
757
798
  throw new Error("registry item contains a non-string component file");
@@ -765,7 +806,7 @@ export async function main(argv) {
765
806
  }
766
807
  let same = true;
767
808
  for (const entry of item.files ?? []) {
768
- const target = uiTargetPath(entry);
809
+ const target = targetPath(entry);
769
810
  if (!target) continue;
770
811
  const local = installedByTarget.get(target);
771
812
  const expectedVariants = expectedVariantsForCompare(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vegastack/design",
3
- "version": "0.7.57",
3
+ "version": "0.7.59",
4
4
  "description": "VegaStack design system — cn utility, icon runtime, Tailwind v4 preset, and the vegastack-design CLI (tokens ship separately as @vegastack/design-tokens)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -3,7 +3,7 @@
3
3
  <!-- GENERATED — do not hand-edit. Regenerated from the design system's component contract,
4
4
  which is the authority for membership and counts. -->
5
5
 
6
- **134 components**, plus 467 animated-icon items, 13 hooks (`use-animation-replay`, `use-announcer`, `use-async-search`, `use-drag-reorder`, `use-file-drop`, `use-inline-edit`, `use-list-nav`, `use-media-query`, `use-mobile`, `use-modal-inert`, `use-overflow`, `use-platform`, `use-tabs-swipe`), 11 starter blocks (`app-shell-01`, `board-01`, `issue-detail-01`, `command-search-01`, `list-page-01`, `login-01`, `notifications-01`, `review-split-01`, `settings-01`, `settings-02`, `status-pages-01`), 68 chart blocks across 7 families, and 6 data libs (`date-time`, `geo-data`, `emoji-data`, `drag-item`, `page-layout`, `tile-overlay`) — 699 registry items in total.
6
+ **135 components**, plus 467 animated-icon items, 13 hooks (`use-animation-replay`, `use-announcer`, `use-async-search`, `use-drag-reorder`, `use-file-drop`, `use-inline-edit`, `use-list-nav`, `use-media-query`, `use-mobile`, `use-modal-inert`, `use-overflow`, `use-platform`, `use-tabs-swipe`), 11 starter blocks (`app-shell-01`, `board-01`, `issue-detail-01`, `command-search-01`, `list-page-01`, `login-01`, `notifications-01`, `review-split-01`, `settings-01`, `settings-02`, `status-pages-01`), 68 chart blocks across 7 families, and 6 data libs (`date-time`, `geo-data`, `emoji-data`, `drag-item`, `page-layout`, `tile-overlay`) — 700 registry items in total.
7
7
 
8
8
  Install any of them with `shadcn add @vegastack/<name>`. Animated icons install as
9
9
  `@vegastack/icon-<name>`; the bare name is reserved for components, so a component whose name
@@ -162,6 +162,7 @@ starts with `icon-` is a component and never an icon.
162
162
 
163
163
  ## Rich text
164
164
 
165
+ - **`template-editor`** — A plain-paragraph editor for text with placeholders: typing @ or {{ opens a searchable list of tokens, the one picked becomes an atomic inline chip, and the value is plain text with {{id}} tokens.
165
166
  - **`text-edit`** — A Tiptap-based, markdown-first rich-text editor with no toolbar and no fill — caret-only as a document, or boxed with a subtle focus border: a slash menu for blocks, a bubble menu for marks and block types, GFM tables with a menu and drag grips, block drag handles, portaled menus, and one onCommit path.
166
167
 
167
168
  ## Chat