@trops/dash-core 0.1.422 → 0.1.425

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.
@@ -28520,13 +28520,16 @@ var schedulerController_1 = schedulerController$2;
28520
28520
  backfillMetadataFromDisk() {
28521
28521
  let changed = false;
28522
28522
  for (const [pkgId, entry] of this.widgets.entries()) {
28523
- const needsBackfill =
28524
- !Array.isArray(entry.componentNames) ||
28525
- entry.componentNames.length === 0 ||
28526
- !Array.isArray(entry.widgets) ||
28527
- entry.widgets.length === 0;
28528
- if (!needsBackfill) continue;
28529
28523
  if (!entry.path || !fs.existsSync(entry.path)) continue;
28524
+ // Always re-enrich from disk — don't gate on whether the cache
28525
+ // looks populated. A package's `dash.json` can gain new widgets
28526
+ // between sessions (author edits, AI-builder adds, etc.), and
28527
+ // skipping enrichment here is exactly how publish ends up
28528
+ // attributing a shared component to the wrong package:
28529
+ // `@scope/bundle` appears to not provide it (stale cache),
28530
+ // so a singleton `@scope/name` wins by elimination.
28531
+ // `enrichEntryFromDisk` already guards against shrinking the
28532
+ // list, so re-running it on every entry is idempotent.
28530
28533
  const before = {
28531
28534
  cn: (entry.componentNames || []).length,
28532
28535
  w: (entry.widgets || []).length,
@@ -28538,7 +28541,7 @@ var schedulerController_1 = schedulerController$2;
28538
28541
  };
28539
28542
  if (after.cn !== before.cn || after.w !== before.w) {
28540
28543
  console.log(
28541
- `[WidgetRegistry] Back-filled metadata for ${pkgId}: ` +
28544
+ `[WidgetRegistry] Refreshed metadata for ${pkgId}: ` +
28542
28545
  `componentNames ${before.cn} → ${after.cn}, widgets ${before.w} → ${after.w}`,
28543
28546
  );
28544
28547
  changed = true;
@@ -62937,6 +62940,15 @@ var properties = {
62937
62940
  "1.0.0"
62938
62941
  ]
62939
62942
  },
62943
+ version: {
62944
+ type: "string",
62945
+ description: "Package version (semver) of the published dashboard. Distinct from workspace.version (integer schema revision). Bumped on each republish.",
62946
+ pattern: "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$",
62947
+ examples: [
62948
+ "1.0.0",
62949
+ "1.2.3-beta"
62950
+ ]
62951
+ },
62940
62952
  name: {
62941
62953
  type: "string",
62942
62954
  description: "Display name of the dashboard",
@@ -63602,16 +63614,87 @@ function stripScopePrefix(fullName, scope) {
63602
63614
  * @returns {Array} Widget dependency objects for the dashboard config
63603
63615
  */
63604
63616
  function buildWidgetDependencies$1(
63605
- componentNames,
63617
+ componentNamesOrRefs,
63606
63618
  widgetRegistry = null,
63607
63619
  componentConfigs = null,
63608
63620
  ) {
63609
63621
  const widgets = [];
63610
63622
  const seen = new Set();
63611
63623
 
63612
- for (const name of componentNames) {
63613
- if (seen.has(name)) continue;
63614
- seen.add(name);
63624
+ // Accept both the legacy string-array shape and the new
63625
+ // `{component, packageId}` ref shape. Refs carry the exact source
63626
+ // package recorded at widget-add time — no guessing needed. Legacy
63627
+ // layout items that predate packageId pass `packageId: null` and
63628
+ // still fall through to the registry fallback.
63629
+ const refs = (componentNamesOrRefs || []).map((entry) =>
63630
+ typeof entry === "string"
63631
+ ? { component: entry, packageId: null }
63632
+ : { component: entry.component, packageId: entry.packageId || null },
63633
+ );
63634
+
63635
+ // Pre-index the installed widgets by packageId for O(1) lookup when
63636
+ // refs specify one. The secondary map by component name is used for
63637
+ // the legacy/fallback path where packageId isn't known. When a
63638
+ // shared component appears without a packageId, rank candidates by
63639
+ // how many of THIS dashboard's widgets they provide so the
63640
+ // best-fit bundle wins over a single-widget package that happens
63641
+ // to share the name.
63642
+ const installedWidgets = widgetRegistry ? widgetRegistry.getWidgets() : [];
63643
+ const byPackageId = new Map();
63644
+ const byComponentName = new Map();
63645
+ for (const w of installedWidgets) {
63646
+ // packageId on the registry entry is usually `@scope/name`; also
63647
+ // index by the bare `scope/name` form since callers occasionally
63648
+ // strip the @.
63649
+ const ids = new Set();
63650
+ if (w.packageId) ids.add(w.packageId);
63651
+ if (w.name) ids.add(w.name);
63652
+ if (w.scope && w.name) {
63653
+ const bareScope = String(w.scope).replace(/^@/, "");
63654
+ const bareName = stripScopePrefix(w.name, w.scope);
63655
+ ids.add(`@${bareScope}/${bareName}`);
63656
+ ids.add(`${bareScope}/${bareName}`);
63657
+ }
63658
+ for (const id of ids) {
63659
+ if (id && !byPackageId.has(id)) byPackageId.set(id, w);
63660
+ }
63661
+ if (Array.isArray(w.componentNames)) {
63662
+ for (const cn of w.componentNames) {
63663
+ if (!byComponentName.has(cn)) byComponentName.set(cn, []);
63664
+ byComponentName.get(cn).push(w);
63665
+ }
63666
+ }
63667
+ }
63668
+
63669
+ const requestedComponentSet = new Set(refs.map((r) => r.component));
63670
+ const rankCandidates = (candidates) =>
63671
+ [...candidates].sort((a, b) => {
63672
+ const aMatches = (a.componentNames || []).filter((n) =>
63673
+ requestedComponentSet.has(n),
63674
+ ).length;
63675
+ const bMatches = (b.componentNames || []).filter((n) =>
63676
+ requestedComponentSet.has(n),
63677
+ ).length;
63678
+ if (aMatches !== bMatches) return bMatches - aMatches;
63679
+ return (b.componentNames?.length || 0) - (a.componentNames?.length || 0);
63680
+ });
63681
+
63682
+ const applyRegistryMatch = (w, name, resolved) => {
63683
+ if (!resolved.scope && w.scope) resolved.scope = w.scope;
63684
+ if (!resolved.packageName || resolved.packageName === name) {
63685
+ resolved.packageName =
63686
+ stripScopePrefix(w.name, w.scope || resolved.scope) || "";
63687
+ }
63688
+ resolved.version = w.version || "*";
63689
+ resolved.author =
63690
+ typeof w.author === "string" ? w.author : w.author?.name || "";
63691
+ };
63692
+
63693
+ for (const ref of refs) {
63694
+ const name = ref.component;
63695
+ const dedupeKey = `${name}|${ref.packageId || ""}`;
63696
+ if (seen.has(dedupeKey)) continue;
63697
+ seen.add(dedupeKey);
63615
63698
 
63616
63699
  let scope = "";
63617
63700
  let packageName = "";
@@ -63627,23 +63710,34 @@ function buildWidgetDependencies$1(
63627
63710
  widgetName = parts[2];
63628
63711
  }
63629
63712
 
63630
- // Try to resolve from widget registry
63631
- if (widgetRegistry) {
63632
- const installedWidgets = widgetRegistry.getWidgets();
63633
- for (const w of installedWidgets) {
63634
- if (w.componentNames && w.componentNames.includes(name)) {
63635
- if (!scope && w.scope) scope = w.scope;
63636
- if (!packageName || packageName === name) {
63637
- packageName = stripScopePrefix(w.name, w.scope || scope) || "";
63638
- }
63639
- version = w.version || "*";
63640
- author =
63641
- typeof w.author === "string" ? w.author : w.author?.name || "";
63642
- break;
63643
- }
63713
+ const resolved = { scope, packageName, version, author };
63714
+
63715
+ // Authoritative path: the layout item told us exactly which
63716
+ // package this widget came from. Look it up directly. No guessing.
63717
+ let matched = false;
63718
+ if (ref.packageId) {
63719
+ const w = byPackageId.get(ref.packageId);
63720
+ if (w) {
63721
+ applyRegistryMatch(w, name, resolved);
63722
+ matched = true;
63644
63723
  }
63645
63724
  }
63646
63725
 
63726
+ // Fallback path: no packageId on the layout item (legacy data).
63727
+ // Rank candidates by how much of this dashboard they cover so
63728
+ // shared-component bundles beat stray singleton packages.
63729
+ if (!matched) {
63730
+ const candidates = byComponentName.get(name);
63731
+ if (Array.isArray(candidates) && candidates.length > 0) {
63732
+ const ranked = rankCandidates(candidates);
63733
+ applyRegistryMatch(ranked[0], name, resolved);
63734
+ }
63735
+ }
63736
+ scope = resolved.scope;
63737
+ packageName = resolved.packageName;
63738
+ version = resolved.version;
63739
+ author = resolved.author;
63740
+
63647
63741
  // Fallback: resolve from component configs (built-in widgets)
63648
63742
  if (componentConfigs && componentConfigs[name]) {
63649
63743
  const config = componentConfigs[name];
@@ -64244,6 +64338,68 @@ function collectComponentNamesFromWorkspace$1(workspace) {
64244
64338
  return Array.from(names);
64245
64339
  }
64246
64340
 
64341
+ /**
64342
+ * Walk the workspace and return one dependency ref per unique
64343
+ * (component, packageId) pair. `packageId` is the exact source
64344
+ * package id (e.g. `"@ai-built/pipeline"`) that was recorded on the
64345
+ * layout item when the widget was added. Items that predate the
64346
+ * packageId field carry `packageId: null`, and the caller falls back
64347
+ * to registry-based resolution for those.
64348
+ *
64349
+ * Unlike `collectComponentNamesFromWorkspace`, this walk is the
64350
+ * authoritative source for publish-time attribution — the publish
64351
+ * flow no longer needs to guess which installed package provides a
64352
+ * shared component when the layout item already says so.
64353
+ *
64354
+ * @param {Object} workspace - Workspace (layout/pages/sidebarLayout)
64355
+ * @returns {Array<{component: string, packageId: string|null}>}
64356
+ */
64357
+ function collectDependencyRefsFromWorkspace$1(workspace) {
64358
+ const byKey = new Map();
64359
+ const walk = (items) => {
64360
+ if (!Array.isArray(items)) return;
64361
+ for (const item of items) {
64362
+ if (!item || typeof item !== "object") continue;
64363
+ const component = item.component;
64364
+ if (
64365
+ component &&
64366
+ component !== "Container" &&
64367
+ component !== "LayoutGridContainer"
64368
+ ) {
64369
+ const packageId = item.packageId || null;
64370
+ const key = `${component}|${packageId || ""}`;
64371
+ if (!byKey.has(key)) byKey.set(key, { component, packageId });
64372
+ }
64373
+ // Grid cells may carry a string component name (new widget
64374
+ // placed directly in a cell) with no corresponding layout item
64375
+ // yet — capture it without a packageId so the fallback path
64376
+ // still picks it up.
64377
+ if (item.grid && typeof item.grid === "object") {
64378
+ for (const [cellKey, cell] of Object.entries(item.grid)) {
64379
+ if (!/^\d+\.\d+$/.test(cellKey)) continue;
64380
+ if (cell && typeof cell.component === "string") {
64381
+ const cellKey2 = `${cell.component}|`;
64382
+ if (!byKey.has(cellKey2)) {
64383
+ byKey.set(cellKey2, {
64384
+ component: cell.component,
64385
+ packageId: null,
64386
+ });
64387
+ }
64388
+ }
64389
+ }
64390
+ }
64391
+ if (Array.isArray(item.items)) walk(item.items);
64392
+ if (Array.isArray(item.layout)) walk(item.layout);
64393
+ }
64394
+ };
64395
+ walk(workspace?.layout);
64396
+ walk(workspace?.sidebarLayout);
64397
+ if (Array.isArray(workspace?.pages)) {
64398
+ for (const page of workspace.pages) walk(page?.layout);
64399
+ }
64400
+ return Array.from(byKey.values());
64401
+ }
64402
+
64247
64403
  /**
64248
64404
  * Extract event wiring across a workspace's main layout, every page
64249
64405
  * layout, and the sidebar layout. Mirrors collectComponentNamesFromWorkspace
@@ -64333,6 +64489,7 @@ function stripPersonalizationFromWorkspace$1(workspace) {
64333
64489
  var dashboardConfigUtils$1 = {
64334
64490
  collectComponentNames: collectComponentNames$1,
64335
64491
  collectComponentNamesFromWorkspace: collectComponentNamesFromWorkspace$1,
64492
+ collectDependencyRefsFromWorkspace: collectDependencyRefsFromWorkspace$1,
64336
64493
  extractEventWiring: extractEventWiring$1,
64337
64494
  extractEventWiringFromWorkspace: extractEventWiringFromWorkspace$1,
64338
64495
  buildWidgetDependencies: buildWidgetDependencies$1,
@@ -65373,6 +65530,7 @@ const {
65373
65530
  const {
65374
65531
  collectComponentNames,
65375
65532
  collectComponentNamesFromWorkspace,
65533
+ collectDependencyRefsFromWorkspace,
65376
65534
  extractEventWiring,
65377
65535
  extractEventWiringFromWorkspace,
65378
65536
  buildWidgetDependencies,
@@ -66486,12 +66644,14 @@ async function collectDashboardDependencies$1(
66486
66644
  };
66487
66645
  }
66488
66646
 
66489
- // 2. Collect component names from main + pages + sidebar layouts
66490
- const componentNames = collectComponentNamesFromWorkspace(workspace);
66647
+ // 2. Collect dependency refs each carries the exact packageId
66648
+ // recorded on the layout item when the widget was added, so
66649
+ // publish attribution is authoritative instead of heuristic.
66650
+ const refs = collectDependencyRefsFromWorkspace(workspace);
66491
66651
 
66492
66652
  // 3. Resolve widget refs (scope, packageName, widgetName, version)
66493
66653
  const deps = buildWidgetDependencies(
66494
- componentNames,
66654
+ refs,
66495
66655
  widgetRegistry,
66496
66656
  options.componentConfigs || null,
66497
66657
  );