@trops/dash-core 0.1.603 → 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
@@ -54910,13 +54910,41 @@ var DashboardLoaderModal = function DashboardLoaderModal(_ref) {
54910
54910
  var KITCHEN_SINK_PACKAGE = "trops/kitchen-sink";
54911
54911
  var STATE = {
54912
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",
54913
54918
  INSTALLING: "installing",
54914
54919
  DONE: "done",
54915
54920
  ERROR: "error"
54916
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
+ }
54917
54943
  var OnboardingModal = function OnboardingModal(_ref) {
54918
54944
  var open = _ref.open,
54919
54945
  appId = _ref.appId,
54946
+ _ref$workspaces = _ref.workspaces,
54947
+ workspaces = _ref$workspaces === void 0 ? [] : _ref$workspaces,
54920
54948
  onOpenDashboard = _ref.onOpenDashboard,
54921
54949
  onDismiss = _ref.onDismiss,
54922
54950
  onComplete = _ref.onComplete;
@@ -54936,6 +54964,16 @@ var OnboardingModal = function OnboardingModal(_ref) {
54936
54964
  setInstallError = _useState6[1];
54937
54965
  var installResultRef = React.useRef(null);
54938
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;
54939
54977
  React.useEffect(function () {
54940
54978
  if (open) {
54941
54979
  setState(STATE.WELCOME);
@@ -54997,7 +55035,7 @@ var OnboardingModal = function OnboardingModal(_ref) {
54997
55035
  })), [markCompletedAndClose, onDismiss]);
54998
55036
  var handleInstall = React.useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
54999
55037
  var _window$mainApi2, _window$mainApi2$onIn;
55000
- var _window$mainApi3, _window$mainApi3$inst, result, _t2;
55038
+ var existing, _window$mainApi3, _window$mainApi3$inst, result, _t2;
55001
55039
  return _regeneratorRuntime.wrap(function (_context3) {
55002
55040
  while (1) switch (_context3.prev = _context3.next) {
55003
55041
  case 0:
@@ -55009,6 +55047,24 @@ var OnboardingModal = function OnboardingModal(_ref) {
55009
55047
  setState(STATE.ERROR);
55010
55048
  return _context3.abrupt("return");
55011
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:
55012
55068
  setState(STATE.INSTALLING);
55013
55069
  setProgressItems([]);
55014
55070
  setInstallError(null);
@@ -55036,42 +55092,49 @@ var OnboardingModal = function OnboardingModal(_ref) {
55036
55092
  return next;
55037
55093
  });
55038
55094
  });
55039
- _context3.prev = 2;
55040
- _context3.next = 3;
55095
+ _context3.prev = 3;
55096
+ _context3.next = 4;
55041
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, {});
55042
- case 3:
55098
+ case 4:
55043
55099
  result = _context3.sent;
55044
55100
  if (cleanupProgressRef.current) {
55045
55101
  cleanupProgressRef.current();
55046
55102
  cleanupProgressRef.current = null;
55047
55103
  }
55048
55104
  if (!(!result || !result.success)) {
55049
- _context3.next = 4;
55105
+ _context3.next = 6;
55050
55106
  break;
55051
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:
55052
55115
  setInstallError((result === null || result === void 0 ? void 0 : result.error) || "Failed to install Kitchen Sink. Check your internet connection and try again.");
55053
55116
  setState(STATE.ERROR);
55054
55117
  return _context3.abrupt("return");
55055
- case 4:
55118
+ case 6:
55056
55119
  installResultRef.current = result;
55057
55120
  setState(STATE.DONE);
55058
- _context3.next = 6;
55121
+ _context3.next = 8;
55059
55122
  break;
55060
- case 5:
55061
- _context3.prev = 5;
55062
- _t2 = _context3["catch"](2);
55123
+ case 7:
55124
+ _context3.prev = 7;
55125
+ _t2 = _context3["catch"](3);
55063
55126
  if (cleanupProgressRef.current) {
55064
55127
  cleanupProgressRef.current();
55065
55128
  cleanupProgressRef.current = null;
55066
55129
  }
55067
55130
  setInstallError((_t2 === null || _t2 === void 0 ? void 0 : _t2.message) || "Installation failed.");
55068
55131
  setState(STATE.ERROR);
55069
- case 6:
55132
+ case 8:
55070
55133
  case "end":
55071
55134
  return _context3.stop();
55072
55135
  }
55073
- }, _callee3, null, [[2, 5]]);
55074
- })), [appId]);
55136
+ }, _callee3, null, [[3, 7]]);
55137
+ })), [appId, workspaces]);
55075
55138
  var handleOpen = React.useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
55076
55139
  var _installResultRef$cur;
55077
55140
  var workspace;
@@ -55096,6 +55159,17 @@ var OnboardingModal = function OnboardingModal(_ref) {
55096
55159
  handleInstall();
55097
55160
  }, [handleInstall]);
55098
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
+
55099
55173
  // The Modal dispatches setIsOpen(false) on Escape / backdrop click.
55100
55174
  // Route any close attempt through handleSkip so the completion flag
55101
55175
  // is always stamped (otherwise Escape would silently re-show the
@@ -55119,6 +55193,15 @@ var OnboardingModal = function OnboardingModal(_ref) {
55119
55193
  onInstall: handleInstall,
55120
55194
  onSkip: handleSkip,
55121
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
55122
55205
  }), state === STATE.INSTALLING && /*#__PURE__*/jsxRuntime.jsx(InstallingBody, {
55123
55206
  items: progressItems,
55124
55207
  borderPanel: borderPanel,
@@ -55148,11 +55231,13 @@ function WelcomeBody(_ref7) {
55148
55231
  }), /*#__PURE__*/jsxRuntime.jsx(DashReact.Heading2, {
55149
55232
  title: "Welcome to Dash"
55150
55233
  })]
55151
- }), /*#__PURE__*/jsxRuntime.jsxs(DashReact.Paragraph, {
55234
+ }), /*#__PURE__*/jsxRuntime.jsx(DashReact.Paragraph, {
55152
55235
  className: "".concat(textMuted, " mb-6"),
55153
- children: ["Get started with the ", /*#__PURE__*/jsxRuntime.jsx("strong", {
55154
- children: "Kitchen Sink"
55155
- }), " 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
+ })
55156
55241
  }), /*#__PURE__*/jsxRuntime.jsxs("div", {
55157
55242
  className: "flex items-center justify-end gap-3 mt-2",
55158
55243
  children: [/*#__PURE__*/jsxRuntime.jsx(DashReact.Button, {
@@ -55313,6 +55398,153 @@ function ErrorBody(_ref1) {
55313
55398
  })]
55314
55399
  });
55315
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
+ }
55316
55548
 
55317
55549
  var DashCommandPalette = function DashCommandPalette(_ref) {
55318
55550
  var isOpen = _ref.isOpen,
@@ -61326,7 +61558,7 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
61326
61558
  onboardingCheckedRef.current = true;
61327
61559
  var cancelled = false;
61328
61560
  _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
61329
- var _window$mainApi, _window$mainApi$getSt, status;
61561
+ var _window$mainApi, _window$mainApi$getSt, status, alreadyHasKitchenSink;
61330
61562
  return _regeneratorRuntime.wrap(function (_context) {
61331
61563
  while (1) switch (_context.prev = _context.next) {
61332
61564
  case 0:
@@ -61341,7 +61573,20 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
61341
61573
  }
61342
61574
  return _context.abrupt("return");
61343
61575
  case 2:
61344
- 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) {
61345
61590
  setIsOnboardingOpen(true);
61346
61591
  } else {
61347
61592
  setIsOnboardingOpen(false);
@@ -63145,6 +63390,7 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
63145
63390
  }), /*#__PURE__*/jsxRuntime.jsx(OnboardingModal, {
63146
63391
  open: isOnboardingOpen === true,
63147
63392
  appId: credentials === null || credentials === void 0 ? void 0 : credentials.appId,
63393
+ workspaces: workspaceConfig,
63148
63394
  onDismiss: function onDismiss() {
63149
63395
  return setIsOnboardingOpen(false);
63150
63396
  },