@trops/dash-core 0.1.602 → 0.1.604

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
@@ -6537,7 +6537,7 @@ var FolderDetail = function FolderDetail(_ref) {
6537
6537
  });
6538
6538
  };
6539
6539
 
6540
- var OptionCard$2 = function OptionCard(_ref) {
6540
+ var OptionCard$3 = function OptionCard(_ref) {
6541
6541
  var icon = _ref.icon,
6542
6542
  title = _ref.title,
6543
6543
  description = _ref.description,
@@ -6586,28 +6586,28 @@ var CreationMethodPicker = function CreationMethodPicker(_ref2) {
6586
6586
  })]
6587
6587
  }), /*#__PURE__*/jsxRuntime.jsxs("div", {
6588
6588
  className: "flex flex-col w-2/3 p-6 pt-10 space-y-3",
6589
- children: [/*#__PURE__*/jsxRuntime.jsx(OptionCard$2, {
6589
+ children: [/*#__PURE__*/jsxRuntime.jsx(OptionCard$3, {
6590
6590
  icon: "plus",
6591
6591
  title: "New Dashboard",
6592
6592
  description: "Start from a blank template and customize your layout",
6593
6593
  onClick: function onClick() {
6594
6594
  return onSelect("template");
6595
6595
  }
6596
- }), /*#__PURE__*/jsxRuntime.jsx(OptionCard$2, {
6596
+ }), /*#__PURE__*/jsxRuntime.jsx(OptionCard$3, {
6597
6597
  icon: "file-zipper",
6598
6598
  title: "Import from File",
6599
6599
  description: "Import a dashboard from a .zip file on your computer",
6600
6600
  onClick: function onClick() {
6601
6601
  return onSelect("import");
6602
6602
  }
6603
- }), /*#__PURE__*/jsxRuntime.jsx(OptionCard$2, {
6603
+ }), /*#__PURE__*/jsxRuntime.jsx(OptionCard$3, {
6604
6604
  icon: "compass",
6605
6605
  title: "Search Registry",
6606
6606
  description: "Browse and install dashboards from the online registry",
6607
6607
  onClick: function onClick() {
6608
6608
  return onSelect("registry");
6609
6609
  }
6610
- }), /*#__PURE__*/jsxRuntime.jsx(OptionCard$2, {
6610
+ }), /*#__PURE__*/jsxRuntime.jsx(OptionCard$3, {
6611
6611
  icon: "wand-magic-sparkles",
6612
6612
  title: "Dashboard Wizard",
6613
6613
  description: "Guided setup \u2014 pick categories, providers, and widgets step by step",
@@ -41733,6 +41733,94 @@ var DashboardDetail = function DashboardDetail(_ref2) {
41733
41733
  });
41734
41734
  };
41735
41735
 
41736
+ var OptionCard$2 = function OptionCard(_ref) {
41737
+ var icon = _ref.icon,
41738
+ title = _ref.title,
41739
+ description = _ref.description,
41740
+ onClick = _ref.onClick,
41741
+ currentTheme = _ref.currentTheme;
41742
+ return /*#__PURE__*/jsxRuntime.jsxs("button", {
41743
+ type: "button",
41744
+ onClick: onClick,
41745
+ className: "w-full flex flex-row items-center gap-4 p-4 rounded-lg text-left transition-opacity ".concat(currentTheme["bg-primary-medium"] || "bg-white/5", " hover:opacity-80"),
41746
+ children: [/*#__PURE__*/jsxRuntime.jsx("div", {
41747
+ className: "flex-shrink-0 h-8 w-8 flex items-center justify-center opacity-60",
41748
+ children: /*#__PURE__*/jsxRuntime.jsx(DashReact.FontAwesomeIcon, {
41749
+ icon: icon,
41750
+ className: "h-5 w-5"
41751
+ })
41752
+ }), /*#__PURE__*/jsxRuntime.jsxs("div", {
41753
+ className: "flex flex-col min-w-0",
41754
+ children: [/*#__PURE__*/jsxRuntime.jsx("span", {
41755
+ className: "text-sm font-medium",
41756
+ children: title
41757
+ }), /*#__PURE__*/jsxRuntime.jsx("span", {
41758
+ className: "text-xs opacity-50 mt-0.5",
41759
+ children: description
41760
+ })]
41761
+ }), /*#__PURE__*/jsxRuntime.jsx("div", {
41762
+ className: "flex-shrink-0 ml-auto opacity-30",
41763
+ children: /*#__PURE__*/jsxRuntime.jsx(DashReact.FontAwesomeIcon, {
41764
+ icon: "chevron-right",
41765
+ className: "h-3 w-3"
41766
+ })
41767
+ })]
41768
+ });
41769
+ };
41770
+
41771
+ /**
41772
+ * NewDashboardChooser — consolidated entry point for the
41773
+ * "New Dashboard" header button in Settings → Dashboards.
41774
+ *
41775
+ * Audit #19 fix: the prior header button was labeled "Marketplace"
41776
+ * which was ambiguous (it set installMode=marketplace, duplicating
41777
+ * the Marketplace tab in the list). Renamed to "New Dashboard"; the
41778
+ * chooser presents the actual creation paths as labeled cards,
41779
+ * matching the ThemeNewChooser pattern.
41780
+ *
41781
+ * Options:
41782
+ * - "marketplace" → registry browser (existing DiscoverDashboardsDetail)
41783
+ * - "wizard" → existing dashboard creation wizard
41784
+ *
41785
+ * The Marketplace TAB in the list view stays — it's the in-place
41786
+ * browse affordance, distinct from this "I want to create a new
41787
+ * dashboard" entry.
41788
+ */
41789
+ var NewDashboardChooser = function NewDashboardChooser(_ref2) {
41790
+ var onSelect = _ref2.onSelect;
41791
+ var _useContext = React.useContext(DashReact.ThemeContext),
41792
+ currentTheme = _useContext.currentTheme;
41793
+ var panelStyles = DashReact.getStylesForItem(DashReact.themeObjects.PANEL, currentTheme, {
41794
+ grow: false
41795
+ });
41796
+ return /*#__PURE__*/jsxRuntime.jsx("div", {
41797
+ className: "flex flex-col flex-1 min-h-0",
41798
+ children: /*#__PURE__*/jsxRuntime.jsxs("div", {
41799
+ className: "flex-1 overflow-y-auto p-6 space-y-3 ".concat(panelStyles.textColor || "text-gray-200"),
41800
+ children: [/*#__PURE__*/jsxRuntime.jsx("span", {
41801
+ className: "text-xs font-semibold opacity-50 block mb-4",
41802
+ children: "CREATE A DASHBOARD"
41803
+ }), /*#__PURE__*/jsxRuntime.jsx(OptionCard$2, {
41804
+ icon: "compass",
41805
+ title: "Search Marketplace",
41806
+ description: "Browse and install community dashboards from the online registry",
41807
+ onClick: function onClick() {
41808
+ return onSelect("marketplace");
41809
+ },
41810
+ currentTheme: currentTheme
41811
+ }), /*#__PURE__*/jsxRuntime.jsx(OptionCard$2, {
41812
+ icon: "wand-magic-sparkles",
41813
+ title: "From Wizard",
41814
+ description: "Build a new dashboard from a layout + theme + widgets",
41815
+ onClick: function onClick() {
41816
+ return onSelect("wizard");
41817
+ },
41818
+ currentTheme: currentTheme
41819
+ })]
41820
+ })
41821
+ });
41822
+ };
41823
+
41736
41824
  function ownKeys$x(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; }
41737
41825
  function _objectSpread$x(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$x(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$x(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
41738
41826
  var DashboardsSection = function DashboardsSection(_ref) {
@@ -41778,7 +41866,9 @@ var DashboardsSection = function DashboardsSection(_ref) {
41778
41866
  _useState10 = _slicedToArray(_useState1, 2),
41779
41867
  viewMode = _useState10[0],
41780
41868
  setViewMode = _useState10[1];
41781
- // null | "marketplace"
41869
+ // null | "picker" | "marketplace"
41870
+ // picker — NewDashboardChooser (Phase 19 / audit #19)
41871
+ // marketplace — DiscoverDashboardsDetail (registry browser)
41782
41872
  var _useState11 = React.useState(null),
41783
41873
  _useState12 = _slicedToArray(_useState11, 2),
41784
41874
  installMode = _useState12[0],
@@ -41864,12 +41954,16 @@ var DashboardsSection = function DashboardsSection(_ref) {
41864
41954
  });
41865
41955
  }
41866
41956
 
41867
- // Respond to external create trigger from header button (marketplace)
41957
+ // Respond to external create trigger from the "New Dashboard"
41958
+ // header button. Audit #19: this now opens the consolidated
41959
+ // NewDashboardChooser (Marketplace + Wizard cards), matching the
41960
+ // ThemeNewChooser pattern. Pre-fix this went straight to
41961
+ // installMode="marketplace" which made the button ambiguous.
41868
41962
  var prevCreateRequested = React.useRef(false);
41869
41963
  React.useEffect(function () {
41870
41964
  if (createRequested && !prevCreateRequested.current) {
41871
41965
  setSelectedId(null);
41872
- setInstallMode("marketplace");
41966
+ setInstallMode("picker");
41873
41967
  }
41874
41968
  prevCreateRequested.current = createRequested;
41875
41969
  if (createRequested && onCreateAcknowledged) {
@@ -41877,6 +41971,16 @@ var DashboardsSection = function DashboardsSection(_ref) {
41877
41971
  }
41878
41972
  // eslint-disable-next-line react-hooks/exhaustive-deps
41879
41973
  }, [createRequested]);
41974
+ function handleChooserSelect(option) {
41975
+ if (option === "marketplace") {
41976
+ setInstallMode("marketplace");
41977
+ } else if (option === "wizard") {
41978
+ // onOpenWizard closes the Settings modal and opens the
41979
+ // dashboard wizard. Provided by AppSettingsModal.
41980
+ setInstallMode(null);
41981
+ if (typeof onOpenWizard === "function") onOpenWizard();
41982
+ }
41983
+ }
41880
41984
  var selectedWorkspace = workspaces.find(function (ws) {
41881
41985
  return ws.id === selectedId;
41882
41986
  });
@@ -41988,7 +42092,11 @@ var DashboardsSection = function DashboardsSection(_ref) {
41988
42092
  })]
41989
42093
  });
41990
42094
  var detailContent = null;
41991
- if (installMode === "marketplace") {
42095
+ if (installMode === "picker") {
42096
+ detailContent = /*#__PURE__*/jsxRuntime.jsx(NewDashboardChooser, {
42097
+ onSelect: handleChooserSelect
42098
+ });
42099
+ } else if (installMode === "marketplace") {
41992
42100
  detailContent = /*#__PURE__*/jsxRuntime.jsx(DiscoverDashboardsDetail, {
41993
42101
  onBack: function onBack() {
41994
42102
  setInstallMode(null);
@@ -48521,12 +48629,15 @@ var OptionCard = function OptionCard(_ref) {
48521
48629
  };
48522
48630
 
48523
48631
  /**
48524
- * InstallWidgetPicker — the 3-option menu shown when "Install Widgets" is clicked.
48632
+ * InstallWidgetPicker — the consolidated chooser shown when the
48633
+ * "New Widget" header button is clicked (audit #19).
48525
48634
  *
48526
48635
  * Options:
48527
- * 1. Search for Widgets (registry browser)
48528
- * 2. Install from File (.zip)
48529
- * 3. Load from Folder
48636
+ * 1. Use Widget Builder (Phase 19 — folds the inline "+ New Widget"
48637
+ * button's function into the chooser so there's one entry point)
48638
+ * 2. Search for Widgets (registry browser)
48639
+ * 3. Install from File (.zip)
48640
+ * 4. Load from Folder
48530
48641
  */
48531
48642
  var InstallWidgetPicker = function InstallWidgetPicker(_ref2) {
48532
48643
  var onSelect = _ref2.onSelect;
@@ -48541,7 +48652,15 @@ var InstallWidgetPicker = function InstallWidgetPicker(_ref2) {
48541
48652
  className: "flex-1 overflow-y-auto p-6 space-y-3 ".concat(panelStyles.textColor || "text-gray-200"),
48542
48653
  children: [/*#__PURE__*/jsxRuntime.jsx("span", {
48543
48654
  className: "text-xs font-semibold opacity-50 block mb-4",
48544
- children: "HOW TO INSTALL"
48655
+ children: "CREATE A WIDGET"
48656
+ }), /*#__PURE__*/jsxRuntime.jsx(OptionCard, {
48657
+ icon: "wand-magic-sparkles",
48658
+ title: "Use Widget Builder",
48659
+ description: "Open the AI Widget Builder to create a new widget from scratch",
48660
+ onClick: function onClick() {
48661
+ return onSelect("builder");
48662
+ },
48663
+ currentTheme: currentTheme
48545
48664
  }), /*#__PURE__*/jsxRuntime.jsx(OptionCard, {
48546
48665
  icon: "compass",
48547
48666
  title: "Search for Widgets",
@@ -49833,7 +49952,14 @@ var WidgetsSection = function WidgetsSection(_ref) {
49833
49952
  setProgressComplete(false);
49834
49953
  }
49835
49954
  function handlePickerSelect(option) {
49836
- if (option === "discover") {
49955
+ if (option === "builder") {
49956
+ // Audit #19: the inline "+ New Widget" button was removed in
49957
+ // favor of this card so there's a single entry point for new-
49958
+ // widget creation. Closes the picker and fires the same event
49959
+ // the inline button used to dispatch.
49960
+ setInstallMode(null);
49961
+ window.dispatchEvent(new Event("dash:open-widget-builder"));
49962
+ } else if (option === "discover") {
49837
49963
  setInstallMode("discover");
49838
49964
  } else if (option === "zip") {
49839
49965
  handleInstallFromZip();
@@ -49931,23 +50057,7 @@ var WidgetsSection = function WidgetsSection(_ref) {
49931
50057
  }
49932
50058
  var listContent = /*#__PURE__*/jsxRuntime.jsxs("div", {
49933
50059
  className: "flex flex-col h-full",
49934
- children: [/*#__PURE__*/jsxRuntime.jsx("div", {
49935
- className: "flex-shrink-0 px-3 pt-2 pb-1",
49936
- children: /*#__PURE__*/jsxRuntime.jsxs("button", {
49937
- type: "button",
49938
- onClick: function onClick() {
49939
- return window.dispatchEvent(new Event("dash:open-widget-builder"));
49940
- },
49941
- className: "w-full flex items-center justify-center gap-2 px-3 py-2 text-sm font-medium rounded bg-indigo-600 hover:bg-indigo-500 text-white",
49942
- "data-testid": "widgets-section-new-widget-button",
49943
- children: [/*#__PURE__*/jsxRuntime.jsx(DashReact.FontAwesomeIcon, {
49944
- icon: "plus",
49945
- className: "text-xs"
49946
- }), /*#__PURE__*/jsxRuntime.jsx("span", {
49947
- children: "New Widget"
49948
- })]
49949
- })
49950
- }), isChecking && packagesWithUpdates.length === 0 && /*#__PURE__*/jsxRuntime.jsxs("div", {
50060
+ children: [isChecking && packagesWithUpdates.length === 0 && /*#__PURE__*/jsxRuntime.jsxs("div", {
49951
50061
  className: "flex-shrink-0 px-3 py-2 border-b border-white/10 bg-gray-800/60 flex items-center gap-2 text-xs text-gray-400",
49952
50062
  "data-testid": "widgets-section-checking-updates",
49953
50063
  children: [/*#__PURE__*/jsxRuntime.jsx(DashReact.FontAwesomeIcon, {
@@ -54583,7 +54693,7 @@ var AppSettingsModal = function AppSettingsModal(_ref) {
54583
54693
  padding: false
54584
54694
  }), (activeSection === "dashboards" || activeSection === "folders" || activeSection === "providers" || activeSection === "themes" || activeSection === "widgets") && /*#__PURE__*/jsxRuntime.jsx(DashReact.ButtonIcon3, {
54585
54695
  icon: "plus",
54586
- text: activeSection === "dashboards" ? "Marketplace" : activeSection === "folders" ? "New Folder" : activeSection === "providers" ? "New Provider" : activeSection === "widgets" ? "Install Widgets" : "New Theme",
54696
+ text: activeSection === "dashboards" ? "New Dashboard" : activeSection === "folders" ? "New Folder" : activeSection === "providers" ? "New Provider" : activeSection === "widgets" ? "New Widget" : "New Theme",
54587
54697
  onClick: function onClick() {
54588
54698
  return setCreateRequested(true);
54589
54699
  },
@@ -54800,13 +54910,41 @@ var DashboardLoaderModal = function DashboardLoaderModal(_ref) {
54800
54910
  var KITCHEN_SINK_PACKAGE = "trops/kitchen-sink";
54801
54911
  var STATE = {
54802
54912
  WELCOME: "welcome",
54913
+ // AUTH_REQUIRED replaces the generic "Install Failed" screen when
54914
+ // the registry returns `authRequired: true`. Surfaces a
54915
+ // benefits-driven sign-in CTA instead of asking the user to
54916
+ // figure out why install died.
54917
+ AUTH_REQUIRED: "auth-required",
54803
54918
  INSTALLING: "installing",
54804
54919
  DONE: "done",
54805
54920
  ERROR: "error"
54806
54921
  };
54922
+
54923
+ /**
54924
+ * Returns the first workspace that was installed from the Kitchen
54925
+ * Sink package, or null. Used for the modal's dedupe check so we
54926
+ * never create a second Kitchen Sink when one already exists.
54927
+ *
54928
+ * Workspaces installed via the registry carry a
54929
+ * `_dashboardConfig.registryPackage` field — historically the
54930
+ * unscoped name ("kitchen-sink"), more recently the scoped form
54931
+ * ("trops/kitchen-sink"). Match on either trailing segment.
54932
+ */
54933
+ function findExistingKitchenSink(workspaces) {
54934
+ if (!Array.isArray(workspaces)) return null;
54935
+ return workspaces.find(function (ws) {
54936
+ var _ws$_dashboardConfig;
54937
+ var pkg = ws === null || ws === void 0 || (_ws$_dashboardConfig = ws._dashboardConfig) === null || _ws$_dashboardConfig === void 0 ? void 0 : _ws$_dashboardConfig.registryPackage;
54938
+ if (typeof pkg !== "string") return false;
54939
+ var trailing = pkg.includes("/") ? pkg.split("/").pop() : pkg;
54940
+ return trailing === "kitchen-sink";
54941
+ }) || null;
54942
+ }
54807
54943
  var OnboardingModal = function OnboardingModal(_ref) {
54808
54944
  var open = _ref.open,
54809
54945
  appId = _ref.appId,
54946
+ _ref$workspaces = _ref.workspaces,
54947
+ workspaces = _ref$workspaces === void 0 ? [] : _ref$workspaces,
54810
54948
  onOpenDashboard = _ref.onOpenDashboard,
54811
54949
  onDismiss = _ref.onDismiss,
54812
54950
  onComplete = _ref.onComplete;
@@ -54826,6 +54964,16 @@ var OnboardingModal = function OnboardingModal(_ref) {
54826
54964
  setInstallError = _useState6[1];
54827
54965
  var installResultRef = React.useRef(null);
54828
54966
  var cleanupProgressRef = React.useRef(null);
54967
+
54968
+ // Reused device-code OAuth state machine (same hook AppUpdatesModal
54969
+ // uses). `authFlow` carries the user code + verification URL once
54970
+ // initiateAuth fires; while non-null we render the polling panel.
54971
+ var _useRegistryAuth = useRegistryAuth(),
54972
+ isAuthenticating = _useRegistryAuth.isAuthenticating,
54973
+ authFlow = _useRegistryAuth.authFlow,
54974
+ authError = _useRegistryAuth.authError,
54975
+ initiateAuth = _useRegistryAuth.initiateAuth,
54976
+ cancelAuth = _useRegistryAuth.cancelAuth;
54829
54977
  React.useEffect(function () {
54830
54978
  if (open) {
54831
54979
  setState(STATE.WELCOME);
@@ -54887,7 +55035,7 @@ var OnboardingModal = function OnboardingModal(_ref) {
54887
55035
  })), [markCompletedAndClose, onDismiss]);
54888
55036
  var handleInstall = React.useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
54889
55037
  var _window$mainApi2, _window$mainApi2$onIn;
54890
- var _window$mainApi3, _window$mainApi3$inst, result, _t2;
55038
+ var existing, _window$mainApi3, _window$mainApi3$inst, result, _t2;
54891
55039
  return _regeneratorRuntime.wrap(function (_context3) {
54892
55040
  while (1) switch (_context3.prev = _context3.next) {
54893
55041
  case 0:
@@ -54899,6 +55047,24 @@ var OnboardingModal = function OnboardingModal(_ref) {
54899
55047
  setState(STATE.ERROR);
54900
55048
  return _context3.abrupt("return");
54901
55049
  case 1:
55050
+ // Dedupe: if the user already has a Kitchen Sink workspace
55051
+ // installed from the registry, skip the install entirely and
55052
+ // route them to the existing one. Without this, the modal
55053
+ // could be re-triggered (e.g. after clearing the
55054
+ // onboarding.completed flag) and would silently create
55055
+ // "Kitchen Sink 2", "Kitchen Sink 3", etc.
55056
+ existing = findExistingKitchenSink(workspaces);
55057
+ if (!existing) {
55058
+ _context3.next = 2;
55059
+ break;
55060
+ }
55061
+ installResultRef.current = {
55062
+ success: true,
55063
+ workspace: existing
55064
+ };
55065
+ setState(STATE.DONE);
55066
+ return _context3.abrupt("return");
55067
+ case 2:
54902
55068
  setState(STATE.INSTALLING);
54903
55069
  setProgressItems([]);
54904
55070
  setInstallError(null);
@@ -54926,42 +55092,49 @@ var OnboardingModal = function OnboardingModal(_ref) {
54926
55092
  return next;
54927
55093
  });
54928
55094
  });
54929
- _context3.prev = 2;
54930
- _context3.next = 3;
55095
+ _context3.prev = 3;
55096
+ _context3.next = 4;
54931
55097
  return (_window$mainApi3 = window.mainApi) === null || _window$mainApi3 === void 0 || (_window$mainApi3 = _window$mainApi3.dashboardConfig) === null || _window$mainApi3 === void 0 || (_window$mainApi3$inst = _window$mainApi3.installDashboardFromRegistry) === null || _window$mainApi3$inst === void 0 ? void 0 : _window$mainApi3$inst.call(_window$mainApi3, appId, KITCHEN_SINK_PACKAGE, {});
54932
- case 3:
55098
+ case 4:
54933
55099
  result = _context3.sent;
54934
55100
  if (cleanupProgressRef.current) {
54935
55101
  cleanupProgressRef.current();
54936
55102
  cleanupProgressRef.current = null;
54937
55103
  }
54938
55104
  if (!(!result || !result.success)) {
54939
- _context3.next = 4;
55105
+ _context3.next = 6;
54940
55106
  break;
54941
55107
  }
55108
+ if (!(result !== null && result !== void 0 && result.authRequired)) {
55109
+ _context3.next = 5;
55110
+ break;
55111
+ }
55112
+ setState(STATE.AUTH_REQUIRED);
55113
+ return _context3.abrupt("return");
55114
+ case 5:
54942
55115
  setInstallError((result === null || result === void 0 ? void 0 : result.error) || "Failed to install Kitchen Sink. Check your internet connection and try again.");
54943
55116
  setState(STATE.ERROR);
54944
55117
  return _context3.abrupt("return");
54945
- case 4:
55118
+ case 6:
54946
55119
  installResultRef.current = result;
54947
55120
  setState(STATE.DONE);
54948
- _context3.next = 6;
55121
+ _context3.next = 8;
54949
55122
  break;
54950
- case 5:
54951
- _context3.prev = 5;
54952
- _t2 = _context3["catch"](2);
55123
+ case 7:
55124
+ _context3.prev = 7;
55125
+ _t2 = _context3["catch"](3);
54953
55126
  if (cleanupProgressRef.current) {
54954
55127
  cleanupProgressRef.current();
54955
55128
  cleanupProgressRef.current = null;
54956
55129
  }
54957
55130
  setInstallError((_t2 === null || _t2 === void 0 ? void 0 : _t2.message) || "Installation failed.");
54958
55131
  setState(STATE.ERROR);
54959
- case 6:
55132
+ case 8:
54960
55133
  case "end":
54961
55134
  return _context3.stop();
54962
55135
  }
54963
- }, _callee3, null, [[2, 5]]);
54964
- })), [appId]);
55136
+ }, _callee3, null, [[3, 7]]);
55137
+ })), [appId, workspaces]);
54965
55138
  var handleOpen = React.useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
54966
55139
  var _installResultRef$cur;
54967
55140
  var workspace;
@@ -54986,6 +55159,17 @@ var OnboardingModal = function OnboardingModal(_ref) {
54986
55159
  handleInstall();
54987
55160
  }, [handleInstall]);
54988
55161
 
55162
+ // From the AUTH_REQUIRED state: kick off device-code OAuth. The
55163
+ // hook opens the browser, polls in the background, and fires our
55164
+ // onAuthorized callback on success. We auto-retry the install
55165
+ // there so the user lands on the DONE state without a second
55166
+ // click.
55167
+ var handleSignIn = React.useCallback(function () {
55168
+ initiateAuth(function () {
55169
+ handleInstall();
55170
+ });
55171
+ }, [initiateAuth, handleInstall]);
55172
+
54989
55173
  // The Modal dispatches setIsOpen(false) on Escape / backdrop click.
54990
55174
  // Route any close attempt through handleSkip so the completion flag
54991
55175
  // is always stamped (otherwise Escape would silently re-show the
@@ -55009,6 +55193,15 @@ var OnboardingModal = function OnboardingModal(_ref) {
55009
55193
  onInstall: handleInstall,
55010
55194
  onSkip: handleSkip,
55011
55195
  textMuted: textMuted
55196
+ }), state === STATE.AUTH_REQUIRED && /*#__PURE__*/jsxRuntime.jsx(AuthRequiredBody, {
55197
+ onSignIn: handleSignIn,
55198
+ onCancelAuth: cancelAuth,
55199
+ onSkip: handleSkip,
55200
+ isAuthenticating: isAuthenticating,
55201
+ authFlow: authFlow,
55202
+ authError: authError,
55203
+ textMuted: textMuted,
55204
+ borderPanel: borderPanel
55012
55205
  }), state === STATE.INSTALLING && /*#__PURE__*/jsxRuntime.jsx(InstallingBody, {
55013
55206
  items: progressItems,
55014
55207
  borderPanel: borderPanel,
@@ -55038,11 +55231,13 @@ function WelcomeBody(_ref7) {
55038
55231
  }), /*#__PURE__*/jsxRuntime.jsx(DashReact.Heading2, {
55039
55232
  title: "Welcome to Dash"
55040
55233
  })]
55041
- }), /*#__PURE__*/jsxRuntime.jsxs(DashReact.Paragraph, {
55234
+ }), /*#__PURE__*/jsxRuntime.jsx(DashReact.Paragraph, {
55042
55235
  className: "".concat(textMuted, " mb-6"),
55043
- children: ["Get started with the ", /*#__PURE__*/jsxRuntime.jsx("strong", {
55044
- children: "Kitchen Sink"
55045
- }), " dashboard \u2014 a curated set of widgets that shows what Dash can do. You can swap anything out, or build your own from scratch later."]
55236
+ children: /*#__PURE__*/jsxRuntime.jsxs("span", {
55237
+ children: ["Get started with the ", /*#__PURE__*/jsxRuntime.jsx("strong", {
55238
+ children: "Kitchen Sink"
55239
+ }), " dashboard \u2014 a curated set of widgets that shows what Dash can do. You can swap anything out, or build your own from scratch later."]
55240
+ })
55046
55241
  }), /*#__PURE__*/jsxRuntime.jsxs("div", {
55047
55242
  className: "flex items-center justify-end gap-3 mt-2",
55048
55243
  children: [/*#__PURE__*/jsxRuntime.jsx(DashReact.Button, {
@@ -55203,6 +55398,153 @@ function ErrorBody(_ref1) {
55203
55398
  })]
55204
55399
  });
55205
55400
  }
55401
+ function BenefitRow(_ref10) {
55402
+ var icon = _ref10.icon,
55403
+ title = _ref10.title,
55404
+ description = _ref10.description,
55405
+ textMuted = _ref10.textMuted;
55406
+ return /*#__PURE__*/jsxRuntime.jsxs("div", {
55407
+ className: "flex items-start gap-3",
55408
+ children: [/*#__PURE__*/jsxRuntime.jsx("div", {
55409
+ className: "flex-shrink-0 w-7 h-7 flex items-center justify-center mt-0.5",
55410
+ children: /*#__PURE__*/jsxRuntime.jsx(DashReact.FontAwesomeIcon, {
55411
+ icon: icon,
55412
+ className: "text-base opacity-70"
55413
+ })
55414
+ }), /*#__PURE__*/jsxRuntime.jsxs("div", {
55415
+ className: "flex flex-col",
55416
+ children: [/*#__PURE__*/jsxRuntime.jsx("span", {
55417
+ className: "text-sm font-medium",
55418
+ children: title
55419
+ }), /*#__PURE__*/jsxRuntime.jsx("span", {
55420
+ className: "text-xs ".concat(textMuted),
55421
+ children: description
55422
+ })]
55423
+ })]
55424
+ });
55425
+ }
55426
+ function AuthRequiredBody(_ref11) {
55427
+ var onSignIn = _ref11.onSignIn,
55428
+ onCancelAuth = _ref11.onCancelAuth,
55429
+ onSkip = _ref11.onSkip,
55430
+ isAuthenticating = _ref11.isAuthenticating,
55431
+ authFlow = _ref11.authFlow,
55432
+ authError = _ref11.authError,
55433
+ textMuted = _ref11.textMuted,
55434
+ borderPanel = _ref11.borderPanel;
55435
+ // While the device-code flow is polling, swap the benefits panel
55436
+ // for the user-code + verification-URL display so the user knows
55437
+ // what to enter in the browser tab we just opened.
55438
+ if (isAuthenticating && authFlow) {
55439
+ return /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
55440
+ children: [/*#__PURE__*/jsxRuntime.jsxs("div", {
55441
+ className: "flex items-center gap-3 mb-2",
55442
+ children: [/*#__PURE__*/jsxRuntime.jsx(DashReact.FontAwesomeIcon, {
55443
+ icon: "circle-notch",
55444
+ className: "text-2xl animate-spin"
55445
+ }), /*#__PURE__*/jsxRuntime.jsx(DashReact.Heading2, {
55446
+ title: "Waiting for browser sign-in"
55447
+ })]
55448
+ }), /*#__PURE__*/jsxRuntime.jsx(DashReact.Paragraph, {
55449
+ className: "".concat(textMuted, " mb-4"),
55450
+ children: /*#__PURE__*/jsxRuntime.jsx("span", {
55451
+ children: "We opened a browser tab where you can sign in to the Dash Registry. Enter this code if prompted:"
55452
+ })
55453
+ }), /*#__PURE__*/jsxRuntime.jsxs("div", {
55454
+ className: "border ".concat(borderPanel, " rounded-md p-4 mb-4 text-center"),
55455
+ "data-testid": "onboarding-auth-user-code",
55456
+ children: [/*#__PURE__*/jsxRuntime.jsx("div", {
55457
+ className: "text-2xl font-mono tracking-widest",
55458
+ children: authFlow.userCode || "—"
55459
+ }), /*#__PURE__*/jsxRuntime.jsx("div", {
55460
+ className: "text-xs ".concat(textMuted, " mt-2 break-all"),
55461
+ children: authFlow.verificationUrlComplete || authFlow.verificationUrl || ""
55462
+ })]
55463
+ }), /*#__PURE__*/jsxRuntime.jsx("div", {
55464
+ className: "flex items-center justify-end gap-3",
55465
+ children: /*#__PURE__*/jsxRuntime.jsx(DashReact.Button, {
55466
+ onClick: onCancelAuth,
55467
+ title: "Cancel",
55468
+ textSize: "text-sm",
55469
+ padding: "py-2 px-4",
55470
+ backgroundColor: "bg-gray-700",
55471
+ textColor: "text-gray-300",
55472
+ hoverTextColor: "hover:text-white",
55473
+ hoverBackgroundColor: "hover:bg-gray-600",
55474
+ "data-testid": "onboarding-auth-cancel-button"
55475
+ })
55476
+ })]
55477
+ });
55478
+ }
55479
+ return /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
55480
+ children: [/*#__PURE__*/jsxRuntime.jsxs("div", {
55481
+ className: "flex items-center gap-3 mb-2",
55482
+ children: [/*#__PURE__*/jsxRuntime.jsx(DashReact.FontAwesomeIcon, {
55483
+ icon: "user-plus",
55484
+ className: "text-2xl"
55485
+ }), /*#__PURE__*/jsxRuntime.jsx(DashReact.Heading2, {
55486
+ title: "Sign in to the Dash Registry"
55487
+ })]
55488
+ }), /*#__PURE__*/jsxRuntime.jsx(DashReact.Paragraph, {
55489
+ className: "".concat(textMuted, " mb-5"),
55490
+ children: /*#__PURE__*/jsxRuntime.jsx("span", {
55491
+ children: "A free Dash account unlocks the community ecosystem. Sign in to continue installing Kitchen Sink \u2014 and to use everything else the registry offers."
55492
+ })
55493
+ }), /*#__PURE__*/jsxRuntime.jsxs("div", {
55494
+ className: "grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6",
55495
+ children: [/*#__PURE__*/jsxRuntime.jsx(BenefitRow, {
55496
+ icon: "cube",
55497
+ title: "Install widgets",
55498
+ description: "Browse and one-click install community widgets",
55499
+ textMuted: textMuted
55500
+ }), /*#__PURE__*/jsxRuntime.jsx(BenefitRow, {
55501
+ icon: "table-cells",
55502
+ title: "Discover dashboards",
55503
+ description: "Like Kitchen Sink \u2014 curated starters built by others",
55504
+ textMuted: textMuted
55505
+ }), /*#__PURE__*/jsxRuntime.jsx(BenefitRow, {
55506
+ icon: "palette",
55507
+ title: "Apply themes",
55508
+ description: "Community-built themes for any taste",
55509
+ textMuted: textMuted
55510
+ }), /*#__PURE__*/jsxRuntime.jsx(BenefitRow, {
55511
+ icon: "upload",
55512
+ title: "Publish your own",
55513
+ description: "Share your widgets and dashboards with the Dash community",
55514
+ textMuted: textMuted
55515
+ })]
55516
+ }), authError && /*#__PURE__*/jsxRuntime.jsx(DashReact.Paragraph, {
55517
+ className: "text-red-400 mb-4",
55518
+ children: /*#__PURE__*/jsxRuntime.jsx("span", {
55519
+ children: authError
55520
+ })
55521
+ }), /*#__PURE__*/jsxRuntime.jsxs("div", {
55522
+ className: "flex items-center justify-end gap-3 mt-2",
55523
+ children: [/*#__PURE__*/jsxRuntime.jsx(DashReact.Button, {
55524
+ onClick: onSkip,
55525
+ title: "Skip for now",
55526
+ textSize: "text-sm",
55527
+ padding: "py-2 px-4",
55528
+ backgroundColor: "bg-gray-700",
55529
+ textColor: "text-gray-300",
55530
+ hoverTextColor: "hover:text-white",
55531
+ hoverBackgroundColor: "hover:bg-gray-600",
55532
+ "data-testid": "onboarding-auth-skip-button"
55533
+ }), /*#__PURE__*/jsxRuntime.jsx(DashReact.Button, {
55534
+ onClick: onSignIn,
55535
+ title: "Sign in to Registry",
55536
+ textSize: "text-sm",
55537
+ padding: "py-2 px-4",
55538
+ backgroundColor: "bg-blue-600",
55539
+ textColor: "text-white",
55540
+ hoverTextColor: "hover:text-white",
55541
+ hoverBackgroundColor: "hover:bg-blue-500",
55542
+ icon: "arrow-up-right-from-square",
55543
+ "data-testid": "onboarding-auth-signin-button"
55544
+ })]
55545
+ })]
55546
+ });
55547
+ }
55206
55548
 
55207
55549
  var DashCommandPalette = function DashCommandPalette(_ref) {
55208
55550
  var isOpen = _ref.isOpen,
@@ -61216,7 +61558,7 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
61216
61558
  onboardingCheckedRef.current = true;
61217
61559
  var cancelled = false;
61218
61560
  _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
61219
- var _window$mainApi, _window$mainApi$getSt, status;
61561
+ var _window$mainApi, _window$mainApi$getSt, status, alreadyHasKitchenSink;
61220
61562
  return _regeneratorRuntime.wrap(function (_context) {
61221
61563
  while (1) switch (_context.prev = _context.next) {
61222
61564
  case 0:
@@ -61231,7 +61573,20 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
61231
61573
  }
61232
61574
  return _context.abrupt("return");
61233
61575
  case 2:
61234
- if (status && status.completed === false && workspaceConfig.length === 0) {
61576
+ // Defense-in-depth: even if `workspaceConfig.length === 0`
61577
+ // briefly reads true during a race between `isLoadingWorkspaces`
61578
+ // and the workspace array populating, don't show the modal
61579
+ // if the user already has a Kitchen Sink dashboard installed
61580
+ // from the registry. Same dedupe rule the modal's install
61581
+ // path applies.
61582
+ alreadyHasKitchenSink = workspaceConfig.some(function (ws) {
61583
+ var _ws$_dashboardConfig;
61584
+ var pkg = ws === null || ws === void 0 || (_ws$_dashboardConfig = ws._dashboardConfig) === null || _ws$_dashboardConfig === void 0 ? void 0 : _ws$_dashboardConfig.registryPackage;
61585
+ if (typeof pkg !== "string") return false;
61586
+ var trailing = pkg.includes("/") ? pkg.split("/").pop() : pkg;
61587
+ return trailing === "kitchen-sink";
61588
+ });
61589
+ if (status && status.completed === false && workspaceConfig.length === 0 && !alreadyHasKitchenSink) {
61235
61590
  setIsOnboardingOpen(true);
61236
61591
  } else {
61237
61592
  setIsOnboardingOpen(false);
@@ -63035,6 +63390,7 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
63035
63390
  }), /*#__PURE__*/jsxRuntime.jsx(OnboardingModal, {
63036
63391
  open: isOnboardingOpen === true,
63037
63392
  appId: credentials === null || credentials === void 0 ? void 0 : credentials.appId,
63393
+ workspaces: workspaceConfig,
63038
63394
  onDismiss: function onDismiss() {
63039
63395
  return setIsOnboardingOpen(false);
63040
63396
  },