@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.esm.js CHANGED
@@ -2549,6 +2549,27 @@ var ThemeWrapper = function ThemeWrapper(_ref) {
2549
2549
  // eslint-disable-next-line react-hooks/exhaustive-deps
2550
2550
  }, [chosenTheme, themeVariant, themeName, themesForApplication, rawThemes]);
2551
2551
 
2552
+ // Broadcast the app-level theme to a SEPARATE global so
2553
+ // components rendered OUTSIDE the React theme tree
2554
+ // (WidgetBuilderModal, AppUpdatesModal — they live as siblings
2555
+ // of DashboardStage in dash-electron's Dash.js render) can fall
2556
+ // back to it when no dashboard-specific override is in play.
2557
+ //
2558
+ // We can't share the same global with DashboardThemeProvider's
2559
+ // per-workspace broadcast because React commits child effects
2560
+ // BEFORE parent effects — ThemeWrapper would always run last
2561
+ // and silently overwrite the workspace theme. Two separate
2562
+ // globals avoid the race: consumers read
2563
+ // `__dashThemeContext` (workspace) first, fall back to
2564
+ // `__dashAppThemeContext` (app-level) when no workspace is open.
2565
+ useEffect(function () {
2566
+ if (typeof window === "undefined") return undefined;
2567
+ if (contextValue !== null && contextValue !== void 0 && contextValue.currentTheme) {
2568
+ window.__dashAppThemeContext = contextValue;
2569
+ window.dispatchEvent(new Event("dash:theme-changed"));
2570
+ }
2571
+ }, [contextValue]);
2572
+
2552
2573
  // Write the active theme's CSS custom properties to :root so any
2553
2574
  // hex-channel tokens (`bg-[var(--primary-700)]` etc. emitted by
2554
2575
  // ThemeModel) resolve app-wide. Without this, saving a hex theme
@@ -2786,13 +2807,42 @@ var useInstalledWidgets = function useInstalledWidgets() {
2786
2807
  error = _useState6[0],
2787
2808
  setError = _useState6[1];
2788
2809
  var refresh = useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
2789
- var _window$mainApi, cMap, builtinWidgets, registryByName, list, installedFromCM, cmSourcePackages, fallbackInstalled, _t;
2810
+ var _window$mainApi, _window$mainApi2, classifyWidget, cMap, builtinWidgets, draftsByPackageDir, draftShortIdToId, allDrafts, _iterator4, _step4, d, shortId, registryByName, list, installedFromCM, cmSourcePackages, fallbackInstalled, _t, _t3;
2790
2811
  return _regeneratorRuntime.wrap(function (_context) {
2791
2812
  while (1) switch (_context.prev = _context.next) {
2792
2813
  case 0:
2793
2814
  setIsLoading(true);
2794
2815
  setError(null);
2795
2816
  _context.prev = 1;
2817
+ classifyWidget = function classifyWidget(reg, fallbackName) {
2818
+ var name = (reg === null || reg === void 0 ? void 0 : reg.name) || fallbackName || "";
2819
+ var path = (reg === null || reg === void 0 ? void 0 : reg.path) || "";
2820
+ // 1. Match against drafts metadata by packageDir (canonical).
2821
+ if (path && draftsByPackageDir.has(path)) {
2822
+ return {
2823
+ kind: "draft",
2824
+ draftId: draftsByPackageDir.get(path).id
2825
+ };
2826
+ }
2827
+ // 2. Fallback: the dir name follows `<base>-draft-<shortId>`;
2828
+ // pluck the shortId and look it up in the drafts list.
2829
+ var m = String(name).match(/-draft-([A-Za-z0-9]+)$/);
2830
+ if (m) {
2831
+ var _shortId = m[1].slice(0, 8);
2832
+ var draftId = draftShortIdToId.get(_shortId) || null;
2833
+ return {
2834
+ kind: "draft",
2835
+ draftId: draftId
2836
+ };
2837
+ }
2838
+ return {
2839
+ kind: "installed",
2840
+ draftId: null
2841
+ };
2842
+ }; // ── Installed widgets from ComponentManager + Registry ───
2843
+ // CM entries with _sourcePackage are registry-installed widgets.
2844
+ // Show each as an individual "installed" entry, enriched with
2845
+ // registry-level metadata (version, path, packageId).
2796
2846
  // ── Built-in widgets from ComponentManager ──────────────
2797
2847
  cMap = ComponentManager.componentMap() || {};
2798
2848
  builtinWidgets = Object.keys(cMap).filter(function (key) {
@@ -2811,33 +2861,80 @@ var useInstalledWidgets = function useInstalledWidgets() {
2811
2861
  version: null,
2812
2862
  path: null,
2813
2863
  source: "builtin",
2864
+ kind: "installed",
2865
+ draftId: null,
2814
2866
  providers: config.providers || [],
2815
2867
  workspace: config.workspace || null,
2816
2868
  componentNames: [key],
2817
2869
  scopedId: key
2818
2870
  };
2819
- }); // ── Installed widgets from ComponentManager + Registry ───
2820
- // CM entries with _sourcePackage are registry-installed widgets.
2821
- // Show each as an individual "installed" entry, enriched with
2822
- // registry-level metadata (version, path, packageId).
2871
+ }); // ── Drafts (in-progress widgets from the AI Builder) ─────
2872
+ // Drafts on disk look like installed packages — their dirs
2873
+ // sit under @ai-built/<name>-draft-<shortId>/ alongside real
2874
+ // installs. We surface them as `kind: "draft"` so consumers
2875
+ // (dashboard picker, Settings → Widgets) can render them
2876
+ // distinctly (Resume/Delete affordances) or filter them out.
2877
+ // The match is `packageDir`-based when available (canonical)
2878
+ // and falls back to a `-draft-` name-pattern check so legacy
2879
+ // drafts without the on-disk metadata still get classified.
2880
+ draftsByPackageDir = new Map();
2881
+ draftShortIdToId = new Map();
2882
+ if (!((_window$mainApi = window.mainApi) !== null && _window$mainApi !== void 0 && (_window$mainApi = _window$mainApi.drafts) !== null && _window$mainApi !== void 0 && _window$mainApi.list)) {
2883
+ _context.next = 6;
2884
+ break;
2885
+ }
2886
+ _context.prev = 2;
2887
+ _context.next = 3;
2888
+ return window.mainApi.drafts.list();
2889
+ case 3:
2890
+ _t = _context.sent;
2891
+ if (_t) {
2892
+ _context.next = 4;
2893
+ break;
2894
+ }
2895
+ _t = [];
2896
+ case 4:
2897
+ allDrafts = _t;
2898
+ _iterator4 = _createForOfIteratorHelper$M(allDrafts);
2899
+ try {
2900
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
2901
+ d = _step4.value;
2902
+ if (d !== null && d !== void 0 && d.packageDir) draftsByPackageDir.set(d.packageDir, d);
2903
+ if (d !== null && d !== void 0 && d.id) {
2904
+ shortId = String(d.id).replace(/^draft-/, "").slice(0, 8);
2905
+ if (shortId) draftShortIdToId.set(shortId, d.id);
2906
+ }
2907
+ }
2908
+ } catch (err) {
2909
+ _iterator4.e(err);
2910
+ } finally {
2911
+ _iterator4.f();
2912
+ }
2913
+ _context.next = 6;
2914
+ break;
2915
+ case 5:
2916
+ _context.prev = 5;
2917
+ _context["catch"](2);
2918
+ case 6:
2823
2919
  registryByName = {};
2824
- if (!((_window$mainApi = window.mainApi) !== null && _window$mainApi !== void 0 && _window$mainApi.widgets)) {
2825
- _context.next = 3;
2920
+ if (!((_window$mainApi2 = window.mainApi) !== null && _window$mainApi2 !== void 0 && _window$mainApi2.widgets)) {
2921
+ _context.next = 8;
2826
2922
  break;
2827
2923
  }
2828
- _context.next = 2;
2924
+ _context.next = 7;
2829
2925
  return window.mainApi.widgets.list();
2830
- case 2:
2926
+ case 7:
2831
2927
  list = _context.sent;
2832
2928
  (list || []).forEach(function (w) {
2833
2929
  registryByName[w.packageId || w.name] = w;
2834
2930
  });
2835
- case 3:
2931
+ case 8:
2836
2932
  installedFromCM = Object.keys(cMap).filter(function (key) {
2837
2933
  return cMap[key].type === "widget" && !!cMap[key]._sourcePackage;
2838
2934
  }).map(function (key) {
2839
2935
  var config = cMap[key];
2840
2936
  var reg = registryByName[config._sourcePackage] || {};
2937
+ var classification = classifyWidget(reg, config._sourcePackage);
2841
2938
  return {
2842
2939
  name: key,
2843
2940
  displayName: config.name || key,
@@ -2848,6 +2945,8 @@ var useInstalledWidgets = function useInstalledWidgets() {
2848
2945
  version: reg.version || null,
2849
2946
  path: reg.path || null,
2850
2947
  source: "installed",
2948
+ kind: classification.kind,
2949
+ draftId: classification.draftId,
2851
2950
  providers: config.providers || [],
2852
2951
  workspace: config.workspace || null,
2853
2952
  componentNames: [key],
@@ -2864,6 +2963,7 @@ var useInstalledWidgets = function useInstalledWidgets() {
2864
2963
  fallbackInstalled = Object.values(registryByName).filter(function (w) {
2865
2964
  return !cmSourcePackages.has(w.name);
2866
2965
  }).map(function (w) {
2966
+ var classification = classifyWidget(w, w.name);
2867
2967
  return {
2868
2968
  name: w.name,
2869
2969
  displayName: w.displayName || w.name,
@@ -2874,6 +2974,8 @@ var useInstalledWidgets = function useInstalledWidgets() {
2874
2974
  version: w.version || null,
2875
2975
  path: w.path || null,
2876
2976
  source: "installed",
2977
+ kind: classification.kind,
2978
+ draftId: classification.draftId,
2877
2979
  providers: w.providers || [],
2878
2980
  workspace: w.workspace || null,
2879
2981
  componentNames: w.componentNames || [],
@@ -2882,31 +2984,31 @@ var useInstalledWidgets = function useInstalledWidgets() {
2882
2984
  };
2883
2985
  });
2884
2986
  setWidgets([].concat(_toConsumableArray(builtinWidgets), _toConsumableArray(installedFromCM), _toConsumableArray(fallbackInstalled)));
2885
- _context.next = 5;
2987
+ _context.next = 10;
2886
2988
  break;
2887
- case 4:
2888
- _context.prev = 4;
2889
- _t = _context["catch"](1);
2890
- setError(_t.message || "Failed to load widgets");
2989
+ case 9:
2990
+ _context.prev = 9;
2991
+ _t3 = _context["catch"](1);
2992
+ setError(_t3.message || "Failed to load widgets");
2891
2993
  setWidgets([]);
2892
- case 5:
2893
- _context.prev = 5;
2994
+ case 10:
2995
+ _context.prev = 10;
2894
2996
  setIsLoading(false);
2895
- return _context.finish(5);
2896
- case 6:
2997
+ return _context.finish(10);
2998
+ case 11:
2897
2999
  case "end":
2898
3000
  return _context.stop();
2899
3001
  }
2900
- }, _callee, null, [[1, 4, 5, 6]]);
3002
+ }, _callee, null, [[1, 9, 10, 11], [2, 5]]);
2901
3003
  })), []);
2902
3004
  var uninstallWidget = useCallback(/*#__PURE__*/function () {
2903
3005
  var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2(widgetName) {
2904
- var _window$mainApi2;
2905
- var widget, packageId, cMap, keysToRemove, _t2;
3006
+ var _window$mainApi3;
3007
+ var widget, packageId, cMap, keysToRemove, _t4;
2906
3008
  return _regeneratorRuntime.wrap(function (_context2) {
2907
3009
  while (1) switch (_context2.prev = _context2.next) {
2908
3010
  case 0:
2909
- if ((_window$mainApi2 = window.mainApi) !== null && _window$mainApi2 !== void 0 && _window$mainApi2.widgets) {
3011
+ if ((_window$mainApi3 = window.mainApi) !== null && _window$mainApi3 !== void 0 && _window$mainApi3.widgets) {
2910
3012
  _context2.next = 1;
2911
3013
  break;
2912
3014
  }
@@ -2937,8 +3039,8 @@ var useInstalledWidgets = function useInstalledWidgets() {
2937
3039
  break;
2938
3040
  case 4:
2939
3041
  _context2.prev = 4;
2940
- _t2 = _context2["catch"](1);
2941
- throw _t2;
3042
+ _t4 = _context2["catch"](1);
3043
+ throw _t4;
2942
3044
  case 5:
2943
3045
  case "end":
2944
3046
  return _context2.stop();
@@ -5725,6 +5827,45 @@ function ThemeBroadcast(_ref) {
5725
5827
  return null;
5726
5828
  }
5727
5829
 
5830
+ /**
5831
+ * Writes the dashboard theme's cssVars to `:root` while mounted, and
5832
+ * restores the previous values on unmount / theme switch.
5833
+ *
5834
+ * ThemeWrapper writes the APP theme's cssVars at the top of the tree.
5835
+ * Without this helper, a dashboard that overrides the app theme with a
5836
+ * hex-channel theme (like "Slack Generic") would carry the right class
5837
+ * names (`bg-[var(--primary-900)]`) but the `--primary-900` variable
5838
+ * on :root would still be the APP theme's value — or undefined if the
5839
+ * app theme is named-family. Hex-themed surfaces then render with no
5840
+ * background. This effect closes that gap by promoting the dashboard
5841
+ * theme's cssVars to :root for the duration of its mount.
5842
+ */
5843
+ function DashboardCssVarsBridge(_ref2) {
5844
+ var cssVars = _ref2.cssVars;
5845
+ useEffect(function () {
5846
+ if (!cssVars || typeof document === "undefined") return undefined;
5847
+ var root = document.documentElement;
5848
+ var previous = {};
5849
+ var keys = Object.keys(cssVars);
5850
+ for (var _i = 0, _keys = keys; _i < _keys.length; _i++) {
5851
+ var key = _keys[_i];
5852
+ previous[key] = root.style.getPropertyValue(key);
5853
+ root.style.setProperty(key, cssVars[key]);
5854
+ }
5855
+ return function () {
5856
+ for (var _i2 = 0, _keys2 = keys; _i2 < _keys2.length; _i2++) {
5857
+ var _key = _keys2[_i2];
5858
+ if (previous[_key]) {
5859
+ root.style.setProperty(_key, previous[_key]);
5860
+ } else {
5861
+ root.style.removeProperty(_key);
5862
+ }
5863
+ }
5864
+ };
5865
+ }, [cssVars]);
5866
+ return null;
5867
+ }
5868
+
5728
5869
  /**
5729
5870
  * DashboardThemeProvider
5730
5871
  *
@@ -5735,9 +5876,10 @@ function ThemeBroadcast(_ref) {
5735
5876
  * App chrome (navbar, tab bar, sidebar) stays OUTSIDE this wrapper
5736
5877
  * and keeps the app theme.
5737
5878
  */
5738
- var DashboardThemeProvider = function DashboardThemeProvider(_ref2) {
5739
- var themeKey = _ref2.themeKey,
5740
- children = _ref2.children;
5879
+ var DashboardThemeProvider = function DashboardThemeProvider(_ref3) {
5880
+ var _contextValue$current;
5881
+ var themeKey = _ref3.themeKey,
5882
+ children = _ref3.children;
5741
5883
  var parentContext = useContext(ThemeContext);
5742
5884
  var themes = parentContext.themes,
5743
5885
  themeVariant = parentContext.themeVariant;
@@ -5769,6 +5911,8 @@ var DashboardThemeProvider = function DashboardThemeProvider(_ref2) {
5769
5911
  value: contextValue,
5770
5912
  children: [/*#__PURE__*/jsx(ThemeBroadcast, {
5771
5913
  ctx: contextValue
5914
+ }), /*#__PURE__*/jsx(DashboardCssVarsBridge, {
5915
+ cssVars: contextValue === null || contextValue === void 0 || (_contextValue$current = contextValue.currentTheme) === null || _contextValue$current === void 0 ? void 0 : _contextValue$current.cssVars
5772
5916
  }), children]
5773
5917
  });
5774
5918
  };
@@ -17604,6 +17748,12 @@ var EnhancedWidgetDropdown = function EnhancedWidgetDropdown(_ref) {
17604
17748
  // Filter widgets based on search, author, and provider
17605
17749
  var getFilteredWidgets = function getFilteredWidgets() {
17606
17750
  var filtered = widgets.filter(function (widget) {
17751
+ // Drafts are in-progress widgets — they appear in
17752
+ // Settings → Widgets (as a Draft chip with Resume/Delete
17753
+ // affordances) but never in the dashboard placement picker
17754
+ // since they're not finished products.
17755
+ if (widget.kind === "draft") return false;
17756
+
17607
17757
  // Search filter
17608
17758
  var searchLower = searchQuery.toLowerCase();
17609
17759
  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) {
@@ -28958,6 +29108,36 @@ var WorkspaceModel = function WorkspaceModel(workspaceItem) {
28958
29108
  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; }
28959
29109
  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; }
28960
29110
 
29111
+ /**
29112
+ * Last-resort family for ANY channel whose value can't be resolved
29113
+ * (unknown name, missing palette entry, missing shade, …). Picked
29114
+ * because it's universally safelist-covered, has every shade from
29115
+ * 50..950, and is visually neutral — so a misconfigured theme
29116
+ * renders "muted" rather than blank/transparent.
29117
+ *
29118
+ * Slack Generic etc. exposed this: when a channel value was missing
29119
+ * or unrecognized, ThemeModel silently emitted `bg-undefined-700`
29120
+ * (not in any safelist) → Tailwind dropped the class → the
29121
+ * dropdown popover rendered transparent. Falling back here means
29122
+ * no token can be "missing" by the time `getStylesForItem` reads it.
29123
+ */
29124
+ var FALLBACK_FAMILY = "gray";
29125
+
29126
+ /**
29127
+ * Resolve a channel value to a safelist-covered Tailwind family
29128
+ * name. Returns the original value when it's a hex (the caller
29129
+ * handles those via CSS variables) or a recognized palette family;
29130
+ * otherwise returns FALLBACK_FAMILY so the resulting class always
29131
+ * renders.
29132
+ */
29133
+ function resolveChannelFamily(channelValue) {
29134
+ if (isHexColor$1(channelValue)) return channelValue;
29135
+ if (typeof channelValue === "string" && channelValue.length > 0 && TAILWIND_PALETTE && TAILWIND_PALETTE[channelValue]) {
29136
+ return channelValue;
29137
+ }
29138
+ return FALLBACK_FAMILY;
29139
+ }
29140
+
28961
29141
  /**
28962
29142
  * getNextLevel
28963
29143
  * Need to generate the levels for tailwind
@@ -28976,6 +29156,11 @@ function invert(shade) {
28976
29156
  * shade. Routes to the arbitrary-value syntax (`bg-[var(--type-shade)]`)
28977
29157
  * when the channel's value is a hex.
28978
29158
  *
29159
+ * Layer 1 fallback (audit follow-up to the Slack Generic bug): if
29160
+ * `channelValue` isn't a hex AND isn't a recognized Tailwind palette
29161
+ * family, fall back to FALLBACK_FAMILY so the emitted class is
29162
+ * always safelist-covered and never resolves to `bg-undefined-700`.
29163
+ *
28979
29164
  * @param {"bg"|"text"|"border"} prefix
28980
29165
  * @param {string} type channel name (primary | secondary | …)
28981
29166
  * @param {number} shade tailwind shade (100..950)
@@ -28985,10 +29170,11 @@ function invert(shade) {
28985
29170
  function classFor(prefix, type, shade, channelValue) {
28986
29171
  var hover = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
28987
29172
  var h = hover ? "hover:" : "";
28988
- if (isHexColor$1(channelValue)) {
29173
+ var resolved = resolveChannelFamily(channelValue);
29174
+ if (isHexColor$1(resolved)) {
28989
29175
  return "".concat(h).concat(prefix, "-[var(--").concat(type, "-").concat(shade, ")]");
28990
29176
  }
28991
- return "".concat(h).concat(prefix, "-").concat(channelValue, "-").concat(shade);
29177
+ return "".concat(h).concat(prefix, "-").concat(resolved, "-").concat(shade);
28992
29178
  }
28993
29179
 
28994
29180
  /**
@@ -29001,8 +29187,10 @@ function classFor(prefix, type, shade, channelValue) {
29001
29187
  * produce a `var(--{type}-{shade})` reference that resolves against
29002
29188
  * the CSS custom properties ThemePreviewProvider writes to :root.
29003
29189
  *
29004
- * Returns null for unknown named-color families or shades caller
29005
- * falls back to className-only reactivity for that token.
29190
+ * Layer 1 fallback: when the channel value or shade can't be
29191
+ * resolved, fall back to FALLBACK_FAMILY's shade so cssValue is
29192
+ * never null. Pairs with classFor's same fallback to guarantee
29193
+ * every (type, shade) tuple yields a renderable token.
29006
29194
  */
29007
29195
  function cssValueFor(type, shade, channelValue) {
29008
29196
  if (isHexColor$1(channelValue)) {
@@ -29015,12 +29203,17 @@ function cssValueFor(type, shade, channelValue) {
29015
29203
  var _hex = shades[shade] || shades[String(shade)];
29016
29204
  if (_hex) return _hex;
29017
29205
  }
29018
- return null;
29206
+ return fallbackCssValue(shade);
29019
29207
  }
29020
29208
  var family = TAILWIND_PALETTE && TAILWIND_PALETTE[channelValue];
29021
- if (!family) return null;
29209
+ if (!family) return fallbackCssValue(shade);
29022
29210
  var hex = family[shade] || family[String(shade)];
29023
- return hex || null;
29211
+ return hex || fallbackCssValue(shade);
29212
+ }
29213
+ function fallbackCssValue(shade) {
29214
+ var fallback = TAILWIND_PALETTE && TAILWIND_PALETTE[FALLBACK_FAMILY];
29215
+ if (!fallback) return null;
29216
+ return fallback[shade] || fallback[String(shade)] || null;
29024
29217
  }
29025
29218
  function gradientFor(direction, type, fromShade, viaShade, toShade, channelValue) {
29026
29219
  if (isHexColor$1(channelValue)) {
@@ -35205,11 +35398,25 @@ var ChannelEditorModal = function ChannelEditorModal(_ref) {
35205
35398
  var nearest = useMemo(function () {
35206
35399
  return nearestSwatch(selectedHex, families);
35207
35400
  }, [selectedHex, families]);
35401
+
35402
+ // Auto-switch the family tab when the underlying selected color
35403
+ // changes (user picked a different channel/shade or the theme
35404
+ // mutated). Track the last hex we synced for so we don't fight
35405
+ // the user's manual tab clicks — without this guard, every
35406
+ // re-render rebuilds `nearest` as a new object reference and the
35407
+ // effect snaps activeFamily back to the family containing the
35408
+ // current color, making it impossible to browse other families.
35409
+ var lastSyncedHexRef = useRef(null);
35208
35410
  useEffect(function () {
35209
- if (!isOpen) return;
35411
+ if (!isOpen) {
35412
+ lastSyncedHexRef.current = null;
35413
+ return;
35414
+ }
35415
+ if (lastSyncedHexRef.current === selectedHex) return;
35416
+ lastSyncedHexRef.current = selectedHex;
35210
35417
  if (nearest) setActiveFamily(nearest.family);
35211
35418
  // eslint-disable-next-line react-hooks/exhaustive-deps
35212
- }, [isOpen, nearest]);
35419
+ }, [isOpen, selectedHex, nearest]);
35213
35420
  function expandChannel(channelKey) {
35214
35421
  setActiveChannel(channelKey);
35215
35422
  setActiveSlot("base");
@@ -48540,7 +48747,27 @@ var InstalledWidgetDetail = function InstalledWidgetDetail(_ref) {
48540
48747
  className: "p-2 rounded bg-red-900/30 border border-red-700 text-xs text-red-400",
48541
48748
  children: updateError
48542
48749
  })
48543
- }), widget.source !== "builtin" && /*#__PURE__*/jsxs("div", {
48750
+ }), widget.source !== "builtin" && widget.kind === "draft" && /*#__PURE__*/jsxs("div", {
48751
+ 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"),
48752
+ children: [/*#__PURE__*/jsx(Button, {
48753
+ title: "Resume",
48754
+ onClick: function onClick() {
48755
+ window.dispatchEvent(new CustomEvent("dash:open-widget-builder", {
48756
+ detail: {
48757
+ resumeDraftId: widget.draftId || null
48758
+ }
48759
+ }));
48760
+ },
48761
+ disabled: !widget.draftId,
48762
+ size: "sm"
48763
+ }), /*#__PURE__*/jsx(Button, {
48764
+ title: "Delete",
48765
+ onClick: function onClick() {
48766
+ return onDelete(widget);
48767
+ },
48768
+ size: "sm"
48769
+ })]
48770
+ }), widget.source !== "builtin" && widget.kind !== "draft" && /*#__PURE__*/jsxs("div", {
48544
48771
  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"),
48545
48772
  children: [updateInfo && /*#__PURE__*/jsx(Button, {
48546
48773
  title: isUpdating ? "Updating..." : "Update Package to v".concat(updateInfo.latestVersion),
@@ -50007,7 +50234,12 @@ var WidgetsSection = function WidgetsSection(_ref) {
50007
50234
  className: "flex items-center gap-2",
50008
50235
  children: [widget.displayName || widget.name, widget.source === "builtin" && /*#__PURE__*/jsx(Tag3, {
50009
50236
  text: "Built-in"
50010
- }), updates.has(widget.name) && /*#__PURE__*/jsx("span", {
50237
+ }), widget.kind === "draft" && /*#__PURE__*/jsx("span", {
50238
+ 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",
50239
+ "data-testid": "widget-draft-chip-".concat(widget.name),
50240
+ title: "In-progress widget \u2014 open to Resume or Delete from the action menu",
50241
+ children: "Draft"
50242
+ }), widget.kind !== "draft" && updates.has(widget.name) && /*#__PURE__*/jsx("span", {
50011
50243
  className: "text-[10px] text-blue-400 font-medium",
50012
50244
  "data-testid": "widget-update-badge-".concat(widget.name),
50013
50245
  children: "Update"