@vegastack/design 0.7.58 → 0.7.60

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.58",
3
+ "version": "0.7.60",
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
+ **136 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`), 12 starter blocks (`app-shell-01`, `board-01`, `issue-detail-01`, `command-search-01`, `list-page-01`, `library-01`, `login-01`, `notifications-01`, `review-split-01`, `settings-01`, `settings-02`, `status-pages-01`), 68 chart blocks across 7 families, and 9 data libs (`date-time`, `geo-data`, `emoji-data`, `drag-item`, `text-anchor`, `text-anchor-doc`, `file-kind`, `page-layout`, `tile-overlay`) — 705 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
@@ -72,7 +72,7 @@ starts with `icon-` is a component and never an icon.
72
72
  - **`empty`** — A zero-data placeholder — always an icon (the icon prop, Inbox by default), title, description and a content slot, with a compact sm size for inline empties. The one empty-state markup: DataList's emptyState and noResults (SearchX, "No matches", Clear filters) are built on it.
73
73
  - **`item`** — A composable row for list and feed content — media, title, description, actions — with a highlighted flash for a just-changed row.
74
74
  - **`kbd`** — A keyboard-key chip, and a group that lays several of them out inline.
75
- - **`markdown-view`** — Render a markdown string to safe, token-styled HTML — headings, lists, code, blockquotes, links, GFM tables — XSS-safe, no raw HTML.
75
+ - **`markdown-view`** — Render a markdown string to safe, token-styled HTML — headings (with optional ids), lists, code, blockquotes, links, GFM tables, mention and file chips, callouts and toggles — XSS-safe, no raw HTML.
76
76
  - **`priority-icon`** — A flag marking priority — urgent, high, medium, low, none — each in a semantic color with filled, outline or dashed fill.
77
77
  - **`relative-time`** — One Intl-based dates & times module — formatRelative, formatDuration, formatDate, formatDateTime, formatDateRange, formatTimeOfDay, formatDueLabel, groupByDay, time-zone cookie + provider — and the RelativeTime, DateTime, Duration and DueLabel components with absolute-time tooltips.
78
78
  - **`status-icon`** — A small status indicator icon — todo, in progress, blocked, done, cancelled — each mapping to a lucide icon and semantic color.
@@ -118,6 +118,7 @@ starts with `icon-` is a component and never an icon.
118
118
 
119
119
  - **`breadcrumb`** — A hierarchical navigation trail — links, separators, the current page, and ellipsis collapse for long paths.
120
120
  - **`command`** — A searchable command palette — filtered, grouped items with keyboard navigation, optionally inside a ⌘K dialog.
121
+ - **`folder-tree`** — A Notion-style navigation tree for a file library — sections of folders, pages and files; folders that load their children on first open with loading, error, empty and Show all rows; one tab stop with tree arrow keys and typeahead; a ⋯ menu per row; pointer drag into a folder or section; and a folders-only picker mode for Move dialogs.
121
122
  - **`menubar`** — A persistent horizontal bar of menus — application-style File / Edit / View navigation.
122
123
  - **`multi-step-form`** — A guarded, branching flow around a Stepper — conditional steps, sync and async advance guards, locking, reachability-derived deep links and resume, and a phone layout chosen from the same predicate. Owns no fields and no validator.
123
124
  - **`navigation-menu`** — A collection of links for navigating websites — triggers that open one shared panel, and plain links styled to match.
@@ -162,7 +163,8 @@ starts with `icon-` is a component and never an icon.
162
163
 
163
164
  ## Rich text
164
165
 
165
- - **`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
+ - **`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.
167
+ - **`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, callouts and toggles, @ mentions, image and file uploads, a heading outline, comment highlights, portaled menus, and one onCommit path.
166
168
 
167
169
  ## Chat
168
170