@xenide-io/the-old-ui-theme 0.6.9 → 0.6.11

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/suite.mjs CHANGED
@@ -1508,7 +1508,7 @@ function SuiteMobileDrawer({
1508
1508
  tabIndex: -1,
1509
1509
  "data-test": panelDataTest,
1510
1510
  className: cn(
1511
- "suite-scroll-lock relative flex h-full w-[86%] max-w-72 flex-col overflow-hidden bg-ph-surface shadow-xl outline-none motion-safe:transition-transform motion-safe:ease-spring-fast",
1511
+ "relative flex h-dvh max-h-dvh w-[86%] max-w-72 flex-col overflow-hidden bg-ph-surface shadow-xl outline-none motion-safe:transition-transform motion-safe:ease-spring-fast",
1512
1512
  side === "right" && "ml-auto",
1513
1513
  shown ? "translate-x-0" : side === "right" ? "translate-x-full" : "-translate-x-full",
1514
1514
  panelClassName
@@ -1539,7 +1539,14 @@ function SuiteMobileDrawer({
1539
1539
  ]
1540
1540
  }
1541
1541
  ) : null,
1542
- children
1542
+ /* @__PURE__ */ jsx10(
1543
+ "div",
1544
+ {
1545
+ "data-test": "suite-mobile-drawer-body",
1546
+ className: "suite-scroll-lock flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-contain pb-[max(0.75rem,env(safe-area-inset-bottom))]",
1547
+ children
1548
+ }
1549
+ )
1543
1550
  ]
1544
1551
  }
1545
1552
  )
@@ -2174,9 +2181,47 @@ import {
2174
2181
  Square,
2175
2182
  WarningTriangle
2176
2183
  } from "iconoir-react";
2177
- import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2184
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2178
2185
  var SuiteAiBlockNoteMessage = lazy(() => import("./ai-message-blocknote-DIUICX3G.mjs"));
2179
2186
  var SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
2187
+ var SUITE_ASK_AI_OPEN_KEY = "shellstack:shelly-open";
2188
+ var SUITE_ASK_AI_OPEN_QUERY = "shelly";
2189
+ var SUITE_MUTATED_EVENT = "shellstack:suite-mutated";
2190
+ var TURTLETIME_TIMER_MUTATED_EVENT = "tt-timer-mutated";
2191
+ function persistSuiteAskAiOpen(open) {
2192
+ if (typeof window === "undefined") return;
2193
+ try {
2194
+ if (open) sessionStorage.setItem(SUITE_ASK_AI_OPEN_KEY, "1");
2195
+ else sessionStorage.removeItem(SUITE_ASK_AI_OPEN_KEY);
2196
+ } catch (e) {
2197
+ }
2198
+ }
2199
+ function readSuiteAskAiOpen() {
2200
+ if (typeof window === "undefined") return false;
2201
+ try {
2202
+ return sessionStorage.getItem(SUITE_ASK_AI_OPEN_KEY) === "1";
2203
+ } catch (e) {
2204
+ return false;
2205
+ }
2206
+ }
2207
+ function pathWithSuiteAskAiOpen(path) {
2208
+ const url = new URL((path || "/").trim() || "/", "http://local.invalid");
2209
+ url.searchParams.set(SUITE_ASK_AI_OPEN_QUERY, "1");
2210
+ return `${url.pathname}${url.search}${url.hash}`;
2211
+ }
2212
+ function consumeSuiteAskAiOpenFromLocation() {
2213
+ if (typeof window === "undefined") return false;
2214
+ const url = new URL(window.location.href);
2215
+ if (url.searchParams.get(SUITE_ASK_AI_OPEN_QUERY) !== "1") return false;
2216
+ persistSuiteAskAiOpen(true);
2217
+ url.searchParams.delete(SUITE_ASK_AI_OPEN_QUERY);
2218
+ window.history.replaceState(
2219
+ window.history.state,
2220
+ "",
2221
+ `${url.pathname}${url.search}${url.hash}`
2222
+ );
2223
+ return true;
2224
+ }
2180
2225
  var pendingAskAiPrompt = null;
2181
2226
  function openSuiteAskAi(detail) {
2182
2227
  var _a;
@@ -2193,6 +2238,18 @@ function consumePendingAskAiPrompt() {
2193
2238
  pendingAskAiPrompt = null;
2194
2239
  return next;
2195
2240
  }
2241
+ function notifySuiteMutated(actions) {
2242
+ if (typeof window === "undefined" || !(actions == null ? void 0 : actions.length)) return;
2243
+ const kinds = actions.map((action) => action.kind);
2244
+ if (kinds.some(
2245
+ (kind) => kind.includes("timer") || kind.includes("time") || kind.includes("hours")
2246
+ )) {
2247
+ window.dispatchEvent(new Event(TURTLETIME_TIMER_MUTATED_EVENT));
2248
+ }
2249
+ window.dispatchEvent(
2250
+ new CustomEvent(SUITE_MUTATED_EVENT, { detail: { kinds } })
2251
+ );
2252
+ }
2196
2253
  function SuiteAiPanel({
2197
2254
  presets = [],
2198
2255
  emptyState = "Ask when you are ready \u2014 overdue work, a running timer, or anything else.",
@@ -2205,13 +2262,17 @@ function SuiteAiPanel({
2205
2262
  open: openProp,
2206
2263
  onOpenChange,
2207
2264
  suiteAppBases,
2208
- onSwitchApp
2265
+ onSwitchApp,
2266
+ onSameAppNavigate,
2267
+ resolveAction
2209
2268
  }) {
2210
2269
  const inputId = useId();
2211
2270
  const [uncontrolledOpen, setUncontrolledOpen] = useState7(false);
2271
+ const [resolvingId, setResolvingId] = useState7("");
2212
2272
  const open = openProp != null ? openProp : uncontrolledOpen;
2213
2273
  const setOpen = useCallback2(
2214
2274
  (next) => {
2275
+ persistSuiteAskAiOpen(next);
2215
2276
  if (openProp === void 0) setUncontrolledOpen(next);
2216
2277
  onOpenChange == null ? void 0 : onOpenChange(next);
2217
2278
  },
@@ -2312,7 +2373,14 @@ function SuiteAiPanel({
2312
2373
  signal: controller.signal
2313
2374
  });
2314
2375
  if (controller.signal.aborted) return;
2315
- setMessages((_a = result.messages) != null ? _a : []);
2376
+ const nextMessages = (_a = result.messages) != null ? _a : [];
2377
+ setMessages(nextMessages);
2378
+ notifySuiteMutated(
2379
+ nextMessages.flatMap((message) => {
2380
+ var _a2;
2381
+ return (_a2 = message.actions) != null ? _a2 : [];
2382
+ })
2383
+ );
2316
2384
  } catch (err) {
2317
2385
  if (controller.signal.aborted) return;
2318
2386
  setMessages(
@@ -2355,6 +2423,29 @@ function SuiteAiPanel({
2355
2423
  );
2356
2424
  }
2357
2425
  }
2426
+ async function handleResolve(actionId, decision) {
2427
+ var _a;
2428
+ if (!resolveAction || resolvingId) return;
2429
+ setResolvingId(actionId);
2430
+ setError("");
2431
+ try {
2432
+ const result = await resolveAction(actionId, decision);
2433
+ const nextMessages = (_a = result.messages) != null ? _a : [];
2434
+ setMessages(nextMessages);
2435
+ notifySuiteMutated(
2436
+ nextMessages.flatMap((message) => {
2437
+ var _a2;
2438
+ return (_a2 = message.actions) != null ? _a2 : [];
2439
+ })
2440
+ );
2441
+ } catch (err) {
2442
+ setError(
2443
+ err instanceof Error ? err.message : "Could not apply that action."
2444
+ );
2445
+ } finally {
2446
+ setResolvingId("");
2447
+ }
2448
+ }
2358
2449
  function onConversationClick(event) {
2359
2450
  var _a;
2360
2451
  if (event.defaultPrevented) return;
@@ -2372,12 +2463,15 @@ function SuiteAiPanel({
2372
2463
  );
2373
2464
  if (!match) return;
2374
2465
  event.preventDefault();
2466
+ persistSuiteAskAiOpen(true);
2375
2467
  if (match.sameApp) {
2376
- window.location.assign(match.path);
2468
+ if (onSameAppNavigate) onSameAppNavigate(match.path);
2469
+ else window.location.assign(pathWithSuiteAskAiOpen(match.path));
2377
2470
  return;
2378
2471
  }
2379
- if (onSwitchApp) onSwitchApp(match.slug, match.path);
2380
- else window.location.assign(anchor.href);
2472
+ const openPath = pathWithSuiteAskAiOpen(match.path);
2473
+ if (onSwitchApp) onSwitchApp(match.slug, openPath);
2474
+ else window.location.assign(new URL(openPath, href).toString());
2381
2475
  }
2382
2476
  if (!open) return null;
2383
2477
  const Icon = BrandIcon != null ? BrandIcon : Sparks;
@@ -2442,6 +2536,7 @@ function SuiteAiPanel({
2442
2536
  )) }) : null
2443
2537
  ] }) : null,
2444
2538
  messages.map((message, index) => {
2539
+ var _a;
2445
2540
  const isUser = message.role === "user";
2446
2541
  if (isUser) {
2447
2542
  return /* @__PURE__ */ jsx17(
@@ -2468,13 +2563,67 @@ function SuiteAiPanel({
2468
2563
  children: /* @__PURE__ */ jsx17(Icon, { className: "h-3.5 w-3.5" })
2469
2564
  }
2470
2565
  ),
2471
- /* @__PURE__ */ jsx17("div", { className: "min-w-0 max-w-[85%] rounded-2xl rounded-bl-md bg-ph-muted px-3 py-2 text-sm leading-relaxed text-ph-ink", children: /* @__PURE__ */ jsx17(
2472
- Suspense,
2473
- {
2474
- fallback: /* @__PURE__ */ jsx17("p", { className: "whitespace-pre-wrap break-words", children: message.content }),
2475
- children: /* @__PURE__ */ jsx17(SuiteAiBlockNoteMessage, { markdown: message.content })
2476
- }
2477
- ) })
2566
+ /* @__PURE__ */ jsxs15("div", { className: "min-w-0 max-w-[85%] space-y-2", children: [
2567
+ /* @__PURE__ */ jsx17("div", { className: "rounded-2xl rounded-bl-md bg-ph-muted px-3 py-2 text-sm leading-relaxed text-ph-ink", children: /* @__PURE__ */ jsx17(
2568
+ Suspense,
2569
+ {
2570
+ fallback: /* @__PURE__ */ jsx17("p", { className: "whitespace-pre-wrap break-words", children: message.content }),
2571
+ children: /* @__PURE__ */ jsx17(SuiteAiBlockNoteMessage, { markdown: message.content })
2572
+ }
2573
+ ) }),
2574
+ ((_a = message.actions) != null ? _a : []).map((action) => /* @__PURE__ */ jsxs15(
2575
+ "div",
2576
+ {
2577
+ "data-test": "ask-ai-action",
2578
+ "data-status": action.status,
2579
+ className: "rounded-xl border border-ph-border bg-ph-surface px-3 py-2 text-sm text-ph-ink",
2580
+ children: [
2581
+ /* @__PURE__ */ jsx17("p", { className: "font-medium", children: action.summary }),
2582
+ action.status === "proposed" ? /* @__PURE__ */ jsxs15("div", { className: "mt-2 flex flex-wrap gap-2", children: [
2583
+ /* @__PURE__ */ jsx17(
2584
+ "button",
2585
+ {
2586
+ type: "button",
2587
+ "data-test": "ask-ai-action-confirm",
2588
+ disabled: Boolean(resolvingId) || !resolveAction,
2589
+ onClick: () => void handleResolve(action.id, "confirm"),
2590
+ className: "inline-flex min-h-8 items-center rounded-lg bg-ph-brand px-2.5 text-xs font-medium text-[color:var(--ph-on-accent)] transition-opacity hover:bg-ph-brand-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40",
2591
+ children: resolvingId === action.id ? "Working\u2026" : "Confirm"
2592
+ }
2593
+ ),
2594
+ /* @__PURE__ */ jsx17(
2595
+ "button",
2596
+ {
2597
+ type: "button",
2598
+ "data-test": "ask-ai-action-cancel",
2599
+ disabled: Boolean(resolvingId) || !resolveAction,
2600
+ onClick: () => void handleResolve(action.id, "cancel"),
2601
+ className: "inline-flex min-h-8 items-center rounded-lg border border-ph-border bg-ph-surface px-2.5 text-xs font-medium text-ph-ink transition-colors hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40",
2602
+ children: "Cancel"
2603
+ }
2604
+ )
2605
+ ] }) : null,
2606
+ action.status === "applied" ? /* @__PURE__ */ jsxs15("p", { className: "mt-1 text-xs text-ph-subtle", children: [
2607
+ "Done",
2608
+ action.href ? /* @__PURE__ */ jsxs15(Fragment5, { children: [
2609
+ " \xB7 ",
2610
+ /* @__PURE__ */ jsx17(
2611
+ "a",
2612
+ {
2613
+ href: action.href,
2614
+ className: "text-ph-brand underline-offset-2 hover:underline",
2615
+ children: "Open"
2616
+ }
2617
+ )
2618
+ ] }) : null
2619
+ ] }) : null,
2620
+ action.status === "cancelled" ? /* @__PURE__ */ jsx17("p", { className: "mt-1 text-xs text-ph-subtle", children: "Cancelled" }) : null,
2621
+ action.status === "failed" ? /* @__PURE__ */ jsx17("p", { className: "mt-1 text-xs text-ph-danger", children: action.error || "Could not apply that." }) : null
2622
+ ]
2623
+ },
2624
+ action.id
2625
+ ))
2626
+ ] })
2478
2627
  ]
2479
2628
  },
2480
2629
  `assistant-${index}`
@@ -2579,7 +2728,7 @@ function SuiteAiPanel({
2579
2728
  // src/suite/components/workspace-switcher.tsx
2580
2729
  import { useState as useState8 } from "react";
2581
2730
  import { Building, Check as Check2, NavArrowDown } from "iconoir-react";
2582
- import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2731
+ import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2583
2732
  function SuiteWorkspaceSwitcher({
2584
2733
  workspaces = [],
2585
2734
  groups,
@@ -2620,7 +2769,7 @@ function SuiteWorkspaceSwitcher({
2620
2769
  "aria-expanded": open,
2621
2770
  children: [
2622
2771
  /* @__PURE__ */ jsx18(Building, { className: "h-4 w-4 shrink-0 text-ph-ink", "aria-hidden": true }),
2623
- !collapsed ? /* @__PURE__ */ jsxs16(Fragment5, { children: [
2772
+ !collapsed ? /* @__PURE__ */ jsxs16(Fragment6, { children: [
2624
2773
  /* @__PURE__ */ jsx18("span", { className: "min-w-0 flex-1 truncate text-sm font-semibold text-ph-ink", children: name }),
2625
2774
  /* @__PURE__ */ jsx18(
2626
2775
  NavArrowDown,
@@ -3007,9 +3156,9 @@ function SuiteSettingsMobileNav({
3007
3156
 
3008
3157
  // src/suite/components/suite-sidebar.tsx
3009
3158
  import Link2 from "next/link";
3010
- import { useEffect as useEffect8 } from "react";
3159
+ import { useCallback as useCallback4, useEffect as useEffect8 } from "react";
3011
3160
  import { NavArrowLeft, Sparks as Sparks2 } from "iconoir-react";
3012
- import { Fragment as Fragment6, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
3161
+ import { Fragment as Fragment7, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
3013
3162
  function renderNode(node, collapsed) {
3014
3163
  if (typeof node === "function") return node(collapsed);
3015
3164
  return node;
@@ -3032,30 +3181,37 @@ function SuiteSidebar({
3032
3181
  }) {
3033
3182
  const showAskAi = Boolean(onAskAiOpenChange && askAi);
3034
3183
  const chatOpen = showAskAi && askAiOpen && !collapsed;
3184
+ const setAskAi = useCallback4(
3185
+ (next) => {
3186
+ persistSuiteAskAiOpen(next);
3187
+ onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(next);
3188
+ },
3189
+ [onAskAiOpenChange]
3190
+ );
3035
3191
  useEffect8(() => {
3036
3192
  if (!onAskAiOpenChange) return;
3037
3193
  function onOpen() {
3038
3194
  onRequestExpand == null ? void 0 : onRequestExpand();
3039
- onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(true);
3195
+ setAskAi(true);
3040
3196
  }
3041
3197
  window.addEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
3042
3198
  return () => window.removeEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
3043
- }, [onAskAiOpenChange, onRequestExpand]);
3199
+ }, [onAskAiOpenChange, onRequestExpand, setAskAi]);
3044
3200
  useEffect8(() => {
3045
3201
  if (!chatOpen) return;
3046
3202
  function onKey(event) {
3047
- if (event.key === "Escape") onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(false);
3203
+ if (event.key === "Escape") setAskAi(false);
3048
3204
  }
3049
3205
  window.addEventListener("keydown", onKey);
3050
3206
  return () => window.removeEventListener("keydown", onKey);
3051
- }, [chatOpen, onAskAiOpenChange]);
3207
+ }, [chatOpen, setAskAi]);
3052
3208
  function toggleAskAi() {
3053
3209
  if (askAiOpen) {
3054
- onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(false);
3210
+ setAskAi(false);
3055
3211
  return;
3056
3212
  }
3057
3213
  if (collapsed) onRequestExpand == null ? void 0 : onRequestExpand();
3058
- onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(true);
3214
+ setAskAi(true);
3059
3215
  }
3060
3216
  const askAiButton = /* @__PURE__ */ jsx22(
3061
3217
  "button",
@@ -3097,7 +3253,7 @@ function SuiteSidebar({
3097
3253
  type: "button",
3098
3254
  "data-test": "suite-ask-ai-back",
3099
3255
  "aria-label": "Back to navigation",
3100
- onClick: () => onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(false),
3256
+ onClick: () => setAskAi(false),
3101
3257
  className: "inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-ph-subtle transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
3102
3258
  children: /* @__PURE__ */ jsx22(NavArrowLeft, { className: "h-4 w-4", "aria-hidden": true })
3103
3259
  }
@@ -3152,7 +3308,7 @@ function SuiteSidebar({
3152
3308
  {
3153
3309
  type: "button",
3154
3310
  "data-test": "suite-ask-ai-back",
3155
- onClick: () => onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(false),
3311
+ onClick: () => setAskAi(false),
3156
3312
  className: "inline-flex h-9 items-center gap-1 rounded-lg px-2 text-sm font-medium text-ph-subtle transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
3157
3313
  children: [
3158
3314
  /* @__PURE__ */ jsx22(NavArrowLeft, { className: "h-4 w-4", "aria-hidden": true }),
@@ -3163,7 +3319,7 @@ function SuiteSidebar({
3163
3319
  /* @__PURE__ */ jsx22("span", { className: "truncate text-sm font-semibold text-ph-ink", children: "Shelly AI" })
3164
3320
  ] }),
3165
3321
  /* @__PURE__ */ jsx22("div", { className: "min-h-0 flex-1", children: askAi })
3166
- ] }) : /* @__PURE__ */ jsxs19(Fragment6, { children: [
3322
+ ] }) : /* @__PURE__ */ jsxs19(Fragment7, { children: [
3167
3323
  /* @__PURE__ */ jsx22("nav", { "aria-label": "Pages", className: "shrink-0 space-y-0.5", children: /* @__PURE__ */ jsx22("div", { className: "space-y-0.5", children: navItems.map((item) => {
3168
3324
  var _a;
3169
3325
  const Icon = item.icon;
@@ -3217,14 +3373,7 @@ function SuiteSidebar({
3217
3373
  );
3218
3374
  return collapsed ? /* @__PURE__ */ jsx22(Tooltip, { content: item.label, side: "right", children: link }, item.label) : link;
3219
3375
  }) }) }),
3220
- secondaryNav ? /* @__PURE__ */ jsx22(
3221
- "div",
3222
- {
3223
- className: "min-h-0 flex-1",
3224
- "data-test": "suite-sidebar-secondary-nav",
3225
- children: secondaryNav
3226
- }
3227
- ) : null
3376
+ secondaryNav ? /* @__PURE__ */ jsx22("div", { className: "shrink-0", "data-test": "suite-sidebar-secondary-nav", children: secondaryNav }) : null
3228
3377
  ] })
3229
3378
  ]
3230
3379
  }
@@ -3363,8 +3512,11 @@ export {
3363
3512
  SIDEBAR_RAIL_WIDTH,
3364
3513
  SUITE_APPS2 as SUITE_APPS,
3365
3514
  SUITE_APP_MAP,
3515
+ SUITE_ASK_AI_OPEN_KEY,
3516
+ SUITE_ASK_AI_OPEN_QUERY,
3366
3517
  SUITE_GLYPHS,
3367
3518
  SUITE_ICON_NAMES,
3519
+ SUITE_MUTATED_EVENT,
3368
3520
  SUITE_OPEN_ASK_AI_EVENT,
3369
3521
  SUITE_SPRINGS,
3370
3522
  SourceAppGlyph,
@@ -3394,6 +3546,7 @@ export {
3394
3546
  SuiteToolbar,
3395
3547
  SuiteUserMenu,
3396
3548
  SuiteWorkspaceSwitcher,
3549
+ TURTLETIME_TIMER_MUTATED_EVENT,
3397
3550
  TodayAskAiCard,
3398
3551
  TodayCalibrating,
3399
3552
  TodayEmptyAction,
@@ -3412,9 +3565,13 @@ export {
3412
3565
  classifySuiteHref,
3413
3566
  cn,
3414
3567
  consumePendingAskAiPrompt,
3568
+ consumeSuiteAskAiOpenFromLocation,
3415
3569
  getTodayGreeting,
3416
3570
  m,
3417
3571
  openSuiteAskAi,
3572
+ pathWithSuiteAskAiOpen,
3573
+ persistSuiteAskAiOpen,
3574
+ readSuiteAskAiOpen,
3418
3575
  resolveSuiteNotificationHref,
3419
3576
  sidebarColumnWidth,
3420
3577
  sourceToSuiteApp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenide-io/the-old-ui-theme",
3
- "version": "0.6.9",
3
+ "version": "0.6.11",
4
4
  "description": "Props-driven React components and theme tokens inspired by classic interface design \u2014 optimized for Next.js apps and internal tools.",
5
5
  "author": "Xenide",
6
6
  "license": "MIT",
@@ -2,7 +2,13 @@ import { describe, expect, it, vi } from "vitest";
2
2
 
3
3
  import {
4
4
  consumePendingAskAiPrompt,
5
+ consumeSuiteAskAiOpenFromLocation,
5
6
  openSuiteAskAi,
7
+ pathWithSuiteAskAiOpen,
8
+ persistSuiteAskAiOpen,
9
+ readSuiteAskAiOpen,
10
+ SUITE_ASK_AI_OPEN_KEY,
11
+ SUITE_ASK_AI_OPEN_QUERY,
6
12
  SUITE_OPEN_ASK_AI_EVENT,
7
13
  } from "./ai-panel";
8
14
 
@@ -18,3 +24,40 @@ describe("openSuiteAskAi", () => {
18
24
  expect(consumePendingAskAiPrompt()).toBeNull();
19
25
  });
20
26
  });
27
+
28
+ describe("persistSuiteAskAiOpen", () => {
29
+ it("remembers that Shelly was open across a same-tab navigation", () => {
30
+ sessionStorage.removeItem(SUITE_ASK_AI_OPEN_KEY);
31
+ persistSuiteAskAiOpen(true);
32
+ expect(readSuiteAskAiOpen()).toBe(true);
33
+ persistSuiteAskAiOpen(false);
34
+ expect(readSuiteAskAiOpen()).toBe(false);
35
+ });
36
+
37
+ it("stamps a cross-app path so the destination can reopen Shelly", () => {
38
+ expect(pathWithSuiteAskAiOpen("/tracker")).toBe(
39
+ `/tracker?${SUITE_ASK_AI_OPEN_QUERY}=1`,
40
+ );
41
+ expect(pathWithSuiteAskAiOpen("/dashboard?scope=mine")).toBe(
42
+ `/dashboard?scope=mine&${SUITE_ASK_AI_OPEN_QUERY}=1`,
43
+ );
44
+ });
45
+
46
+ it("opens Shelly from a cross-app query and then strips it", () => {
47
+ sessionStorage.removeItem(SUITE_ASK_AI_OPEN_KEY);
48
+ window.history.replaceState(
49
+ null,
50
+ "",
51
+ `/tracker?${SUITE_ASK_AI_OPEN_QUERY}=1`,
52
+ );
53
+ expect(consumeSuiteAskAiOpenFromLocation()).toBe(true);
54
+ expect(readSuiteAskAiOpen()).toBe(true);
55
+ expect(window.location.search).toBe("");
56
+ });
57
+
58
+ it("does not reopen Shelly from a leftover flag when switching apps", () => {
59
+ persistSuiteAskAiOpen(true);
60
+ window.history.replaceState(null, "", "/tracker");
61
+ expect(consumeSuiteAskAiOpenFromLocation()).toBe(false);
62
+ });
63
+ });