@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.esm.js CHANGED
@@ -54892,13 +54892,41 @@ var DashboardLoaderModal = function DashboardLoaderModal(_ref) {
54892
54892
  var KITCHEN_SINK_PACKAGE = "trops/kitchen-sink";
54893
54893
  var STATE = {
54894
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",
54895
54900
  INSTALLING: "installing",
54896
54901
  DONE: "done",
54897
54902
  ERROR: "error"
54898
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
+ }
54899
54925
  var OnboardingModal = function OnboardingModal(_ref) {
54900
54926
  var open = _ref.open,
54901
54927
  appId = _ref.appId,
54928
+ _ref$workspaces = _ref.workspaces,
54929
+ workspaces = _ref$workspaces === void 0 ? [] : _ref$workspaces,
54902
54930
  onOpenDashboard = _ref.onOpenDashboard,
54903
54931
  onDismiss = _ref.onDismiss,
54904
54932
  onComplete = _ref.onComplete;
@@ -54918,6 +54946,16 @@ var OnboardingModal = function OnboardingModal(_ref) {
54918
54946
  setInstallError = _useState6[1];
54919
54947
  var installResultRef = useRef(null);
54920
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;
54921
54959
  useEffect(function () {
54922
54960
  if (open) {
54923
54961
  setState(STATE.WELCOME);
@@ -54979,7 +55017,7 @@ var OnboardingModal = function OnboardingModal(_ref) {
54979
55017
  })), [markCompletedAndClose, onDismiss]);
54980
55018
  var handleInstall = useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
54981
55019
  var _window$mainApi2, _window$mainApi2$onIn;
54982
- var _window$mainApi3, _window$mainApi3$inst, result, _t2;
55020
+ var existing, _window$mainApi3, _window$mainApi3$inst, result, _t2;
54983
55021
  return _regeneratorRuntime.wrap(function (_context3) {
54984
55022
  while (1) switch (_context3.prev = _context3.next) {
54985
55023
  case 0:
@@ -54991,6 +55029,24 @@ var OnboardingModal = function OnboardingModal(_ref) {
54991
55029
  setState(STATE.ERROR);
54992
55030
  return _context3.abrupt("return");
54993
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:
54994
55050
  setState(STATE.INSTALLING);
54995
55051
  setProgressItems([]);
54996
55052
  setInstallError(null);
@@ -55018,42 +55074,49 @@ var OnboardingModal = function OnboardingModal(_ref) {
55018
55074
  return next;
55019
55075
  });
55020
55076
  });
55021
- _context3.prev = 2;
55022
- _context3.next = 3;
55077
+ _context3.prev = 3;
55078
+ _context3.next = 4;
55023
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, {});
55024
- case 3:
55080
+ case 4:
55025
55081
  result = _context3.sent;
55026
55082
  if (cleanupProgressRef.current) {
55027
55083
  cleanupProgressRef.current();
55028
55084
  cleanupProgressRef.current = null;
55029
55085
  }
55030
55086
  if (!(!result || !result.success)) {
55031
- _context3.next = 4;
55087
+ _context3.next = 6;
55032
55088
  break;
55033
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:
55034
55097
  setInstallError((result === null || result === void 0 ? void 0 : result.error) || "Failed to install Kitchen Sink. Check your internet connection and try again.");
55035
55098
  setState(STATE.ERROR);
55036
55099
  return _context3.abrupt("return");
55037
- case 4:
55100
+ case 6:
55038
55101
  installResultRef.current = result;
55039
55102
  setState(STATE.DONE);
55040
- _context3.next = 6;
55103
+ _context3.next = 8;
55041
55104
  break;
55042
- case 5:
55043
- _context3.prev = 5;
55044
- _t2 = _context3["catch"](2);
55105
+ case 7:
55106
+ _context3.prev = 7;
55107
+ _t2 = _context3["catch"](3);
55045
55108
  if (cleanupProgressRef.current) {
55046
55109
  cleanupProgressRef.current();
55047
55110
  cleanupProgressRef.current = null;
55048
55111
  }
55049
55112
  setInstallError((_t2 === null || _t2 === void 0 ? void 0 : _t2.message) || "Installation failed.");
55050
55113
  setState(STATE.ERROR);
55051
- case 6:
55114
+ case 8:
55052
55115
  case "end":
55053
55116
  return _context3.stop();
55054
55117
  }
55055
- }, _callee3, null, [[2, 5]]);
55056
- })), [appId]);
55118
+ }, _callee3, null, [[3, 7]]);
55119
+ })), [appId, workspaces]);
55057
55120
  var handleOpen = useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
55058
55121
  var _installResultRef$cur;
55059
55122
  var workspace;
@@ -55078,6 +55141,17 @@ var OnboardingModal = function OnboardingModal(_ref) {
55078
55141
  handleInstall();
55079
55142
  }, [handleInstall]);
55080
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
+
55081
55155
  // The Modal dispatches setIsOpen(false) on Escape / backdrop click.
55082
55156
  // Route any close attempt through handleSkip so the completion flag
55083
55157
  // is always stamped (otherwise Escape would silently re-show the
@@ -55101,6 +55175,15 @@ var OnboardingModal = function OnboardingModal(_ref) {
55101
55175
  onInstall: handleInstall,
55102
55176
  onSkip: handleSkip,
55103
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
55104
55187
  }), state === STATE.INSTALLING && /*#__PURE__*/jsx(InstallingBody, {
55105
55188
  items: progressItems,
55106
55189
  borderPanel: borderPanel,
@@ -55130,11 +55213,13 @@ function WelcomeBody(_ref7) {
55130
55213
  }), /*#__PURE__*/jsx(Heading2, {
55131
55214
  title: "Welcome to Dash"
55132
55215
  })]
55133
- }), /*#__PURE__*/jsxs(Paragraph, {
55216
+ }), /*#__PURE__*/jsx(Paragraph, {
55134
55217
  className: "".concat(textMuted, " mb-6"),
55135
- children: ["Get started with the ", /*#__PURE__*/jsx("strong", {
55136
- children: "Kitchen Sink"
55137
- }), " 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
+ })
55138
55223
  }), /*#__PURE__*/jsxs("div", {
55139
55224
  className: "flex items-center justify-end gap-3 mt-2",
55140
55225
  children: [/*#__PURE__*/jsx(Button, {
@@ -55295,6 +55380,153 @@ function ErrorBody(_ref1) {
55295
55380
  })]
55296
55381
  });
55297
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
+ }
55298
55530
 
55299
55531
  var DashCommandPalette = function DashCommandPalette(_ref) {
55300
55532
  var isOpen = _ref.isOpen,
@@ -61308,7 +61540,7 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
61308
61540
  onboardingCheckedRef.current = true;
61309
61541
  var cancelled = false;
61310
61542
  _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
61311
- var _window$mainApi, _window$mainApi$getSt, status;
61543
+ var _window$mainApi, _window$mainApi$getSt, status, alreadyHasKitchenSink;
61312
61544
  return _regeneratorRuntime.wrap(function (_context) {
61313
61545
  while (1) switch (_context.prev = _context.next) {
61314
61546
  case 0:
@@ -61323,7 +61555,20 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
61323
61555
  }
61324
61556
  return _context.abrupt("return");
61325
61557
  case 2:
61326
- 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) {
61327
61572
  setIsOnboardingOpen(true);
61328
61573
  } else {
61329
61574
  setIsOnboardingOpen(false);
@@ -63127,6 +63372,7 @@ var DashboardStageInner = function DashboardStageInner(_ref3) {
63127
63372
  }), /*#__PURE__*/jsx(OnboardingModal, {
63128
63373
  open: isOnboardingOpen === true,
63129
63374
  appId: credentials === null || credentials === void 0 ? void 0 : credentials.appId,
63375
+ workspaces: workspaceConfig,
63130
63376
  onDismiss: function onDismiss() {
63131
63377
  return setIsOnboardingOpen(false);
63132
63378
  },