@trops/dash-core 0.1.604 → 0.1.606

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
@@ -2567,6 +2567,27 @@ var ThemeWrapper = function ThemeWrapper(_ref) {
2567
2567
  // eslint-disable-next-line react-hooks/exhaustive-deps
2568
2568
  }, [chosenTheme, themeVariant, themeName, themesForApplication, rawThemes]);
2569
2569
 
2570
+ // Broadcast the app-level theme to a SEPARATE global so
2571
+ // components rendered OUTSIDE the React theme tree
2572
+ // (WidgetBuilderModal, AppUpdatesModal — they live as siblings
2573
+ // of DashboardStage in dash-electron's Dash.js render) can fall
2574
+ // back to it when no dashboard-specific override is in play.
2575
+ //
2576
+ // We can't share the same global with DashboardThemeProvider's
2577
+ // per-workspace broadcast because React commits child effects
2578
+ // BEFORE parent effects — ThemeWrapper would always run last
2579
+ // and silently overwrite the workspace theme. Two separate
2580
+ // globals avoid the race: consumers read
2581
+ // `__dashThemeContext` (workspace) first, fall back to
2582
+ // `__dashAppThemeContext` (app-level) when no workspace is open.
2583
+ React.useEffect(function () {
2584
+ if (typeof window === "undefined") return undefined;
2585
+ if (contextValue !== null && contextValue !== void 0 && contextValue.currentTheme) {
2586
+ window.__dashAppThemeContext = contextValue;
2587
+ window.dispatchEvent(new Event("dash:theme-changed"));
2588
+ }
2589
+ }, [contextValue]);
2590
+
2570
2591
  // Write the active theme's CSS custom properties to :root so any
2571
2592
  // hex-channel tokens (`bg-[var(--primary-700)]` etc. emitted by
2572
2593
  // ThemeModel) resolve app-wide. Without this, saving a hex theme
@@ -2804,13 +2825,42 @@ var useInstalledWidgets = function useInstalledWidgets() {
2804
2825
  error = _useState6[0],
2805
2826
  setError = _useState6[1];
2806
2827
  var refresh = React.useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
2807
- var _window$mainApi, cMap, builtinWidgets, registryByName, list, installedFromCM, cmSourcePackages, fallbackInstalled, _t;
2828
+ var _window$mainApi, _window$mainApi2, classifyWidget, cMap, builtinWidgets, draftsByPackageDir, draftShortIdToId, allDrafts, _iterator4, _step4, d, shortId, registryByName, list, installedFromCM, cmSourcePackages, fallbackInstalled, _t, _t3;
2808
2829
  return _regeneratorRuntime.wrap(function (_context) {
2809
2830
  while (1) switch (_context.prev = _context.next) {
2810
2831
  case 0:
2811
2832
  setIsLoading(true);
2812
2833
  setError(null);
2813
2834
  _context.prev = 1;
2835
+ classifyWidget = function classifyWidget(reg, fallbackName) {
2836
+ var name = (reg === null || reg === void 0 ? void 0 : reg.name) || fallbackName || "";
2837
+ var path = (reg === null || reg === void 0 ? void 0 : reg.path) || "";
2838
+ // 1. Match against drafts metadata by packageDir (canonical).
2839
+ if (path && draftsByPackageDir.has(path)) {
2840
+ return {
2841
+ kind: "draft",
2842
+ draftId: draftsByPackageDir.get(path).id
2843
+ };
2844
+ }
2845
+ // 2. Fallback: the dir name follows `<base>-draft-<shortId>`;
2846
+ // pluck the shortId and look it up in the drafts list.
2847
+ var m = String(name).match(/-draft-([A-Za-z0-9]+)$/);
2848
+ if (m) {
2849
+ var _shortId = m[1].slice(0, 8);
2850
+ var draftId = draftShortIdToId.get(_shortId) || null;
2851
+ return {
2852
+ kind: "draft",
2853
+ draftId: draftId
2854
+ };
2855
+ }
2856
+ return {
2857
+ kind: "installed",
2858
+ draftId: null
2859
+ };
2860
+ }; // ── Installed widgets from ComponentManager + Registry ───
2861
+ // CM entries with _sourcePackage are registry-installed widgets.
2862
+ // Show each as an individual "installed" entry, enriched with
2863
+ // registry-level metadata (version, path, packageId).
2814
2864
  // ── Built-in widgets from ComponentManager ──────────────
2815
2865
  cMap = ComponentManager.componentMap() || {};
2816
2866
  builtinWidgets = Object.keys(cMap).filter(function (key) {
@@ -2829,33 +2879,80 @@ var useInstalledWidgets = function useInstalledWidgets() {
2829
2879
  version: null,
2830
2880
  path: null,
2831
2881
  source: "builtin",
2882
+ kind: "installed",
2883
+ draftId: null,
2832
2884
  providers: config.providers || [],
2833
2885
  workspace: config.workspace || null,
2834
2886
  componentNames: [key],
2835
2887
  scopedId: key
2836
2888
  };
2837
- }); // ── Installed widgets from ComponentManager + Registry ───
2838
- // CM entries with _sourcePackage are registry-installed widgets.
2839
- // Show each as an individual "installed" entry, enriched with
2840
- // registry-level metadata (version, path, packageId).
2889
+ }); // ── Drafts (in-progress widgets from the AI Builder) ─────
2890
+ // Drafts on disk look like installed packages — their dirs
2891
+ // sit under @ai-built/<name>-draft-<shortId>/ alongside real
2892
+ // installs. We surface them as `kind: "draft"` so consumers
2893
+ // (dashboard picker, Settings → Widgets) can render them
2894
+ // distinctly (Resume/Delete affordances) or filter them out.
2895
+ // The match is `packageDir`-based when available (canonical)
2896
+ // and falls back to a `-draft-` name-pattern check so legacy
2897
+ // drafts without the on-disk metadata still get classified.
2898
+ draftsByPackageDir = new Map();
2899
+ draftShortIdToId = new Map();
2900
+ if (!((_window$mainApi = window.mainApi) !== null && _window$mainApi !== void 0 && (_window$mainApi = _window$mainApi.drafts) !== null && _window$mainApi !== void 0 && _window$mainApi.list)) {
2901
+ _context.next = 6;
2902
+ break;
2903
+ }
2904
+ _context.prev = 2;
2905
+ _context.next = 3;
2906
+ return window.mainApi.drafts.list();
2907
+ case 3:
2908
+ _t = _context.sent;
2909
+ if (_t) {
2910
+ _context.next = 4;
2911
+ break;
2912
+ }
2913
+ _t = [];
2914
+ case 4:
2915
+ allDrafts = _t;
2916
+ _iterator4 = _createForOfIteratorHelper$M(allDrafts);
2917
+ try {
2918
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
2919
+ d = _step4.value;
2920
+ if (d !== null && d !== void 0 && d.packageDir) draftsByPackageDir.set(d.packageDir, d);
2921
+ if (d !== null && d !== void 0 && d.id) {
2922
+ shortId = String(d.id).replace(/^draft-/, "").slice(0, 8);
2923
+ if (shortId) draftShortIdToId.set(shortId, d.id);
2924
+ }
2925
+ }
2926
+ } catch (err) {
2927
+ _iterator4.e(err);
2928
+ } finally {
2929
+ _iterator4.f();
2930
+ }
2931
+ _context.next = 6;
2932
+ break;
2933
+ case 5:
2934
+ _context.prev = 5;
2935
+ _context["catch"](2);
2936
+ case 6:
2841
2937
  registryByName = {};
2842
- if (!((_window$mainApi = window.mainApi) !== null && _window$mainApi !== void 0 && _window$mainApi.widgets)) {
2843
- _context.next = 3;
2938
+ if (!((_window$mainApi2 = window.mainApi) !== null && _window$mainApi2 !== void 0 && _window$mainApi2.widgets)) {
2939
+ _context.next = 8;
2844
2940
  break;
2845
2941
  }
2846
- _context.next = 2;
2942
+ _context.next = 7;
2847
2943
  return window.mainApi.widgets.list();
2848
- case 2:
2944
+ case 7:
2849
2945
  list = _context.sent;
2850
2946
  (list || []).forEach(function (w) {
2851
2947
  registryByName[w.packageId || w.name] = w;
2852
2948
  });
2853
- case 3:
2949
+ case 8:
2854
2950
  installedFromCM = Object.keys(cMap).filter(function (key) {
2855
2951
  return cMap[key].type === "widget" && !!cMap[key]._sourcePackage;
2856
2952
  }).map(function (key) {
2857
2953
  var config = cMap[key];
2858
2954
  var reg = registryByName[config._sourcePackage] || {};
2955
+ var classification = classifyWidget(reg, config._sourcePackage);
2859
2956
  return {
2860
2957
  name: key,
2861
2958
  displayName: config.name || key,
@@ -2866,6 +2963,8 @@ var useInstalledWidgets = function useInstalledWidgets() {
2866
2963
  version: reg.version || null,
2867
2964
  path: reg.path || null,
2868
2965
  source: "installed",
2966
+ kind: classification.kind,
2967
+ draftId: classification.draftId,
2869
2968
  providers: config.providers || [],
2870
2969
  workspace: config.workspace || null,
2871
2970
  componentNames: [key],
@@ -2882,6 +2981,7 @@ var useInstalledWidgets = function useInstalledWidgets() {
2882
2981
  fallbackInstalled = Object.values(registryByName).filter(function (w) {
2883
2982
  return !cmSourcePackages.has(w.name);
2884
2983
  }).map(function (w) {
2984
+ var classification = classifyWidget(w, w.name);
2885
2985
  return {
2886
2986
  name: w.name,
2887
2987
  displayName: w.displayName || w.name,
@@ -2892,6 +2992,8 @@ var useInstalledWidgets = function useInstalledWidgets() {
2892
2992
  version: w.version || null,
2893
2993
  path: w.path || null,
2894
2994
  source: "installed",
2995
+ kind: classification.kind,
2996
+ draftId: classification.draftId,
2895
2997
  providers: w.providers || [],
2896
2998
  workspace: w.workspace || null,
2897
2999
  componentNames: w.componentNames || [],
@@ -2900,31 +3002,31 @@ var useInstalledWidgets = function useInstalledWidgets() {
2900
3002
  };
2901
3003
  });
2902
3004
  setWidgets([].concat(_toConsumableArray(builtinWidgets), _toConsumableArray(installedFromCM), _toConsumableArray(fallbackInstalled)));
2903
- _context.next = 5;
3005
+ _context.next = 10;
2904
3006
  break;
2905
- case 4:
2906
- _context.prev = 4;
2907
- _t = _context["catch"](1);
2908
- setError(_t.message || "Failed to load widgets");
3007
+ case 9:
3008
+ _context.prev = 9;
3009
+ _t3 = _context["catch"](1);
3010
+ setError(_t3.message || "Failed to load widgets");
2909
3011
  setWidgets([]);
2910
- case 5:
2911
- _context.prev = 5;
3012
+ case 10:
3013
+ _context.prev = 10;
2912
3014
  setIsLoading(false);
2913
- return _context.finish(5);
2914
- case 6:
3015
+ return _context.finish(10);
3016
+ case 11:
2915
3017
  case "end":
2916
3018
  return _context.stop();
2917
3019
  }
2918
- }, _callee, null, [[1, 4, 5, 6]]);
3020
+ }, _callee, null, [[1, 9, 10, 11], [2, 5]]);
2919
3021
  })), []);
2920
3022
  var uninstallWidget = React.useCallback(/*#__PURE__*/function () {
2921
3023
  var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2(widgetName) {
2922
- var _window$mainApi2;
2923
- var widget, packageId, cMap, keysToRemove, _t2;
3024
+ var _window$mainApi3;
3025
+ var widget, packageId, cMap, keysToRemove, _t4;
2924
3026
  return _regeneratorRuntime.wrap(function (_context2) {
2925
3027
  while (1) switch (_context2.prev = _context2.next) {
2926
3028
  case 0:
2927
- if ((_window$mainApi2 = window.mainApi) !== null && _window$mainApi2 !== void 0 && _window$mainApi2.widgets) {
3029
+ if ((_window$mainApi3 = window.mainApi) !== null && _window$mainApi3 !== void 0 && _window$mainApi3.widgets) {
2928
3030
  _context2.next = 1;
2929
3031
  break;
2930
3032
  }
@@ -2955,8 +3057,8 @@ var useInstalledWidgets = function useInstalledWidgets() {
2955
3057
  break;
2956
3058
  case 4:
2957
3059
  _context2.prev = 4;
2958
- _t2 = _context2["catch"](1);
2959
- throw _t2;
3060
+ _t4 = _context2["catch"](1);
3061
+ throw _t4;
2960
3062
  case 5:
2961
3063
  case "end":
2962
3064
  return _context2.stop();
@@ -5743,6 +5845,45 @@ function ThemeBroadcast(_ref) {
5743
5845
  return null;
5744
5846
  }
5745
5847
 
5848
+ /**
5849
+ * Writes the dashboard theme's cssVars to `:root` while mounted, and
5850
+ * restores the previous values on unmount / theme switch.
5851
+ *
5852
+ * ThemeWrapper writes the APP theme's cssVars at the top of the tree.
5853
+ * Without this helper, a dashboard that overrides the app theme with a
5854
+ * hex-channel theme (like "Slack Generic") would carry the right class
5855
+ * names (`bg-[var(--primary-900)]`) but the `--primary-900` variable
5856
+ * on :root would still be the APP theme's value — or undefined if the
5857
+ * app theme is named-family. Hex-themed surfaces then render with no
5858
+ * background. This effect closes that gap by promoting the dashboard
5859
+ * theme's cssVars to :root for the duration of its mount.
5860
+ */
5861
+ function DashboardCssVarsBridge(_ref2) {
5862
+ var cssVars = _ref2.cssVars;
5863
+ React.useEffect(function () {
5864
+ if (!cssVars || typeof document === "undefined") return undefined;
5865
+ var root = document.documentElement;
5866
+ var previous = {};
5867
+ var keys = Object.keys(cssVars);
5868
+ for (var _i = 0, _keys = keys; _i < _keys.length; _i++) {
5869
+ var key = _keys[_i];
5870
+ previous[key] = root.style.getPropertyValue(key);
5871
+ root.style.setProperty(key, cssVars[key]);
5872
+ }
5873
+ return function () {
5874
+ for (var _i2 = 0, _keys2 = keys; _i2 < _keys2.length; _i2++) {
5875
+ var _key = _keys2[_i2];
5876
+ if (previous[_key]) {
5877
+ root.style.setProperty(_key, previous[_key]);
5878
+ } else {
5879
+ root.style.removeProperty(_key);
5880
+ }
5881
+ }
5882
+ };
5883
+ }, [cssVars]);
5884
+ return null;
5885
+ }
5886
+
5746
5887
  /**
5747
5888
  * DashboardThemeProvider
5748
5889
  *
@@ -5753,9 +5894,10 @@ function ThemeBroadcast(_ref) {
5753
5894
  * App chrome (navbar, tab bar, sidebar) stays OUTSIDE this wrapper
5754
5895
  * and keeps the app theme.
5755
5896
  */
5756
- var DashboardThemeProvider = function DashboardThemeProvider(_ref2) {
5757
- var themeKey = _ref2.themeKey,
5758
- children = _ref2.children;
5897
+ var DashboardThemeProvider = function DashboardThemeProvider(_ref3) {
5898
+ var _contextValue$current;
5899
+ var themeKey = _ref3.themeKey,
5900
+ children = _ref3.children;
5759
5901
  var parentContext = React.useContext(DashReact.ThemeContext);
5760
5902
  var themes = parentContext.themes,
5761
5903
  themeVariant = parentContext.themeVariant;
@@ -5787,6 +5929,8 @@ var DashboardThemeProvider = function DashboardThemeProvider(_ref2) {
5787
5929
  value: contextValue,
5788
5930
  children: [/*#__PURE__*/jsxRuntime.jsx(ThemeBroadcast, {
5789
5931
  ctx: contextValue
5932
+ }), /*#__PURE__*/jsxRuntime.jsx(DashboardCssVarsBridge, {
5933
+ cssVars: contextValue === null || contextValue === void 0 || (_contextValue$current = contextValue.currentTheme) === null || _contextValue$current === void 0 ? void 0 : _contextValue$current.cssVars
5790
5934
  }), children]
5791
5935
  });
5792
5936
  };
@@ -17622,6 +17766,12 @@ var EnhancedWidgetDropdown = function EnhancedWidgetDropdown(_ref) {
17622
17766
  // Filter widgets based on search, author, and provider
17623
17767
  var getFilteredWidgets = function getFilteredWidgets() {
17624
17768
  var filtered = widgets.filter(function (widget) {
17769
+ // Drafts are in-progress widgets — they appear in
17770
+ // Settings → Widgets (as a Draft chip with Resume/Delete
17771
+ // affordances) but never in the dashboard placement picker
17772
+ // since they're not finished products.
17773
+ if (widget.kind === "draft") return false;
17774
+
17625
17775
  // Search filter
17626
17776
  var searchLower = searchQuery.toLowerCase();
17627
17777
  var matchesSearch = !searchQuery || (widget.name || "").toLowerCase().includes(searchLower) || (widget.description || "").toLowerCase().includes(searchLower) || (widget.key || "").toLowerCase().includes(searchLower) || (widget.packageName || "").toLowerCase().includes(searchLower) || (widget.packageTags || []).some(function (t) {
@@ -28976,6 +29126,36 @@ var WorkspaceModel = function WorkspaceModel(workspaceItem) {
28976
29126
  function ownKeys$H(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
28977
29127
  function _objectSpread$H(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$H(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$H(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
28978
29128
 
29129
+ /**
29130
+ * Last-resort family for ANY channel whose value can't be resolved
29131
+ * (unknown name, missing palette entry, missing shade, …). Picked
29132
+ * because it's universally safelist-covered, has every shade from
29133
+ * 50..950, and is visually neutral — so a misconfigured theme
29134
+ * renders "muted" rather than blank/transparent.
29135
+ *
29136
+ * Slack Generic etc. exposed this: when a channel value was missing
29137
+ * or unrecognized, ThemeModel silently emitted `bg-undefined-700`
29138
+ * (not in any safelist) → Tailwind dropped the class → the
29139
+ * dropdown popover rendered transparent. Falling back here means
29140
+ * no token can be "missing" by the time `getStylesForItem` reads it.
29141
+ */
29142
+ var FALLBACK_FAMILY = "gray";
29143
+
29144
+ /**
29145
+ * Resolve a channel value to a safelist-covered Tailwind family
29146
+ * name. Returns the original value when it's a hex (the caller
29147
+ * handles those via CSS variables) or a recognized palette family;
29148
+ * otherwise returns FALLBACK_FAMILY so the resulting class always
29149
+ * renders.
29150
+ */
29151
+ function resolveChannelFamily(channelValue) {
29152
+ if (DashReact.isHexColor(channelValue)) return channelValue;
29153
+ if (typeof channelValue === "string" && channelValue.length > 0 && DashReact.TAILWIND_PALETTE && DashReact.TAILWIND_PALETTE[channelValue]) {
29154
+ return channelValue;
29155
+ }
29156
+ return FALLBACK_FAMILY;
29157
+ }
29158
+
28979
29159
  /**
28980
29160
  * getNextLevel
28981
29161
  * Need to generate the levels for tailwind
@@ -28994,6 +29174,11 @@ function invert(shade) {
28994
29174
  * shade. Routes to the arbitrary-value syntax (`bg-[var(--type-shade)]`)
28995
29175
  * when the channel's value is a hex.
28996
29176
  *
29177
+ * Layer 1 fallback (audit follow-up to the Slack Generic bug): if
29178
+ * `channelValue` isn't a hex AND isn't a recognized Tailwind palette
29179
+ * family, fall back to FALLBACK_FAMILY so the emitted class is
29180
+ * always safelist-covered and never resolves to `bg-undefined-700`.
29181
+ *
28997
29182
  * @param {"bg"|"text"|"border"} prefix
28998
29183
  * @param {string} type channel name (primary | secondary | …)
28999
29184
  * @param {number} shade tailwind shade (100..950)
@@ -29003,10 +29188,11 @@ function invert(shade) {
29003
29188
  function classFor(prefix, type, shade, channelValue) {
29004
29189
  var hover = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
29005
29190
  var h = hover ? "hover:" : "";
29006
- if (DashReact.isHexColor(channelValue)) {
29191
+ var resolved = resolveChannelFamily(channelValue);
29192
+ if (DashReact.isHexColor(resolved)) {
29007
29193
  return "".concat(h).concat(prefix, "-[var(--").concat(type, "-").concat(shade, ")]");
29008
29194
  }
29009
- return "".concat(h).concat(prefix, "-").concat(channelValue, "-").concat(shade);
29195
+ return "".concat(h).concat(prefix, "-").concat(resolved, "-").concat(shade);
29010
29196
  }
29011
29197
 
29012
29198
  /**
@@ -29019,8 +29205,10 @@ function classFor(prefix, type, shade, channelValue) {
29019
29205
  * produce a `var(--{type}-{shade})` reference that resolves against
29020
29206
  * the CSS custom properties ThemePreviewProvider writes to :root.
29021
29207
  *
29022
- * Returns null for unknown named-color families or shades caller
29023
- * falls back to className-only reactivity for that token.
29208
+ * Layer 1 fallback: when the channel value or shade can't be
29209
+ * resolved, fall back to FALLBACK_FAMILY's shade so cssValue is
29210
+ * never null. Pairs with classFor's same fallback to guarantee
29211
+ * every (type, shade) tuple yields a renderable token.
29024
29212
  */
29025
29213
  function cssValueFor(type, shade, channelValue) {
29026
29214
  if (DashReact.isHexColor(channelValue)) {
@@ -29033,12 +29221,17 @@ function cssValueFor(type, shade, channelValue) {
29033
29221
  var _hex = shades[shade] || shades[String(shade)];
29034
29222
  if (_hex) return _hex;
29035
29223
  }
29036
- return null;
29224
+ return fallbackCssValue(shade);
29037
29225
  }
29038
29226
  var family = DashReact.TAILWIND_PALETTE && DashReact.TAILWIND_PALETTE[channelValue];
29039
- if (!family) return null;
29227
+ if (!family) return fallbackCssValue(shade);
29040
29228
  var hex = family[shade] || family[String(shade)];
29041
- return hex || null;
29229
+ return hex || fallbackCssValue(shade);
29230
+ }
29231
+ function fallbackCssValue(shade) {
29232
+ var fallback = DashReact.TAILWIND_PALETTE && DashReact.TAILWIND_PALETTE[FALLBACK_FAMILY];
29233
+ if (!fallback) return null;
29234
+ return fallback[shade] || fallback[String(shade)] || null;
29042
29235
  }
29043
29236
  function gradientFor(direction, type, fromShade, viaShade, toShade, channelValue) {
29044
29237
  if (DashReact.isHexColor(channelValue)) {
@@ -35223,11 +35416,25 @@ var ChannelEditorModal = function ChannelEditorModal(_ref) {
35223
35416
  var nearest = React.useMemo(function () {
35224
35417
  return nearestSwatch(selectedHex, families);
35225
35418
  }, [selectedHex, families]);
35419
+
35420
+ // Auto-switch the family tab when the underlying selected color
35421
+ // changes (user picked a different channel/shade or the theme
35422
+ // mutated). Track the last hex we synced for so we don't fight
35423
+ // the user's manual tab clicks — without this guard, every
35424
+ // re-render rebuilds `nearest` as a new object reference and the
35425
+ // effect snaps activeFamily back to the family containing the
35426
+ // current color, making it impossible to browse other families.
35427
+ var lastSyncedHexRef = React.useRef(null);
35226
35428
  React.useEffect(function () {
35227
- if (!isOpen) return;
35429
+ if (!isOpen) {
35430
+ lastSyncedHexRef.current = null;
35431
+ return;
35432
+ }
35433
+ if (lastSyncedHexRef.current === selectedHex) return;
35434
+ lastSyncedHexRef.current = selectedHex;
35228
35435
  if (nearest) setActiveFamily(nearest.family);
35229
35436
  // eslint-disable-next-line react-hooks/exhaustive-deps
35230
- }, [isOpen, nearest]);
35437
+ }, [isOpen, selectedHex, nearest]);
35231
35438
  function expandChannel(channelKey) {
35232
35439
  setActiveChannel(channelKey);
35233
35440
  setActiveSlot("base");
@@ -48558,7 +48765,27 @@ var InstalledWidgetDetail = function InstalledWidgetDetail(_ref) {
48558
48765
  className: "p-2 rounded bg-red-900/30 border border-red-700 text-xs text-red-400",
48559
48766
  children: updateError
48560
48767
  })
48561
- }), widget.source !== "builtin" && /*#__PURE__*/jsxRuntime.jsxs("div", {
48768
+ }), widget.source !== "builtin" && widget.kind === "draft" && /*#__PURE__*/jsxRuntime.jsxs("div", {
48769
+ className: "flex-shrink-0 flex flex-row justify-end gap-2 px-6 py-4 border-t ".concat(currentTheme["border-primary-medium"] || "border-white/10"),
48770
+ children: [/*#__PURE__*/jsxRuntime.jsx(DashReact.Button, {
48771
+ title: "Resume",
48772
+ onClick: function onClick() {
48773
+ window.dispatchEvent(new CustomEvent("dash:open-widget-builder", {
48774
+ detail: {
48775
+ resumeDraftId: widget.draftId || null
48776
+ }
48777
+ }));
48778
+ },
48779
+ disabled: !widget.draftId,
48780
+ size: "sm"
48781
+ }), /*#__PURE__*/jsxRuntime.jsx(DashReact.Button, {
48782
+ title: "Delete",
48783
+ onClick: function onClick() {
48784
+ return onDelete(widget);
48785
+ },
48786
+ size: "sm"
48787
+ })]
48788
+ }), widget.source !== "builtin" && widget.kind !== "draft" && /*#__PURE__*/jsxRuntime.jsxs("div", {
48562
48789
  className: "flex-shrink-0 flex flex-row justify-end gap-2 px-6 py-4 border-t ".concat(currentTheme["border-primary-medium"] || "border-white/10"),
48563
48790
  children: [updateInfo && /*#__PURE__*/jsxRuntime.jsx(DashReact.Button, {
48564
48791
  title: isUpdating ? "Updating..." : "Update Package to v".concat(updateInfo.latestVersion),
@@ -50025,7 +50252,12 @@ var WidgetsSection = function WidgetsSection(_ref) {
50025
50252
  className: "flex items-center gap-2",
50026
50253
  children: [widget.displayName || widget.name, widget.source === "builtin" && /*#__PURE__*/jsxRuntime.jsx(DashReact.Tag3, {
50027
50254
  text: "Built-in"
50028
- }), updates.has(widget.name) && /*#__PURE__*/jsxRuntime.jsx("span", {
50255
+ }), widget.kind === "draft" && /*#__PURE__*/jsxRuntime.jsx("span", {
50256
+ className: "px-1.5 py-0.5 rounded-full text-[10px] font-medium bg-amber-900/40 text-amber-200 border border-amber-700/30",
50257
+ "data-testid": "widget-draft-chip-".concat(widget.name),
50258
+ title: "In-progress widget \u2014 open to Resume or Delete from the action menu",
50259
+ children: "Draft"
50260
+ }), widget.kind !== "draft" && updates.has(widget.name) && /*#__PURE__*/jsxRuntime.jsx("span", {
50029
50261
  className: "text-[10px] text-blue-400 font-medium",
50030
50262
  "data-testid": "widget-update-badge-".concat(widget.name),
50031
50263
  children: "Update"