@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.esm.js CHANGED
@@ -6519,7 +6519,7 @@ var FolderDetail = function FolderDetail(_ref) {
6519
6519
  });
6520
6520
  };
6521
6521
 
6522
- var OptionCard$2 = function OptionCard(_ref) {
6522
+ var OptionCard$3 = function OptionCard(_ref) {
6523
6523
  var icon = _ref.icon,
6524
6524
  title = _ref.title,
6525
6525
  description = _ref.description,
@@ -6568,28 +6568,28 @@ var CreationMethodPicker = function CreationMethodPicker(_ref2) {
6568
6568
  })]
6569
6569
  }), /*#__PURE__*/jsxs("div", {
6570
6570
  className: "flex flex-col w-2/3 p-6 pt-10 space-y-3",
6571
- children: [/*#__PURE__*/jsx(OptionCard$2, {
6571
+ children: [/*#__PURE__*/jsx(OptionCard$3, {
6572
6572
  icon: "plus",
6573
6573
  title: "New Dashboard",
6574
6574
  description: "Start from a blank template and customize your layout",
6575
6575
  onClick: function onClick() {
6576
6576
  return onSelect("template");
6577
6577
  }
6578
- }), /*#__PURE__*/jsx(OptionCard$2, {
6578
+ }), /*#__PURE__*/jsx(OptionCard$3, {
6579
6579
  icon: "file-zipper",
6580
6580
  title: "Import from File",
6581
6581
  description: "Import a dashboard from a .zip file on your computer",
6582
6582
  onClick: function onClick() {
6583
6583
  return onSelect("import");
6584
6584
  }
6585
- }), /*#__PURE__*/jsx(OptionCard$2, {
6585
+ }), /*#__PURE__*/jsx(OptionCard$3, {
6586
6586
  icon: "compass",
6587
6587
  title: "Search Registry",
6588
6588
  description: "Browse and install dashboards from the online registry",
6589
6589
  onClick: function onClick() {
6590
6590
  return onSelect("registry");
6591
6591
  }
6592
- }), /*#__PURE__*/jsx(OptionCard$2, {
6592
+ }), /*#__PURE__*/jsx(OptionCard$3, {
6593
6593
  icon: "wand-magic-sparkles",
6594
6594
  title: "Dashboard Wizard",
6595
6595
  description: "Guided setup \u2014 pick categories, providers, and widgets step by step",
@@ -41715,6 +41715,94 @@ var DashboardDetail = function DashboardDetail(_ref2) {
41715
41715
  });
41716
41716
  };
41717
41717
 
41718
+ var OptionCard$2 = function OptionCard(_ref) {
41719
+ var icon = _ref.icon,
41720
+ title = _ref.title,
41721
+ description = _ref.description,
41722
+ onClick = _ref.onClick,
41723
+ currentTheme = _ref.currentTheme;
41724
+ return /*#__PURE__*/jsxs("button", {
41725
+ type: "button",
41726
+ onClick: onClick,
41727
+ 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"),
41728
+ children: [/*#__PURE__*/jsx("div", {
41729
+ className: "flex-shrink-0 h-8 w-8 flex items-center justify-center opacity-60",
41730
+ children: /*#__PURE__*/jsx(FontAwesomeIcon, {
41731
+ icon: icon,
41732
+ className: "h-5 w-5"
41733
+ })
41734
+ }), /*#__PURE__*/jsxs("div", {
41735
+ className: "flex flex-col min-w-0",
41736
+ children: [/*#__PURE__*/jsx("span", {
41737
+ className: "text-sm font-medium",
41738
+ children: title
41739
+ }), /*#__PURE__*/jsx("span", {
41740
+ className: "text-xs opacity-50 mt-0.5",
41741
+ children: description
41742
+ })]
41743
+ }), /*#__PURE__*/jsx("div", {
41744
+ className: "flex-shrink-0 ml-auto opacity-30",
41745
+ children: /*#__PURE__*/jsx(FontAwesomeIcon, {
41746
+ icon: "chevron-right",
41747
+ className: "h-3 w-3"
41748
+ })
41749
+ })]
41750
+ });
41751
+ };
41752
+
41753
+ /**
41754
+ * NewDashboardChooser — consolidated entry point for the
41755
+ * "New Dashboard" header button in Settings → Dashboards.
41756
+ *
41757
+ * Audit #19 fix: the prior header button was labeled "Marketplace"
41758
+ * which was ambiguous (it set installMode=marketplace, duplicating
41759
+ * the Marketplace tab in the list). Renamed to "New Dashboard"; the
41760
+ * chooser presents the actual creation paths as labeled cards,
41761
+ * matching the ThemeNewChooser pattern.
41762
+ *
41763
+ * Options:
41764
+ * - "marketplace" → registry browser (existing DiscoverDashboardsDetail)
41765
+ * - "wizard" → existing dashboard creation wizard
41766
+ *
41767
+ * The Marketplace TAB in the list view stays — it's the in-place
41768
+ * browse affordance, distinct from this "I want to create a new
41769
+ * dashboard" entry.
41770
+ */
41771
+ var NewDashboardChooser = function NewDashboardChooser(_ref2) {
41772
+ var onSelect = _ref2.onSelect;
41773
+ var _useContext = useContext(ThemeContext),
41774
+ currentTheme = _useContext.currentTheme;
41775
+ var panelStyles = getStylesForItem(themeObjects.PANEL, currentTheme, {
41776
+ grow: false
41777
+ });
41778
+ return /*#__PURE__*/jsx("div", {
41779
+ className: "flex flex-col flex-1 min-h-0",
41780
+ children: /*#__PURE__*/jsxs("div", {
41781
+ className: "flex-1 overflow-y-auto p-6 space-y-3 ".concat(panelStyles.textColor || "text-gray-200"),
41782
+ children: [/*#__PURE__*/jsx("span", {
41783
+ className: "text-xs font-semibold opacity-50 block mb-4",
41784
+ children: "CREATE A DASHBOARD"
41785
+ }), /*#__PURE__*/jsx(OptionCard$2, {
41786
+ icon: "compass",
41787
+ title: "Search Marketplace",
41788
+ description: "Browse and install community dashboards from the online registry",
41789
+ onClick: function onClick() {
41790
+ return onSelect("marketplace");
41791
+ },
41792
+ currentTheme: currentTheme
41793
+ }), /*#__PURE__*/jsx(OptionCard$2, {
41794
+ icon: "wand-magic-sparkles",
41795
+ title: "From Wizard",
41796
+ description: "Build a new dashboard from a layout + theme + widgets",
41797
+ onClick: function onClick() {
41798
+ return onSelect("wizard");
41799
+ },
41800
+ currentTheme: currentTheme
41801
+ })]
41802
+ })
41803
+ });
41804
+ };
41805
+
41718
41806
  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; }
41719
41807
  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; }
41720
41808
  var DashboardsSection = function DashboardsSection(_ref) {
@@ -41760,7 +41848,9 @@ var DashboardsSection = function DashboardsSection(_ref) {
41760
41848
  _useState10 = _slicedToArray(_useState1, 2),
41761
41849
  viewMode = _useState10[0],
41762
41850
  setViewMode = _useState10[1];
41763
- // null | "marketplace"
41851
+ // null | "picker" | "marketplace"
41852
+ // picker — NewDashboardChooser (Phase 19 / audit #19)
41853
+ // marketplace — DiscoverDashboardsDetail (registry browser)
41764
41854
  var _useState11 = useState(null),
41765
41855
  _useState12 = _slicedToArray(_useState11, 2),
41766
41856
  installMode = _useState12[0],
@@ -41846,12 +41936,16 @@ var DashboardsSection = function DashboardsSection(_ref) {
41846
41936
  });
41847
41937
  }
41848
41938
 
41849
- // Respond to external create trigger from header button (marketplace)
41939
+ // Respond to external create trigger from the "New Dashboard"
41940
+ // header button. Audit #19: this now opens the consolidated
41941
+ // NewDashboardChooser (Marketplace + Wizard cards), matching the
41942
+ // ThemeNewChooser pattern. Pre-fix this went straight to
41943
+ // installMode="marketplace" which made the button ambiguous.
41850
41944
  var prevCreateRequested = useRef(false);
41851
41945
  useEffect(function () {
41852
41946
  if (createRequested && !prevCreateRequested.current) {
41853
41947
  setSelectedId(null);
41854
- setInstallMode("marketplace");
41948
+ setInstallMode("picker");
41855
41949
  }
41856
41950
  prevCreateRequested.current = createRequested;
41857
41951
  if (createRequested && onCreateAcknowledged) {
@@ -41859,6 +41953,16 @@ var DashboardsSection = function DashboardsSection(_ref) {
41859
41953
  }
41860
41954
  // eslint-disable-next-line react-hooks/exhaustive-deps
41861
41955
  }, [createRequested]);
41956
+ function handleChooserSelect(option) {
41957
+ if (option === "marketplace") {
41958
+ setInstallMode("marketplace");
41959
+ } else if (option === "wizard") {
41960
+ // onOpenWizard closes the Settings modal and opens the
41961
+ // dashboard wizard. Provided by AppSettingsModal.
41962
+ setInstallMode(null);
41963
+ if (typeof onOpenWizard === "function") onOpenWizard();
41964
+ }
41965
+ }
41862
41966
  var selectedWorkspace = workspaces.find(function (ws) {
41863
41967
  return ws.id === selectedId;
41864
41968
  });
@@ -41970,7 +42074,11 @@ var DashboardsSection = function DashboardsSection(_ref) {
41970
42074
  })]
41971
42075
  });
41972
42076
  var detailContent = null;
41973
- if (installMode === "marketplace") {
42077
+ if (installMode === "picker") {
42078
+ detailContent = /*#__PURE__*/jsx(NewDashboardChooser, {
42079
+ onSelect: handleChooserSelect
42080
+ });
42081
+ } else if (installMode === "marketplace") {
41974
42082
  detailContent = /*#__PURE__*/jsx(DiscoverDashboardsDetail, {
41975
42083
  onBack: function onBack() {
41976
42084
  setInstallMode(null);
@@ -48503,12 +48611,15 @@ var OptionCard = function OptionCard(_ref) {
48503
48611
  };
48504
48612
 
48505
48613
  /**
48506
- * InstallWidgetPicker — the 3-option menu shown when "Install Widgets" is clicked.
48614
+ * InstallWidgetPicker — the consolidated chooser shown when the
48615
+ * "New Widget" header button is clicked (audit #19).
48507
48616
  *
48508
48617
  * Options:
48509
- * 1. Search for Widgets (registry browser)
48510
- * 2. Install from File (.zip)
48511
- * 3. Load from Folder
48618
+ * 1. Use Widget Builder (Phase 19 — folds the inline "+ New Widget"
48619
+ * button's function into the chooser so there's one entry point)
48620
+ * 2. Search for Widgets (registry browser)
48621
+ * 3. Install from File (.zip)
48622
+ * 4. Load from Folder
48512
48623
  */
48513
48624
  var InstallWidgetPicker = function InstallWidgetPicker(_ref2) {
48514
48625
  var onSelect = _ref2.onSelect;
@@ -48523,7 +48634,15 @@ var InstallWidgetPicker = function InstallWidgetPicker(_ref2) {
48523
48634
  className: "flex-1 overflow-y-auto p-6 space-y-3 ".concat(panelStyles.textColor || "text-gray-200"),
48524
48635
  children: [/*#__PURE__*/jsx("span", {
48525
48636
  className: "text-xs font-semibold opacity-50 block mb-4",
48526
- children: "HOW TO INSTALL"
48637
+ children: "CREATE A WIDGET"
48638
+ }), /*#__PURE__*/jsx(OptionCard, {
48639
+ icon: "wand-magic-sparkles",
48640
+ title: "Use Widget Builder",
48641
+ description: "Open the AI Widget Builder to create a new widget from scratch",
48642
+ onClick: function onClick() {
48643
+ return onSelect("builder");
48644
+ },
48645
+ currentTheme: currentTheme
48527
48646
  }), /*#__PURE__*/jsx(OptionCard, {
48528
48647
  icon: "compass",
48529
48648
  title: "Search for Widgets",
@@ -49815,7 +49934,14 @@ var WidgetsSection = function WidgetsSection(_ref) {
49815
49934
  setProgressComplete(false);
49816
49935
  }
49817
49936
  function handlePickerSelect(option) {
49818
- if (option === "discover") {
49937
+ if (option === "builder") {
49938
+ // Audit #19: the inline "+ New Widget" button was removed in
49939
+ // favor of this card so there's a single entry point for new-
49940
+ // widget creation. Closes the picker and fires the same event
49941
+ // the inline button used to dispatch.
49942
+ setInstallMode(null);
49943
+ window.dispatchEvent(new Event("dash:open-widget-builder"));
49944
+ } else if (option === "discover") {
49819
49945
  setInstallMode("discover");
49820
49946
  } else if (option === "zip") {
49821
49947
  handleInstallFromZip();
@@ -49913,23 +50039,7 @@ var WidgetsSection = function WidgetsSection(_ref) {
49913
50039
  }
49914
50040
  var listContent = /*#__PURE__*/jsxs("div", {
49915
50041
  className: "flex flex-col h-full",
49916
- children: [/*#__PURE__*/jsx("div", {
49917
- className: "flex-shrink-0 px-3 pt-2 pb-1",
49918
- children: /*#__PURE__*/jsxs("button", {
49919
- type: "button",
49920
- onClick: function onClick() {
49921
- return window.dispatchEvent(new Event("dash:open-widget-builder"));
49922
- },
49923
- 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",
49924
- "data-testid": "widgets-section-new-widget-button",
49925
- children: [/*#__PURE__*/jsx(FontAwesomeIcon, {
49926
- icon: "plus",
49927
- className: "text-xs"
49928
- }), /*#__PURE__*/jsx("span", {
49929
- children: "New Widget"
49930
- })]
49931
- })
49932
- }), isChecking && packagesWithUpdates.length === 0 && /*#__PURE__*/jsxs("div", {
50042
+ children: [isChecking && packagesWithUpdates.length === 0 && /*#__PURE__*/jsxs("div", {
49933
50043
  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",
49934
50044
  "data-testid": "widgets-section-checking-updates",
49935
50045
  children: [/*#__PURE__*/jsx(FontAwesomeIcon, {
@@ -54565,7 +54675,7 @@ var AppSettingsModal = function AppSettingsModal(_ref) {
54565
54675
  padding: false
54566
54676
  }), (activeSection === "dashboards" || activeSection === "folders" || activeSection === "providers" || activeSection === "themes" || activeSection === "widgets") && /*#__PURE__*/jsx(ButtonIcon3, {
54567
54677
  icon: "plus",
54568
- text: activeSection === "dashboards" ? "Marketplace" : activeSection === "folders" ? "New Folder" : activeSection === "providers" ? "New Provider" : activeSection === "widgets" ? "Install Widgets" : "New Theme",
54678
+ text: activeSection === "dashboards" ? "New Dashboard" : activeSection === "folders" ? "New Folder" : activeSection === "providers" ? "New Provider" : activeSection === "widgets" ? "New Widget" : "New Theme",
54569
54679
  onClick: function onClick() {
54570
54680
  return setCreateRequested(true);
54571
54681
  },
@@ -54782,13 +54892,41 @@ var DashboardLoaderModal = function DashboardLoaderModal(_ref) {
54782
54892
  var KITCHEN_SINK_PACKAGE = "trops/kitchen-sink";
54783
54893
  var STATE = {
54784
54894
  WELCOME: "welcome",
54895
+ // AUTH_REQUIRED replaces the generic "Install Failed" screen when
54896
+ // the registry returns `authRequired: true`. Surfaces a
54897
+ // benefits-driven sign-in CTA instead of asking the user to
54898
+ // figure out why install died.
54899
+ AUTH_REQUIRED: "auth-required",
54785
54900
  INSTALLING: "installing",
54786
54901
  DONE: "done",
54787
54902
  ERROR: "error"
54788
54903
  };
54904
+
54905
+ /**
54906
+ * Returns the first workspace that was installed from the Kitchen
54907
+ * Sink package, or null. Used for the modal's dedupe check so we
54908
+ * never create a second Kitchen Sink when one already exists.
54909
+ *
54910
+ * Workspaces installed via the registry carry a
54911
+ * `_dashboardConfig.registryPackage` field — historically the
54912
+ * unscoped name ("kitchen-sink"), more recently the scoped form
54913
+ * ("trops/kitchen-sink"). Match on either trailing segment.
54914
+ */
54915
+ function findExistingKitchenSink(workspaces) {
54916
+ if (!Array.isArray(workspaces)) return null;
54917
+ return workspaces.find(function (ws) {
54918
+ var _ws$_dashboardConfig;
54919
+ var pkg = ws === null || ws === void 0 || (_ws$_dashboardConfig = ws._dashboardConfig) === null || _ws$_dashboardConfig === void 0 ? void 0 : _ws$_dashboardConfig.registryPackage;
54920
+ if (typeof pkg !== "string") return false;
54921
+ var trailing = pkg.includes("/") ? pkg.split("/").pop() : pkg;
54922
+ return trailing === "kitchen-sink";
54923
+ }) || null;
54924
+ }
54789
54925
  var OnboardingModal = function OnboardingModal(_ref) {
54790
54926
  var open = _ref.open,
54791
54927
  appId = _ref.appId,
54928
+ _ref$workspaces = _ref.workspaces,
54929
+ workspaces = _ref$workspaces === void 0 ? [] : _ref$workspaces,
54792
54930
  onOpenDashboard = _ref.onOpenDashboard,
54793
54931
  onDismiss = _ref.onDismiss,
54794
54932
  onComplete = _ref.onComplete;
@@ -54808,6 +54946,16 @@ var OnboardingModal = function OnboardingModal(_ref) {
54808
54946
  setInstallError = _useState6[1];
54809
54947
  var installResultRef = useRef(null);
54810
54948
  var cleanupProgressRef = useRef(null);
54949
+
54950
+ // Reused device-code OAuth state machine (same hook AppUpdatesModal
54951
+ // uses). `authFlow` carries the user code + verification URL once
54952
+ // initiateAuth fires; while non-null we render the polling panel.
54953
+ var _useRegistryAuth = useRegistryAuth(),
54954
+ isAuthenticating = _useRegistryAuth.isAuthenticating,
54955
+ authFlow = _useRegistryAuth.authFlow,
54956
+ authError = _useRegistryAuth.authError,
54957
+ initiateAuth = _useRegistryAuth.initiateAuth,
54958
+ cancelAuth = _useRegistryAuth.cancelAuth;
54811
54959
  useEffect(function () {
54812
54960
  if (open) {
54813
54961
  setState(STATE.WELCOME);
@@ -54869,7 +55017,7 @@ var OnboardingModal = function OnboardingModal(_ref) {
54869
55017
  })), [markCompletedAndClose, onDismiss]);
54870
55018
  var handleInstall = useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
54871
55019
  var _window$mainApi2, _window$mainApi2$onIn;
54872
- var _window$mainApi3, _window$mainApi3$inst, result, _t2;
55020
+ var existing, _window$mainApi3, _window$mainApi3$inst, result, _t2;
54873
55021
  return _regeneratorRuntime.wrap(function (_context3) {
54874
55022
  while (1) switch (_context3.prev = _context3.next) {
54875
55023
  case 0:
@@ -54881,6 +55029,24 @@ var OnboardingModal = function OnboardingModal(_ref) {
54881
55029
  setState(STATE.ERROR);
54882
55030
  return _context3.abrupt("return");
54883
55031
  case 1:
55032
+ // Dedupe: if the user already has a Kitchen Sink workspace
55033
+ // installed from the registry, skip the install entirely and
55034
+ // route them to the existing one. Without this, the modal
55035
+ // could be re-triggered (e.g. after clearing the
55036
+ // onboarding.completed flag) and would silently create
55037
+ // "Kitchen Sink 2", "Kitchen Sink 3", etc.
55038
+ existing = findExistingKitchenSink(workspaces);
55039
+ if (!existing) {
55040
+ _context3.next = 2;
55041
+ break;
55042
+ }
55043
+ installResultRef.current = {
55044
+ success: true,
55045
+ workspace: existing
55046
+ };
55047
+ setState(STATE.DONE);
55048
+ return _context3.abrupt("return");
55049
+ case 2:
54884
55050
  setState(STATE.INSTALLING);
54885
55051
  setProgressItems([]);
54886
55052
  setInstallError(null);
@@ -54908,42 +55074,49 @@ var OnboardingModal = function OnboardingModal(_ref) {
54908
55074
  return next;
54909
55075
  });
54910
55076
  });
54911
- _context3.prev = 2;
54912
- _context3.next = 3;
55077
+ _context3.prev = 3;
55078
+ _context3.next = 4;
54913
55079
  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, {});
54914
- case 3:
55080
+ case 4:
54915
55081
  result = _context3.sent;
54916
55082
  if (cleanupProgressRef.current) {
54917
55083
  cleanupProgressRef.current();
54918
55084
  cleanupProgressRef.current = null;
54919
55085
  }
54920
55086
  if (!(!result || !result.success)) {
54921
- _context3.next = 4;
55087
+ _context3.next = 6;
54922
55088
  break;
54923
55089
  }
55090
+ if (!(result !== null && result !== void 0 && result.authRequired)) {
55091
+ _context3.next = 5;
55092
+ break;
55093
+ }
55094
+ setState(STATE.AUTH_REQUIRED);
55095
+ return _context3.abrupt("return");
55096
+ case 5:
54924
55097
  setInstallError((result === null || result === void 0 ? void 0 : result.error) || "Failed to install Kitchen Sink. Check your internet connection and try again.");
54925
55098
  setState(STATE.ERROR);
54926
55099
  return _context3.abrupt("return");
54927
- case 4:
55100
+ case 6:
54928
55101
  installResultRef.current = result;
54929
55102
  setState(STATE.DONE);
54930
- _context3.next = 6;
55103
+ _context3.next = 8;
54931
55104
  break;
54932
- case 5:
54933
- _context3.prev = 5;
54934
- _t2 = _context3["catch"](2);
55105
+ case 7:
55106
+ _context3.prev = 7;
55107
+ _t2 = _context3["catch"](3);
54935
55108
  if (cleanupProgressRef.current) {
54936
55109
  cleanupProgressRef.current();
54937
55110
  cleanupProgressRef.current = null;
54938
55111
  }
54939
55112
  setInstallError((_t2 === null || _t2 === void 0 ? void 0 : _t2.message) || "Installation failed.");
54940
55113
  setState(STATE.ERROR);
54941
- case 6:
55114
+ case 8:
54942
55115
  case "end":
54943
55116
  return _context3.stop();
54944
55117
  }
54945
- }, _callee3, null, [[2, 5]]);
54946
- })), [appId]);
55118
+ }, _callee3, null, [[3, 7]]);
55119
+ })), [appId, workspaces]);
54947
55120
  var handleOpen = useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
54948
55121
  var _installResultRef$cur;
54949
55122
  var workspace;
@@ -54968,6 +55141,17 @@ var OnboardingModal = function OnboardingModal(_ref) {
54968
55141
  handleInstall();
54969
55142
  }, [handleInstall]);
54970
55143
 
55144
+ // From the AUTH_REQUIRED state: kick off device-code OAuth. The
55145
+ // hook opens the browser, polls in the background, and fires our
55146
+ // onAuthorized callback on success. We auto-retry the install
55147
+ // there so the user lands on the DONE state without a second
55148
+ // click.
55149
+ var handleSignIn = useCallback(function () {
55150
+ initiateAuth(function () {
55151
+ handleInstall();
55152
+ });
55153
+ }, [initiateAuth, handleInstall]);
55154
+
54971
55155
  // The Modal dispatches setIsOpen(false) on Escape / backdrop click.
54972
55156
  // Route any close attempt through handleSkip so the completion flag
54973
55157
  // is always stamped (otherwise Escape would silently re-show the
@@ -54991,6 +55175,15 @@ var OnboardingModal = function OnboardingModal(_ref) {
54991
55175
  onInstall: handleInstall,
54992
55176
  onSkip: handleSkip,
54993
55177
  textMuted: textMuted
55178
+ }), state === STATE.AUTH_REQUIRED && /*#__PURE__*/jsx(AuthRequiredBody, {
55179
+ onSignIn: handleSignIn,
55180
+ onCancelAuth: cancelAuth,
55181
+ onSkip: handleSkip,
55182
+ isAuthenticating: isAuthenticating,
55183
+ authFlow: authFlow,
55184
+ authError: authError,
55185
+ textMuted: textMuted,
55186
+ borderPanel: borderPanel
54994
55187
  }), state === STATE.INSTALLING && /*#__PURE__*/jsx(InstallingBody, {
54995
55188
  items: progressItems,
54996
55189
  borderPanel: borderPanel,
@@ -55020,11 +55213,13 @@ function WelcomeBody(_ref7) {
55020
55213
  }), /*#__PURE__*/jsx(Heading2, {
55021
55214
  title: "Welcome to Dash"
55022
55215
  })]
55023
- }), /*#__PURE__*/jsxs(Paragraph, {
55216
+ }), /*#__PURE__*/jsx(Paragraph, {
55024
55217
  className: "".concat(textMuted, " mb-6"),
55025
- children: ["Get started with the ", /*#__PURE__*/jsx("strong", {
55026
- children: "Kitchen Sink"
55027
- }), " 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."]
55218
+ children: /*#__PURE__*/jsxs("span", {
55219
+ children: ["Get started with the ", /*#__PURE__*/jsx("strong", {
55220
+ children: "Kitchen Sink"
55221
+ }), " 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."]
55222
+ })
55028
55223
  }), /*#__PURE__*/jsxs("div", {
55029
55224
  className: "flex items-center justify-end gap-3 mt-2",
55030
55225
  children: [/*#__PURE__*/jsx(Button, {
@@ -55185,6 +55380,153 @@ function ErrorBody(_ref1) {
55185
55380
  })]
55186
55381
  });
55187
55382
  }
55383
+ function BenefitRow(_ref10) {
55384
+ var icon = _ref10.icon,
55385
+ title = _ref10.title,
55386
+ description = _ref10.description,
55387
+ textMuted = _ref10.textMuted;
55388
+ return /*#__PURE__*/jsxs("div", {
55389
+ className: "flex items-start gap-3",
55390
+ children: [/*#__PURE__*/jsx("div", {
55391
+ className: "flex-shrink-0 w-7 h-7 flex items-center justify-center mt-0.5",
55392
+ children: /*#__PURE__*/jsx(FontAwesomeIcon, {
55393
+ icon: icon,
55394
+ className: "text-base opacity-70"
55395
+ })
55396
+ }), /*#__PURE__*/jsxs("div", {
55397
+ className: "flex flex-col",
55398
+ children: [/*#__PURE__*/jsx("span", {
55399
+ className: "text-sm font-medium",
55400
+ children: title
55401
+ }), /*#__PURE__*/jsx("span", {
55402
+ className: "text-xs ".concat(textMuted),
55403
+ children: description
55404
+ })]
55405
+ })]
55406
+ });
55407
+ }
55408
+ function AuthRequiredBody(_ref11) {
55409
+ var onSignIn = _ref11.onSignIn,
55410
+ onCancelAuth = _ref11.onCancelAuth,
55411
+ onSkip = _ref11.onSkip,
55412
+ isAuthenticating = _ref11.isAuthenticating,
55413
+ authFlow = _ref11.authFlow,
55414
+ authError = _ref11.authError,
55415
+ textMuted = _ref11.textMuted,
55416
+ borderPanel = _ref11.borderPanel;
55417
+ // While the device-code flow is polling, swap the benefits panel
55418
+ // for the user-code + verification-URL display so the user knows
55419
+ // what to enter in the browser tab we just opened.
55420
+ if (isAuthenticating && authFlow) {
55421
+ return /*#__PURE__*/jsxs(Fragment, {
55422
+ children: [/*#__PURE__*/jsxs("div", {
55423
+ className: "flex items-center gap-3 mb-2",
55424
+ children: [/*#__PURE__*/jsx(FontAwesomeIcon, {
55425
+ icon: "circle-notch",
55426
+ className: "text-2xl animate-spin"
55427
+ }), /*#__PURE__*/jsx(Heading2, {
55428
+ title: "Waiting for browser sign-in"
55429
+ })]
55430
+ }), /*#__PURE__*/jsx(Paragraph, {
55431
+ className: "".concat(textMuted, " mb-4"),
55432
+ children: /*#__PURE__*/jsx("span", {
55433
+ children: "We opened a browser tab where you can sign in to the Dash Registry. Enter this code if prompted:"
55434
+ })
55435
+ }), /*#__PURE__*/jsxs("div", {
55436
+ className: "border ".concat(borderPanel, " rounded-md p-4 mb-4 text-center"),
55437
+ "data-testid": "onboarding-auth-user-code",
55438
+ children: [/*#__PURE__*/jsx("div", {
55439
+ className: "text-2xl font-mono tracking-widest",
55440
+ children: authFlow.userCode || "—"
55441
+ }), /*#__PURE__*/jsx("div", {
55442
+ className: "text-xs ".concat(textMuted, " mt-2 break-all"),
55443
+ children: authFlow.verificationUrlComplete || authFlow.verificationUrl || ""
55444
+ })]
55445
+ }), /*#__PURE__*/jsx("div", {
55446
+ className: "flex items-center justify-end gap-3",
55447
+ children: /*#__PURE__*/jsx(Button, {
55448
+ onClick: onCancelAuth,
55449
+ title: "Cancel",
55450
+ textSize: "text-sm",
55451
+ padding: "py-2 px-4",
55452
+ backgroundColor: "bg-gray-700",
55453
+ textColor: "text-gray-300",
55454
+ hoverTextColor: "hover:text-white",
55455
+ hoverBackgroundColor: "hover:bg-gray-600",
55456
+ "data-testid": "onboarding-auth-cancel-button"
55457
+ })
55458
+ })]
55459
+ });
55460
+ }
55461
+ return /*#__PURE__*/jsxs(Fragment, {
55462
+ children: [/*#__PURE__*/jsxs("div", {
55463
+ className: "flex items-center gap-3 mb-2",
55464
+ children: [/*#__PURE__*/jsx(FontAwesomeIcon, {
55465
+ icon: "user-plus",
55466
+ className: "text-2xl"
55467
+ }), /*#__PURE__*/jsx(Heading2, {
55468
+ title: "Sign in to the Dash Registry"
55469
+ })]
55470
+ }), /*#__PURE__*/jsx(Paragraph, {
55471
+ className: "".concat(textMuted, " mb-5"),
55472
+ children: /*#__PURE__*/jsx("span", {
55473
+ 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."
55474
+ })
55475
+ }), /*#__PURE__*/jsxs("div", {
55476
+ className: "grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6",
55477
+ children: [/*#__PURE__*/jsx(BenefitRow, {
55478
+ icon: "cube",
55479
+ title: "Install widgets",
55480
+ description: "Browse and one-click install community widgets",
55481
+ textMuted: textMuted
55482
+ }), /*#__PURE__*/jsx(BenefitRow, {
55483
+ icon: "table-cells",
55484
+ title: "Discover dashboards",
55485
+ description: "Like Kitchen Sink \u2014 curated starters built by others",
55486
+ textMuted: textMuted
55487
+ }), /*#__PURE__*/jsx(BenefitRow, {
55488
+ icon: "palette",
55489
+ title: "Apply themes",
55490
+ description: "Community-built themes for any taste",
55491
+ textMuted: textMuted
55492
+ }), /*#__PURE__*/jsx(BenefitRow, {
55493
+ icon: "upload",
55494
+ title: "Publish your own",
55495
+ description: "Share your widgets and dashboards with the Dash community",
55496
+ textMuted: textMuted
55497
+ })]
55498
+ }), authError && /*#__PURE__*/jsx(Paragraph, {
55499
+ className: "text-red-400 mb-4",
55500
+ children: /*#__PURE__*/jsx("span", {
55501
+ children: authError
55502
+ })
55503
+ }), /*#__PURE__*/jsxs("div", {
55504
+ className: "flex items-center justify-end gap-3 mt-2",
55505
+ children: [/*#__PURE__*/jsx(Button, {
55506
+ onClick: onSkip,
55507
+ title: "Skip for now",
55508
+ textSize: "text-sm",
55509
+ padding: "py-2 px-4",
55510
+ backgroundColor: "bg-gray-700",
55511
+ textColor: "text-gray-300",
55512
+ hoverTextColor: "hover:text-white",
55513
+ hoverBackgroundColor: "hover:bg-gray-600",
55514
+ "data-testid": "onboarding-auth-skip-button"
55515
+ }), /*#__PURE__*/jsx(Button, {
55516
+ onClick: onSignIn,
55517
+ title: "Sign in to Registry",
55518
+ textSize: "text-sm",
55519
+ padding: "py-2 px-4",
55520
+ backgroundColor: "bg-blue-600",
55521
+ textColor: "text-white",
55522
+ hoverTextColor: "hover:text-white",
55523
+ hoverBackgroundColor: "hover:bg-blue-500",
55524
+ icon: "arrow-up-right-from-square",
55525
+ "data-testid": "onboarding-auth-signin-button"
55526
+ })]
55527
+ })]
55528
+ });
55529
+ }
55188
55530
 
55189
55531
  var DashCommandPalette = function DashCommandPalette(_ref) {
55190
55532
  var isOpen = _ref.isOpen,
@@ -61198,7 +61540,7 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
61198
61540
  onboardingCheckedRef.current = true;
61199
61541
  var cancelled = false;
61200
61542
  _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
61201
- var _window$mainApi, _window$mainApi$getSt, status;
61543
+ var _window$mainApi, _window$mainApi$getSt, status, alreadyHasKitchenSink;
61202
61544
  return _regeneratorRuntime.wrap(function (_context) {
61203
61545
  while (1) switch (_context.prev = _context.next) {
61204
61546
  case 0:
@@ -61213,7 +61555,20 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
61213
61555
  }
61214
61556
  return _context.abrupt("return");
61215
61557
  case 2:
61216
- if (status && status.completed === false && workspaceConfig.length === 0) {
61558
+ // Defense-in-depth: even if `workspaceConfig.length === 0`
61559
+ // briefly reads true during a race between `isLoadingWorkspaces`
61560
+ // and the workspace array populating, don't show the modal
61561
+ // if the user already has a Kitchen Sink dashboard installed
61562
+ // from the registry. Same dedupe rule the modal's install
61563
+ // path applies.
61564
+ alreadyHasKitchenSink = workspaceConfig.some(function (ws) {
61565
+ var _ws$_dashboardConfig;
61566
+ var pkg = ws === null || ws === void 0 || (_ws$_dashboardConfig = ws._dashboardConfig) === null || _ws$_dashboardConfig === void 0 ? void 0 : _ws$_dashboardConfig.registryPackage;
61567
+ if (typeof pkg !== "string") return false;
61568
+ var trailing = pkg.includes("/") ? pkg.split("/").pop() : pkg;
61569
+ return trailing === "kitchen-sink";
61570
+ });
61571
+ if (status && status.completed === false && workspaceConfig.length === 0 && !alreadyHasKitchenSink) {
61217
61572
  setIsOnboardingOpen(true);
61218
61573
  } else {
61219
61574
  setIsOnboardingOpen(false);
@@ -63017,6 +63372,7 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
63017
63372
  }), /*#__PURE__*/jsx(OnboardingModal, {
63018
63373
  open: isOnboardingOpen === true,
63019
63374
  appId: credentials === null || credentials === void 0 ? void 0 : credentials.appId,
63375
+ workspaces: workspaceConfig,
63020
63376
  onDismiss: function onDismiss() {
63021
63377
  return setIsOnboardingOpen(false);
63022
63378
  },