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

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.d.mts CHANGED
@@ -477,9 +477,24 @@ declare function SuiteThemeProvider({ config, children, }: {
477
477
  declare function useSuiteTheme(): SuiteThemeContextValue;
478
478
 
479
479
  declare const SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
480
+ declare const SUITE_ASK_AI_OPEN_KEY = "shellstack:shelly-open";
481
+ declare const SUITE_MUTATED_EVENT = "shellstack:suite-mutated";
482
+ declare const TURTLETIME_TIMER_MUTATED_EVENT = "tt-timer-mutated";
480
483
  type SuiteAskAiOpenDetail = {
481
484
  prompt?: string;
482
485
  };
486
+ declare function persistSuiteAskAiOpen(open: boolean): void;
487
+ declare function readSuiteAskAiOpen(): boolean;
488
+ type SuiteAiActionStatus = "proposed" | "applied" | "cancelled" | "failed";
489
+ interface SuiteAiAction {
490
+ id: string;
491
+ kind: string;
492
+ status: SuiteAiActionStatus;
493
+ summary: string;
494
+ href?: string;
495
+ error?: string;
496
+ payload?: Record<string, unknown>;
497
+ }
483
498
  /** Open the suite Ask AI panel from anywhere (Today card, command palette, …). */
484
499
  declare function openSuiteAskAi(detail?: SuiteAskAiOpenDetail): void;
485
500
  /** Consume a prompt queued before the panel mounted. */
@@ -492,12 +507,13 @@ interface SuiteAiChatMessage {
492
507
  role: "user" | "assistant";
493
508
  content: string;
494
509
  created_at?: string;
510
+ actions?: SuiteAiAction[];
495
511
  }
496
512
  /**
497
513
  * Suite-wide Ask AI chat — Notion-style continuous thread.
498
514
  * Lives in the sidebar (or any parent that mounts it). Not Kraken Deep Research.
499
515
  */
500
- declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessage, clearChat, brandIcon: BrandIcon, spinner: Spinner, open: openProp, onOpenChange, suiteAppBases, onSwitchApp, }: {
516
+ declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessage, clearChat, brandIcon: BrandIcon, spinner: Spinner, open: openProp, onOpenChange, suiteAppBases, onSwitchApp, onSameAppNavigate, resolveAction, }: {
501
517
  presets?: SuiteAiPreset[];
502
518
  emptyState?: ReactNode;
503
519
  title?: string;
@@ -525,6 +541,11 @@ declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessa
525
541
  suiteAppBases?: Partial<Record<SuiteAppSlug, string>>;
526
542
  /** Cross-app SSO. Same-app links just navigate to `path`. */
527
543
  onSwitchApp?: (app: SuiteAppSlug, path: string) => void;
544
+ /** Client-side same-app route change — keeps Shelly mounted. */
545
+ onSameAppNavigate?: (path: string) => void;
546
+ resolveAction?: (id: string, decision: "confirm" | "cancel") => Promise<{
547
+ messages: SuiteAiChatMessage[];
548
+ }>;
528
549
  }): react.JSX.Element | null;
529
550
 
530
551
  interface SuiteWorkspaceEntry {
@@ -951,4 +972,4 @@ interface SuiteAppLayoutProps {
951
972
  */
952
973
  declare function SuiteAppLayout({ sidebar, children, mobileHeader, bottomNav, sidebarWidth, collapsed, lockMainScroll, onStartResize, onResizeBy, onResetWidth, className, }: SuiteAppLayoutProps): react.JSX.Element;
953
974
 
954
- export { APP_ACCENTS, APP_GLYPHS, AnimatedMoon, AnimatedSun, AppSwitcher, AppSwitcherChevron, AppSwitcherMark, CommandPaletteHost, DeferredChrome, SIDEBAR_COLLAPSE_THRESHOLD, SIDEBAR_DEFAULT_WIDTH, SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_EXPANDED_WIDTH, SIDEBAR_RAIL_WIDTH, SUITE_APPS, SUITE_APP_MAP, SUITE_GLYPHS, SUITE_ICON_NAMES, SUITE_OPEN_ASK_AI_EVENT, SUITE_SPRINGS, SourceAppGlyph, type SuiteAccentSlug, type SuiteAiChatMessage, SuiteAiPanel, type SuiteAiPreset, type SuiteAppAccent, type SuiteAppDefinition, type SuiteAppEntry, SuiteAppIcon, type SuiteAppIconProps, SuiteAppLayout, type SuiteAppLayoutProps, type SuiteAppSlug, SuiteAppStrip, type SuiteAskAiOpenDetail, SuiteBottomNav, type SuiteBottomNavItem, SuiteBreadcrumbs, type SuiteBreadcrumbsProps, type SuiteCommandItem, type SuiteCommandPaletteComponent, type SuiteDropdownItemComponent, type SuiteDropdownMenuComponent, type SuiteDropdownMenuProps, SuiteEmptyState, type SuiteGlyph, type SuiteGlyphElement, type SuiteHrefTarget, SuiteIcon, type SuiteIconName, type SuiteIconProps, SuiteMobileDrawer, SuiteMobileHeader, SuiteMotionProvider, type SuiteNotification, SuiteNotificationBell, type SuiteNotificationsResponse, SuitePage, SuitePageHeader, type SuitePageWidth, type SuiteResolvedTheme, SuiteSectionHeader, SuiteSettingsLayout, type SuiteSettingsLayoutProps, SuiteSettingsMobileNav, type SuiteSettingsNavItem, SuiteSidebar, type SuiteSidebarNavItem, SuiteSkeleton, SuiteSkeletonCard, SuiteSkeletonList, type SuiteSpinnerComponent, type SuiteSpringName, SuiteTabList, type SuiteTheme, type SuiteThemeConfig, type SuiteThemeContextValue, SuiteThemeProvider, SuiteToolbar, SuiteUserMenu, type SuiteUserMenuProps, type SuiteWorkspaceEntry, type SuiteWorkspaceGroup, SuiteWorkspaceSwitcher, TodayAskAiCard, TodayCalibrating, TodayEmptyAction, TodayEmptyState, TodayHero, TodayLayout, TodayPageFrame, TodayPanel, type TodayPrimaryAccent, TodayPrimaryAction, TodaySection, TodayTimeIcon, TodayTopBar, appSwitcherMarkClass, appSwitcherMenuItemClass, appSwitcherTriggerClass, classifySuiteHref, cn, consumePendingAskAiPrompt, getTodayGreeting, openSuiteAskAi, resolveSuiteNotificationHref, sidebarColumnWidth, sourceToSuiteApp, suiteAppBaseUrl, suiteAppLabel, useIdleMount, useSidebarWidth, useSuiteTheme };
975
+ export { APP_ACCENTS, APP_GLYPHS, AnimatedMoon, AnimatedSun, AppSwitcher, AppSwitcherChevron, AppSwitcherMark, CommandPaletteHost, DeferredChrome, SIDEBAR_COLLAPSE_THRESHOLD, SIDEBAR_DEFAULT_WIDTH, SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_EXPANDED_WIDTH, SIDEBAR_RAIL_WIDTH, SUITE_APPS, SUITE_APP_MAP, SUITE_ASK_AI_OPEN_KEY, SUITE_GLYPHS, SUITE_ICON_NAMES, SUITE_MUTATED_EVENT, SUITE_OPEN_ASK_AI_EVENT, SUITE_SPRINGS, SourceAppGlyph, type SuiteAccentSlug, type SuiteAiAction, type SuiteAiActionStatus, type SuiteAiChatMessage, SuiteAiPanel, type SuiteAiPreset, type SuiteAppAccent, type SuiteAppDefinition, type SuiteAppEntry, SuiteAppIcon, type SuiteAppIconProps, SuiteAppLayout, type SuiteAppLayoutProps, type SuiteAppSlug, SuiteAppStrip, type SuiteAskAiOpenDetail, SuiteBottomNav, type SuiteBottomNavItem, SuiteBreadcrumbs, type SuiteBreadcrumbsProps, type SuiteCommandItem, type SuiteCommandPaletteComponent, type SuiteDropdownItemComponent, type SuiteDropdownMenuComponent, type SuiteDropdownMenuProps, SuiteEmptyState, type SuiteGlyph, type SuiteGlyphElement, type SuiteHrefTarget, SuiteIcon, type SuiteIconName, type SuiteIconProps, SuiteMobileDrawer, SuiteMobileHeader, SuiteMotionProvider, type SuiteNotification, SuiteNotificationBell, type SuiteNotificationsResponse, SuitePage, SuitePageHeader, type SuitePageWidth, type SuiteResolvedTheme, SuiteSectionHeader, SuiteSettingsLayout, type SuiteSettingsLayoutProps, SuiteSettingsMobileNav, type SuiteSettingsNavItem, SuiteSidebar, type SuiteSidebarNavItem, SuiteSkeleton, SuiteSkeletonCard, SuiteSkeletonList, type SuiteSpinnerComponent, type SuiteSpringName, SuiteTabList, type SuiteTheme, type SuiteThemeConfig, type SuiteThemeContextValue, SuiteThemeProvider, SuiteToolbar, SuiteUserMenu, type SuiteUserMenuProps, type SuiteWorkspaceEntry, type SuiteWorkspaceGroup, SuiteWorkspaceSwitcher, TURTLETIME_TIMER_MUTATED_EVENT, TodayAskAiCard, TodayCalibrating, TodayEmptyAction, TodayEmptyState, TodayHero, TodayLayout, TodayPageFrame, TodayPanel, type TodayPrimaryAccent, TodayPrimaryAction, TodaySection, TodayTimeIcon, TodayTopBar, appSwitcherMarkClass, appSwitcherMenuItemClass, appSwitcherTriggerClass, classifySuiteHref, cn, consumePendingAskAiPrompt, getTodayGreeting, openSuiteAskAi, persistSuiteAskAiOpen, readSuiteAskAiOpen, resolveSuiteNotificationHref, sidebarColumnWidth, sourceToSuiteApp, suiteAppBaseUrl, suiteAppLabel, useIdleMount, useSidebarWidth, useSuiteTheme };
package/dist/suite.d.ts CHANGED
@@ -477,9 +477,24 @@ declare function SuiteThemeProvider({ config, children, }: {
477
477
  declare function useSuiteTheme(): SuiteThemeContextValue;
478
478
 
479
479
  declare const SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
480
+ declare const SUITE_ASK_AI_OPEN_KEY = "shellstack:shelly-open";
481
+ declare const SUITE_MUTATED_EVENT = "shellstack:suite-mutated";
482
+ declare const TURTLETIME_TIMER_MUTATED_EVENT = "tt-timer-mutated";
480
483
  type SuiteAskAiOpenDetail = {
481
484
  prompt?: string;
482
485
  };
486
+ declare function persistSuiteAskAiOpen(open: boolean): void;
487
+ declare function readSuiteAskAiOpen(): boolean;
488
+ type SuiteAiActionStatus = "proposed" | "applied" | "cancelled" | "failed";
489
+ interface SuiteAiAction {
490
+ id: string;
491
+ kind: string;
492
+ status: SuiteAiActionStatus;
493
+ summary: string;
494
+ href?: string;
495
+ error?: string;
496
+ payload?: Record<string, unknown>;
497
+ }
483
498
  /** Open the suite Ask AI panel from anywhere (Today card, command palette, …). */
484
499
  declare function openSuiteAskAi(detail?: SuiteAskAiOpenDetail): void;
485
500
  /** Consume a prompt queued before the panel mounted. */
@@ -492,12 +507,13 @@ interface SuiteAiChatMessage {
492
507
  role: "user" | "assistant";
493
508
  content: string;
494
509
  created_at?: string;
510
+ actions?: SuiteAiAction[];
495
511
  }
496
512
  /**
497
513
  * Suite-wide Ask AI chat — Notion-style continuous thread.
498
514
  * Lives in the sidebar (or any parent that mounts it). Not Kraken Deep Research.
499
515
  */
500
- declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessage, clearChat, brandIcon: BrandIcon, spinner: Spinner, open: openProp, onOpenChange, suiteAppBases, onSwitchApp, }: {
516
+ declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessage, clearChat, brandIcon: BrandIcon, spinner: Spinner, open: openProp, onOpenChange, suiteAppBases, onSwitchApp, onSameAppNavigate, resolveAction, }: {
501
517
  presets?: SuiteAiPreset[];
502
518
  emptyState?: ReactNode;
503
519
  title?: string;
@@ -525,6 +541,11 @@ declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessa
525
541
  suiteAppBases?: Partial<Record<SuiteAppSlug, string>>;
526
542
  /** Cross-app SSO. Same-app links just navigate to `path`. */
527
543
  onSwitchApp?: (app: SuiteAppSlug, path: string) => void;
544
+ /** Client-side same-app route change — keeps Shelly mounted. */
545
+ onSameAppNavigate?: (path: string) => void;
546
+ resolveAction?: (id: string, decision: "confirm" | "cancel") => Promise<{
547
+ messages: SuiteAiChatMessage[];
548
+ }>;
528
549
  }): react.JSX.Element | null;
529
550
 
530
551
  interface SuiteWorkspaceEntry {
@@ -951,4 +972,4 @@ interface SuiteAppLayoutProps {
951
972
  */
952
973
  declare function SuiteAppLayout({ sidebar, children, mobileHeader, bottomNav, sidebarWidth, collapsed, lockMainScroll, onStartResize, onResizeBy, onResetWidth, className, }: SuiteAppLayoutProps): react.JSX.Element;
953
974
 
954
- export { APP_ACCENTS, APP_GLYPHS, AnimatedMoon, AnimatedSun, AppSwitcher, AppSwitcherChevron, AppSwitcherMark, CommandPaletteHost, DeferredChrome, SIDEBAR_COLLAPSE_THRESHOLD, SIDEBAR_DEFAULT_WIDTH, SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_EXPANDED_WIDTH, SIDEBAR_RAIL_WIDTH, SUITE_APPS, SUITE_APP_MAP, SUITE_GLYPHS, SUITE_ICON_NAMES, SUITE_OPEN_ASK_AI_EVENT, SUITE_SPRINGS, SourceAppGlyph, type SuiteAccentSlug, type SuiteAiChatMessage, SuiteAiPanel, type SuiteAiPreset, type SuiteAppAccent, type SuiteAppDefinition, type SuiteAppEntry, SuiteAppIcon, type SuiteAppIconProps, SuiteAppLayout, type SuiteAppLayoutProps, type SuiteAppSlug, SuiteAppStrip, type SuiteAskAiOpenDetail, SuiteBottomNav, type SuiteBottomNavItem, SuiteBreadcrumbs, type SuiteBreadcrumbsProps, type SuiteCommandItem, type SuiteCommandPaletteComponent, type SuiteDropdownItemComponent, type SuiteDropdownMenuComponent, type SuiteDropdownMenuProps, SuiteEmptyState, type SuiteGlyph, type SuiteGlyphElement, type SuiteHrefTarget, SuiteIcon, type SuiteIconName, type SuiteIconProps, SuiteMobileDrawer, SuiteMobileHeader, SuiteMotionProvider, type SuiteNotification, SuiteNotificationBell, type SuiteNotificationsResponse, SuitePage, SuitePageHeader, type SuitePageWidth, type SuiteResolvedTheme, SuiteSectionHeader, SuiteSettingsLayout, type SuiteSettingsLayoutProps, SuiteSettingsMobileNav, type SuiteSettingsNavItem, SuiteSidebar, type SuiteSidebarNavItem, SuiteSkeleton, SuiteSkeletonCard, SuiteSkeletonList, type SuiteSpinnerComponent, type SuiteSpringName, SuiteTabList, type SuiteTheme, type SuiteThemeConfig, type SuiteThemeContextValue, SuiteThemeProvider, SuiteToolbar, SuiteUserMenu, type SuiteUserMenuProps, type SuiteWorkspaceEntry, type SuiteWorkspaceGroup, SuiteWorkspaceSwitcher, TodayAskAiCard, TodayCalibrating, TodayEmptyAction, TodayEmptyState, TodayHero, TodayLayout, TodayPageFrame, TodayPanel, type TodayPrimaryAccent, TodayPrimaryAction, TodaySection, TodayTimeIcon, TodayTopBar, appSwitcherMarkClass, appSwitcherMenuItemClass, appSwitcherTriggerClass, classifySuiteHref, cn, consumePendingAskAiPrompt, getTodayGreeting, openSuiteAskAi, resolveSuiteNotificationHref, sidebarColumnWidth, sourceToSuiteApp, suiteAppBaseUrl, suiteAppLabel, useIdleMount, useSidebarWidth, useSuiteTheme };
975
+ export { APP_ACCENTS, APP_GLYPHS, AnimatedMoon, AnimatedSun, AppSwitcher, AppSwitcherChevron, AppSwitcherMark, CommandPaletteHost, DeferredChrome, SIDEBAR_COLLAPSE_THRESHOLD, SIDEBAR_DEFAULT_WIDTH, SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_EXPANDED_WIDTH, SIDEBAR_RAIL_WIDTH, SUITE_APPS, SUITE_APP_MAP, SUITE_ASK_AI_OPEN_KEY, SUITE_GLYPHS, SUITE_ICON_NAMES, SUITE_MUTATED_EVENT, SUITE_OPEN_ASK_AI_EVENT, SUITE_SPRINGS, SourceAppGlyph, type SuiteAccentSlug, type SuiteAiAction, type SuiteAiActionStatus, type SuiteAiChatMessage, SuiteAiPanel, type SuiteAiPreset, type SuiteAppAccent, type SuiteAppDefinition, type SuiteAppEntry, SuiteAppIcon, type SuiteAppIconProps, SuiteAppLayout, type SuiteAppLayoutProps, type SuiteAppSlug, SuiteAppStrip, type SuiteAskAiOpenDetail, SuiteBottomNav, type SuiteBottomNavItem, SuiteBreadcrumbs, type SuiteBreadcrumbsProps, type SuiteCommandItem, type SuiteCommandPaletteComponent, type SuiteDropdownItemComponent, type SuiteDropdownMenuComponent, type SuiteDropdownMenuProps, SuiteEmptyState, type SuiteGlyph, type SuiteGlyphElement, type SuiteHrefTarget, SuiteIcon, type SuiteIconName, type SuiteIconProps, SuiteMobileDrawer, SuiteMobileHeader, SuiteMotionProvider, type SuiteNotification, SuiteNotificationBell, type SuiteNotificationsResponse, SuitePage, SuitePageHeader, type SuitePageWidth, type SuiteResolvedTheme, SuiteSectionHeader, SuiteSettingsLayout, type SuiteSettingsLayoutProps, SuiteSettingsMobileNav, type SuiteSettingsNavItem, SuiteSidebar, type SuiteSidebarNavItem, SuiteSkeleton, SuiteSkeletonCard, SuiteSkeletonList, type SuiteSpinnerComponent, type SuiteSpringName, SuiteTabList, type SuiteTheme, type SuiteThemeConfig, type SuiteThemeContextValue, SuiteThemeProvider, SuiteToolbar, SuiteUserMenu, type SuiteUserMenuProps, type SuiteWorkspaceEntry, type SuiteWorkspaceGroup, SuiteWorkspaceSwitcher, TURTLETIME_TIMER_MUTATED_EVENT, TodayAskAiCard, TodayCalibrating, TodayEmptyAction, TodayEmptyState, TodayHero, TodayLayout, TodayPageFrame, TodayPanel, type TodayPrimaryAccent, TodayPrimaryAction, TodaySection, TodayTimeIcon, TodayTopBar, appSwitcherMarkClass, appSwitcherMenuItemClass, appSwitcherTriggerClass, classifySuiteHref, cn, consumePendingAskAiPrompt, getTodayGreeting, openSuiteAskAi, persistSuiteAskAiOpen, readSuiteAskAiOpen, resolveSuiteNotificationHref, sidebarColumnWidth, sourceToSuiteApp, suiteAppBaseUrl, suiteAppLabel, useIdleMount, useSidebarWidth, useSuiteTheme };
package/dist/suite.js CHANGED
@@ -243,8 +243,10 @@ __export(suite_exports, {
243
243
  SIDEBAR_RAIL_WIDTH: () => SIDEBAR_RAIL_WIDTH,
244
244
  SUITE_APPS: () => SUITE_APPS2,
245
245
  SUITE_APP_MAP: () => SUITE_APP_MAP,
246
+ SUITE_ASK_AI_OPEN_KEY: () => SUITE_ASK_AI_OPEN_KEY,
246
247
  SUITE_GLYPHS: () => SUITE_GLYPHS,
247
248
  SUITE_ICON_NAMES: () => SUITE_ICON_NAMES,
249
+ SUITE_MUTATED_EVENT: () => SUITE_MUTATED_EVENT,
248
250
  SUITE_OPEN_ASK_AI_EVENT: () => SUITE_OPEN_ASK_AI_EVENT,
249
251
  SUITE_SPRINGS: () => SUITE_SPRINGS,
250
252
  SourceAppGlyph: () => SourceAppGlyph,
@@ -274,6 +276,7 @@ __export(suite_exports, {
274
276
  SuiteToolbar: () => SuiteToolbar,
275
277
  SuiteUserMenu: () => SuiteUserMenu,
276
278
  SuiteWorkspaceSwitcher: () => SuiteWorkspaceSwitcher,
279
+ TURTLETIME_TIMER_MUTATED_EVENT: () => TURTLETIME_TIMER_MUTATED_EVENT,
277
280
  TodayAskAiCard: () => TodayAskAiCard,
278
281
  TodayCalibrating: () => TodayCalibrating,
279
282
  TodayEmptyAction: () => TodayEmptyAction,
@@ -295,6 +298,8 @@ __export(suite_exports, {
295
298
  getTodayGreeting: () => getTodayGreeting,
296
299
  m: () => import_react14.m,
297
300
  openSuiteAskAi: () => openSuiteAskAi,
301
+ persistSuiteAskAiOpen: () => persistSuiteAskAiOpen,
302
+ readSuiteAskAiOpen: () => readSuiteAskAiOpen,
298
303
  resolveSuiteNotificationHref: () => resolveSuiteNotificationHref,
299
304
  sidebarColumnWidth: () => sidebarColumnWidth,
300
305
  sourceToSuiteApp: () => sourceToSuiteApp,
@@ -2736,6 +2741,25 @@ function Tooltip({
2736
2741
  var import_jsx_runtime21 = require("react/jsx-runtime");
2737
2742
  var SuiteAiBlockNoteMessage2 = (0, import_react11.lazy)(() => Promise.resolve().then(() => (init_ai_message_blocknote(), ai_message_blocknote_exports)));
2738
2743
  var SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
2744
+ var SUITE_ASK_AI_OPEN_KEY = "shellstack:shelly-open";
2745
+ var SUITE_MUTATED_EVENT = "shellstack:suite-mutated";
2746
+ var TURTLETIME_TIMER_MUTATED_EVENT = "tt-timer-mutated";
2747
+ function persistSuiteAskAiOpen(open) {
2748
+ if (typeof window === "undefined") return;
2749
+ try {
2750
+ if (open) sessionStorage.setItem(SUITE_ASK_AI_OPEN_KEY, "1");
2751
+ else sessionStorage.removeItem(SUITE_ASK_AI_OPEN_KEY);
2752
+ } catch (e) {
2753
+ }
2754
+ }
2755
+ function readSuiteAskAiOpen() {
2756
+ if (typeof window === "undefined") return false;
2757
+ try {
2758
+ return sessionStorage.getItem(SUITE_ASK_AI_OPEN_KEY) === "1";
2759
+ } catch (e) {
2760
+ return false;
2761
+ }
2762
+ }
2739
2763
  var pendingAskAiPrompt = null;
2740
2764
  function openSuiteAskAi(detail) {
2741
2765
  var _a;
@@ -2752,6 +2776,18 @@ function consumePendingAskAiPrompt() {
2752
2776
  pendingAskAiPrompt = null;
2753
2777
  return next;
2754
2778
  }
2779
+ function notifySuiteMutated(actions) {
2780
+ if (typeof window === "undefined" || !(actions == null ? void 0 : actions.length)) return;
2781
+ const kinds = actions.map((action) => action.kind);
2782
+ if (kinds.some(
2783
+ (kind) => kind.includes("timer") || kind.includes("time") || kind.includes("hours")
2784
+ )) {
2785
+ window.dispatchEvent(new Event(TURTLETIME_TIMER_MUTATED_EVENT));
2786
+ }
2787
+ window.dispatchEvent(
2788
+ new CustomEvent(SUITE_MUTATED_EVENT, { detail: { kinds } })
2789
+ );
2790
+ }
2755
2791
  function SuiteAiPanel({
2756
2792
  presets = [],
2757
2793
  emptyState = "Ask when you are ready \u2014 overdue work, a running timer, or anything else.",
@@ -2764,13 +2800,17 @@ function SuiteAiPanel({
2764
2800
  open: openProp,
2765
2801
  onOpenChange,
2766
2802
  suiteAppBases,
2767
- onSwitchApp
2803
+ onSwitchApp,
2804
+ onSameAppNavigate,
2805
+ resolveAction
2768
2806
  }) {
2769
2807
  const inputId = (0, import_react11.useId)();
2770
2808
  const [uncontrolledOpen, setUncontrolledOpen] = (0, import_react11.useState)(false);
2809
+ const [resolvingId, setResolvingId] = (0, import_react11.useState)("");
2771
2810
  const open = openProp != null ? openProp : uncontrolledOpen;
2772
2811
  const setOpen = (0, import_react11.useCallback)(
2773
2812
  (next) => {
2813
+ persistSuiteAskAiOpen(next);
2774
2814
  if (openProp === void 0) setUncontrolledOpen(next);
2775
2815
  onOpenChange == null ? void 0 : onOpenChange(next);
2776
2816
  },
@@ -2871,7 +2911,14 @@ function SuiteAiPanel({
2871
2911
  signal: controller.signal
2872
2912
  });
2873
2913
  if (controller.signal.aborted) return;
2874
- setMessages((_a = result.messages) != null ? _a : []);
2914
+ const nextMessages = (_a = result.messages) != null ? _a : [];
2915
+ setMessages(nextMessages);
2916
+ notifySuiteMutated(
2917
+ nextMessages.flatMap((message) => {
2918
+ var _a2;
2919
+ return (_a2 = message.actions) != null ? _a2 : [];
2920
+ })
2921
+ );
2875
2922
  } catch (err) {
2876
2923
  if (controller.signal.aborted) return;
2877
2924
  setMessages(
@@ -2914,6 +2961,29 @@ function SuiteAiPanel({
2914
2961
  );
2915
2962
  }
2916
2963
  }
2964
+ async function handleResolve(actionId, decision) {
2965
+ var _a;
2966
+ if (!resolveAction || resolvingId) return;
2967
+ setResolvingId(actionId);
2968
+ setError("");
2969
+ try {
2970
+ const result = await resolveAction(actionId, decision);
2971
+ const nextMessages = (_a = result.messages) != null ? _a : [];
2972
+ setMessages(nextMessages);
2973
+ notifySuiteMutated(
2974
+ nextMessages.flatMap((message) => {
2975
+ var _a2;
2976
+ return (_a2 = message.actions) != null ? _a2 : [];
2977
+ })
2978
+ );
2979
+ } catch (err) {
2980
+ setError(
2981
+ err instanceof Error ? err.message : "Could not apply that action."
2982
+ );
2983
+ } finally {
2984
+ setResolvingId("");
2985
+ }
2986
+ }
2917
2987
  function onConversationClick(event) {
2918
2988
  var _a;
2919
2989
  if (event.defaultPrevented) return;
@@ -2931,8 +3001,10 @@ function SuiteAiPanel({
2931
3001
  );
2932
3002
  if (!match) return;
2933
3003
  event.preventDefault();
3004
+ persistSuiteAskAiOpen(true);
2934
3005
  if (match.sameApp) {
2935
- window.location.assign(match.path);
3006
+ if (onSameAppNavigate) onSameAppNavigate(match.path);
3007
+ else window.location.assign(match.path);
2936
3008
  return;
2937
3009
  }
2938
3010
  if (onSwitchApp) onSwitchApp(match.slug, match.path);
@@ -3001,6 +3073,7 @@ function SuiteAiPanel({
3001
3073
  )) }) : null
3002
3074
  ] }) : null,
3003
3075
  messages.map((message, index) => {
3076
+ var _a;
3004
3077
  const isUser = message.role === "user";
3005
3078
  if (isUser) {
3006
3079
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
@@ -3027,13 +3100,67 @@ function SuiteAiPanel({
3027
3100
  children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Icon, { className: "h-3.5 w-3.5" })
3028
3101
  }
3029
3102
  ),
3030
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("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__ */ (0, import_jsx_runtime21.jsx)(
3031
- import_react11.Suspense,
3032
- {
3033
- fallback: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "whitespace-pre-wrap break-words", children: message.content }),
3034
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SuiteAiBlockNoteMessage2, { markdown: message.content })
3035
- }
3036
- ) })
3103
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "min-w-0 max-w-[85%] space-y-2", children: [
3104
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "rounded-2xl rounded-bl-md bg-ph-muted px-3 py-2 text-sm leading-relaxed text-ph-ink", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3105
+ import_react11.Suspense,
3106
+ {
3107
+ fallback: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "whitespace-pre-wrap break-words", children: message.content }),
3108
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SuiteAiBlockNoteMessage2, { markdown: message.content })
3109
+ }
3110
+ ) }),
3111
+ ((_a = message.actions) != null ? _a : []).map((action) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
3112
+ "div",
3113
+ {
3114
+ "data-test": "ask-ai-action",
3115
+ "data-status": action.status,
3116
+ className: "rounded-xl border border-ph-border bg-ph-surface px-3 py-2 text-sm text-ph-ink",
3117
+ children: [
3118
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "font-medium", children: action.summary }),
3119
+ action.status === "proposed" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "mt-2 flex flex-wrap gap-2", children: [
3120
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3121
+ "button",
3122
+ {
3123
+ type: "button",
3124
+ "data-test": "ask-ai-action-confirm",
3125
+ disabled: Boolean(resolvingId) || !resolveAction,
3126
+ onClick: () => void handleResolve(action.id, "confirm"),
3127
+ 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",
3128
+ children: resolvingId === action.id ? "Working\u2026" : "Confirm"
3129
+ }
3130
+ ),
3131
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3132
+ "button",
3133
+ {
3134
+ type: "button",
3135
+ "data-test": "ask-ai-action-cancel",
3136
+ disabled: Boolean(resolvingId) || !resolveAction,
3137
+ onClick: () => void handleResolve(action.id, "cancel"),
3138
+ 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",
3139
+ children: "Cancel"
3140
+ }
3141
+ )
3142
+ ] }) : null,
3143
+ action.status === "applied" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("p", { className: "mt-1 text-xs text-ph-subtle", children: [
3144
+ "Done",
3145
+ action.href ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
3146
+ " \xB7 ",
3147
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3148
+ "a",
3149
+ {
3150
+ href: action.href,
3151
+ className: "text-ph-brand underline-offset-2 hover:underline",
3152
+ children: "Open"
3153
+ }
3154
+ )
3155
+ ] }) : null
3156
+ ] }) : null,
3157
+ action.status === "cancelled" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "mt-1 text-xs text-ph-subtle", children: "Cancelled" }) : null,
3158
+ action.status === "failed" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "mt-1 text-xs text-ph-danger", children: action.error || "Could not apply that." }) : null
3159
+ ]
3160
+ },
3161
+ action.id
3162
+ ))
3163
+ ] })
3037
3164
  ]
3038
3165
  },
3039
3166
  `assistant-${index}`
@@ -3587,30 +3714,37 @@ function SuiteSidebar({
3587
3714
  }) {
3588
3715
  const showAskAi = Boolean(onAskAiOpenChange && askAi);
3589
3716
  const chatOpen = showAskAi && askAiOpen && !collapsed;
3717
+ const setAskAi = (0, import_react16.useCallback)(
3718
+ (next) => {
3719
+ persistSuiteAskAiOpen(next);
3720
+ onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(next);
3721
+ },
3722
+ [onAskAiOpenChange]
3723
+ );
3590
3724
  (0, import_react16.useEffect)(() => {
3591
3725
  if (!onAskAiOpenChange) return;
3592
3726
  function onOpen() {
3593
3727
  onRequestExpand == null ? void 0 : onRequestExpand();
3594
- onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(true);
3728
+ setAskAi(true);
3595
3729
  }
3596
3730
  window.addEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
3597
3731
  return () => window.removeEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
3598
- }, [onAskAiOpenChange, onRequestExpand]);
3732
+ }, [onAskAiOpenChange, onRequestExpand, setAskAi]);
3599
3733
  (0, import_react16.useEffect)(() => {
3600
3734
  if (!chatOpen) return;
3601
3735
  function onKey(event) {
3602
- if (event.key === "Escape") onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(false);
3736
+ if (event.key === "Escape") setAskAi(false);
3603
3737
  }
3604
3738
  window.addEventListener("keydown", onKey);
3605
3739
  return () => window.removeEventListener("keydown", onKey);
3606
- }, [chatOpen, onAskAiOpenChange]);
3740
+ }, [chatOpen, setAskAi]);
3607
3741
  function toggleAskAi() {
3608
3742
  if (askAiOpen) {
3609
- onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(false);
3743
+ setAskAi(false);
3610
3744
  return;
3611
3745
  }
3612
3746
  if (collapsed) onRequestExpand == null ? void 0 : onRequestExpand();
3613
- onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(true);
3747
+ setAskAi(true);
3614
3748
  }
3615
3749
  const askAiButton = /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
3616
3750
  "button",
@@ -3652,7 +3786,7 @@ function SuiteSidebar({
3652
3786
  type: "button",
3653
3787
  "data-test": "suite-ask-ai-back",
3654
3788
  "aria-label": "Back to navigation",
3655
- onClick: () => onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(false),
3789
+ onClick: () => setAskAi(false),
3656
3790
  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",
3657
3791
  children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_iconoir_react10.NavArrowLeft, { className: "h-4 w-4", "aria-hidden": true })
3658
3792
  }
@@ -3707,7 +3841,7 @@ function SuiteSidebar({
3707
3841
  {
3708
3842
  type: "button",
3709
3843
  "data-test": "suite-ask-ai-back",
3710
- onClick: () => onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(false),
3844
+ onClick: () => setAskAi(false),
3711
3845
  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",
3712
3846
  children: [
3713
3847
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_iconoir_react10.NavArrowLeft, { className: "h-4 w-4", "aria-hidden": true }),
@@ -3919,8 +4053,10 @@ function SuiteAppLayout({
3919
4053
  SIDEBAR_RAIL_WIDTH,
3920
4054
  SUITE_APPS,
3921
4055
  SUITE_APP_MAP,
4056
+ SUITE_ASK_AI_OPEN_KEY,
3922
4057
  SUITE_GLYPHS,
3923
4058
  SUITE_ICON_NAMES,
4059
+ SUITE_MUTATED_EVENT,
3924
4060
  SUITE_OPEN_ASK_AI_EVENT,
3925
4061
  SUITE_SPRINGS,
3926
4062
  SourceAppGlyph,
@@ -3950,6 +4086,7 @@ function SuiteAppLayout({
3950
4086
  SuiteToolbar,
3951
4087
  SuiteUserMenu,
3952
4088
  SuiteWorkspaceSwitcher,
4089
+ TURTLETIME_TIMER_MUTATED_EVENT,
3953
4090
  TodayAskAiCard,
3954
4091
  TodayCalibrating,
3955
4092
  TodayEmptyAction,
@@ -3971,6 +4108,8 @@ function SuiteAppLayout({
3971
4108
  getTodayGreeting,
3972
4109
  m,
3973
4110
  openSuiteAskAi,
4111
+ persistSuiteAskAiOpen,
4112
+ readSuiteAskAiOpen,
3974
4113
  resolveSuiteNotificationHref,
3975
4114
  sidebarColumnWidth,
3976
4115
  sourceToSuiteApp,
package/dist/suite.mjs CHANGED
@@ -2174,9 +2174,28 @@ import {
2174
2174
  Square,
2175
2175
  WarningTriangle
2176
2176
  } from "iconoir-react";
2177
- import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2177
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2178
2178
  var SuiteAiBlockNoteMessage = lazy(() => import("./ai-message-blocknote-DIUICX3G.mjs"));
2179
2179
  var SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
2180
+ var SUITE_ASK_AI_OPEN_KEY = "shellstack:shelly-open";
2181
+ var SUITE_MUTATED_EVENT = "shellstack:suite-mutated";
2182
+ var TURTLETIME_TIMER_MUTATED_EVENT = "tt-timer-mutated";
2183
+ function persistSuiteAskAiOpen(open) {
2184
+ if (typeof window === "undefined") return;
2185
+ try {
2186
+ if (open) sessionStorage.setItem(SUITE_ASK_AI_OPEN_KEY, "1");
2187
+ else sessionStorage.removeItem(SUITE_ASK_AI_OPEN_KEY);
2188
+ } catch (e) {
2189
+ }
2190
+ }
2191
+ function readSuiteAskAiOpen() {
2192
+ if (typeof window === "undefined") return false;
2193
+ try {
2194
+ return sessionStorage.getItem(SUITE_ASK_AI_OPEN_KEY) === "1";
2195
+ } catch (e) {
2196
+ return false;
2197
+ }
2198
+ }
2180
2199
  var pendingAskAiPrompt = null;
2181
2200
  function openSuiteAskAi(detail) {
2182
2201
  var _a;
@@ -2193,6 +2212,18 @@ function consumePendingAskAiPrompt() {
2193
2212
  pendingAskAiPrompt = null;
2194
2213
  return next;
2195
2214
  }
2215
+ function notifySuiteMutated(actions) {
2216
+ if (typeof window === "undefined" || !(actions == null ? void 0 : actions.length)) return;
2217
+ const kinds = actions.map((action) => action.kind);
2218
+ if (kinds.some(
2219
+ (kind) => kind.includes("timer") || kind.includes("time") || kind.includes("hours")
2220
+ )) {
2221
+ window.dispatchEvent(new Event(TURTLETIME_TIMER_MUTATED_EVENT));
2222
+ }
2223
+ window.dispatchEvent(
2224
+ new CustomEvent(SUITE_MUTATED_EVENT, { detail: { kinds } })
2225
+ );
2226
+ }
2196
2227
  function SuiteAiPanel({
2197
2228
  presets = [],
2198
2229
  emptyState = "Ask when you are ready \u2014 overdue work, a running timer, or anything else.",
@@ -2205,13 +2236,17 @@ function SuiteAiPanel({
2205
2236
  open: openProp,
2206
2237
  onOpenChange,
2207
2238
  suiteAppBases,
2208
- onSwitchApp
2239
+ onSwitchApp,
2240
+ onSameAppNavigate,
2241
+ resolveAction
2209
2242
  }) {
2210
2243
  const inputId = useId();
2211
2244
  const [uncontrolledOpen, setUncontrolledOpen] = useState7(false);
2245
+ const [resolvingId, setResolvingId] = useState7("");
2212
2246
  const open = openProp != null ? openProp : uncontrolledOpen;
2213
2247
  const setOpen = useCallback2(
2214
2248
  (next) => {
2249
+ persistSuiteAskAiOpen(next);
2215
2250
  if (openProp === void 0) setUncontrolledOpen(next);
2216
2251
  onOpenChange == null ? void 0 : onOpenChange(next);
2217
2252
  },
@@ -2312,7 +2347,14 @@ function SuiteAiPanel({
2312
2347
  signal: controller.signal
2313
2348
  });
2314
2349
  if (controller.signal.aborted) return;
2315
- setMessages((_a = result.messages) != null ? _a : []);
2350
+ const nextMessages = (_a = result.messages) != null ? _a : [];
2351
+ setMessages(nextMessages);
2352
+ notifySuiteMutated(
2353
+ nextMessages.flatMap((message) => {
2354
+ var _a2;
2355
+ return (_a2 = message.actions) != null ? _a2 : [];
2356
+ })
2357
+ );
2316
2358
  } catch (err) {
2317
2359
  if (controller.signal.aborted) return;
2318
2360
  setMessages(
@@ -2355,6 +2397,29 @@ function SuiteAiPanel({
2355
2397
  );
2356
2398
  }
2357
2399
  }
2400
+ async function handleResolve(actionId, decision) {
2401
+ var _a;
2402
+ if (!resolveAction || resolvingId) return;
2403
+ setResolvingId(actionId);
2404
+ setError("");
2405
+ try {
2406
+ const result = await resolveAction(actionId, decision);
2407
+ const nextMessages = (_a = result.messages) != null ? _a : [];
2408
+ setMessages(nextMessages);
2409
+ notifySuiteMutated(
2410
+ nextMessages.flatMap((message) => {
2411
+ var _a2;
2412
+ return (_a2 = message.actions) != null ? _a2 : [];
2413
+ })
2414
+ );
2415
+ } catch (err) {
2416
+ setError(
2417
+ err instanceof Error ? err.message : "Could not apply that action."
2418
+ );
2419
+ } finally {
2420
+ setResolvingId("");
2421
+ }
2422
+ }
2358
2423
  function onConversationClick(event) {
2359
2424
  var _a;
2360
2425
  if (event.defaultPrevented) return;
@@ -2372,8 +2437,10 @@ function SuiteAiPanel({
2372
2437
  );
2373
2438
  if (!match) return;
2374
2439
  event.preventDefault();
2440
+ persistSuiteAskAiOpen(true);
2375
2441
  if (match.sameApp) {
2376
- window.location.assign(match.path);
2442
+ if (onSameAppNavigate) onSameAppNavigate(match.path);
2443
+ else window.location.assign(match.path);
2377
2444
  return;
2378
2445
  }
2379
2446
  if (onSwitchApp) onSwitchApp(match.slug, match.path);
@@ -2442,6 +2509,7 @@ function SuiteAiPanel({
2442
2509
  )) }) : null
2443
2510
  ] }) : null,
2444
2511
  messages.map((message, index) => {
2512
+ var _a;
2445
2513
  const isUser = message.role === "user";
2446
2514
  if (isUser) {
2447
2515
  return /* @__PURE__ */ jsx17(
@@ -2468,13 +2536,67 @@ function SuiteAiPanel({
2468
2536
  children: /* @__PURE__ */ jsx17(Icon, { className: "h-3.5 w-3.5" })
2469
2537
  }
2470
2538
  ),
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
- ) })
2539
+ /* @__PURE__ */ jsxs15("div", { className: "min-w-0 max-w-[85%] space-y-2", children: [
2540
+ /* @__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(
2541
+ Suspense,
2542
+ {
2543
+ fallback: /* @__PURE__ */ jsx17("p", { className: "whitespace-pre-wrap break-words", children: message.content }),
2544
+ children: /* @__PURE__ */ jsx17(SuiteAiBlockNoteMessage, { markdown: message.content })
2545
+ }
2546
+ ) }),
2547
+ ((_a = message.actions) != null ? _a : []).map((action) => /* @__PURE__ */ jsxs15(
2548
+ "div",
2549
+ {
2550
+ "data-test": "ask-ai-action",
2551
+ "data-status": action.status,
2552
+ className: "rounded-xl border border-ph-border bg-ph-surface px-3 py-2 text-sm text-ph-ink",
2553
+ children: [
2554
+ /* @__PURE__ */ jsx17("p", { className: "font-medium", children: action.summary }),
2555
+ action.status === "proposed" ? /* @__PURE__ */ jsxs15("div", { className: "mt-2 flex flex-wrap gap-2", children: [
2556
+ /* @__PURE__ */ jsx17(
2557
+ "button",
2558
+ {
2559
+ type: "button",
2560
+ "data-test": "ask-ai-action-confirm",
2561
+ disabled: Boolean(resolvingId) || !resolveAction,
2562
+ onClick: () => void handleResolve(action.id, "confirm"),
2563
+ 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",
2564
+ children: resolvingId === action.id ? "Working\u2026" : "Confirm"
2565
+ }
2566
+ ),
2567
+ /* @__PURE__ */ jsx17(
2568
+ "button",
2569
+ {
2570
+ type: "button",
2571
+ "data-test": "ask-ai-action-cancel",
2572
+ disabled: Boolean(resolvingId) || !resolveAction,
2573
+ onClick: () => void handleResolve(action.id, "cancel"),
2574
+ 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",
2575
+ children: "Cancel"
2576
+ }
2577
+ )
2578
+ ] }) : null,
2579
+ action.status === "applied" ? /* @__PURE__ */ jsxs15("p", { className: "mt-1 text-xs text-ph-subtle", children: [
2580
+ "Done",
2581
+ action.href ? /* @__PURE__ */ jsxs15(Fragment5, { children: [
2582
+ " \xB7 ",
2583
+ /* @__PURE__ */ jsx17(
2584
+ "a",
2585
+ {
2586
+ href: action.href,
2587
+ className: "text-ph-brand underline-offset-2 hover:underline",
2588
+ children: "Open"
2589
+ }
2590
+ )
2591
+ ] }) : null
2592
+ ] }) : null,
2593
+ action.status === "cancelled" ? /* @__PURE__ */ jsx17("p", { className: "mt-1 text-xs text-ph-subtle", children: "Cancelled" }) : null,
2594
+ action.status === "failed" ? /* @__PURE__ */ jsx17("p", { className: "mt-1 text-xs text-ph-danger", children: action.error || "Could not apply that." }) : null
2595
+ ]
2596
+ },
2597
+ action.id
2598
+ ))
2599
+ ] })
2478
2600
  ]
2479
2601
  },
2480
2602
  `assistant-${index}`
@@ -2579,7 +2701,7 @@ function SuiteAiPanel({
2579
2701
  // src/suite/components/workspace-switcher.tsx
2580
2702
  import { useState as useState8 } from "react";
2581
2703
  import { Building, Check as Check2, NavArrowDown } from "iconoir-react";
2582
- import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2704
+ import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2583
2705
  function SuiteWorkspaceSwitcher({
2584
2706
  workspaces = [],
2585
2707
  groups,
@@ -2620,7 +2742,7 @@ function SuiteWorkspaceSwitcher({
2620
2742
  "aria-expanded": open,
2621
2743
  children: [
2622
2744
  /* @__PURE__ */ jsx18(Building, { className: "h-4 w-4 shrink-0 text-ph-ink", "aria-hidden": true }),
2623
- !collapsed ? /* @__PURE__ */ jsxs16(Fragment5, { children: [
2745
+ !collapsed ? /* @__PURE__ */ jsxs16(Fragment6, { children: [
2624
2746
  /* @__PURE__ */ jsx18("span", { className: "min-w-0 flex-1 truncate text-sm font-semibold text-ph-ink", children: name }),
2625
2747
  /* @__PURE__ */ jsx18(
2626
2748
  NavArrowDown,
@@ -3007,9 +3129,9 @@ function SuiteSettingsMobileNav({
3007
3129
 
3008
3130
  // src/suite/components/suite-sidebar.tsx
3009
3131
  import Link2 from "next/link";
3010
- import { useEffect as useEffect8 } from "react";
3132
+ import { useCallback as useCallback4, useEffect as useEffect8 } from "react";
3011
3133
  import { NavArrowLeft, Sparks as Sparks2 } from "iconoir-react";
3012
- import { Fragment as Fragment6, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
3134
+ import { Fragment as Fragment7, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
3013
3135
  function renderNode(node, collapsed) {
3014
3136
  if (typeof node === "function") return node(collapsed);
3015
3137
  return node;
@@ -3032,30 +3154,37 @@ function SuiteSidebar({
3032
3154
  }) {
3033
3155
  const showAskAi = Boolean(onAskAiOpenChange && askAi);
3034
3156
  const chatOpen = showAskAi && askAiOpen && !collapsed;
3157
+ const setAskAi = useCallback4(
3158
+ (next) => {
3159
+ persistSuiteAskAiOpen(next);
3160
+ onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(next);
3161
+ },
3162
+ [onAskAiOpenChange]
3163
+ );
3035
3164
  useEffect8(() => {
3036
3165
  if (!onAskAiOpenChange) return;
3037
3166
  function onOpen() {
3038
3167
  onRequestExpand == null ? void 0 : onRequestExpand();
3039
- onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(true);
3168
+ setAskAi(true);
3040
3169
  }
3041
3170
  window.addEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
3042
3171
  return () => window.removeEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
3043
- }, [onAskAiOpenChange, onRequestExpand]);
3172
+ }, [onAskAiOpenChange, onRequestExpand, setAskAi]);
3044
3173
  useEffect8(() => {
3045
3174
  if (!chatOpen) return;
3046
3175
  function onKey(event) {
3047
- if (event.key === "Escape") onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(false);
3176
+ if (event.key === "Escape") setAskAi(false);
3048
3177
  }
3049
3178
  window.addEventListener("keydown", onKey);
3050
3179
  return () => window.removeEventListener("keydown", onKey);
3051
- }, [chatOpen, onAskAiOpenChange]);
3180
+ }, [chatOpen, setAskAi]);
3052
3181
  function toggleAskAi() {
3053
3182
  if (askAiOpen) {
3054
- onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(false);
3183
+ setAskAi(false);
3055
3184
  return;
3056
3185
  }
3057
3186
  if (collapsed) onRequestExpand == null ? void 0 : onRequestExpand();
3058
- onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(true);
3187
+ setAskAi(true);
3059
3188
  }
3060
3189
  const askAiButton = /* @__PURE__ */ jsx22(
3061
3190
  "button",
@@ -3097,7 +3226,7 @@ function SuiteSidebar({
3097
3226
  type: "button",
3098
3227
  "data-test": "suite-ask-ai-back",
3099
3228
  "aria-label": "Back to navigation",
3100
- onClick: () => onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(false),
3229
+ onClick: () => setAskAi(false),
3101
3230
  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
3231
  children: /* @__PURE__ */ jsx22(NavArrowLeft, { className: "h-4 w-4", "aria-hidden": true })
3103
3232
  }
@@ -3152,7 +3281,7 @@ function SuiteSidebar({
3152
3281
  {
3153
3282
  type: "button",
3154
3283
  "data-test": "suite-ask-ai-back",
3155
- onClick: () => onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(false),
3284
+ onClick: () => setAskAi(false),
3156
3285
  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
3286
  children: [
3158
3287
  /* @__PURE__ */ jsx22(NavArrowLeft, { className: "h-4 w-4", "aria-hidden": true }),
@@ -3163,7 +3292,7 @@ function SuiteSidebar({
3163
3292
  /* @__PURE__ */ jsx22("span", { className: "truncate text-sm font-semibold text-ph-ink", children: "Shelly AI" })
3164
3293
  ] }),
3165
3294
  /* @__PURE__ */ jsx22("div", { className: "min-h-0 flex-1", children: askAi })
3166
- ] }) : /* @__PURE__ */ jsxs19(Fragment6, { children: [
3295
+ ] }) : /* @__PURE__ */ jsxs19(Fragment7, { children: [
3167
3296
  /* @__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
3297
  var _a;
3169
3298
  const Icon = item.icon;
@@ -3363,8 +3492,10 @@ export {
3363
3492
  SIDEBAR_RAIL_WIDTH,
3364
3493
  SUITE_APPS2 as SUITE_APPS,
3365
3494
  SUITE_APP_MAP,
3495
+ SUITE_ASK_AI_OPEN_KEY,
3366
3496
  SUITE_GLYPHS,
3367
3497
  SUITE_ICON_NAMES,
3498
+ SUITE_MUTATED_EVENT,
3368
3499
  SUITE_OPEN_ASK_AI_EVENT,
3369
3500
  SUITE_SPRINGS,
3370
3501
  SourceAppGlyph,
@@ -3394,6 +3525,7 @@ export {
3394
3525
  SuiteToolbar,
3395
3526
  SuiteUserMenu,
3396
3527
  SuiteWorkspaceSwitcher,
3528
+ TURTLETIME_TIMER_MUTATED_EVENT,
3397
3529
  TodayAskAiCard,
3398
3530
  TodayCalibrating,
3399
3531
  TodayEmptyAction,
@@ -3415,6 +3547,8 @@ export {
3415
3547
  getTodayGreeting,
3416
3548
  m,
3417
3549
  openSuiteAskAi,
3550
+ persistSuiteAskAiOpen,
3551
+ readSuiteAskAiOpen,
3418
3552
  resolveSuiteNotificationHref,
3419
3553
  sidebarColumnWidth,
3420
3554
  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.10",
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",
@@ -3,6 +3,9 @@ import { describe, expect, it, vi } from "vitest";
3
3
  import {
4
4
  consumePendingAskAiPrompt,
5
5
  openSuiteAskAi,
6
+ persistSuiteAskAiOpen,
7
+ readSuiteAskAiOpen,
8
+ SUITE_ASK_AI_OPEN_KEY,
6
9
  SUITE_OPEN_ASK_AI_EVENT,
7
10
  } from "./ai-panel";
8
11
 
@@ -18,3 +21,13 @@ describe("openSuiteAskAi", () => {
18
21
  expect(consumePendingAskAiPrompt()).toBeNull();
19
22
  });
20
23
  });
24
+
25
+ describe("persistSuiteAskAiOpen", () => {
26
+ it("remembers that Shelly was open across a same-tab navigation", () => {
27
+ sessionStorage.removeItem(SUITE_ASK_AI_OPEN_KEY);
28
+ persistSuiteAskAiOpen(true);
29
+ expect(readSuiteAskAiOpen()).toBe(true);
30
+ persistSuiteAskAiOpen(false);
31
+ expect(readSuiteAskAiOpen()).toBe(false);
32
+ });
33
+ });
@@ -31,11 +31,49 @@ import {
31
31
  const SuiteAiBlockNoteMessage = lazy(() => import("./ai-message-blocknote"));
32
32
 
33
33
  export const SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
34
+ export const SUITE_ASK_AI_OPEN_KEY = "shellstack:shelly-open";
35
+ export const SUITE_MUTATED_EVENT = "shellstack:suite-mutated";
36
+ export const TURTLETIME_TIMER_MUTATED_EVENT = "tt-timer-mutated";
34
37
 
35
38
  export type SuiteAskAiOpenDetail = {
36
39
  prompt?: string;
37
40
  };
38
41
 
42
+ export function persistSuiteAskAiOpen(open: boolean): void {
43
+ if (typeof window === "undefined") return;
44
+ try {
45
+ if (open) sessionStorage.setItem(SUITE_ASK_AI_OPEN_KEY, "1");
46
+ else sessionStorage.removeItem(SUITE_ASK_AI_OPEN_KEY);
47
+ } catch {
48
+ // private mode / blocked storage
49
+ }
50
+ }
51
+
52
+ export function readSuiteAskAiOpen(): boolean {
53
+ if (typeof window === "undefined") return false;
54
+ try {
55
+ return sessionStorage.getItem(SUITE_ASK_AI_OPEN_KEY) === "1";
56
+ } catch {
57
+ return false;
58
+ }
59
+ }
60
+
61
+ export type SuiteAiActionStatus =
62
+ | "proposed"
63
+ | "applied"
64
+ | "cancelled"
65
+ | "failed";
66
+
67
+ export interface SuiteAiAction {
68
+ id: string;
69
+ kind: string;
70
+ status: SuiteAiActionStatus;
71
+ summary: string;
72
+ href?: string;
73
+ error?: string;
74
+ payload?: Record<string, unknown>;
75
+ }
76
+
39
77
  let pendingAskAiPrompt: string | null = null;
40
78
 
41
79
  /** Open the suite Ask AI panel from anywhere (Today card, command palette, …). */
@@ -65,6 +103,25 @@ export interface SuiteAiChatMessage {
65
103
  role: "user" | "assistant";
66
104
  content: string;
67
105
  created_at?: string;
106
+ actions?: SuiteAiAction[];
107
+ }
108
+
109
+ function notifySuiteMutated(actions?: SuiteAiAction[]): void {
110
+ if (typeof window === "undefined" || !actions?.length) return;
111
+ const kinds = actions.map((action) => action.kind);
112
+ if (
113
+ kinds.some(
114
+ (kind) =>
115
+ kind.includes("timer") ||
116
+ kind.includes("time") ||
117
+ kind.includes("hours"),
118
+ )
119
+ ) {
120
+ window.dispatchEvent(new Event(TURTLETIME_TIMER_MUTATED_EVENT));
121
+ }
122
+ window.dispatchEvent(
123
+ new CustomEvent(SUITE_MUTATED_EVENT, { detail: { kinds } }),
124
+ );
68
125
  }
69
126
 
70
127
  /**
@@ -84,6 +141,8 @@ export function SuiteAiPanel({
84
141
  onOpenChange,
85
142
  suiteAppBases,
86
143
  onSwitchApp,
144
+ onSameAppNavigate,
145
+ resolveAction,
87
146
  }: {
88
147
  presets?: SuiteAiPreset[];
89
148
  emptyState?: ReactNode;
@@ -105,12 +164,20 @@ export function SuiteAiPanel({
105
164
  suiteAppBases?: Partial<Record<SuiteAppSlug, string>>;
106
165
  /** Cross-app SSO. Same-app links just navigate to `path`. */
107
166
  onSwitchApp?: (app: SuiteAppSlug, path: string) => void;
167
+ /** Client-side same-app route change — keeps Shelly mounted. */
168
+ onSameAppNavigate?: (path: string) => void;
169
+ resolveAction?: (
170
+ id: string,
171
+ decision: "confirm" | "cancel",
172
+ ) => Promise<{ messages: SuiteAiChatMessage[] }>;
108
173
  }) {
109
174
  const inputId = useId();
110
175
  const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
176
+ const [resolvingId, setResolvingId] = useState("");
111
177
  const open = openProp ?? uncontrolledOpen;
112
178
  const setOpen = useCallback(
113
179
  (next: boolean) => {
180
+ persistSuiteAskAiOpen(next);
114
181
  if (openProp === undefined) setUncontrolledOpen(next);
115
182
  onOpenChange?.(next);
116
183
  },
@@ -215,7 +282,11 @@ export function SuiteAiPanel({
215
282
  signal: controller.signal,
216
283
  });
217
284
  if (controller.signal.aborted) return;
218
- setMessages(result.messages ?? []);
285
+ const nextMessages = result.messages ?? [];
286
+ setMessages(nextMessages);
287
+ notifySuiteMutated(
288
+ nextMessages.flatMap((message) => message.actions ?? []),
289
+ );
219
290
  } catch (err) {
220
291
  if (controller.signal.aborted) return;
221
292
  setMessages((prev) =>
@@ -264,6 +335,29 @@ export function SuiteAiPanel({
264
335
  }
265
336
  }
266
337
 
338
+ async function handleResolve(
339
+ actionId: string,
340
+ decision: "confirm" | "cancel",
341
+ ) {
342
+ if (!resolveAction || resolvingId) return;
343
+ setResolvingId(actionId);
344
+ setError("");
345
+ try {
346
+ const result = await resolveAction(actionId, decision);
347
+ const nextMessages = result.messages ?? [];
348
+ setMessages(nextMessages);
349
+ notifySuiteMutated(
350
+ nextMessages.flatMap((message) => message.actions ?? []),
351
+ );
352
+ } catch (err) {
353
+ setError(
354
+ err instanceof Error ? err.message : "Could not apply that action.",
355
+ );
356
+ } finally {
357
+ setResolvingId("");
358
+ }
359
+ }
360
+
267
361
  function onConversationClick(event: ReactMouseEvent<HTMLDivElement>) {
268
362
  if (event.defaultPrevented) return;
269
363
  if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
@@ -280,8 +374,10 @@ export function SuiteAiPanel({
280
374
  );
281
375
  if (!match) return;
282
376
  event.preventDefault();
377
+ persistSuiteAskAiOpen(true);
283
378
  if (match.sameApp) {
284
- window.location.assign(match.path);
379
+ if (onSameAppNavigate) onSameAppNavigate(match.path);
380
+ else window.location.assign(match.path);
285
381
  return;
286
382
  }
287
383
  if (onSwitchApp) onSwitchApp(match.slug, match.path);
@@ -391,16 +487,74 @@ export function SuiteAiPanel({
391
487
  >
392
488
  <Icon className="h-3.5 w-3.5" />
393
489
  </span>
394
- <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">
395
- <Suspense
396
- fallback={
397
- <p className="whitespace-pre-wrap break-words">
398
- {message.content}
399
- </p>
400
- }
401
- >
402
- <SuiteAiBlockNoteMessage markdown={message.content} />
403
- </Suspense>
490
+ <div className="min-w-0 max-w-[85%] space-y-2">
491
+ <div className="rounded-2xl rounded-bl-md bg-ph-muted px-3 py-2 text-sm leading-relaxed text-ph-ink">
492
+ <Suspense
493
+ fallback={
494
+ <p className="whitespace-pre-wrap break-words">
495
+ {message.content}
496
+ </p>
497
+ }
498
+ >
499
+ <SuiteAiBlockNoteMessage markdown={message.content} />
500
+ </Suspense>
501
+ </div>
502
+ {(message.actions ?? []).map((action) => (
503
+ <div
504
+ key={action.id}
505
+ data-test="ask-ai-action"
506
+ data-status={action.status}
507
+ className="rounded-xl border border-ph-border bg-ph-surface px-3 py-2 text-sm text-ph-ink"
508
+ >
509
+ <p className="font-medium">{action.summary}</p>
510
+ {action.status === "proposed" ? (
511
+ <div className="mt-2 flex flex-wrap gap-2">
512
+ <button
513
+ type="button"
514
+ data-test="ask-ai-action-confirm"
515
+ disabled={Boolean(resolvingId) || !resolveAction}
516
+ onClick={() => void handleResolve(action.id, "confirm")}
517
+ 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"
518
+ >
519
+ {resolvingId === action.id ? "Working…" : "Confirm"}
520
+ </button>
521
+ <button
522
+ type="button"
523
+ data-test="ask-ai-action-cancel"
524
+ disabled={Boolean(resolvingId) || !resolveAction}
525
+ onClick={() => void handleResolve(action.id, "cancel")}
526
+ 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"
527
+ >
528
+ Cancel
529
+ </button>
530
+ </div>
531
+ ) : null}
532
+ {action.status === "applied" ? (
533
+ <p className="mt-1 text-xs text-ph-subtle">
534
+ Done
535
+ {action.href ? (
536
+ <>
537
+ {" · "}
538
+ <a
539
+ href={action.href}
540
+ className="text-ph-brand underline-offset-2 hover:underline"
541
+ >
542
+ Open
543
+ </a>
544
+ </>
545
+ ) : null}
546
+ </p>
547
+ ) : null}
548
+ {action.status === "cancelled" ? (
549
+ <p className="mt-1 text-xs text-ph-subtle">Cancelled</p>
550
+ ) : null}
551
+ {action.status === "failed" ? (
552
+ <p className="mt-1 text-xs text-ph-danger">
553
+ {action.error || "Could not apply that."}
554
+ </p>
555
+ ) : null}
556
+ </div>
557
+ ))}
404
558
  </div>
405
559
  </div>
406
560
  );
@@ -1,12 +1,15 @@
1
1
  "use client";
2
2
 
3
3
  import Link from "next/link";
4
- import { useEffect, type ReactNode } from "react";
4
+ import { useCallback, useEffect, type ReactNode } from "react";
5
5
  import { NavArrowLeft, Sparks } from "iconoir-react";
6
6
 
7
7
  import { Tooltip } from "../../components/ui/Tooltip";
8
8
  import { cn } from "../lib/cn";
9
- import { SUITE_OPEN_ASK_AI_EVENT } from "./ai-panel";
9
+ import {
10
+ persistSuiteAskAiOpen,
11
+ SUITE_OPEN_ASK_AI_EVENT,
12
+ } from "./ai-panel";
10
13
  import type { SuiteNavIcon } from "./suite-bottom-nav";
11
14
 
12
15
  type CollapsedNode = ReactNode | ((collapsed: boolean) => ReactNode);
@@ -84,32 +87,40 @@ export function SuiteSidebar({
84
87
  const showAskAi = Boolean(onAskAiOpenChange && askAi);
85
88
  const chatOpen = showAskAi && askAiOpen && !collapsed;
86
89
 
90
+ const setAskAi = useCallback(
91
+ (next: boolean) => {
92
+ persistSuiteAskAiOpen(next);
93
+ onAskAiOpenChange?.(next);
94
+ },
95
+ [onAskAiOpenChange],
96
+ );
97
+
87
98
  useEffect(() => {
88
99
  if (!onAskAiOpenChange) return;
89
100
  function onOpen() {
90
101
  onRequestExpand?.();
91
- onAskAiOpenChange?.(true);
102
+ setAskAi(true);
92
103
  }
93
104
  window.addEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
94
105
  return () => window.removeEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
95
- }, [onAskAiOpenChange, onRequestExpand]);
106
+ }, [onAskAiOpenChange, onRequestExpand, setAskAi]);
96
107
 
97
108
  useEffect(() => {
98
109
  if (!chatOpen) return;
99
110
  function onKey(event: KeyboardEvent) {
100
- if (event.key === "Escape") onAskAiOpenChange?.(false);
111
+ if (event.key === "Escape") setAskAi(false);
101
112
  }
102
113
  window.addEventListener("keydown", onKey);
103
114
  return () => window.removeEventListener("keydown", onKey);
104
- }, [chatOpen, onAskAiOpenChange]);
115
+ }, [chatOpen, setAskAi]);
105
116
 
106
117
  function toggleAskAi() {
107
118
  if (askAiOpen) {
108
- onAskAiOpenChange?.(false);
119
+ setAskAi(false);
109
120
  return;
110
121
  }
111
122
  if (collapsed) onRequestExpand?.();
112
- onAskAiOpenChange?.(true);
123
+ setAskAi(true);
113
124
  }
114
125
 
115
126
  const askAiButton = (
@@ -151,7 +162,7 @@ export function SuiteSidebar({
151
162
  type="button"
152
163
  data-test="suite-ask-ai-back"
153
164
  aria-label="Back to navigation"
154
- onClick={() => onAskAiOpenChange?.(false)}
165
+ onClick={() => setAskAi(false)}
155
166
  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"
156
167
  >
157
168
  <NavArrowLeft className="h-4 w-4" aria-hidden />
@@ -213,7 +224,7 @@ export function SuiteSidebar({
213
224
  <button
214
225
  type="button"
215
226
  data-test="suite-ask-ai-back"
216
- onClick={() => onAskAiOpenChange?.(false)}
227
+ onClick={() => setAskAi(false)}
217
228
  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"
218
229
  >
219
230
  <NavArrowLeft className="h-4 w-4" aria-hidden />
@@ -53,7 +53,14 @@ export {
53
53
  SuiteAiPanel,
54
54
  openSuiteAskAi,
55
55
  consumePendingAskAiPrompt,
56
+ persistSuiteAskAiOpen,
57
+ readSuiteAskAiOpen,
56
58
  SUITE_OPEN_ASK_AI_EVENT,
59
+ SUITE_ASK_AI_OPEN_KEY,
60
+ SUITE_MUTATED_EVENT,
61
+ TURTLETIME_TIMER_MUTATED_EVENT,
62
+ type SuiteAiAction,
63
+ type SuiteAiActionStatus,
57
64
  type SuiteAiPreset,
58
65
  type SuiteAiChatMessage,
59
66
  type SuiteAskAiOpenDetail,