@trops/dash-core 0.1.416 → 0.1.417

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.
@@ -63840,7 +63840,13 @@ function generateRegistryManifest(dashboardConfig, options = {}) {
63840
63840
  .toLowerCase();
63841
63841
 
63842
63842
  const githubUser = options.githubUser || "";
63843
- const version = "1.0.0";
63843
+ // Prefer an explicitly-passed version (caller resolved it already,
63844
+ // e.g. via resolveNextVersion + bump). Fall back to the dashboard's
63845
+ // own version, then a 1.0.0 baseline. The previous hardcoded
63846
+ // "1.0.0" meant every republish looked identical to the registry;
63847
+ // latestVersion never advanced, so downstream update-check never
63848
+ // saw a diff and no notification fired.
63849
+ const version = options.version || dashboardConfig.version || "1.0.0";
63844
63850
  const visibility = options.visibility === "private" ? "private" : "public";
63845
63851
 
63846
63852
  const manifest = {
@@ -64000,11 +64006,27 @@ function buildDashboardPreview(source) {
64000
64006
  * @returns {Array} Update records with workspace info and version comparison
64001
64007
  */
64002
64008
  function checkDashboardUpdates(workspaces = [], registryPackages = []) {
64003
- const registryByName = new Map();
64009
+ // Index the registry by canonical `@scope/name` so the lookup
64010
+ // survives installs that capture scope differently from the
64011
+ // registry's raw `name` field. The previous map was keyed by bare
64012
+ // `pkg.name`, which misses anytime the installed config recorded
64013
+ // `@scope/name` (our new publish flow writes that) or when two
64014
+ // users publish the same bare name. Key both forms for safety.
64015
+ const registryByKey = new Map();
64016
+ const asKey = (scope, name) => {
64017
+ if (!name) return null;
64018
+ if (!scope) return name;
64019
+ const bareScope = String(scope).replace(/^@/, "");
64020
+ return `@${bareScope}/${name}`;
64021
+ };
64004
64022
  for (const pkg of registryPackages) {
64005
- if (pkg.name && (pkg.type || "widget") === "dashboard") {
64006
- registryByName.set(pkg.name, pkg);
64007
- }
64023
+ if (!pkg.name) continue;
64024
+ if ((pkg.type || "widget") !== "dashboard") continue;
64025
+ const scoped = asKey(pkg.scope, pkg.name);
64026
+ if (scoped) registryByKey.set(scoped, pkg);
64027
+ // Back-compat: also store under bare name so installed configs
64028
+ // that predate the scope-aware write continue to match.
64029
+ registryByKey.set(pkg.name, pkg);
64008
64030
  }
64009
64031
 
64010
64032
  const updates = [];
@@ -64013,7 +64035,24 @@ function checkDashboardUpdates(workspaces = [], registryPackages = []) {
64013
64035
  const config = ws._dashboardConfig;
64014
64036
  if (!config || !config.registryPackage) continue;
64015
64037
 
64016
- const registryPkg = registryByName.get(config.registryPackage);
64038
+ // Lookup chain: try the scoped form first, fall back to bare. The
64039
+ // installed config may record either. Scope is stored on the
64040
+ // config by the install flow (or set here by the publish persist
64041
+ // step we just added).
64042
+ const installedScope =
64043
+ config.registryScope ||
64044
+ (config.registryPackage.startsWith("@")
64045
+ ? config.registryPackage.slice(1).split("/")[0]
64046
+ : null);
64047
+ const installedName = config.registryPackage.includes("/")
64048
+ ? config.registryPackage.split("/").pop()
64049
+ : config.registryPackage;
64050
+ const scopedKey = asKey(installedScope, installedName);
64051
+
64052
+ const registryPkg =
64053
+ (scopedKey && registryByKey.get(scopedKey)) ||
64054
+ registryByKey.get(config.registryPackage) ||
64055
+ registryByKey.get(installedName);
64017
64056
  if (!registryPkg) continue;
64018
64057
 
64019
64058
  const installedVersion = config.installedVersion || "0.0.0";
@@ -64024,6 +64063,7 @@ function checkDashboardUpdates(workspaces = [], registryPackages = []) {
64024
64063
  workspaceId: ws.id,
64025
64064
  workspaceName: ws.name || ws.label || "",
64026
64065
  registryPackage: config.registryPackage,
64066
+ registryScope: installedScope,
64027
64067
  installedVersion,
64028
64068
  latestVersion,
64029
64069
  importedAt: config.importedAt || null,
@@ -64395,6 +64435,128 @@ var registryApiController$3 = {
64395
64435
  REGISTRY_BASE_URL,
64396
64436
  };
64397
64437
 
64438
+ /**
64439
+ * widgetPublishManifest.js
64440
+ *
64441
+ * Pure helpers for widget-publish flow — version bumping, package-name
64442
+ * parsing, and manifest generation. No electron / fs / adm-zip deps so
64443
+ * these can be unit-tested directly.
64444
+ */
64445
+
64446
+ var widgetPublishManifest;
64447
+ var hasRequiredWidgetPublishManifest;
64448
+
64449
+ function requireWidgetPublishManifest () {
64450
+ if (hasRequiredWidgetPublishManifest) return widgetPublishManifest;
64451
+ hasRequiredWidgetPublishManifest = 1;
64452
+ const SEMVER_RE =
64453
+ /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
64454
+
64455
+ function bumpVersion(current, type) {
64456
+ if (!current || typeof current !== "string") return "1.0.0";
64457
+ const match = current.match(SEMVER_RE);
64458
+ if (!match) return current;
64459
+ let [, major, minor, patch] = match;
64460
+ major = Number(major);
64461
+ minor = Number(minor);
64462
+ patch = Number(patch);
64463
+ switch (type) {
64464
+ case "major":
64465
+ return `${major + 1}.0.0`;
64466
+ case "minor":
64467
+ return `${major}.${minor + 1}.0`;
64468
+ case "patch":
64469
+ default:
64470
+ return `${major}.${minor}.${patch + 1}`;
64471
+ }
64472
+ }
64473
+
64474
+ function resolveNextVersion(currentVersion, options = {}) {
64475
+ if (options.version) return options.version;
64476
+ if (options.bump) return bumpVersion(currentVersion, options.bump);
64477
+ return currentVersion;
64478
+ }
64479
+
64480
+ function parsePackageName(pkgName) {
64481
+ if (!pkgName) return { scope: null, name: "" };
64482
+ const m = pkgName.match(/^@([^/]+)\/(.+)$/);
64483
+ if (m) return { scope: m[1], name: m[2] };
64484
+ return { scope: null, name: pkgName };
64485
+ }
64486
+
64487
+ function generateWidgetRegistryManifest(
64488
+ packageJson,
64489
+ widgetConfigs,
64490
+ options = {},
64491
+ ) {
64492
+ const parsed = parsePackageName(packageJson.name || "");
64493
+ const scope = options.scope || parsed.scope || "";
64494
+ const name = options.name || parsed.name || packageJson.name || "";
64495
+ const version = options.version || packageJson.version || "1.0.0";
64496
+ const visibility = options.visibility === "private" ? "private" : "public";
64497
+
64498
+ const providerKeys = new Set();
64499
+ const providers = [];
64500
+ for (const cfg of widgetConfigs || []) {
64501
+ if (!Array.isArray(cfg.providers)) continue;
64502
+ for (const p of cfg.providers) {
64503
+ const key = `${p.type}:${p.providerClass || "mcp"}`;
64504
+ if (providerKeys.has(key)) continue;
64505
+ providerKeys.add(key);
64506
+ providers.push({
64507
+ type: p.type,
64508
+ required: p.required !== false,
64509
+ providerClass: p.providerClass || "mcp",
64510
+ });
64511
+ }
64512
+ }
64513
+
64514
+ const widgets = (widgetConfigs || []).map((cfg) => ({
64515
+ name: cfg.component || cfg.name,
64516
+ displayName: cfg.name || cfg.component,
64517
+ description: cfg.description || "",
64518
+ icon: cfg.icon || "square",
64519
+ providers: Array.isArray(cfg.providers)
64520
+ ? cfg.providers.map((p) => ({
64521
+ type: p.type,
64522
+ required: p.required !== false,
64523
+ providerClass: p.providerClass || "mcp",
64524
+ }))
64525
+ : [],
64526
+ }));
64527
+
64528
+ return {
64529
+ scope,
64530
+ name,
64531
+ displayName: options.displayName || packageJson.displayName || name,
64532
+ version,
64533
+ type: "widget",
64534
+ visibility,
64535
+ description: options.description || packageJson.description || "",
64536
+ author:
64537
+ options.authorName ||
64538
+ (typeof packageJson.author === "string"
64539
+ ? packageJson.author
64540
+ : packageJson.author?.name || ""),
64541
+ category: options.category || "general",
64542
+ tags: Array.isArray(options.tags) ? options.tags : [],
64543
+ icon: options.icon || "puzzle-piece",
64544
+ providers,
64545
+ widgets,
64546
+ appOrigin: options.appOrigin || "",
64547
+ publishedAt: new Date().toISOString(),
64548
+ };
64549
+ }
64550
+
64551
+ widgetPublishManifest = {
64552
+ bumpVersion,
64553
+ resolveNextVersion,
64554
+ parsePackageName,
64555
+ generateWidgetRegistryManifest,
64556
+ };
64557
+ return widgetPublishManifest;
64558
+ }
64559
+
64398
64560
  /**
64399
64561
  * themeRegistryController.js
64400
64562
  *
@@ -64439,14 +64601,20 @@ function generateThemeRegistryManifest(themeData, themeKey, options = {}) {
64439
64601
  const sanitizedName = sanitizeName(humanName);
64440
64602
  const colors = extractColors(themeData);
64441
64603
  const visibility = options.visibility === "private" ? "private" : "public";
64604
+ // Prefer an explicitly-resolved version (caller already bumped),
64605
+ // then the theme's own stored version, then the 1.0.0 baseline.
64606
+ // The old hardcoded 1.0.0 meant republishes silently clobbered the
64607
+ // registry record — update notifications never fired downstream.
64608
+ const version = options.version || themeData.version || "1.0.0";
64442
64609
 
64443
64610
  return {
64444
64611
  scope: options.scope || "",
64445
64612
  name: sanitizedName,
64446
64613
  displayName: humanName,
64447
- author: options.authorName || "",
64614
+ author:
64615
+ options.authorName || themeData.author || options.fallbackAuthor || "",
64448
64616
  description: options.description || "",
64449
- version: "1.0.0",
64617
+ version,
64450
64618
  visibility,
64451
64619
  type: "theme",
64452
64620
  category: "general",
@@ -64516,6 +64684,7 @@ function extractColors(themeData) {
64516
64684
  */
64517
64685
  async function prepareThemeForPublish$1(win, appId, themeKey, options = {}) {
64518
64686
  try {
64687
+ const { resolveNextVersion } = requireWidgetPublishManifest();
64519
64688
  // Read the theme data
64520
64689
  const themesResult = themeController$3.listThemesForApplication(win, appId);
64521
64690
  if (themesResult.error) {
@@ -64549,11 +64718,33 @@ async function prepareThemeForPublish$1(win, appId, themeKey, options = {}) {
64549
64718
  };
64550
64719
  }
64551
64720
 
64721
+ // Resolve version: prefer explicit, then bump the theme's stored
64722
+ // version, then start at 1.0.0. Without this themes always
64723
+ // published as 1.0.0 and update-check could never diff.
64724
+ const previousVersion = themeData.version || "1.0.0";
64725
+ const nextVersion = resolveNextVersion(previousVersion, {
64726
+ bump: options.bump,
64727
+ version: options.version,
64728
+ });
64729
+
64730
+ // Author fallback chain (F7): explicit → theme data → registry
64731
+ // profile displayName/username → blank. Matches the widget
64732
+ // author-normalization shape so ai-built / scaffolded themes
64733
+ // don't ship to the registry with a blank author field.
64734
+ const resolvedAuthor =
64735
+ options.authorName ||
64736
+ themeData.author ||
64737
+ profile?.displayName ||
64738
+ profile?.username ||
64739
+ "";
64740
+
64552
64741
  // Generate manifest
64553
64742
  const manifest = generateThemeRegistryManifest(themeData, themeKey, {
64554
64743
  ...options,
64555
64744
  scope,
64556
64745
  appOrigin: appId,
64746
+ version: nextVersion,
64747
+ authorName: resolvedAuthor,
64557
64748
  });
64558
64749
 
64559
64750
  // Validate colors
@@ -64610,6 +64801,35 @@ async function prepareThemeForPublish$1(win, appId, themeKey, options = {}) {
64610
64801
  "[ThemeRegistryController] Registry publish result:",
64611
64802
  registryResult,
64612
64803
  );
64804
+ // Persist the resolved version + author back onto the theme so
64805
+ // the NEXT publish picks up from here. Without this, the
64806
+ // publisher would be bumping from 1.0.0 every time and the
64807
+ // manifest's author normalization would be re-applied every
64808
+ // run (OK but confusing).
64809
+ if (registryResult?.success) {
64810
+ try {
64811
+ const updatedTheme = {
64812
+ ...themeData,
64813
+ version: nextVersion,
64814
+ author: resolvedAuthor || themeData.author,
64815
+ _registryMeta: {
64816
+ ...(themeData._registryMeta || {}),
64817
+ packageName: `${scope}/${manifest.name}`,
64818
+ scope,
64819
+ lastPublishedAt: new Date().toISOString(),
64820
+ lastPublishedVersion: nextVersion,
64821
+ },
64822
+ };
64823
+ themeController$3.saveThemeForApplication(win, appId, {
64824
+ key: themeKey,
64825
+ theme: updatedTheme,
64826
+ });
64827
+ } catch (persistErr) {
64828
+ console.warn(
64829
+ `[ThemeRegistryController] Version persist failed (continuing): ${persistErr.message}`,
64830
+ );
64831
+ }
64832
+ }
64613
64833
  }
64614
64834
 
64615
64835
  return {
@@ -64950,12 +65170,120 @@ function getThemePublishPreview$1(appId, themeKey) {
64950
65170
  }
64951
65171
  }
64952
65172
 
65173
+ /**
65174
+ * Check installed themes for available updates against the registry.
65175
+ *
65176
+ * Reads every theme from the app's theme file, picks the ones that
65177
+ * carry a `_registryMeta.packageName` (i.e. were installed from the
65178
+ * registry, not locally created), resolves each against the registry
65179
+ * index by `@scope/name` (with a bare-name fallback), and returns a
65180
+ * diff record for each stale theme.
65181
+ *
65182
+ * Mirrors `checkDashboardUpdatesForApp` — callable standalone, works
65183
+ * the same way on the renderer side.
65184
+ *
65185
+ * @param {BrowserWindow} win
65186
+ * @param {string} appId
65187
+ * @returns {Promise<{success, updates, totalInstalled, error?}>}
65188
+ */
65189
+ async function checkThemeUpdatesForApp$1(win, appId) {
65190
+ try {
65191
+ const { fetchRegistryIndex } = registryController$3;
65192
+ const themesResult = themeController$3.listThemesForApplication(win, appId);
65193
+ if (themesResult.error) {
65194
+ return {
65195
+ success: false,
65196
+ error: themesResult.message || "Failed to read themes",
65197
+ updates: [],
65198
+ };
65199
+ }
65200
+ const themes = themesResult.themes || {};
65201
+
65202
+ // Filter to registry-installed themes only.
65203
+ const installed = [];
65204
+ for (const [themeKey, themeData] of Object.entries(themes)) {
65205
+ const meta = themeData?._registryMeta;
65206
+ if (!meta?.packageName) continue;
65207
+ installed.push({
65208
+ themeKey,
65209
+ packageName: meta.packageName,
65210
+ scope: meta.scope || null,
65211
+ version: themeData.version || meta.lastPublishedVersion || "0.0.0",
65212
+ });
65213
+ }
65214
+
65215
+ if (installed.length === 0) {
65216
+ return { success: true, updates: [], totalInstalled: 0 };
65217
+ }
65218
+
65219
+ const index = await fetchRegistryIndex();
65220
+ const packages = (index.packages || []).filter(
65221
+ (p) => (p.type || "widget") === "theme",
65222
+ );
65223
+
65224
+ // Index registry packages by scoped + bare key, same pattern as
65225
+ // dashboard update check.
65226
+ const registryByKey = new Map();
65227
+ for (const pkg of packages) {
65228
+ if (!pkg.name) continue;
65229
+ if (pkg.scope) {
65230
+ const bareScope = String(pkg.scope).replace(/^@/, "");
65231
+ registryByKey.set(`@${bareScope}/${pkg.name}`, pkg);
65232
+ }
65233
+ registryByKey.set(pkg.name, pkg);
65234
+ }
65235
+
65236
+ const updates = [];
65237
+ for (const inst of installed) {
65238
+ const scope = inst.scope
65239
+ ? String(inst.scope).replace(/^@/, "")
65240
+ : inst.packageName.startsWith("@")
65241
+ ? inst.packageName.slice(1).split("/")[0]
65242
+ : null;
65243
+ const bareName = inst.packageName.includes("/")
65244
+ ? inst.packageName.split("/").pop()
65245
+ : inst.packageName;
65246
+ const scopedKey = scope ? `@${scope}/${bareName}` : null;
65247
+ const registryPkg =
65248
+ (scopedKey && registryByKey.get(scopedKey)) ||
65249
+ registryByKey.get(inst.packageName) ||
65250
+ registryByKey.get(bareName);
65251
+ if (!registryPkg) continue;
65252
+
65253
+ const latestVersion = registryPkg.version || "0.0.0";
65254
+ if (inst.version !== latestVersion) {
65255
+ updates.push({
65256
+ themeKey: inst.themeKey,
65257
+ packageName: inst.packageName,
65258
+ scope,
65259
+ installedVersion: inst.version,
65260
+ latestVersion,
65261
+ downloadUrl: registryPkg.downloadUrl || null,
65262
+ });
65263
+ }
65264
+ }
65265
+
65266
+ return {
65267
+ success: true,
65268
+ updates,
65269
+ totalInstalled: installed.length,
65270
+ };
65271
+ } catch (err) {
65272
+ console.error(
65273
+ "[ThemeRegistryController] Error checking theme updates:",
65274
+ err,
65275
+ );
65276
+ return { success: false, error: err.message, updates: [] };
65277
+ }
65278
+ }
65279
+
64953
65280
  var themeRegistryController$1 = {
64954
65281
  prepareThemeForPublish: prepareThemeForPublish$1,
64955
65282
  installThemeFromRegistry: installThemeFromRegistry$1,
64956
65283
  getThemePublishPreview: getThemePublishPreview$1,
64957
65284
  generateThemeRegistryManifest,
64958
65285
  extractColors,
65286
+ checkThemeUpdatesForApp: checkThemeUpdatesForApp$1,
64959
65287
  };
64960
65288
 
64961
65289
  /**
@@ -65420,7 +65748,30 @@ async function processDashboardConfig(
65420
65748
  dashboardConfig.widgets.length
65421
65749
  ) {
65422
65750
  const installedWidgets = widgetRegistry.getWidgets();
65423
- const installedPackages = new Set(installedWidgets.map((w) => w.name));
65751
+ // Build a canonical id set — `@scope/name` — so lookups survive
65752
+ // the publisher/installer having the widget under different
65753
+ // keys. The widget-registry entry carries `w.packageId`,
65754
+ // `w.name`, and `w.scope`; the dashboard dep carries
65755
+ // `dep.package` (now scope-remapped at publish time). Seeding
65756
+ // every form we've seen avoids silent "missing widget" misses
65757
+ // on bare-vs-scoped mismatch.
65758
+ const canonicalId = (w) => {
65759
+ if (w?.packageId) return w.packageId;
65760
+ if (w?.scope && w?.name) {
65761
+ const scope = String(w.scope).replace(/^@/, "");
65762
+ const bareName = String(w.name).replace(new RegExp(`^@?${scope}/`), "");
65763
+ return `@${scope}/${bareName}`;
65764
+ }
65765
+ return w?.name || null;
65766
+ };
65767
+ const installedPackages = new Set();
65768
+ for (const w of installedWidgets) {
65769
+ const id = canonicalId(w);
65770
+ if (id) installedPackages.add(id);
65771
+ // Back-compat: keep bare name in the set too, so older
65772
+ // dashboard configs (pre-scope-remap) still match.
65773
+ if (w?.name) installedPackages.add(w.name);
65774
+ }
65424
65775
 
65425
65776
  // Emit initial "pending" state for all widgets
65426
65777
  for (let i = 0; i < widgetTotal; i++) {
@@ -65458,7 +65809,20 @@ async function processDashboardConfig(
65458
65809
  const displayName =
65459
65810
  widgetDep.displayName || widgetDep.name || packageName;
65460
65811
 
65461
- if (installedPackages.has(packageName)) {
65812
+ // Try both the fully-scoped id and a bare-name fallback — the
65813
+ // installed set holds both forms, but the dashboard dep may
65814
+ // carry either shape. Without this, a widget stored locally as
65815
+ // `@trops/pipeline` and referenced as `pipeline` (or vice
65816
+ // versa) silently flagged as "failed" even though it was
65817
+ // installed.
65818
+ const isInstalled =
65819
+ installedPackages.has(packageName) ||
65820
+ (packageName?.includes("/") &&
65821
+ installedPackages.has(packageName.split("/").pop())) ||
65822
+ (packageName &&
65823
+ !packageName.startsWith("@") &&
65824
+ installedPackages.has(`@${packageName}`));
65825
+ if (isInstalled) {
65462
65826
  installSummary.alreadyInstalled.push(packageName);
65463
65827
  win.webContents.send(DASHBOARD_CONFIG_INSTALL_PROGRESS, {
65464
65828
  packageName,
@@ -65490,6 +65854,10 @@ async function processDashboardConfig(
65490
65854
  );
65491
65855
  installSummary.installed.push({ packageName, config });
65492
65856
  installedPackages.add(packageName);
65857
+ // Also add the canonical form so a subsequent dep that
65858
+ // references it under a different shape still hits.
65859
+ const installedCanonical = canonicalId(config);
65860
+ if (installedCanonical) installedPackages.add(installedCanonical);
65493
65861
  win.webContents.send(DASHBOARD_CONFIG_INSTALL_PROGRESS, {
65494
65862
  packageName,
65495
65863
  displayName,
@@ -66227,16 +66595,30 @@ async function getDashboardPublishPlan$1(
66227
66595
  }
66228
66596
  }
66229
66597
 
66598
+ // Scopes that only exist on the publisher's machine and aren't
66599
+ // resolvable from the registry as-is. Any dep under one of these
66600
+ // scopes MUST be republished under the caller's scope for the
66601
+ // dashboard's widget refs to work on another machine. The modal
66602
+ // uses this flag to auto-check + lock such rows.
66603
+ const LOCAL_ONLY_SCOPES = new Set(["ai-built", "@ai-built"]);
66604
+
66230
66605
  const widgets = deps.widgets.map((w) => {
66231
66606
  const publishScope = publishScopeFor(w);
66232
66607
  const key =
66233
66608
  publishScope && w.packageName
66234
66609
  ? `${publishScope}/${w.packageName}`
66235
66610
  : null;
66611
+ const isLocalOnlyScope =
66612
+ !!w.scope && LOCAL_ONLY_SCOPES.has(String(w.scope).replace(/^@/, ""));
66236
66613
  return {
66237
66614
  ...w,
66238
66615
  publishScope,
66239
66616
  registry: key ? resolvedByKey.get(key) || null : null,
66617
+ // True when this widget cannot be installed as-is under its
66618
+ // local scope — the dashboard publish MUST republish it under
66619
+ // the caller's scope alongside the dashboard itself. The
66620
+ // modal treats this as mandatory rather than opt-in.
66621
+ requiresRepublish: isLocalOnlyScope,
66240
66622
  };
66241
66623
  });
66242
66624
 
@@ -66300,6 +66682,7 @@ async function prepareDashboardForPublish$1(
66300
66682
  const {
66301
66683
  generateRegistryManifest,
66302
66684
  } = dashboardConfigUtils$1;
66685
+ const { resolveNextVersion } = requireWidgetPublishManifest();
66303
66686
 
66304
66687
  // 1. Read workspace
66305
66688
  const filename = path$2.join(
@@ -66332,6 +66715,19 @@ async function prepareDashboardForPublish$1(
66332
66715
  };
66333
66716
  }
66334
66717
 
66718
+ // Resolve the version this publish will ship. Previous publishes
66719
+ // store the last version on workspace._dashboardConfig.version; new
66720
+ // dashboards start at 1.0.0. Caller may pass `options.version`
66721
+ // (explicit) or `options.bump` ("patch"/"minor"/"major"). Without
66722
+ // this, every dashboard republish used a hardcoded 1.0.0 — the
66723
+ // registry never saw a new version, so the update-check never
66724
+ // fired a notification for installers.
66725
+ const previousVersion = workspace._dashboardConfig?.version || "1.0.0";
66726
+ const nextVersion = resolveNextVersion(previousVersion, {
66727
+ bump: options.bump,
66728
+ version: options.version,
66729
+ });
66730
+
66335
66731
  // Strip publisher-specific personalization (userPrefs,
66336
66732
  // selectedProviders) from every widget instance before we snapshot
66337
66733
  // the workspace into the dashboardConfig. Without this, every
@@ -66368,6 +66764,10 @@ async function prepareDashboardForPublish$1(
66368
66764
  schemaVersion: CURRENT_SCHEMA_VERSION,
66369
66765
  name: workspace.name || workspace.label || "Dashboard",
66370
66766
  description: options.description || "",
66767
+ // Package version (semver). Distinct from workspace.version
66768
+ // (schema revision, integer). Persisted on the workspace after a
66769
+ // successful publish so the next publish resolves from here.
66770
+ version: nextVersion,
66371
66771
  ...(options.authorName
66372
66772
  ? { author: { name: options.authorName, id: options.authorId || "" } }
66373
66773
  : {}),
@@ -66522,6 +66922,7 @@ async function prepareDashboardForPublish$1(
66522
66922
  repository: options.repository || "",
66523
66923
  appOrigin: appId,
66524
66924
  visibility: options.visibility || "public",
66925
+ version: nextVersion,
66525
66926
  });
66526
66927
 
66527
66928
  // 9. Show save dialog for the publish package
@@ -66569,6 +66970,35 @@ async function prepareDashboardForPublish$1(
66569
66970
  console.log(
66570
66971
  `[DashboardConfigController] Published to registry: ${registrySubmission.registryUrl}`,
66571
66972
  );
66973
+ // Persist the resolved next version back onto the workspace
66974
+ // so the NEXT publish resolves from it. Without this, the
66975
+ // publisher would have to re-enter the same version every
66976
+ // time (or keep bumping from 1.0.0, masking that the
66977
+ // registry already advanced).
66978
+ try {
66979
+ const workspaceController = workspaceController_1;
66980
+ const nextWorkspace = {
66981
+ ...workspace,
66982
+ _dashboardConfig: {
66983
+ ...(workspace._dashboardConfig || {}),
66984
+ version: nextVersion,
66985
+ registryPackage: manifest.name,
66986
+ registryScope: manifest.scope || manifest.githubUser,
66987
+ },
66988
+ };
66989
+ workspaceController.saveWorkspaceForApplication(
66990
+ win,
66991
+ appId,
66992
+ nextWorkspace,
66993
+ );
66994
+ } catch (persistErr) {
66995
+ // Non-fatal — registry is the source of truth for
66996
+ // latestVersion, so the next publish can still resolve
66997
+ // against it if this workspace write fails.
66998
+ console.warn(
66999
+ `[DashboardConfigController] Version persistence failed (continuing): ${persistErr.message}`,
67000
+ );
67001
+ }
66572
67002
  } else {
66573
67003
  console.warn(
66574
67004
  `[DashboardConfigController] Registry publish failed: ${registrySubmission.error}`,
@@ -73033,120 +73463,6 @@ var dashboardRatingsController = {
73033
73463
  enrichPackagesWithRatings: enrichPackagesWithRatings$1,
73034
73464
  };
73035
73465
 
73036
- /**
73037
- * widgetPublishManifest.js
73038
- *
73039
- * Pure helpers for widget-publish flow — version bumping, package-name
73040
- * parsing, and manifest generation. No electron / fs / adm-zip deps so
73041
- * these can be unit-tested directly.
73042
- */
73043
-
73044
- const SEMVER_RE =
73045
- /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
73046
-
73047
- function bumpVersion(current, type) {
73048
- if (!current || typeof current !== "string") return "1.0.0";
73049
- const match = current.match(SEMVER_RE);
73050
- if (!match) return current;
73051
- let [, major, minor, patch] = match;
73052
- major = Number(major);
73053
- minor = Number(minor);
73054
- patch = Number(patch);
73055
- switch (type) {
73056
- case "major":
73057
- return `${major + 1}.0.0`;
73058
- case "minor":
73059
- return `${major}.${minor + 1}.0`;
73060
- case "patch":
73061
- default:
73062
- return `${major}.${minor}.${patch + 1}`;
73063
- }
73064
- }
73065
-
73066
- function resolveNextVersion$1(currentVersion, options = {}) {
73067
- if (options.version) return options.version;
73068
- if (options.bump) return bumpVersion(currentVersion, options.bump);
73069
- return currentVersion;
73070
- }
73071
-
73072
- function parsePackageName$1(pkgName) {
73073
- if (!pkgName) return { scope: null, name: "" };
73074
- const m = pkgName.match(/^@([^/]+)\/(.+)$/);
73075
- if (m) return { scope: m[1], name: m[2] };
73076
- return { scope: null, name: pkgName };
73077
- }
73078
-
73079
- function generateWidgetRegistryManifest$1(
73080
- packageJson,
73081
- widgetConfigs,
73082
- options = {},
73083
- ) {
73084
- const parsed = parsePackageName$1(packageJson.name || "");
73085
- const scope = options.scope || parsed.scope || "";
73086
- const name = options.name || parsed.name || packageJson.name || "";
73087
- const version = options.version || packageJson.version || "1.0.0";
73088
- const visibility = options.visibility === "private" ? "private" : "public";
73089
-
73090
- const providerKeys = new Set();
73091
- const providers = [];
73092
- for (const cfg of widgetConfigs || []) {
73093
- if (!Array.isArray(cfg.providers)) continue;
73094
- for (const p of cfg.providers) {
73095
- const key = `${p.type}:${p.providerClass || "mcp"}`;
73096
- if (providerKeys.has(key)) continue;
73097
- providerKeys.add(key);
73098
- providers.push({
73099
- type: p.type,
73100
- required: p.required !== false,
73101
- providerClass: p.providerClass || "mcp",
73102
- });
73103
- }
73104
- }
73105
-
73106
- const widgets = (widgetConfigs || []).map((cfg) => ({
73107
- name: cfg.component || cfg.name,
73108
- displayName: cfg.name || cfg.component,
73109
- description: cfg.description || "",
73110
- icon: cfg.icon || "square",
73111
- providers: Array.isArray(cfg.providers)
73112
- ? cfg.providers.map((p) => ({
73113
- type: p.type,
73114
- required: p.required !== false,
73115
- providerClass: p.providerClass || "mcp",
73116
- }))
73117
- : [],
73118
- }));
73119
-
73120
- return {
73121
- scope,
73122
- name,
73123
- displayName: options.displayName || packageJson.displayName || name,
73124
- version,
73125
- type: "widget",
73126
- visibility,
73127
- description: options.description || packageJson.description || "",
73128
- author:
73129
- options.authorName ||
73130
- (typeof packageJson.author === "string"
73131
- ? packageJson.author
73132
- : packageJson.author?.name || ""),
73133
- category: options.category || "general",
73134
- tags: Array.isArray(options.tags) ? options.tags : [],
73135
- icon: options.icon || "puzzle-piece",
73136
- providers,
73137
- widgets,
73138
- appOrigin: options.appOrigin || "",
73139
- publishedAt: new Date().toISOString(),
73140
- };
73141
- }
73142
-
73143
- var widgetPublishManifest = {
73144
- bumpVersion,
73145
- resolveNextVersion: resolveNextVersion$1,
73146
- parsePackageName: parsePackageName$1,
73147
- generateWidgetRegistryManifest: generateWidgetRegistryManifest$1,
73148
- };
73149
-
73150
73466
  /**
73151
73467
  * widgetRegistryController.js
73152
73468
  *
@@ -73176,7 +73492,7 @@ const {
73176
73492
  resolveNextVersion,
73177
73493
  parsePackageName,
73178
73494
  generateWidgetRegistryManifest,
73179
- } = widgetPublishManifest;
73495
+ } = requireWidgetPublishManifest();
73180
73496
 
73181
73497
  /**
73182
73498
  * Resilient widget lookup. Callers pass identifiers in different shapes —
@@ -73652,6 +73968,7 @@ const {
73652
73968
  prepareThemeForPublish,
73653
73969
  installThemeFromRegistry,
73654
73970
  getThemePublishPreview,
73971
+ checkThemeUpdatesForApp,
73655
73972
  } = themeRegistryController$1;
73656
73973
  const {
73657
73974
  prepareWidgetForPublish,
@@ -73745,6 +74062,7 @@ var controller = {
73745
74062
  prepareThemeForPublish,
73746
74063
  installThemeFromRegistry,
73747
74064
  getThemePublishPreview,
74065
+ checkThemeUpdatesForApp,
73748
74066
  prepareWidgetForPublish,
73749
74067
  inspectWidgetPackage,
73750
74068
  assignRoles,