@xenide-io/the-old-ui-theme 0.4.4 → 0.4.8

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.
Files changed (76) hide show
  1. package/dist/{chunk-54ZR4VL5.mjs → chunk-RZXHZWUB.mjs} +44 -17
  2. package/dist/{index-B5NOyoKS.d.mts → index-Od4pIP4F.d.mts} +7 -1
  3. package/dist/{index-B5NOyoKS.d.ts → index-Od4pIP4F.d.ts} +7 -1
  4. package/dist/index.d.mts +2 -2
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +37 -10
  7. package/dist/index.mjs +1 -1
  8. package/dist/suite.d.mts +152 -41
  9. package/dist/suite.d.ts +152 -41
  10. package/dist/suite.js +1366 -671
  11. package/dist/suite.mjs +1304 -610
  12. package/dist/ui.d.mts +1 -1
  13. package/dist/ui.d.ts +1 -1
  14. package/dist/ui.js +37 -10
  15. package/dist/ui.mjs +1 -1
  16. package/package.json +7 -3
  17. package/src/components/ui/Modal.tsx +70 -26
  18. package/src/styles/suite-skin.css +284 -24
  19. package/src/suite/components/ai-panel.test.ts +20 -0
  20. package/src/suite/components/ai-panel.tsx +494 -103
  21. package/src/suite/components/suite-app-layout.tsx +48 -28
  22. package/src/suite/components/suite-layout.tsx +84 -40
  23. package/src/suite/components/suite-mobile-drawer.tsx +35 -22
  24. package/src/suite/components/suite-notification-bell.tsx +9 -3
  25. package/src/suite/components/suite-settings-layout.tsx +82 -0
  26. package/src/suite/components/suite-settings-mobile-nav.tsx +18 -17
  27. package/src/suite/components/suite-skeleton.tsx +3 -3
  28. package/src/suite/components/today-calibrating.tsx +11 -35
  29. package/src/suite/components/today-page-frame.tsx +19 -11
  30. package/src/suite/components/today-ui.tsx +276 -19
  31. package/src/suite/index.ts +41 -25
  32. package/src/suite/lib/apps.ts +32 -2
  33. package/src/suite/lib/use-sidebar-width.ts +114 -0
  34. package/src/components/sections/AccordionShowcase.tsx +0 -45
  35. package/src/components/sections/AlertShowcase.tsx +0 -40
  36. package/src/components/sections/AvatarShowcase.tsx +0 -80
  37. package/src/components/sections/BadgeShowcase.tsx +0 -65
  38. package/src/components/sections/ButtonShowcase.tsx +0 -308
  39. package/src/components/sections/CalendarShowcase.tsx +0 -35
  40. package/src/components/sections/CardShowcase.tsx +0 -159
  41. package/src/components/sections/CodeMockupShowcase.tsx +0 -56
  42. package/src/components/sections/ColorPalette.tsx +0 -117
  43. package/src/components/sections/CommandPaletteShowcase.tsx +0 -48
  44. package/src/components/sections/CountdownShowcase.tsx +0 -43
  45. package/src/components/sections/DiffShowcase.tsx +0 -70
  46. package/src/components/sections/DrawerShowcase.tsx +0 -97
  47. package/src/components/sections/DropdownShowcase.tsx +0 -98
  48. package/src/components/sections/EmptyStateShowcase.tsx +0 -50
  49. package/src/components/sections/FilterChipsShowcase.tsx +0 -98
  50. package/src/components/sections/FormShowcase.tsx +0 -127
  51. package/src/components/sections/HoverCardShowcase.tsx +0 -50
  52. package/src/components/sections/IconShowcase.tsx +0 -121
  53. package/src/components/sections/IndicatorShowcase.tsx +0 -52
  54. package/src/components/sections/KbdShowcase.tsx +0 -57
  55. package/src/components/sections/ModalShowcase.tsx +0 -211
  56. package/src/components/sections/NavigationShowcase.tsx +0 -199
  57. package/src/components/sections/NewComponentsShowcase.tsx +0 -1052
  58. package/src/components/sections/ProductLayoutsShowcase.tsx +0 -78
  59. package/src/components/sections/ProgressShowcase.tsx +0 -82
  60. package/src/components/sections/QuillChartShowcase.tsx +0 -92
  61. package/src/components/sections/QuillDashboardShowcase.tsx +0 -149
  62. package/src/components/sections/SegmentedControlShowcase.tsx +0 -49
  63. package/src/components/sections/SetupThemeShowcase.tsx +0 -262
  64. package/src/components/sections/SidebarShowcase.tsx +0 -152
  65. package/src/components/sections/StackShowcase.tsx +0 -33
  66. package/src/components/sections/StepperShowcase.tsx +0 -37
  67. package/src/components/sections/SuiteShowcase.tsx +0 -669
  68. package/src/components/sections/SwapShowcase.tsx +0 -99
  69. package/src/components/sections/TabShowcase.tsx +0 -84
  70. package/src/components/sections/TableShowcase.tsx +0 -142
  71. package/src/components/sections/TimelineShowcase.tsx +0 -83
  72. package/src/components/sections/ToastShowcase.tsx +0 -108
  73. package/src/components/sections/TooltipShowcase.tsx +0 -38
  74. package/src/components/sections/UtilityShowcase.tsx +0 -81
  75. package/src/styles/excel-themes.css +0 -110
  76. package/src/styles/lemon-primitives.css +0 -550
package/dist/suite.mjs CHANGED
@@ -15,24 +15,32 @@ function TodayPageFrame({ children }) {
15
15
  "div",
16
16
  {
17
17
  "data-test": "today-page",
18
- className: "today-page-frame relative flex min-h-0 flex-1 flex-col overflow-y-auto overflow-x-hidden",
18
+ className: "today-page-frame relative flex min-h-0 w-full flex-1 flex-col overflow-x-hidden",
19
19
  children: [
20
20
  /* @__PURE__ */ jsxs(
21
21
  "div",
22
22
  {
23
- className: "pointer-events-none absolute inset-x-0 top-0 h-[34rem] overflow-hidden",
23
+ className: "pointer-events-none absolute inset-0 overflow-hidden",
24
24
  "aria-hidden": true,
25
25
  children: [
26
26
  /* @__PURE__ */ jsx("div", { className: "today-page-frame__glow absolute inset-0" }),
27
- /* @__PURE__ */ jsx("div", { className: "today-page-frame__sun absolute -right-16 -top-16 h-72 w-72 rounded-full bg-gradient-to-br from-amber-300/30 via-rose-300/20 to-rose-400/10 blur-3xl opacity-60 motion-safe:animate-[pulse_8s_ease-in-out_infinite] motion-reduce:opacity-40" }),
28
- /* @__PURE__ */ jsx("div", { className: "today-page-frame__top-fade absolute inset-x-0 top-0 h-64" }),
29
- /* @__PURE__ */ jsx("div", { className: "today-page-frame__cloud today-page-frame__cloud--1 absolute -left-24 top-28 h-80 w-[26rem] rounded-[45%]" }),
30
- /* @__PURE__ */ jsx("div", { className: "today-page-frame__cloud today-page-frame__cloud--2 absolute -right-20 top-40 h-72 w-80 rounded-[40%]" }),
31
- /* @__PURE__ */ jsx("div", { className: "today-page-frame__cloud today-page-frame__cloud--3 absolute left-[28%] top-[58%] h-64 w-[22rem] rounded-[50%]" })
27
+ /* @__PURE__ */ jsx("div", { className: "today-page-frame__top-fade absolute inset-x-0 top-0 h-[28rem]" }),
28
+ /* @__PURE__ */ jsx("div", { className: "today-page-frame__cloud today-page-frame__cloud--1 absolute -left-28 top-16 h-96 w-[30rem] rounded-[45%]" }),
29
+ /* @__PURE__ */ jsx("div", { className: "today-page-frame__cloud today-page-frame__cloud--2 absolute -right-24 top-24 h-80 w-[22rem] rounded-[40%]" }),
30
+ /* @__PURE__ */ jsx("div", { className: "today-page-frame__cloud today-page-frame__cloud--3 absolute left-[22%] top-48 h-72 w-[24rem] rounded-[50%]" }),
31
+ /* @__PURE__ */ jsx("div", { className: "today-page-frame__cloud today-page-frame__cloud--4 absolute right-[18%] top-[42%] h-64 w-[20rem] rounded-[48%]" }),
32
+ /* @__PURE__ */ jsx("div", { className: "today-page-frame__cloud today-page-frame__cloud--5 absolute left-[8%] top-[68%] h-56 w-[18rem] rounded-[42%]" })
32
33
  ]
33
34
  }
34
35
  ),
35
- /* @__PURE__ */ jsx("div", { className: "relative flex-1 px-4 pb-5 pt-2 sm:px-6 sm:pb-6 sm:pt-3 lg:px-8 [&_.list-row]:min-h-[52px] [&_.list-row]:sm:min-h-[44px]", children })
36
+ /* @__PURE__ */ jsx(
37
+ "div",
38
+ {
39
+ "data-width": "full",
40
+ className: "suite-page-stage suite-page-inset relative z-[1] flex w-full min-w-0 flex-1 flex-col [&_.list-row]:min-h-[52px] [&_.list-row]:sm:min-h-[44px]",
41
+ children
42
+ }
43
+ )
36
44
  ]
37
45
  }
38
46
  );
@@ -392,7 +400,7 @@ function SuiteIcon({
392
400
  }
393
401
 
394
402
  // src/suite/components/today-ui.tsx
395
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
403
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
396
404
  var APP_META = {
397
405
  shellstack: { name: "ShellStack" },
398
406
  tides: { name: "Tides" },
@@ -485,15 +493,15 @@ function AnimatedSun({ className }) {
485
493
  /* @__PURE__ */ jsx5("stop", { stopColor: "#f59e0b" }),
486
494
  /* @__PURE__ */ jsx5("stop", { offset: "1", stopColor: "#fb923c" })
487
495
  ] }) }),
488
- /* @__PURE__ */ jsx5("g", { className: "origin-center motion-safe:animate-[spin_20s_linear_infinite] motion-reduce:animate-none", children: [0, 45, 90, 135, 180, 225, 270, 315].map((angle) => /* @__PURE__ */ jsx5(
496
+ /* @__PURE__ */ jsx5("g", { className: "origin-center opacity-80 motion-safe:animate-[pulse_8s_ease-in-out_infinite] motion-reduce:animate-none", children: [0, 45, 90, 135, 180, 225, 270, 315].map((angle) => /* @__PURE__ */ jsx5(
489
497
  "line",
490
498
  {
491
499
  x1: "20",
492
- y1: "4",
500
+ y1: "5",
493
501
  x2: "20",
494
502
  y2: "9",
495
503
  stroke: "#fbbf24",
496
- strokeWidth: "2",
504
+ strokeWidth: "1.75",
497
505
  strokeLinecap: "round",
498
506
  transform: `rotate(${angle} 20 20)`
499
507
  },
@@ -527,10 +535,9 @@ function AnimatedMoon({ className }) {
527
535
  fill: "url(#animatedMoonBody)"
528
536
  }
529
537
  ),
530
- /* @__PURE__ */ jsxs5("g", { className: "motion-safe:animate-[pulse_4s_ease-in-out_infinite] motion-reduce:animate-none", children: [
531
- /* @__PURE__ */ jsx5("circle", { cx: "29", cy: "10", r: "1.4", fill: "#c7d2fe" }),
532
- /* @__PURE__ */ jsx5("circle", { cx: "34", cy: "16", r: "1", fill: "#c7d2fe" }),
533
- /* @__PURE__ */ jsx5("circle", { cx: "25", cy: "5.5", r: "0.9", fill: "#c7d2fe" })
538
+ /* @__PURE__ */ jsxs5("g", { className: "opacity-70 motion-safe:animate-[pulse_10s_ease-in-out_infinite] motion-reduce:animate-none", children: [
539
+ /* @__PURE__ */ jsx5("circle", { cx: "29", cy: "10", r: "1.2", fill: "#c7d2fe" }),
540
+ /* @__PURE__ */ jsx5("circle", { cx: "34", cy: "16", r: "0.9", fill: "#c7d2fe" })
534
541
  ] })
535
542
  ]
536
543
  }
@@ -592,30 +599,193 @@ function getTodayGreeting(user, stats) {
592
599
  const overdueCount = (_d = stats == null ? void 0 : stats.overdueCount) != null ? _d : 0;
593
600
  const hours = (_e = stats == null ? void 0 : stats.hours) != null ? _e : 0;
594
601
  if (overdueCount > 0) {
595
- return `${greeting}. You have ${taskCount} task${taskCount === 1 ? "" : "s"} due today, ${overdueCount} overdue.`;
602
+ return `${greeting}. ${taskCount} task${taskCount === 1 ? "" : "s"} due today, ${overdueCount} overdue.`;
596
603
  }
597
604
  if (taskCount > 0) {
598
- return `${greeting}. You have ${taskCount} task${taskCount === 1 ? "" : "s"} due today.`;
605
+ return `${greeting}. ${taskCount} task${taskCount === 1 ? "" : "s"} due today.`;
599
606
  }
600
607
  if (hours > 0) {
601
- return `${greeting}. You logged ${hours} hour${hours === 1 ? "" : "s"} today.`;
608
+ return `${greeting}. ${hours} hour${hours === 1 ? "" : "s"} logged today.`;
602
609
  }
603
610
  if (period === "evening" || period === "night") {
604
- return `${greeting}. Time to wrap up.`;
611
+ return `${greeting}. Nice work \u2014 time to wrap up.`;
605
612
  }
606
- return `${greeting}. Nothing overdue \u2014 take a nice, deep breath.`;
613
+ return `${greeting}. Clear queue \u2014 take a nice, deep breath.`;
607
614
  }
608
615
  function TodayHero({
609
- message = "Take a nice, deep breath."
616
+ message = "Take a nice, deep breath.",
617
+ brief,
618
+ briefMeta
619
+ }) {
620
+ return /* @__PURE__ */ jsxs5("div", { className: "mt-4 sm:mt-6", children: [
621
+ /* @__PURE__ */ jsx5(
622
+ "p",
623
+ {
624
+ className: "today-hero max-w-2xl font-display text-lg font-semibold tracking-tight text-ph-ink sm:text-xl sm:leading-snug",
625
+ children: message
626
+ },
627
+ typeof message === "string" ? message : "hero"
628
+ ),
629
+ brief ? /* @__PURE__ */ jsxs5("div", { className: "mt-4 max-w-2xl", "data-test": "today-focus-brief", children: [
630
+ /* @__PURE__ */ jsx5("p", { className: "text-[15px] leading-[1.65] text-ph-ink", children: brief }),
631
+ briefMeta ? /* @__PURE__ */ jsx5("p", { className: "mt-1.5 text-xs text-ph-mutedtext", children: briefMeta }) : null
632
+ ] }) : null
633
+ ] });
634
+ }
635
+ function TodayPrimaryAction({
636
+ label,
637
+ description,
638
+ icon,
639
+ accent = "neutral",
640
+ onClick,
641
+ href,
642
+ loading = false,
643
+ active = false,
644
+ className,
645
+ dataTest = "today-primary-action"
610
646
  }) {
611
- return /* @__PURE__ */ jsx5("div", { className: "mt-8 text-center sm:mt-10", children: /* @__PURE__ */ jsx5(
612
- "p",
647
+ const classes = cn(
648
+ "today-primary-action group relative flex w-full max-w-md items-center gap-3 overflow-hidden rounded-xl px-3.5 py-2.5 text-left transition sm:px-4 sm:py-3",
649
+ `today-primary-action--${accent}`,
650
+ active && "today-primary-action--active",
651
+ loading && "pointer-events-none opacity-70",
652
+ className
653
+ );
654
+ const body = /* @__PURE__ */ jsxs5(Fragment2, { children: [
655
+ /* @__PURE__ */ jsx5("span", { className: "today-primary-action__icon inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-lg sm:h-10 sm:w-10", children: icon }),
656
+ /* @__PURE__ */ jsxs5("span", { className: "min-w-0 flex-1", children: [
657
+ /* @__PURE__ */ jsx5("span", { className: "block text-sm font-semibold tracking-tight sm:text-base", children: label }),
658
+ description ? /* @__PURE__ */ jsx5("span", { className: "mt-0.5 block text-xs opacity-80 sm:text-sm", children: description }) : null
659
+ ] }),
660
+ /* @__PURE__ */ jsx5(
661
+ "span",
662
+ {
663
+ className: "today-primary-action__chevron shrink-0 text-base opacity-60 transition group-hover:translate-x-0.5 group-hover:opacity-100",
664
+ "aria-hidden": true,
665
+ children: "\u2192"
666
+ }
667
+ )
668
+ ] });
669
+ if (href && !onClick) {
670
+ return /* @__PURE__ */ jsx5(
671
+ "a",
672
+ {
673
+ href,
674
+ className: classes,
675
+ "data-test": dataTest,
676
+ "aria-busy": loading,
677
+ children: body
678
+ }
679
+ );
680
+ }
681
+ return /* @__PURE__ */ jsx5(
682
+ "button",
613
683
  {
614
- className: "today-hero font-display text-2xl font-bold tracking-tight text-ph-ink sm:text-[1.75rem] sm:leading-tight",
615
- children: message
616
- },
617
- typeof message === "string" ? message : "hero"
618
- ) });
684
+ type: "button",
685
+ onClick,
686
+ disabled: loading,
687
+ className: classes,
688
+ "data-test": dataTest,
689
+ "aria-busy": loading,
690
+ children: body
691
+ }
692
+ );
693
+ }
694
+ function TodayEmptyAction({
695
+ title,
696
+ description,
697
+ actionLabel,
698
+ href,
699
+ onClick,
700
+ dataTest
701
+ }) {
702
+ return /* @__PURE__ */ jsxs5(
703
+ "div",
704
+ {
705
+ className: "rounded-lg border border-dashed border-ph-border/80 bg-ph-muted/30 px-4 py-4 text-center",
706
+ "data-test": dataTest,
707
+ children: [
708
+ /* @__PURE__ */ jsx5("p", { className: "text-sm font-semibold text-ph-ink", children: title }),
709
+ description ? /* @__PURE__ */ jsx5("p", { className: "mx-auto mt-1 max-w-sm text-xs leading-relaxed text-ph-subtle sm:text-sm", children: description }) : null,
710
+ actionLabel && (href || onClick) ? href ? /* @__PURE__ */ jsx5(
711
+ "a",
712
+ {
713
+ href,
714
+ className: "mt-2.5 inline-flex text-sm font-semibold text-ph-brand hover:underline",
715
+ children: actionLabel
716
+ }
717
+ ) : /* @__PURE__ */ jsx5(
718
+ "button",
719
+ {
720
+ type: "button",
721
+ onClick,
722
+ className: "mt-2.5 inline-flex text-sm font-semibold text-ph-brand hover:underline",
723
+ children: actionLabel
724
+ }
725
+ ) : null
726
+ ]
727
+ }
728
+ );
729
+ }
730
+ function TodayLayout({
731
+ primary,
732
+ main,
733
+ aside,
734
+ className
735
+ }) {
736
+ return /* @__PURE__ */ jsxs5(
737
+ "div",
738
+ {
739
+ className: cn("mt-5 space-y-6 sm:mt-6", className),
740
+ "data-test": "today-layout",
741
+ children: [
742
+ primary ? /* @__PURE__ */ jsx5("div", { className: "max-w-md", children: primary }) : null,
743
+ /* @__PURE__ */ jsxs5("div", { className: "grid gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(16rem,20rem)] lg:items-start lg:gap-8", children: [
744
+ /* @__PURE__ */ jsx5("div", { className: "min-w-0 space-y-8", children: main }),
745
+ aside ? /* @__PURE__ */ jsx5("aside", { className: "space-y-3 lg:sticky lg:top-6 lg:self-start", children: aside }) : null
746
+ ] })
747
+ ]
748
+ }
749
+ );
750
+ }
751
+ function TodayAskAiCard({
752
+ title = "Ask AI",
753
+ description = "Ask about your workspace, draft something, or look a fact up online.",
754
+ cta = "Ask",
755
+ className,
756
+ onClick,
757
+ href
758
+ }) {
759
+ const classes = cn(
760
+ "group flex w-full items-start gap-3.5 rounded-2xl border border-ph-border/70 bg-ph-surface px-4 py-3.5 text-left shadow-[0_1px_12px_-8px_rgba(15,23,42,0.1)] transition hover:border-ph-brand/35 hover:bg-[color-mix(in_oklab,var(--ph-accent)_6%,var(--ph-surface))] sm:px-5",
761
+ className
762
+ );
763
+ const body = /* @__PURE__ */ jsxs5(Fragment2, { children: [
764
+ /* @__PURE__ */ jsx5("span", { className: "inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl bg-ph-brand/15 text-ph-brand shadow-sm ring-1 ring-black/[0.06]", children: /* @__PURE__ */ jsx5(SuiteIcon, { name: "stack-hex", accent: "shellstack", className: "h-5 w-5" }) }),
765
+ /* @__PURE__ */ jsxs5("span", { className: "min-w-0 flex-1", children: [
766
+ /* @__PURE__ */ jsxs5("span", { className: "flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1", children: [
767
+ /* @__PURE__ */ jsx5("span", { className: "font-display text-sm font-bold tracking-tight text-ph-ink", children: title }),
768
+ /* @__PURE__ */ jsx5("span", { className: "text-sm font-medium text-ph-brand transition group-hover:underline", children: cta })
769
+ ] }),
770
+ /* @__PURE__ */ jsx5("span", { className: "mt-1 block text-sm leading-relaxed text-ph-subtle", children: description })
771
+ ] })
772
+ ] });
773
+ if (onClick) {
774
+ return /* @__PURE__ */ jsx5(
775
+ "button",
776
+ {
777
+ type: "button",
778
+ onClick,
779
+ className: classes,
780
+ "data-test": "today-ask-ai",
781
+ children: body
782
+ }
783
+ );
784
+ }
785
+ if (href) {
786
+ return /* @__PURE__ */ jsx5("a", { href, className: classes, "data-test": "today-ask-ai", children: body });
787
+ }
788
+ return /* @__PURE__ */ jsx5("div", { className: classes, "data-test": "today-ask-ai", children: body });
619
789
  }
620
790
  function TodaySection({
621
791
  icon: Icon,
@@ -626,7 +796,7 @@ function TodaySection({
626
796
  testId,
627
797
  children
628
798
  }) {
629
- return /* @__PURE__ */ jsxs5("section", { "data-test": testId, className: "space-y-4", children: [
799
+ return /* @__PURE__ */ jsxs5("section", { "data-test": testId, className: "space-y-5", children: [
630
800
  /* @__PURE__ */ jsxs5("div", { className: "flex items-start justify-between gap-4", children: [
631
801
  /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 items-start gap-3.5", children: [
632
802
  /* @__PURE__ */ jsx5(
@@ -657,8 +827,7 @@ function TodayPanel({
657
827
  "div",
658
828
  {
659
829
  className: cn(
660
- "border-ph-border/70 rounded-2xl border bg-ph-surface p-6 shadow-[0_2px_28px_-10px_rgba(15,23,42,0.14)] transition-all duration-200 ease-out sm:p-6",
661
- "hover:shadow-[0_8px_32px_-12px_rgba(15,23,42,0.18)] hover:-translate-y-0.5 motion-reduce:transition-none motion-reduce:hover:transform-none",
830
+ "border-ph-border/70 rounded-2xl border bg-ph-surface p-5 shadow-[0_1px_16px_-8px_rgba(15,23,42,0.12)] sm:p-5",
662
831
  className
663
832
  ),
664
833
  children
@@ -836,6 +1005,70 @@ function AppSwitcher({
836
1005
  import { useCallback, useEffect, useState as useState2 } from "react";
837
1006
  import { useRouter } from "next/navigation";
838
1007
  import { Bell } from "iconoir-react";
1008
+
1009
+ // src/suite/lib/apps.ts
1010
+ var SUITE_APPS2 = [
1011
+ {
1012
+ slug: "shellstack",
1013
+ name: "ShellStack",
1014
+ description: "Organisation and billing",
1015
+ icon: "/icon-shellstack.svg",
1016
+ landing: "/home",
1017
+ baseUrlEnv: "NEXT_PUBLIC_SHELLSTACK_URL",
1018
+ baseUrlFallback: "http://localhost:3002"
1019
+ },
1020
+ {
1021
+ slug: "tides",
1022
+ name: "Tides",
1023
+ description: "Projects and tasks",
1024
+ icon: "/tides-icon.svg",
1025
+ landing: "/today",
1026
+ baseUrlEnv: "NEXT_PUBLIC_TIDES_URL",
1027
+ baseUrlFallback: "http://localhost:3001"
1028
+ },
1029
+ {
1030
+ slug: "turtletime",
1031
+ name: "TurtleTime",
1032
+ description: "Time tracking",
1033
+ icon: "/turtletime-icon.svg",
1034
+ landing: "/today",
1035
+ baseUrlEnv: "NEXT_PUBLIC_TURTLETIME_URL",
1036
+ baseUrlFallback: "http://localhost:3000"
1037
+ },
1038
+ {
1039
+ slug: "kraken",
1040
+ name: "Kraken",
1041
+ description: "Documents and knowledge",
1042
+ icon: "/kraken-icon.svg",
1043
+ landing: "/today",
1044
+ baseUrlEnv: "NEXT_PUBLIC_KRAKEN_URL",
1045
+ baseUrlFallback: "http://localhost:3003"
1046
+ }
1047
+ ];
1048
+ var SUITE_APP_MAP = Object.fromEntries(SUITE_APPS2.map((app) => [app.slug, app]));
1049
+ function suiteAppBaseUrl(slug) {
1050
+ const def = SUITE_APP_MAP[slug];
1051
+ const fromEnv = typeof process !== "undefined" ? process.env[def.baseUrlEnv] : void 0;
1052
+ return (fromEnv || def.baseUrlFallback).replace(/\/$/, "");
1053
+ }
1054
+ var SOURCE_APP_SLUG = {
1055
+ turtletime: "turtletime",
1056
+ tides: "tides",
1057
+ kraken: "kraken",
1058
+ portal: "shellstack",
1059
+ shellstack: "shellstack"
1060
+ };
1061
+ function resolveSuiteNotificationHref(href, sourceApp) {
1062
+ const value = (href || "").trim();
1063
+ if (!value) return "";
1064
+ if (/^(https?:|mailto:|\/\/)/i.test(value)) return value;
1065
+ const path = value.startsWith("/") ? value : `/${value}`;
1066
+ const slug = SOURCE_APP_SLUG[(sourceApp || "").toLowerCase()];
1067
+ if (!slug) return path;
1068
+ return `${suiteAppBaseUrl(slug)}${path}`;
1069
+ }
1070
+
1071
+ // src/suite/components/suite-notification-bell.tsx
839
1072
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
840
1073
  var POLL_MS = 6e4;
841
1074
  function timeAgo(iso) {
@@ -926,9 +1159,13 @@ function SuiteNotificationBell({
926
1159
  } catch (e) {
927
1160
  }
928
1161
  }
929
- if (!n.href) return;
930
- if (/^https?:\/\//i.test(n.href)) window.location.assign(n.href);
931
- else router.push(n.href);
1162
+ const target = resolveSuiteNotificationHref(n.href, n.source_app);
1163
+ if (!target) return;
1164
+ if (/^https?:\/\//i.test(target)) {
1165
+ window.location.assign(target);
1166
+ return;
1167
+ }
1168
+ router.push(target);
932
1169
  },
933
1170
  [router, markRead]
934
1171
  );
@@ -1064,11 +1301,11 @@ function useIdleMount(timeoutMs = 1200) {
1064
1301
  }
1065
1302
 
1066
1303
  // src/suite/components/deferred-chrome.tsx
1067
- import { Fragment as Fragment2, jsx as jsx9 } from "react/jsx-runtime";
1304
+ import { Fragment as Fragment3, jsx as jsx9 } from "react/jsx-runtime";
1068
1305
  function DeferredChrome({ children }) {
1069
1306
  const ready = useIdleMount();
1070
1307
  if (!ready) return null;
1071
- return /* @__PURE__ */ jsx9(Fragment2, { children });
1308
+ return /* @__PURE__ */ jsx9(Fragment3, { children });
1072
1309
  }
1073
1310
 
1074
1311
  // src/suite/components/suite-mobile-drawer.tsx
@@ -1081,6 +1318,7 @@ function SuiteMobileDrawer({
1081
1318
  children,
1082
1319
  side = "left",
1083
1320
  showCloseButton = false,
1321
+ title,
1084
1322
  closeLabel = "Close navigation",
1085
1323
  backdropLabel = "Close navigation",
1086
1324
  dialogLabel = "Navigation",
@@ -1158,23 +1396,35 @@ function SuiteMobileDrawer({
1158
1396
  tabIndex: -1,
1159
1397
  "data-test": panelDataTest,
1160
1398
  className: cn(
1161
- "suite-scroll-lock relative flex h-full w-[86%] max-w-64 flex-col overflow-hidden bg-ph-surface shadow-xl outline-none motion-safe:transition-transform motion-safe:ease-spring-fast",
1399
+ "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",
1162
1400
  side === "right" && "ml-auto",
1163
1401
  shown ? "translate-x-0" : side === "right" ? "translate-x-full" : "-translate-x-full",
1164
1402
  panelClassName
1165
1403
  ),
1166
1404
  style: { transitionDuration: `${durationMs}ms` },
1167
1405
  children: [
1168
- showCloseButton ? /* @__PURE__ */ jsx10("div", { className: "flex h-14 shrink-0 items-center justify-end border-b border-ph-border px-3", children: /* @__PURE__ */ jsx10(
1169
- "button",
1406
+ showCloseButton ? /* @__PURE__ */ jsxs8(
1407
+ "div",
1170
1408
  {
1171
- type: "button",
1172
- onClick: onClose,
1173
- className: "-mr-1.5 inline-flex h-11 w-11 items-center justify-center rounded-lg text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
1174
- "aria-label": closeLabel,
1175
- children: /* @__PURE__ */ jsx10(X, { className: "h-4 w-4", "aria-hidden": true })
1409
+ className: cn(
1410
+ "flex h-14 shrink-0 items-center border-b border-ph-border px-3",
1411
+ title ? "justify-between" : "justify-end"
1412
+ ),
1413
+ children: [
1414
+ title ? /* @__PURE__ */ jsx10("span", { className: "truncate text-sm font-semibold text-ph-ink", children: title }) : null,
1415
+ /* @__PURE__ */ jsx10(
1416
+ "button",
1417
+ {
1418
+ type: "button",
1419
+ onClick: onClose,
1420
+ className: "-mr-1.5 inline-flex h-11 w-11 items-center justify-center rounded-lg text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
1421
+ "aria-label": closeLabel,
1422
+ children: /* @__PURE__ */ jsx10(X, { className: "h-4 w-4", "aria-hidden": true })
1423
+ }
1424
+ )
1425
+ ]
1176
1426
  }
1177
- ) }) : null,
1427
+ ) : null,
1178
1428
  children
1179
1429
  ]
1180
1430
  }
@@ -1231,74 +1481,361 @@ function SuiteMobileHeader({
1231
1481
  );
1232
1482
  }
1233
1483
 
1234
- // src/suite/components/suite-user-menu.tsx
1235
- import Image2 from "next/image";
1236
- import { useRouter as useRouter2 } from "next/navigation";
1237
- import { LogOut, Settings } from "iconoir-react";
1484
+ // src/suite/components/suite-layout.tsx
1485
+ import {
1486
+ useEffect as useEffect5,
1487
+ useRef as useRef2,
1488
+ useState as useState6
1489
+ } from "react";
1238
1490
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1239
- function computeInitials(name, email) {
1240
- const source = (name || email || "?").trim();
1241
- return source.split(/[\s@]+/).filter(Boolean).slice(0, 2).map((part) => part[0]).join("").toUpperCase();
1491
+ function SuiteBreadcrumbs({ items, className }) {
1492
+ return /* @__PURE__ */ jsx12("nav", { "aria-label": "Breadcrumb", className, children: /* @__PURE__ */ jsx12("ol", { className: "flex min-w-0 items-center gap-1.5", children: items.map((item, index) => {
1493
+ const isLast = index === items.length - 1;
1494
+ return /* @__PURE__ */ jsxs10("li", { className: "flex min-w-0 items-center gap-1.5", children: [
1495
+ index > 0 ? /* @__PURE__ */ jsx12("span", { className: "select-none text-ph-border", "aria-hidden": "true", children: "/" }) : null,
1496
+ item.href && !isLast ? /* @__PURE__ */ jsx12(
1497
+ "a",
1498
+ {
1499
+ href: item.href,
1500
+ className: "truncate transition hover:text-ph-ink",
1501
+ children: item.label
1502
+ }
1503
+ ) : /* @__PURE__ */ jsx12(
1504
+ "span",
1505
+ {
1506
+ className: cn(
1507
+ "truncate",
1508
+ isLast ? "font-medium text-ph-ink" : "text-ph-subtle"
1509
+ ),
1510
+ "aria-current": isLast ? "page" : void 0,
1511
+ children: item.label
1512
+ }
1513
+ )
1514
+ ] }, index);
1515
+ }) }) });
1242
1516
  }
1243
- function SuiteUserMenu({
1244
- name,
1245
- email,
1246
- image,
1247
- settingsHref,
1248
- onSignOut,
1249
- fallbackInitials,
1250
- dataTest = "suite-user-menu",
1251
- className
1517
+ function SuitePage({
1518
+ children,
1519
+ width = "full",
1520
+ inset = true,
1521
+ className,
1522
+ as: Component = "div",
1523
+ dataTest,
1524
+ id,
1525
+ role,
1526
+ "aria-live": ariaLive
1252
1527
  }) {
1253
- const router = useRouter2();
1254
- const initials = fallbackInitials != null ? fallbackInitials : computeInitials(name, email);
1255
- const avatar = /* @__PURE__ */ jsx12("span", { className: "inline-flex h-11 w-11 items-center justify-center rounded-full transition-colors hover:bg-ph-muted", children: image ? /* @__PURE__ */ jsx12(
1256
- Image2,
1528
+ return /* @__PURE__ */ jsx12(
1529
+ Component,
1257
1530
  {
1258
- src: image,
1259
- alt: "",
1260
- width: 32,
1261
- height: 32,
1262
- className: "h-8 w-8 rounded-full object-cover",
1263
- unoptimized: true
1531
+ id,
1532
+ "data-test": dataTest,
1533
+ "data-width": width,
1534
+ role,
1535
+ "aria-live": ariaLive,
1536
+ className: cn(
1537
+ "suite-page-stage flex w-full min-w-0 flex-col",
1538
+ inset && "suite-page-inset",
1539
+ className
1540
+ ),
1541
+ children
1542
+ }
1543
+ );
1544
+ }
1545
+ function SuitePageHeader({
1546
+ title,
1547
+ description,
1548
+ eyebrow,
1549
+ actions,
1550
+ className,
1551
+ titleClassName,
1552
+ dataTest,
1553
+ /** Optional icon rendered before the title. */
1554
+ icon,
1555
+ /** Use the animated sun/moon icon from the Today page. */
1556
+ todayIcon,
1557
+ todayIconClassName,
1558
+ /** Small badge/pill next to the title (e.g. "Beta", org name). */
1559
+ badge,
1560
+ /** Breadcrumb line rendered above the title. */
1561
+ breadcrumb,
1562
+ /** Stick to the top of the scrollport (desktop page titles). */
1563
+ sticky = false
1564
+ }) {
1565
+ const headerRef = useRef2(null);
1566
+ const [stuck, setStuck] = useState6(false);
1567
+ useEffect5(() => {
1568
+ var _a;
1569
+ if (!sticky) return;
1570
+ const el = headerRef.current;
1571
+ if (!el) return;
1572
+ const sentinel = document.createElement("div");
1573
+ sentinel.setAttribute("aria-hidden", "true");
1574
+ sentinel.style.cssText = "position:relative;height:1px;width:100%;margin:0;padding:0;pointer-events:none";
1575
+ (_a = el.parentElement) == null ? void 0 : _a.insertBefore(sentinel, el);
1576
+ const observer = new IntersectionObserver(
1577
+ ([entry]) => {
1578
+ setStuck(!entry.isIntersecting);
1579
+ },
1580
+ { threshold: [1], root: null }
1581
+ );
1582
+ observer.observe(sentinel);
1583
+ return () => {
1584
+ observer.disconnect();
1585
+ sentinel.remove();
1586
+ };
1587
+ }, [sticky]);
1588
+ const resolvedIcon = todayIcon ? /* @__PURE__ */ jsx12(
1589
+ TodayTimeIcon,
1590
+ {
1591
+ className: cn(
1592
+ "h-7 w-7 text-amber-500 sm:h-8 sm:w-8",
1593
+ todayIconClassName
1594
+ )
1264
1595
  }
1265
- ) : /* @__PURE__ */ jsx12("span", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-ph-muted text-sm font-semibold text-ph-brand", children: initials }) });
1596
+ ) : icon;
1266
1597
  return /* @__PURE__ */ jsxs10(
1267
- DropdownMenu,
1598
+ "header",
1268
1599
  {
1269
- trigger: avatar,
1270
- "aria-label": "Account menu",
1271
- align: "end",
1272
- side: "bottom",
1273
- sideOffset: 6,
1274
- collisionPadding: 16,
1275
- modal: false,
1276
- className,
1600
+ ref: headerRef,
1277
1601
  "data-test": dataTest,
1602
+ "data-stuck": sticky ? stuck ? "true" : "false" : void 0,
1603
+ className: cn(
1604
+ "flex items-start justify-between gap-4 pb-2 sm:pb-3",
1605
+ // No extra pt here — page inset sets the shared title baseline.
1606
+ // Stuck state adds pad via .suite-page-header-sticky[data-stuck].
1607
+ sticky && "suite-page-header-sticky",
1608
+ className
1609
+ ),
1278
1610
  children: [
1279
- /* @__PURE__ */ jsxs10("div", { className: "px-2 py-1.5", children: [
1280
- /* @__PURE__ */ jsx12("p", { className: "truncate text-sm font-medium text-ph-ink", children: name || email || "Account" }),
1281
- email ? /* @__PURE__ */ jsx12("p", { className: "truncate text-xs text-ph-mutedtext", children: email }) : null
1282
- ] }),
1283
- /* @__PURE__ */ jsx12("div", { className: "my-1 border-t border-ph-border" }),
1284
- /* @__PURE__ */ jsxs10(
1285
- DropdownItem,
1286
- {
1287
- "data-test": `${dataTest}-settings`,
1288
- onClick: () => router.push(settingsHref),
1611
+ /* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1", children: [
1612
+ eyebrow ? /* @__PURE__ */ jsx12("div", { className: "mb-0.5 flex min-w-0 items-center gap-1.5 text-xs text-ph-subtle", children: eyebrow }) : null,
1613
+ breadcrumb ? /* @__PURE__ */ jsx12("div", { className: "mb-0.5 flex min-w-0 items-center gap-1.5 text-xs text-ph-subtle", children: breadcrumb }) : null,
1614
+ /* @__PURE__ */ jsxs10("div", { className: "flex min-w-0 items-center gap-3", children: [
1615
+ resolvedIcon ? /* @__PURE__ */ jsx12(
1616
+ "div",
1617
+ {
1618
+ className: "hidden shrink-0 sm:flex sm:items-center [&_svg]:h-7 [&_svg]:w-7 sm:[&_svg]:h-8 sm:[&_svg]:w-8",
1619
+ "aria-hidden": "true",
1620
+ children: resolvedIcon
1621
+ }
1622
+ ) : null,
1623
+ /* @__PURE__ */ jsxs10("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", children: [
1624
+ /* @__PURE__ */ jsx12(
1625
+ "h1",
1626
+ {
1627
+ className: cn(
1628
+ "font-display text-[1.625rem] font-bold leading-none tracking-tight text-ph-ink sm:text-[2.25rem]",
1629
+ titleClassName
1630
+ ),
1631
+ children: title
1632
+ }
1633
+ ),
1634
+ badge ? /* @__PURE__ */ jsx12("span", { className: "rounded-md bg-ph-muted px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-ph-subtle", children: badge }) : null
1635
+ ] })
1636
+ ] }),
1637
+ description ? /* @__PURE__ */ jsx12(
1638
+ "div",
1639
+ {
1640
+ className: cn(
1641
+ "mt-1.5 max-w-2xl text-pretty text-sm leading-5 text-ph-subtle",
1642
+ resolvedIcon && "sm:pl-11"
1643
+ ),
1644
+ children: description
1645
+ }
1646
+ ) : null
1647
+ ] }),
1648
+ actions ? /* @__PURE__ */ jsx12("div", { className: "flex shrink-0 items-center gap-2 self-center", children: actions }) : null
1649
+ ]
1650
+ }
1651
+ );
1652
+ }
1653
+ function SuiteToolbar({
1654
+ children,
1655
+ className,
1656
+ label = "Page tools",
1657
+ dataTest
1658
+ }) {
1659
+ return /* @__PURE__ */ jsx12(
1660
+ "div",
1661
+ {
1662
+ role: "toolbar",
1663
+ "aria-label": label,
1664
+ "data-test": dataTest,
1665
+ className: cn("flex min-w-0 flex-wrap items-center gap-2", className),
1666
+ children
1667
+ }
1668
+ );
1669
+ }
1670
+ function SuiteTabList({
1671
+ children,
1672
+ className,
1673
+ label,
1674
+ dataTest
1675
+ }) {
1676
+ return /* @__PURE__ */ jsx12(
1677
+ "nav",
1678
+ {
1679
+ "aria-label": label,
1680
+ "data-test": dataTest,
1681
+ className: cn(
1682
+ "flex min-w-0 flex-wrap gap-2 [&_a]:min-h-[48px] [&_button]:min-h-[48px] sm:[&_a]:min-h-[44px] sm:[&_button]:min-h-[44px]",
1683
+ className
1684
+ ),
1685
+ children
1686
+ }
1687
+ );
1688
+ }
1689
+ function SuiteSectionHeader({
1690
+ title,
1691
+ description,
1692
+ action,
1693
+ className
1694
+ }) {
1695
+ return /* @__PURE__ */ jsxs10(
1696
+ "div",
1697
+ {
1698
+ className: cn(
1699
+ "flex min-w-0 flex-col gap-4 sm:flex-row sm:items-end sm:justify-between",
1700
+ className
1701
+ ),
1702
+ children: [
1703
+ /* @__PURE__ */ jsxs10("div", { className: "min-w-0", children: [
1704
+ /* @__PURE__ */ jsx12("h2", { className: "font-display text-balance text-lg font-semibold tracking-tight text-ph-ink sm:text-xl", children: title }),
1705
+ description ? /* @__PURE__ */ jsx12("div", { className: "mt-1 max-w-2xl text-pretty text-sm leading-5 text-ph-subtle", children: description }) : null
1706
+ ] }),
1707
+ action ? /* @__PURE__ */ jsx12("div", { className: "flex min-h-[44px] shrink-0 items-center", children: action }) : null
1708
+ ]
1709
+ }
1710
+ );
1711
+ }
1712
+
1713
+ // src/suite/components/suite-settings-layout.tsx
1714
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1715
+ function SuiteSettingsLayout({
1716
+ children,
1717
+ sidebar,
1718
+ mobileNav,
1719
+ loading,
1720
+ title = "Settings",
1721
+ description,
1722
+ header,
1723
+ dataTest = "settings-page",
1724
+ headerDataTest = "settings-page-header",
1725
+ className,
1726
+ contentClassName
1727
+ }) {
1728
+ return /* @__PURE__ */ jsxs11(
1729
+ SuitePage,
1730
+ {
1731
+ dataTest,
1732
+ className: cn("flex h-full flex-col overflow-hidden", className),
1733
+ children: [
1734
+ header != null ? header : /* @__PURE__ */ jsx13(
1735
+ SuitePageHeader,
1736
+ {
1737
+ sticky: true,
1738
+ dataTest: headerDataTest,
1739
+ title,
1740
+ description
1741
+ }
1742
+ ),
1743
+ loading != null ? loading : /* @__PURE__ */ jsxs11("div", { className: "suite-page-body mt-6 flex min-h-0 flex-1 flex-col", children: [
1744
+ mobileNav ? /* @__PURE__ */ jsx13("div", { className: "sticky top-0 z-20 mb-2 shrink-0 bg-ph-canvas/95 backdrop-blur lg:hidden", children: mobileNav }) : null,
1745
+ /* @__PURE__ */ jsxs11("div", { className: "flex min-h-0 flex-1 flex-col gap-6 lg:flex-row lg:gap-8", children: [
1746
+ /* @__PURE__ */ jsx13(
1747
+ "aside",
1748
+ {
1749
+ "aria-label": "Settings",
1750
+ className: "hidden w-56 shrink-0 bg-transparent lg:block lg:self-stretch lg:overflow-y-auto lg:overscroll-contain",
1751
+ children: sidebar
1752
+ }
1753
+ ),
1754
+ /* @__PURE__ */ jsx13(
1755
+ "div",
1756
+ {
1757
+ className: cn(
1758
+ "min-h-0 min-w-0 flex-1 overflow-y-auto overscroll-contain pb-6",
1759
+ contentClassName
1760
+ ),
1761
+ children
1762
+ }
1763
+ )
1764
+ ] })
1765
+ ] })
1766
+ ]
1767
+ }
1768
+ );
1769
+ }
1770
+
1771
+ // src/suite/components/suite-user-menu.tsx
1772
+ import Image2 from "next/image";
1773
+ import { useRouter as useRouter2 } from "next/navigation";
1774
+ import { LogOut, Settings } from "iconoir-react";
1775
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1776
+ function computeInitials(name, email) {
1777
+ const source = (name || email || "?").trim();
1778
+ return source.split(/[\s@]+/).filter(Boolean).slice(0, 2).map((part) => part[0]).join("").toUpperCase();
1779
+ }
1780
+ function SuiteUserMenu({
1781
+ name,
1782
+ email,
1783
+ image,
1784
+ settingsHref,
1785
+ onSignOut,
1786
+ fallbackInitials,
1787
+ dataTest = "suite-user-menu",
1788
+ className
1789
+ }) {
1790
+ const router = useRouter2();
1791
+ const initials = fallbackInitials != null ? fallbackInitials : computeInitials(name, email);
1792
+ const avatar = /* @__PURE__ */ jsx14("span", { className: "inline-flex h-11 w-11 items-center justify-center rounded-full transition-colors hover:bg-ph-muted", children: image ? /* @__PURE__ */ jsx14(
1793
+ Image2,
1794
+ {
1795
+ src: image,
1796
+ alt: "",
1797
+ width: 32,
1798
+ height: 32,
1799
+ className: "h-8 w-8 rounded-full object-cover",
1800
+ unoptimized: true
1801
+ }
1802
+ ) : /* @__PURE__ */ jsx14("span", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-ph-muted text-sm font-semibold text-ph-brand", children: initials }) });
1803
+ return /* @__PURE__ */ jsxs12(
1804
+ DropdownMenu,
1805
+ {
1806
+ trigger: avatar,
1807
+ "aria-label": "Account menu",
1808
+ align: "end",
1809
+ side: "bottom",
1810
+ sideOffset: 6,
1811
+ collisionPadding: 16,
1812
+ modal: false,
1813
+ className,
1814
+ "data-test": dataTest,
1815
+ children: [
1816
+ /* @__PURE__ */ jsxs12("div", { className: "px-2 py-1.5", children: [
1817
+ /* @__PURE__ */ jsx14("p", { className: "truncate text-sm font-medium text-ph-ink", children: name || email || "Account" }),
1818
+ email ? /* @__PURE__ */ jsx14("p", { className: "truncate text-xs text-ph-mutedtext", children: email }) : null
1819
+ ] }),
1820
+ /* @__PURE__ */ jsx14("div", { className: "my-1 border-t border-ph-border" }),
1821
+ /* @__PURE__ */ jsxs12(
1822
+ DropdownItem,
1823
+ {
1824
+ "data-test": `${dataTest}-settings`,
1825
+ onClick: () => router.push(settingsHref),
1289
1826
  children: [
1290
- /* @__PURE__ */ jsx12(Settings, { className: "h-4 w-4 text-ph-mutedtext", "aria-hidden": true }),
1827
+ /* @__PURE__ */ jsx14(Settings, { className: "h-4 w-4 text-ph-mutedtext", "aria-hidden": true }),
1291
1828
  "Profile settings"
1292
1829
  ]
1293
1830
  }
1294
1831
  ),
1295
- /* @__PURE__ */ jsxs10(
1832
+ /* @__PURE__ */ jsxs12(
1296
1833
  DropdownItem,
1297
1834
  {
1298
1835
  "data-test": `${dataTest}-sign-out`,
1299
1836
  onClick: onSignOut,
1300
1837
  children: [
1301
- /* @__PURE__ */ jsx12(LogOut, { className: "h-4 w-4 text-ph-mutedtext", "aria-hidden": true }),
1838
+ /* @__PURE__ */ jsx14(LogOut, { className: "h-4 w-4 text-ph-mutedtext", "aria-hidden": true }),
1302
1839
  "Sign out"
1303
1840
  ]
1304
1841
  }
@@ -1310,7 +1847,7 @@ function SuiteUserMenu({
1310
1847
 
1311
1848
  // src/suite/components/suite-bottom-nav.tsx
1312
1849
  import Link from "next/link";
1313
- import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1850
+ import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
1314
1851
  function SuiteBottomNav({
1315
1852
  items,
1316
1853
  ariaLabel = "Primary navigation",
@@ -1323,8 +1860,8 @@ function SuiteBottomNav({
1323
1860
  );
1324
1861
  }
1325
1862
  const activeIndex = items.findIndex((item) => item.active);
1326
- return /* @__PURE__ */ jsxs11(Fragment3, { children: [
1327
- /* @__PURE__ */ jsx13(
1863
+ return /* @__PURE__ */ jsxs13(Fragment4, { children: [
1864
+ /* @__PURE__ */ jsx15(
1328
1865
  "div",
1329
1866
  {
1330
1867
  "aria-hidden": true,
@@ -1332,7 +1869,7 @@ function SuiteBottomNav({
1332
1869
  style: { height: "calc(60px + env(safe-area-inset-bottom))" }
1333
1870
  }
1334
1871
  ),
1335
- /* @__PURE__ */ jsxs11(
1872
+ /* @__PURE__ */ jsxs13(
1336
1873
  "nav",
1337
1874
  {
1338
1875
  "aria-label": ariaLabel,
@@ -1347,12 +1884,12 @@ function SuiteBottomNav({
1347
1884
  "--suite-nav-active": activeIndex >= 0 ? activeIndex : 0
1348
1885
  },
1349
1886
  children: [
1350
- activeIndex >= 0 ? /* @__PURE__ */ jsx13("span", { "aria-hidden": true, className: "suite-bottom-nav__indicator" }) : null,
1887
+ activeIndex >= 0 ? /* @__PURE__ */ jsx15("span", { "aria-hidden": true, className: "suite-bottom-nav__indicator" }) : null,
1351
1888
  items.map((item) => {
1352
1889
  var _a;
1353
1890
  const Icon = item.icon;
1354
1891
  const active = Boolean(item.active);
1355
- return /* @__PURE__ */ jsxs11(
1892
+ return /* @__PURE__ */ jsxs13(
1356
1893
  Link,
1357
1894
  {
1358
1895
  href: item.href,
@@ -1363,14 +1900,14 @@ function SuiteBottomNav({
1363
1900
  active && "font-semibold text-ph-brand"
1364
1901
  ),
1365
1902
  children: [
1366
- /* @__PURE__ */ jsx13(
1903
+ /* @__PURE__ */ jsx15(
1367
1904
  "span",
1368
1905
  {
1369
1906
  className: cn(
1370
1907
  "suite-bottom-nav__icon relative z-10 inline-flex items-center justify-center",
1371
1908
  active && "suite-bottom-nav__icon--active"
1372
1909
  ),
1373
- children: /* @__PURE__ */ jsx13(
1910
+ children: /* @__PURE__ */ jsx15(
1374
1911
  Icon,
1375
1912
  {
1376
1913
  className: "h-6 w-6",
@@ -1380,7 +1917,7 @@ function SuiteBottomNav({
1380
1917
  )
1381
1918
  }
1382
1919
  ),
1383
- /* @__PURE__ */ jsx13("span", { className: "relative z-10 max-w-full truncate", children: item.label })
1920
+ /* @__PURE__ */ jsx15("span", { className: "relative z-10 max-w-full truncate", children: item.label })
1384
1921
  ]
1385
1922
  },
1386
1923
  item.href
@@ -1392,117 +1929,39 @@ function SuiteBottomNav({
1392
1929
  ] });
1393
1930
  }
1394
1931
 
1395
- // src/suite/lib/apps.ts
1396
- var SUITE_APPS2 = [
1397
- {
1398
- slug: "shellstack",
1399
- name: "ShellStack",
1400
- description: "Organisation and billing",
1401
- icon: "/icon-shellstack.svg",
1402
- landing: "/home",
1403
- baseUrlEnv: "NEXT_PUBLIC_SHELLSTACK_URL",
1404
- baseUrlFallback: "http://localhost:3002"
1405
- },
1406
- {
1407
- slug: "tides",
1408
- name: "Tides",
1409
- description: "Projects and tasks",
1410
- icon: "/tides-icon.svg",
1411
- landing: "/today",
1412
- baseUrlEnv: "NEXT_PUBLIC_TIDES_URL",
1413
- baseUrlFallback: "http://localhost:3001"
1414
- },
1415
- {
1416
- slug: "turtletime",
1417
- name: "TurtleTime",
1418
- description: "Time tracking",
1419
- icon: "/turtletime-icon.svg",
1420
- landing: "/tracker",
1421
- baseUrlEnv: "NEXT_PUBLIC_TURTLETIME_URL",
1422
- baseUrlFallback: "http://localhost:3000"
1423
- },
1424
- {
1425
- slug: "kraken",
1426
- name: "Kraken",
1427
- description: "Documents and knowledge",
1428
- icon: "/kraken-icon.svg",
1429
- landing: "/today",
1430
- baseUrlEnv: "NEXT_PUBLIC_KRAKEN_URL",
1431
- baseUrlFallback: "http://localhost:3003"
1432
- }
1433
- ];
1434
- var SUITE_APP_MAP = Object.fromEntries(SUITE_APPS2.map((app) => [app.slug, app]));
1435
- function suiteAppBaseUrl(slug) {
1436
- const def = SUITE_APP_MAP[slug];
1437
- const fromEnv = typeof process !== "undefined" ? process.env[def.baseUrlEnv] : void 0;
1438
- return (fromEnv || def.baseUrlFallback).replace(/\/$/, "");
1439
- }
1440
-
1441
1932
  // src/suite/components/today-calibrating.tsx
1442
1933
  import { Refresh as RefreshCw2, SunLight as Sun } from "iconoir-react";
1443
- import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1934
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1444
1935
  function TodayCalibrating({
1445
1936
  onRetry,
1446
1937
  retrying = false,
1447
1938
  className,
1448
1939
  spinner: Spinner,
1449
- variant = "ring",
1940
+ variant: _variant = "ring",
1450
1941
  retrySpinnerClassName = "text-ph-brand"
1451
1942
  }) {
1452
- return /* @__PURE__ */ jsxs12(
1943
+ void _variant;
1944
+ return /* @__PURE__ */ jsxs14(
1453
1945
  "div",
1454
1946
  {
1455
1947
  className: cn(
1456
- "relative flex min-h-[14rem] flex-col items-center justify-center overflow-hidden rounded-2xl border border-ph-border bg-ph-surface px-5 py-8 text-center shadow-sm",
1948
+ "suite-auth-handoff relative flex min-h-[14rem] flex-col items-center justify-center overflow-hidden rounded-2xl border border-ph-border bg-ph-surface px-5 py-8 text-center shadow-sm",
1457
1949
  className
1458
1950
  ),
1459
1951
  "data-test": "today-calibrating",
1460
1952
  children: [
1461
- /* @__PURE__ */ jsx14(
1953
+ /* @__PURE__ */ jsx16(
1462
1954
  "div",
1463
1955
  {
1464
- className: "pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_35%,rgba(251,191,36,0.12),transparent_55%)]",
1956
+ className: "pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_35%,rgba(251,191,36,0.1),transparent_55%)]",
1465
1957
  "aria-hidden": true
1466
1958
  }
1467
1959
  ),
1468
- /* @__PURE__ */ jsxs12("div", { className: "relative mb-6 flex h-24 w-24 items-center justify-center", children: [
1469
- variant === "ring" ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
1470
- /* @__PURE__ */ jsx14(
1471
- Spinner,
1472
- {
1473
- size: "lg",
1474
- className: "absolute inset-0 m-auto opacity-40",
1475
- label: "Loading today"
1476
- }
1477
- ),
1478
- /* @__PURE__ */ jsx14(
1479
- "span",
1480
- {
1481
- className: "bg-ph-muted/30 absolute inset-2 animate-ping rounded-full",
1482
- "aria-hidden": true
1483
- }
1484
- )
1485
- ] }) : null,
1486
- /* @__PURE__ */ jsx14("span", { className: "relative flex h-14 w-14 items-center justify-center rounded-2xl border border-ph-border bg-ph-surface text-ph-ink shadow-sm", children: /* @__PURE__ */ jsx14(
1487
- Sun,
1488
- {
1489
- className: cn("h-9 w-9", variant === "pulse" && "animate-pulse"),
1490
- strokeWidth: 2,
1491
- "aria-hidden": true
1492
- }
1493
- ) })
1494
- ] }),
1495
- /* @__PURE__ */ jsx14("h2", { className: "relative text-base font-semibold tracking-tight text-ph-ink sm:text-lg md:text-xl", children: "Calibrating your day" }),
1496
- /* @__PURE__ */ jsx14("p", { className: "relative mt-1.5 max-w-sm text-sm sm:text-[15px] leading-relaxed text-ph-subtle", children: "Pulling together tasks, time and updates from your workspace\u2026" }),
1497
- /* @__PURE__ */ jsx14("div", { className: "relative mt-5 flex gap-1.5", "aria-hidden": true, children: [0, 1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx14(
1498
- "span",
1499
- {
1500
- className: "h-1.5 w-5 animate-pulse rounded-full bg-ph-muted",
1501
- style: { animationDelay: `${i * 120}ms` }
1502
- },
1503
- i
1504
- )) }),
1505
- onRetry ? /* @__PURE__ */ jsxs12(
1960
+ /* @__PURE__ */ jsx16("div", { className: "relative mb-5 flex h-14 w-14 items-center justify-center rounded-2xl border border-ph-border bg-ph-surface text-ph-ink shadow-sm", children: /* @__PURE__ */ jsx16(Sun, { className: "h-8 w-8", strokeWidth: 2, "aria-hidden": true }) }),
1961
+ /* @__PURE__ */ jsx16("h2", { className: "relative text-base font-semibold tracking-tight text-ph-ink sm:text-lg md:text-xl", children: "Calibrating your day" }),
1962
+ /* @__PURE__ */ jsx16("p", { className: "relative mt-1.5 max-w-sm text-sm sm:text-[15px] leading-relaxed text-ph-subtle", children: "Pulling together tasks, time and updates from your workspace\u2026" }),
1963
+ /* @__PURE__ */ jsx16("div", { className: "relative mt-5 flex justify-center", children: /* @__PURE__ */ jsx16(Spinner, { size: "sm", className: "opacity-70", label: "Loading today" }) }),
1964
+ onRetry ? /* @__PURE__ */ jsxs14(
1506
1965
  "button",
1507
1966
  {
1508
1967
  type: "button",
@@ -1510,7 +1969,7 @@ function TodayCalibrating({
1510
1969
  disabled: retrying,
1511
1970
  className: "relative mt-6 inline-flex items-center gap-2 rounded-xl border border-ph-border bg-ph-canvas px-4 py-2 text-sm font-medium text-ph-ink transition hover:bg-ph-muted disabled:opacity-60",
1512
1971
  children: [
1513
- retrying ? /* @__PURE__ */ jsx14(Spinner, { size: "sm", className: retrySpinnerClassName }) : /* @__PURE__ */ jsx14(RefreshCw2, { className: "h-4 w-4", "aria-hidden": true }),
1972
+ retrying ? /* @__PURE__ */ jsx16(Spinner, { size: "sm", className: retrySpinnerClassName }) : /* @__PURE__ */ jsx16(RefreshCw2, { className: "h-4 w-4", "aria-hidden": true }),
1514
1973
  "Try again"
1515
1974
  ]
1516
1975
  }
@@ -1525,11 +1984,11 @@ import {
1525
1984
  createContext,
1526
1985
  useCallback as useCallback2,
1527
1986
  useContext,
1528
- useEffect as useEffect5,
1987
+ useEffect as useEffect6,
1529
1988
  useMemo,
1530
- useState as useState6
1989
+ useState as useState7
1531
1990
  } from "react";
1532
- import { jsx as jsx15 } from "react/jsx-runtime";
1991
+ import { jsx as jsx17 } from "react/jsx-runtime";
1533
1992
  var SuiteThemeContext = createContext(null);
1534
1993
  function systemTheme(fallback) {
1535
1994
  if (typeof window === "undefined") return fallback;
@@ -1567,7 +2026,7 @@ function SuiteThemeProvider({
1567
2026
  config,
1568
2027
  children
1569
2028
  }) {
1570
- const [state, setState] = useState6(() => {
2029
+ const [state, setState] = useState7(() => {
1571
2030
  const theme2 = readStoredTheme(config.storageKey);
1572
2031
  if (typeof document === "undefined") {
1573
2032
  return { theme: theme2, resolvedTheme: config.fallbackTheme };
@@ -1577,7 +2036,7 @@ function SuiteThemeProvider({
1577
2036
  return { theme: theme2, resolvedTheme: resolvedTheme2 };
1578
2037
  });
1579
2038
  const { theme, resolvedTheme } = state;
1580
- useEffect5(() => {
2039
+ useEffect6(() => {
1581
2040
  if (theme !== "system") return;
1582
2041
  const media = window.matchMedia("(prefers-color-scheme: dark)");
1583
2042
  const update = () => {
@@ -1607,7 +2066,7 @@ function SuiteThemeProvider({
1607
2066
  () => ({ theme, resolvedTheme, setTheme, toggleTheme }),
1608
2067
  [theme, resolvedTheme, setTheme, toggleTheme]
1609
2068
  );
1610
- return /* @__PURE__ */ jsx15(SuiteThemeContext.Provider, { value, children });
2069
+ return /* @__PURE__ */ jsx17(SuiteThemeContext.Provider, { value, children });
1611
2070
  }
1612
2071
  function useSuiteTheme() {
1613
2072
  const ctx = useContext(SuiteThemeContext);
@@ -1616,39 +2075,170 @@ function useSuiteTheme() {
1616
2075
  }
1617
2076
 
1618
2077
  // src/suite/components/ai-panel.tsx
1619
- import { useState as useState7 } from "react";
1620
- import { Check as Check2, Copy, Xmark as X2 } from "iconoir-react";
1621
- import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
2078
+ import {
2079
+ useCallback as useCallback3,
2080
+ useEffect as useEffect7,
2081
+ useRef as useRef3,
2082
+ useState as useState8
2083
+ } from "react";
2084
+ import { Check as Check2, Copy, Sparks, Xmark as X2 } from "iconoir-react";
2085
+ import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
2086
+ var SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
2087
+ function openSuiteAskAi(detail) {
2088
+ if (typeof window === "undefined") return;
2089
+ window.dispatchEvent(
2090
+ new CustomEvent(SUITE_OPEN_ASK_AI_EVENT, {
2091
+ detail: detail != null ? detail : {}
2092
+ })
2093
+ );
2094
+ }
2095
+ var LAUNCHER_EDGE_GAP = 12;
2096
+ var LAUNCHER_STORAGE_KEY = "suite-ask-ai-launcher-position";
2097
+ function resolveSuiteAiLauncherPosition(side, desiredY, viewportWidth, viewportHeight, launcherWidth, launcherHeight) {
2098
+ const maxX = Math.max(
2099
+ LAUNCHER_EDGE_GAP,
2100
+ viewportWidth - launcherWidth - LAUNCHER_EDGE_GAP
2101
+ );
2102
+ const maxY = Math.max(
2103
+ LAUNCHER_EDGE_GAP,
2104
+ viewportHeight - launcherHeight - LAUNCHER_EDGE_GAP
2105
+ );
2106
+ return {
2107
+ x: side === "left" ? LAUNCHER_EDGE_GAP : maxX,
2108
+ y: Math.min(Math.max(desiredY, LAUNCHER_EDGE_GAP), maxY)
2109
+ };
2110
+ }
1622
2111
  function SuiteAiPanel({
1623
- presets,
1624
- emptyState,
1625
- fetchStatus,
1626
- complete,
2112
+ presets = [],
2113
+ emptyState = "Ask anything about your workspace, or look something up online.",
2114
+ title = "Ask AI",
2115
+ fetchChat,
2116
+ sendMessage,
2117
+ clearChat,
1627
2118
  brandIcon: BrandIcon,
1628
- spinner: Spinner
2119
+ spinner: Spinner,
2120
+ hideLauncher = false
1629
2121
  }) {
1630
- const [open, setOpen] = useState7(false);
1631
- const [prompt, setPrompt] = useState7("");
1632
- const [loading, setLoading] = useState7(false);
1633
- const [answer, setAnswer] = useState7("");
1634
- const [error, setError] = useState7("");
1635
- const [copied, setCopied] = useState7(false);
2122
+ const [open, setOpen] = useState8(false);
2123
+ const [prompt, setPrompt] = useState8("");
2124
+ const [messages, setMessages] = useState8([]);
2125
+ const [loading, setLoading] = useState8(false);
2126
+ const [hydrating, setHydrating] = useState8(false);
2127
+ const [error, setError] = useState8("");
2128
+ const [copiedId, setCopiedId] = useState8(null);
2129
+ const [launcherSide, setLauncherSide] = useState8("right");
2130
+ const [launcherPosition, setLauncherPosition] = useState8(null);
2131
+ const [draggingLauncher, setDraggingLauncher] = useState8(false);
2132
+ const scrollerRef = useRef3(null);
2133
+ const launcherRef = useRef3(null);
2134
+ const launcherSideRef = useRef3("right");
2135
+ const launcherPositionRef = useRef3(null);
2136
+ const launcherDragRef = useRef3(null);
2137
+ const suppressLauncherClickRef = useRef3(false);
2138
+ const pendingPromptRef = useRef3(null);
2139
+ useEffect7(() => {
2140
+ const launcher = launcherRef.current;
2141
+ if (!launcher) return;
2142
+ let side = "right";
2143
+ let desiredY = window.innerHeight - launcher.offsetHeight - 20;
2144
+ try {
2145
+ const stored = JSON.parse(
2146
+ localStorage.getItem(LAUNCHER_STORAGE_KEY) || "null"
2147
+ );
2148
+ if ((stored == null ? void 0 : stored.side) === "left" || (stored == null ? void 0 : stored.side) === "right")
2149
+ side = stored.side;
2150
+ if (typeof (stored == null ? void 0 : stored.y) === "number") desiredY = stored.y;
2151
+ } catch (e) {
2152
+ }
2153
+ launcherSideRef.current = side;
2154
+ setLauncherSide(side);
2155
+ const placeLauncher = () => {
2156
+ var _a, _b;
2157
+ const element = launcherRef.current;
2158
+ if (!element) return;
2159
+ const next = resolveSuiteAiLauncherPosition(
2160
+ launcherSideRef.current,
2161
+ (_b = (_a = launcherPositionRef.current) == null ? void 0 : _a.y) != null ? _b : desiredY,
2162
+ window.innerWidth,
2163
+ window.innerHeight,
2164
+ element.offsetWidth,
2165
+ element.offsetHeight
2166
+ );
2167
+ launcherPositionRef.current = next;
2168
+ setLauncherPosition(next);
2169
+ };
2170
+ placeLauncher();
2171
+ window.addEventListener("resize", placeLauncher);
2172
+ return () => window.removeEventListener("resize", placeLauncher);
2173
+ }, []);
2174
+ const scrollToBottom = useCallback3(() => {
2175
+ const el = scrollerRef.current;
2176
+ if (!el) return;
2177
+ el.scrollTop = el.scrollHeight;
2178
+ }, []);
2179
+ const hydrate = useCallback3(async () => {
2180
+ var _a;
2181
+ setHydrating(true);
2182
+ setError("");
2183
+ try {
2184
+ const data = await fetchChat();
2185
+ setMessages((_a = data.messages) != null ? _a : []);
2186
+ if (!data.configured) {
2187
+ setError("ShellStack AI is not configured for this workspace yet.");
2188
+ }
2189
+ } catch (err) {
2190
+ setError(
2191
+ err instanceof Error ? err.message : "Could not load your Ask AI chat."
2192
+ );
2193
+ } finally {
2194
+ setHydrating(false);
2195
+ }
2196
+ }, [fetchChat]);
2197
+ useEffect7(() => {
2198
+ function onOpen(event) {
2199
+ var _a;
2200
+ const detail = event.detail;
2201
+ setOpen(true);
2202
+ if ((_a = detail == null ? void 0 : detail.prompt) == null ? void 0 : _a.trim()) {
2203
+ pendingPromptRef.current = detail.prompt.trim();
2204
+ setPrompt(detail.prompt.trim());
2205
+ }
2206
+ }
2207
+ window.addEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
2208
+ return () => window.removeEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
2209
+ }, []);
2210
+ useEffect7(() => {
2211
+ if (!open) return;
2212
+ void hydrate().then(() => {
2213
+ const pending = pendingPromptRef.current;
2214
+ pendingPromptRef.current = null;
2215
+ if (pending) {
2216
+ setPrompt(pending);
2217
+ }
2218
+ });
2219
+ }, [open, hydrate]);
2220
+ useEffect7(() => {
2221
+ if (open) scrollToBottom();
2222
+ }, [messages, loading, open, scrollToBottom]);
1636
2223
  async function ask(text) {
2224
+ var _a;
1637
2225
  const trimmed = text.trim();
1638
2226
  if (!trimmed || loading) return;
1639
2227
  setLoading(true);
1640
2228
  setError("");
1641
- setAnswer("");
1642
- setCopied(false);
2229
+ setPrompt("");
2230
+ setMessages((prev) => [...prev, { role: "user", content: trimmed }]);
1643
2231
  try {
1644
- const status = await fetchStatus();
1645
- if (!status.configured) {
1646
- setError("ShellStack AI is not configured for this workspace yet.");
1647
- return;
1648
- }
1649
- const result = await complete({ prompt: trimmed });
1650
- setAnswer(result.text);
2232
+ const result = await sendMessage({ prompt: trimmed });
2233
+ setMessages((_a = result.messages) != null ? _a : []);
1651
2234
  } catch (err) {
2235
+ setMessages(
2236
+ (prev) => {
2237
+ var _a2, _b;
2238
+ return ((_a2 = prev[prev.length - 1]) == null ? void 0 : _a2.role) === "user" && ((_b = prev[prev.length - 1]) == null ? void 0 : _b.content) === trimmed ? prev.slice(0, -1) : prev;
2239
+ }
2240
+ );
2241
+ setPrompt(trimmed);
1652
2242
  setError(
1653
2243
  err instanceof Error ? err.message : "The AI request failed. Try again."
1654
2244
  );
@@ -1656,133 +2246,395 @@ function SuiteAiPanel({
1656
2246
  setLoading(false);
1657
2247
  }
1658
2248
  }
1659
- async function copyAnswer() {
1660
- if (!answer) return;
2249
+ async function handleClear() {
2250
+ if (loading) return;
1661
2251
  try {
1662
- await navigator.clipboard.writeText(answer);
1663
- setCopied(true);
1664
- window.setTimeout(() => setCopied(false), 1500);
2252
+ await clearChat();
2253
+ setMessages([]);
2254
+ setError("");
2255
+ } catch (err) {
2256
+ setError(
2257
+ err instanceof Error ? err.message : "Could not clear this chat."
2258
+ );
2259
+ }
2260
+ }
2261
+ async function copyMessage(index, content) {
2262
+ try {
2263
+ await navigator.clipboard.writeText(content);
2264
+ setCopiedId(index);
2265
+ window.setTimeout(() => setCopiedId(null), 1500);
1665
2266
  } catch (e) {
1666
2267
  }
1667
2268
  }
1668
- return /* @__PURE__ */ jsxs13(Fragment5, { children: [
1669
- /* @__PURE__ */ jsxs13(
1670
- "button",
1671
- {
1672
- type: "button",
1673
- "data-test": "ai-panel-launcher",
1674
- onClick: () => setOpen(true),
1675
- className: "fixed bottom-5 right-5 z-30 flex items-center gap-2 rounded-full border border-ph-border bg-ph-surface px-4 py-2.5 text-sm font-medium text-ph-ink shadow-ph hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
1676
- "aria-label": "Open ShellStack AI",
1677
- children: [
1678
- /* @__PURE__ */ jsx16(BrandIcon, { className: "h-4 w-4 text-ph-brand" }),
1679
- "Ask AI"
1680
- ]
1681
- }
1682
- ),
1683
- open && /* @__PURE__ */ jsxs13("div", { className: "fixed inset-0 z-40", role: "dialog", "aria-modal": "true", children: [
1684
- /* @__PURE__ */ jsx16(
2269
+ function saveLauncherPosition(side, position) {
2270
+ try {
2271
+ localStorage.setItem(
2272
+ LAUNCHER_STORAGE_KEY,
2273
+ JSON.stringify({ side, y: position.y })
2274
+ );
2275
+ } catch (e) {
2276
+ }
2277
+ }
2278
+ function dockLauncher(side, desiredY) {
2279
+ const launcher = launcherRef.current;
2280
+ if (!launcher) return;
2281
+ const next = resolveSuiteAiLauncherPosition(
2282
+ side,
2283
+ desiredY,
2284
+ window.innerWidth,
2285
+ window.innerHeight,
2286
+ launcher.offsetWidth,
2287
+ launcher.offsetHeight
2288
+ );
2289
+ launcherSideRef.current = side;
2290
+ launcherPositionRef.current = next;
2291
+ setLauncherSide(side);
2292
+ setLauncherPosition(next);
2293
+ saveLauncherPosition(side, next);
2294
+ }
2295
+ function startLauncherDrag(event) {
2296
+ if (event.pointerType === "mouse" && event.button !== 0) return;
2297
+ const rect = event.currentTarget.getBoundingClientRect();
2298
+ launcherDragRef.current = {
2299
+ pointerId: event.pointerId,
2300
+ offsetX: event.clientX - rect.left,
2301
+ offsetY: event.clientY - rect.top,
2302
+ startX: event.clientX,
2303
+ startY: event.clientY
2304
+ };
2305
+ suppressLauncherClickRef.current = false;
2306
+ event.currentTarget.setPointerCapture(event.pointerId);
2307
+ setDraggingLauncher(true);
2308
+ }
2309
+ function moveLauncher(event) {
2310
+ const drag = launcherDragRef.current;
2311
+ if (!drag || drag.pointerId !== event.pointerId) return;
2312
+ const launcher = event.currentTarget;
2313
+ const maxX = Math.max(
2314
+ LAUNCHER_EDGE_GAP,
2315
+ window.innerWidth - launcher.offsetWidth - LAUNCHER_EDGE_GAP
2316
+ );
2317
+ const maxY = Math.max(
2318
+ LAUNCHER_EDGE_GAP,
2319
+ window.innerHeight - launcher.offsetHeight - LAUNCHER_EDGE_GAP
2320
+ );
2321
+ const next = {
2322
+ x: Math.min(
2323
+ Math.max(event.clientX - drag.offsetX, LAUNCHER_EDGE_GAP),
2324
+ maxX
2325
+ ),
2326
+ y: Math.min(
2327
+ Math.max(event.clientY - drag.offsetY, LAUNCHER_EDGE_GAP),
2328
+ maxY
2329
+ )
2330
+ };
2331
+ if (Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) > 4) {
2332
+ suppressLauncherClickRef.current = true;
2333
+ }
2334
+ launcherPositionRef.current = next;
2335
+ setLauncherPosition(next);
2336
+ }
2337
+ function finishLauncherDrag(event) {
2338
+ var _a, _b, _c, _d;
2339
+ const drag = launcherDragRef.current;
2340
+ if (!drag || drag.pointerId !== event.pointerId) return;
2341
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
2342
+ event.currentTarget.releasePointerCapture(event.pointerId);
2343
+ }
2344
+ launcherDragRef.current = null;
2345
+ setDraggingLauncher(false);
2346
+ const rect = event.currentTarget.getBoundingClientRect();
2347
+ const currentX = (_b = (_a = launcherPositionRef.current) == null ? void 0 : _a.x) != null ? _b : rect.left;
2348
+ const side = currentX + rect.width / 2 < window.innerWidth / 2 ? "left" : "right";
2349
+ dockLauncher(side, (_d = (_c = launcherPositionRef.current) == null ? void 0 : _c.y) != null ? _d : rect.top);
2350
+ }
2351
+ function moveLauncherWithKeyboard(event) {
2352
+ var _a, _b;
2353
+ if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key))
2354
+ return;
2355
+ event.preventDefault();
2356
+ const currentY = (_b = (_a = launcherPositionRef.current) == null ? void 0 : _a.y) != null ? _b : event.currentTarget.getBoundingClientRect().top;
2357
+ if (event.key === "ArrowLeft") dockLauncher("left", currentY);
2358
+ else if (event.key === "ArrowRight") dockLauncher("right", currentY);
2359
+ else
2360
+ dockLauncher(
2361
+ launcherSideRef.current,
2362
+ currentY + (event.key === "ArrowUp" ? -24 : 24)
2363
+ );
2364
+ }
2365
+ const Icon = BrandIcon != null ? BrandIcon : Sparks;
2366
+ return /* @__PURE__ */ jsxs15(Fragment5, { children: [
2367
+ !hideLauncher ? /* @__PURE__ */ jsxs15(Fragment5, { children: [
2368
+ /* @__PURE__ */ jsxs15(
2369
+ "button",
2370
+ {
2371
+ ref: launcherRef,
2372
+ type: "button",
2373
+ "data-test": "ai-panel-launcher",
2374
+ onClick: () => {
2375
+ if (suppressLauncherClickRef.current) {
2376
+ suppressLauncherClickRef.current = false;
2377
+ return;
2378
+ }
2379
+ setOpen(true);
2380
+ },
2381
+ onPointerDown: startLauncherDrag,
2382
+ onPointerMove: moveLauncher,
2383
+ onPointerUp: finishLauncherDrag,
2384
+ onPointerCancel: finishLauncherDrag,
2385
+ onKeyDown: moveLauncherWithKeyboard,
2386
+ className: `fixed z-30 flex touch-none select-none items-center gap-2 rounded-full border border-ph-border bg-ph-surface px-4 py-2.5 text-sm font-medium text-ph-ink shadow-ph hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand ${draggingLauncher ? "cursor-grabbing" : "cursor-grab"}`,
2387
+ style: launcherPosition ? { left: launcherPosition.x, top: launcherPosition.y } : { bottom: 20, right: 20 },
2388
+ "aria-label": "Open Ask AI",
2389
+ "aria-describedby": "suite-ask-ai-launcher-help",
2390
+ children: [
2391
+ /* @__PURE__ */ jsx18(Icon, { className: "h-4 w-4 text-ph-brand" }),
2392
+ "Ask AI"
2393
+ ]
2394
+ }
2395
+ ),
2396
+ /* @__PURE__ */ jsx18("span", { id: "suite-ask-ai-launcher-help", className: "sr-only", children: "Drag to reposition, or use arrow keys. The launcher docks to the nearest screen edge." })
2397
+ ] }) : null,
2398
+ open ? /* @__PURE__ */ jsxs15("div", { className: "fixed inset-0 z-40", role: "dialog", "aria-modal": "true", children: [
2399
+ /* @__PURE__ */ jsx18(
1685
2400
  "button",
1686
2401
  {
1687
2402
  type: "button",
1688
2403
  className: "absolute inset-0 bg-black/25",
1689
2404
  onClick: () => setOpen(false),
1690
- "aria-label": "Close AI panel"
2405
+ "aria-label": "Close Ask AI"
1691
2406
  }
1692
2407
  ),
1693
- /* @__PURE__ */ jsxs13("aside", { className: "absolute inset-y-0 right-0 flex w-full max-w-md flex-col border-l border-ph-border bg-ph-surface shadow-2xl", children: [
1694
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between border-b border-ph-border px-4 py-3", children: [
1695
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", children: [
1696
- /* @__PURE__ */ jsx16(BrandIcon, { className: "h-4 w-4 text-ph-brand" }),
1697
- /* @__PURE__ */ jsx16("span", { className: "text-sm font-semibold text-ph-ink", children: "ShellStack AI" })
1698
- ] }),
1699
- /* @__PURE__ */ jsx16(
1700
- "button",
1701
- {
1702
- type: "button",
1703
- onClick: () => setOpen(false),
1704
- className: "rounded p-1 hover:bg-ph-muted",
1705
- "aria-label": "Close",
1706
- children: /* @__PURE__ */ jsx16(X2, { className: "h-4 w-4" })
1707
- }
1708
- )
1709
- ] }),
1710
- /* @__PURE__ */ jsx16("div", { className: "flex flex-wrap gap-1.5 border-b border-ph-border px-4 py-2", children: presets.map((preset) => /* @__PURE__ */ jsx16(
1711
- "button",
1712
- {
1713
- type: "button",
1714
- onClick: () => {
1715
- setPrompt(preset.prompt);
1716
- void ask(preset.prompt);
1717
- },
1718
- disabled: loading,
1719
- className: "rounded-md border border-ph-border px-2 py-1 text-xs text-ph-ink hover:bg-ph-muted disabled:opacity-50",
1720
- children: preset.label
1721
- },
1722
- preset.label
1723
- )) }),
1724
- /* @__PURE__ */ jsxs13("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
1725
- error && /* @__PURE__ */ jsx16("p", { className: "bg-ph-danger/10 rounded-lg px-3 py-2 text-sm text-ph-danger", children: error }),
1726
- loading && /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2 text-sm text-ph-mutedtext", children: [
1727
- /* @__PURE__ */ jsx16(Spinner, { className: "h-4 w-4 animate-spin" }),
1728
- "Thinking\u2026"
1729
- ] }),
1730
- answer && !loading && /* @__PURE__ */ jsxs13("div", { className: "rounded-lg bg-ph-muted px-3 py-2 text-sm text-ph-ink", children: [
1731
- /* @__PURE__ */ jsx16("p", { className: "whitespace-pre-wrap", children: answer }),
1732
- /* @__PURE__ */ jsxs13(
2408
+ /* @__PURE__ */ jsxs15(
2409
+ "aside",
2410
+ {
2411
+ className: `absolute inset-y-0 flex w-full max-w-md flex-col bg-ph-surface shadow-2xl ${launcherSide === "left" ? "left-0 border-r border-ph-border" : "right-0 border-l border-ph-border"}`,
2412
+ "data-test": "suite-ask-ai-panel",
2413
+ children: [
2414
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center justify-between border-b border-ph-border px-4 py-3", children: [
2415
+ /* @__PURE__ */ jsxs15("div", { className: "min-w-0", children: [
2416
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2", children: [
2417
+ /* @__PURE__ */ jsx18(Icon, { className: "h-4 w-4 shrink-0 text-ph-brand" }),
2418
+ /* @__PURE__ */ jsx18("span", { className: "text-sm font-semibold text-ph-ink", children: title })
2419
+ ] }),
2420
+ /* @__PURE__ */ jsx18("p", { className: "mt-0.5 text-[11px] text-ph-mutedtext", children: "Shared across your suite \xB7 same chat in every app" })
2421
+ ] }),
2422
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-1", children: [
2423
+ messages.length > 0 ? /* @__PURE__ */ jsx18(
2424
+ "button",
2425
+ {
2426
+ type: "button",
2427
+ onClick: () => void handleClear(),
2428
+ disabled: loading,
2429
+ className: "rounded-md px-2 py-1 text-xs text-ph-mutedtext hover:bg-ph-muted hover:text-ph-ink disabled:opacity-50",
2430
+ "data-test": "ask-ai-clear",
2431
+ children: "Clear"
2432
+ }
2433
+ ) : null,
2434
+ /* @__PURE__ */ jsx18(
2435
+ "button",
2436
+ {
2437
+ type: "button",
2438
+ onClick: () => setOpen(false),
2439
+ className: "rounded p-1 hover:bg-ph-muted",
2440
+ "aria-label": "Close",
2441
+ children: /* @__PURE__ */ jsx18(X2, { className: "h-4 w-4" })
2442
+ }
2443
+ )
2444
+ ] })
2445
+ ] }),
2446
+ presets.length > 0 && messages.length === 0 ? /* @__PURE__ */ jsx18("div", { className: "flex flex-wrap gap-1.5 border-b border-ph-border px-4 py-2", children: presets.map((preset) => /* @__PURE__ */ jsx18(
1733
2447
  "button",
1734
2448
  {
1735
2449
  type: "button",
1736
- onClick: () => void copyAnswer(),
1737
- className: "mt-2 inline-flex items-center gap-1 text-xs text-ph-mutedtext hover:text-ph-ink",
2450
+ onClick: () => void ask(preset.prompt),
2451
+ disabled: loading || hydrating,
2452
+ className: "rounded-full border border-ph-border px-2.5 py-1 text-xs text-ph-ink hover:bg-ph-muted disabled:opacity-50",
2453
+ children: preset.label
2454
+ },
2455
+ preset.label
2456
+ )) }) : null,
2457
+ /* @__PURE__ */ jsxs15(
2458
+ "div",
2459
+ {
2460
+ ref: scrollerRef,
2461
+ className: "flex-1 space-y-3 overflow-y-auto p-4",
1738
2462
  children: [
1739
- copied ? /* @__PURE__ */ jsx16(Check2, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx16(Copy, { className: "h-3 w-3" }),
1740
- copied ? "Copied" : "Copy"
2463
+ error ? /* @__PURE__ */ jsx18("p", { className: "bg-ph-danger/10 rounded-lg px-3 py-2 text-sm text-ph-danger", children: error }) : null,
2464
+ hydrating && messages.length === 0 ? /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2 text-sm text-ph-mutedtext", children: [
2465
+ /* @__PURE__ */ jsx18(Spinner, { className: "h-4 w-4 animate-spin" }),
2466
+ "Loading chat\u2026"
2467
+ ] }) : null,
2468
+ !hydrating && messages.length === 0 && !error ? /* @__PURE__ */ jsxs15("div", { className: "mt-10 px-2 text-center", children: [
2469
+ /* @__PURE__ */ jsx18("p", { className: "font-display text-base font-semibold text-ph-ink", children: "How can I help?" }),
2470
+ /* @__PURE__ */ jsx18("p", { className: "mt-2 text-sm leading-relaxed text-ph-subtle", children: emptyState })
2471
+ ] }) : null,
2472
+ messages.map((message, index) => {
2473
+ const isUser = message.role === "user";
2474
+ return /* @__PURE__ */ jsxs15(
2475
+ "div",
2476
+ {
2477
+ className: isUser ? "ml-8 rounded-2xl bg-ph-brand/10 px-3.5 py-2.5 text-sm text-ph-ink" : "mr-4 rounded-2xl bg-ph-muted px-3.5 py-2.5 text-sm text-ph-ink",
2478
+ "data-test": isUser ? "ask-ai-user" : "ask-ai-assistant",
2479
+ children: [
2480
+ /* @__PURE__ */ jsx18("p", { className: "whitespace-pre-wrap", children: message.content }),
2481
+ !isUser ? /* @__PURE__ */ jsxs15(
2482
+ "button",
2483
+ {
2484
+ type: "button",
2485
+ onClick: () => void copyMessage(index, message.content),
2486
+ className: "mt-2 inline-flex items-center gap-1 text-xs text-ph-mutedtext hover:text-ph-ink",
2487
+ children: [
2488
+ copiedId === index ? /* @__PURE__ */ jsx18(Check2, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx18(Copy, { className: "h-3 w-3" }),
2489
+ copiedId === index ? "Copied" : "Copy"
2490
+ ]
2491
+ }
2492
+ ) : null
2493
+ ]
2494
+ },
2495
+ `${message.role}-${index}-${message.content.slice(0, 24)}`
2496
+ );
2497
+ }),
2498
+ loading ? /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2 text-sm text-ph-mutedtext", children: [
2499
+ /* @__PURE__ */ jsx18(Spinner, { className: "h-4 w-4 animate-spin" }),
2500
+ "Thinking\u2026"
2501
+ ] }) : null
1741
2502
  ]
1742
2503
  }
1743
- )
1744
- ] }),
1745
- !answer && !loading && !error && /* @__PURE__ */ jsx16("p", { className: "mt-8 text-center text-xs text-ph-mutedtext", children: emptyState })
1746
- ] }),
1747
- /* @__PURE__ */ jsx16("div", { className: "border-t border-ph-border p-3", children: /* @__PURE__ */ jsxs13("div", { className: "flex gap-2", children: [
1748
- /* @__PURE__ */ jsx16(
1749
- "textarea",
1750
- {
1751
- value: prompt,
1752
- onChange: (e) => setPrompt(e.target.value),
1753
- onKeyDown: (e) => {
1754
- if (e.key === "Enter" && !e.shiftKey) {
1755
- e.preventDefault();
1756
- void ask(prompt);
2504
+ ),
2505
+ /* @__PURE__ */ jsx18("div", { className: "border-t border-ph-border p-3", children: /* @__PURE__ */ jsxs15("div", { className: "flex gap-2", children: [
2506
+ /* @__PURE__ */ jsx18(
2507
+ "textarea",
2508
+ {
2509
+ value: prompt,
2510
+ onChange: (e) => setPrompt(e.target.value),
2511
+ onKeyDown: (e) => {
2512
+ if (e.key === "Enter" && !e.shiftKey) {
2513
+ e.preventDefault();
2514
+ void ask(prompt);
2515
+ }
2516
+ },
2517
+ placeholder: "Ask anything\u2026",
2518
+ rows: 2,
2519
+ disabled: loading,
2520
+ "data-test": "ask-ai-input",
2521
+ className: "flex-1 resize-none rounded-xl border border-ph-border bg-ph-canvas p-2.5 text-sm text-ph-ink outline-none focus:ring-1 focus:ring-ph-brand"
1757
2522
  }
1758
- },
1759
- placeholder: "Ask anything\u2026",
1760
- rows: 2,
1761
- disabled: loading,
1762
- className: "flex-1 resize-none rounded-lg border border-ph-border bg-ph-canvas p-2 text-sm text-ph-ink outline-none focus:ring-1 focus:ring-ph-brand"
1763
- }
1764
- ),
1765
- /* @__PURE__ */ jsx16(
1766
- "button",
1767
- {
1768
- type: "button",
1769
- onClick: () => void ask(prompt),
1770
- disabled: loading || !prompt.trim(),
1771
- className: "self-end rounded-lg bg-ph-brand px-3 py-2 text-sm font-medium text-white disabled:opacity-50",
1772
- children: "Ask"
1773
- }
1774
- )
1775
- ] }) })
1776
- ] })
1777
- ] })
2523
+ ),
2524
+ /* @__PURE__ */ jsx18(
2525
+ "button",
2526
+ {
2527
+ type: "button",
2528
+ onClick: () => void ask(prompt),
2529
+ disabled: loading || !prompt.trim(),
2530
+ "data-test": "ask-ai-send",
2531
+ className: "self-end rounded-xl bg-ph-brand px-3.5 py-2 text-sm font-medium text-white disabled:opacity-50",
2532
+ children: "Ask"
2533
+ }
2534
+ )
2535
+ ] }) })
2536
+ ]
2537
+ }
2538
+ )
2539
+ ] }) : null
1778
2540
  ] });
1779
2541
  }
1780
2542
 
2543
+ // src/suite/lib/use-sidebar-width.ts
2544
+ import {
2545
+ useCallback as useCallback4,
2546
+ useEffect as useEffect8,
2547
+ useState as useState9
2548
+ } from "react";
2549
+ var SIDEBAR_RAIL_WIDTH = 56;
2550
+ var SIDEBAR_COLLAPSE_THRESHOLD = 80;
2551
+ var SIDEBAR_MIN_EXPANDED_WIDTH = 180;
2552
+ var SIDEBAR_MAX_WIDTH = 320;
2553
+ var SIDEBAR_DEFAULT_WIDTH = 320;
2554
+ function clampWidth(width) {
2555
+ return Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_RAIL_WIDTH, width));
2556
+ }
2557
+ function snapWidth(width) {
2558
+ if (width <= SIDEBAR_COLLAPSE_THRESHOLD) return SIDEBAR_RAIL_WIDTH;
2559
+ if (width < SIDEBAR_MIN_EXPANDED_WIDTH) return SIDEBAR_MIN_EXPANDED_WIDTH;
2560
+ return Math.min(SIDEBAR_MAX_WIDTH, width);
2561
+ }
2562
+ function useSidebarWidth(storageKey) {
2563
+ const [width, setWidth] = useState9(SIDEBAR_DEFAULT_WIDTH);
2564
+ const [narrowViewport, setNarrowViewport] = useState9(false);
2565
+ const collapsed = narrowViewport || width <= SIDEBAR_COLLAPSE_THRESHOLD;
2566
+ useEffect8(() => {
2567
+ let stored = null;
2568
+ try {
2569
+ stored = localStorage.getItem(storageKey);
2570
+ } catch (e) {
2571
+ }
2572
+ if (!stored) return;
2573
+ const parsed = Number.parseInt(stored, 10);
2574
+ if (!Number.isNaN(parsed)) {
2575
+ queueMicrotask(() => setWidth(snapWidth(clampWidth(parsed))));
2576
+ }
2577
+ }, [storageKey]);
2578
+ useEffect8(() => {
2579
+ const media = window.matchMedia("(max-width: 639px)");
2580
+ const sync = () => setNarrowViewport(media.matches);
2581
+ sync();
2582
+ media.addEventListener("change", sync);
2583
+ return () => media.removeEventListener("change", sync);
2584
+ }, []);
2585
+ const persist = useCallback4(
2586
+ (value) => {
2587
+ try {
2588
+ localStorage.setItem(storageKey, String(value));
2589
+ } catch (e) {
2590
+ }
2591
+ },
2592
+ [storageKey]
2593
+ );
2594
+ const setCollapsed = useCallback4(
2595
+ (value) => {
2596
+ setWidth((current) => {
2597
+ const next = value ? SIDEBAR_RAIL_WIDTH : current <= SIDEBAR_COLLAPSE_THRESHOLD ? SIDEBAR_DEFAULT_WIDTH : snapWidth(current);
2598
+ persist(next);
2599
+ return next;
2600
+ });
2601
+ },
2602
+ [persist]
2603
+ );
2604
+ const resetWidth = useCallback4(() => {
2605
+ setWidth(SIDEBAR_DEFAULT_WIDTH);
2606
+ persist(SIDEBAR_DEFAULT_WIDTH);
2607
+ }, [persist]);
2608
+ const startResize = useCallback4(
2609
+ (event) => {
2610
+ event.preventDefault();
2611
+ const startX = event.clientX;
2612
+ const startWidth = width;
2613
+ const onMove = (moveEvent) => {
2614
+ setWidth(clampWidth(startWidth + moveEvent.clientX - startX));
2615
+ };
2616
+ const onUp = () => {
2617
+ document.removeEventListener("pointermove", onMove);
2618
+ document.removeEventListener("pointerup", onUp);
2619
+ setWidth((current) => {
2620
+ const snapped = snapWidth(current);
2621
+ persist(snapped);
2622
+ return snapped;
2623
+ });
2624
+ };
2625
+ document.addEventListener("pointermove", onMove);
2626
+ document.addEventListener("pointerup", onUp);
2627
+ },
2628
+ [persist, width]
2629
+ );
2630
+ return { width, collapsed, setCollapsed, resetWidth, startResize };
2631
+ }
2632
+
1781
2633
  // src/suite/lib/motion.tsx
1782
2634
  import { LazyMotion, domAnimation, m } from "motion/react";
1783
- import { jsx as jsx17 } from "react/jsx-runtime";
2635
+ import { jsx as jsx19 } from "react/jsx-runtime";
1784
2636
  function SuiteMotionProvider({ children }) {
1785
- return /* @__PURE__ */ jsx17(LazyMotion, { features: domAnimation, strict: true, children });
2637
+ return /* @__PURE__ */ jsx19(LazyMotion, { features: domAnimation, strict: true, children });
1786
2638
  }
1787
2639
  var SUITE_SPRINGS = {
1788
2640
  /** Snappy panel/sheet entrances — small overshoot. */
@@ -1793,217 +2645,25 @@ var SUITE_SPRINGS = {
1793
2645
  press: { type: "spring", stiffness: 600, damping: 35, mass: 0.5 }
1794
2646
  };
1795
2647
 
1796
- // src/suite/components/suite-layout.tsx
1797
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1798
- var WIDTHS = {
1799
- full: "max-w-none",
1800
- wide: "max-w-7xl",
1801
- content: "max-w-6xl",
1802
- narrow: "max-w-4xl"
1803
- };
1804
- function SuiteBreadcrumbs({ items, className }) {
1805
- return /* @__PURE__ */ jsx18("nav", { "aria-label": "Breadcrumb", className, children: /* @__PURE__ */ jsx18("ol", { className: "flex min-w-0 items-center gap-1.5", children: items.map((item, index) => {
1806
- const isLast = index === items.length - 1;
1807
- return /* @__PURE__ */ jsxs14("li", { className: "flex min-w-0 items-center gap-1.5", children: [
1808
- index > 0 ? /* @__PURE__ */ jsx18("span", { className: "select-none text-ph-border", "aria-hidden": "true", children: "/" }) : null,
1809
- item.href && !isLast ? /* @__PURE__ */ jsx18(
1810
- "a",
1811
- {
1812
- href: item.href,
1813
- className: "truncate transition hover:text-ph-ink",
1814
- children: item.label
1815
- }
1816
- ) : /* @__PURE__ */ jsx18(
1817
- "span",
1818
- {
1819
- className: cn(
1820
- "truncate",
1821
- isLast ? "font-medium text-ph-ink" : "text-ph-subtle"
1822
- ),
1823
- "aria-current": isLast ? "page" : void 0,
1824
- children: item.label
1825
- }
1826
- )
1827
- ] }, index);
1828
- }) }) });
1829
- }
1830
- function SuitePage({
1831
- children,
1832
- width = "wide",
1833
- inset = true,
1834
- className,
1835
- as: Component = "div",
1836
- dataTest,
1837
- id,
1838
- role,
1839
- "aria-live": ariaLive
1840
- }) {
1841
- return /* @__PURE__ */ jsx18(
1842
- Component,
1843
- {
1844
- id,
1845
- "data-test": dataTest,
1846
- role,
1847
- "aria-live": ariaLive,
1848
- className: cn(
1849
- "mx-auto flex w-full min-w-0 flex-col",
1850
- WIDTHS[width],
1851
- // Tight inset keeps page titles aligned without excessive whitespace.
1852
- inset && "px-4 pb-5 pt-2 sm:px-6 sm:pb-6 sm:pt-3 lg:px-8",
1853
- className
1854
- ),
1855
- children
1856
- }
1857
- );
1858
- }
1859
- function SuitePageHeader({
1860
- title,
1861
- description,
1862
- eyebrow,
1863
- actions,
1864
- className,
1865
- titleClassName,
1866
- dataTest,
1867
- /** Optional icon rendered before the title. */
1868
- icon,
1869
- /** Use the animated sun/moon icon from the Today page. */
1870
- todayIcon,
1871
- todayIconClassName,
1872
- /** Small badge/pill next to the title (e.g. "Beta", org name). */
1873
- badge,
1874
- /** Breadcrumb line rendered above the title. */
1875
- breadcrumb
1876
- }) {
1877
- const resolvedIcon = todayIcon ? /* @__PURE__ */ jsx18(
1878
- TodayTimeIcon,
1879
- {
1880
- className: cn(
1881
- "h-6 w-6 sm:h-7 sm:w-7 text-amber-500",
1882
- todayIconClassName
1883
- )
1884
- }
1885
- ) : icon;
1886
- return /* @__PURE__ */ jsxs14(
1887
- "header",
1888
- {
1889
- "data-test": dataTest,
1890
- className: cn(
1891
- "flex items-start justify-between gap-4 pb-2 sm:pb-3",
1892
- className
1893
- ),
1894
- children: [
1895
- /* @__PURE__ */ jsxs14("div", { className: "flex min-w-0 items-start gap-3", children: [
1896
- resolvedIcon ? /* @__PURE__ */ jsx18(
1897
- "div",
1898
- {
1899
- className: "hidden shrink-0 pt-1 sm:block",
1900
- "aria-hidden": "true",
1901
- children: resolvedIcon
1902
- }
1903
- ) : null,
1904
- /* @__PURE__ */ jsxs14("div", { className: "flex min-w-0 flex-col gap-1", children: [
1905
- eyebrow ? /* @__PURE__ */ jsx18("div", { className: "mb-0.5 flex min-w-0 items-center gap-1.5 text-xs text-ph-subtle", children: eyebrow }) : null,
1906
- breadcrumb ? /* @__PURE__ */ jsx18("div", { className: "mb-0.5 flex min-w-0 items-center gap-1.5 text-xs text-ph-subtle", children: breadcrumb }) : null,
1907
- /* @__PURE__ */ jsxs14("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", children: [
1908
- /* @__PURE__ */ jsx18(
1909
- "h1",
1910
- {
1911
- className: cn(
1912
- "font-display text-[1.625rem] font-bold leading-tight tracking-tight text-ph-ink sm:text-[2.25rem]",
1913
- titleClassName
1914
- ),
1915
- children: title
1916
- }
1917
- ),
1918
- badge ? /* @__PURE__ */ jsx18("span", { className: "rounded-md bg-ph-muted px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-ph-subtle", children: badge }) : null
1919
- ] }),
1920
- description ? /* @__PURE__ */ jsx18("div", { className: "mt-1 max-w-2xl text-pretty text-sm leading-5 text-ph-subtle", children: description }) : null
1921
- ] })
1922
- ] }),
1923
- actions ? /* @__PURE__ */ jsx18("div", { className: "flex shrink-0 items-center gap-2 pt-1", children: actions }) : null
1924
- ]
1925
- }
1926
- );
1927
- }
1928
- function SuiteToolbar({
1929
- children,
1930
- className,
1931
- label = "Page tools",
1932
- dataTest
1933
- }) {
1934
- return /* @__PURE__ */ jsx18(
1935
- "div",
1936
- {
1937
- role: "toolbar",
1938
- "aria-label": label,
1939
- "data-test": dataTest,
1940
- className: cn("flex min-w-0 flex-wrap items-center gap-2", className),
1941
- children
1942
- }
1943
- );
1944
- }
1945
- function SuiteTabList({
1946
- children,
1947
- className,
1948
- label,
1949
- dataTest
1950
- }) {
1951
- return /* @__PURE__ */ jsx18(
1952
- "nav",
1953
- {
1954
- "aria-label": label,
1955
- "data-test": dataTest,
1956
- className: cn(
1957
- "flex min-w-0 flex-wrap gap-2 [&_a]:min-h-[48px] [&_button]:min-h-[48px] sm:[&_a]:min-h-[44px] sm:[&_button]:min-h-[44px]",
1958
- className
1959
- ),
1960
- children
1961
- }
1962
- );
1963
- }
1964
- function SuiteSectionHeader({
1965
- title,
1966
- description,
1967
- action,
1968
- className
1969
- }) {
1970
- return /* @__PURE__ */ jsxs14(
1971
- "div",
1972
- {
1973
- className: cn(
1974
- "flex min-w-0 flex-col gap-4 sm:flex-row sm:items-end sm:justify-between",
1975
- className
1976
- ),
1977
- children: [
1978
- /* @__PURE__ */ jsxs14("div", { className: "min-w-0", children: [
1979
- /* @__PURE__ */ jsx18("h2", { className: "font-display text-balance text-lg font-semibold tracking-tight text-ph-ink sm:text-xl", children: title }),
1980
- description ? /* @__PURE__ */ jsx18("div", { className: "mt-1 max-w-2xl text-pretty text-sm leading-5 text-ph-subtle", children: description }) : null
1981
- ] }),
1982
- action ? /* @__PURE__ */ jsx18("div", { className: "flex min-h-[44px] shrink-0 items-center", children: action }) : null
1983
- ]
1984
- }
1985
- );
1986
- }
1987
-
1988
2648
  // src/suite/components/suite-skeleton.tsx
1989
- import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
2649
+ import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
1990
2650
  function SuiteSkeleton({
1991
2651
  className,
1992
2652
  lines = 1,
1993
2653
  circle,
1994
2654
  width
1995
2655
  }) {
1996
- return /* @__PURE__ */ jsx19(
2656
+ return /* @__PURE__ */ jsx20(
1997
2657
  "div",
1998
2658
  {
1999
2659
  className: cn("space-y-2", circle ? "flex flex-col items-center" : ""),
2000
2660
  "aria-hidden": true,
2001
- children: Array.from({ length: lines }).map((_, index) => /* @__PURE__ */ jsx19(
2661
+ children: Array.from({ length: lines }).map((_, index) => /* @__PURE__ */ jsx20(
2002
2662
  "div",
2003
2663
  {
2004
2664
  className: "animate-[skeleton-stagger_0.6s_ease-out_both]",
2005
2665
  style: { animationDelay: `${index * 80}ms` },
2006
- children: /* @__PURE__ */ jsx19(
2666
+ children: /* @__PURE__ */ jsx20(
2007
2667
  "div",
2008
2668
  {
2009
2669
  className: cn(
@@ -2027,35 +2687,35 @@ function SuiteSkeletonCard({
2027
2687
  rows = 3,
2028
2688
  header = true
2029
2689
  }) {
2030
- return /* @__PURE__ */ jsxs15(
2690
+ return /* @__PURE__ */ jsxs16(
2031
2691
  "div",
2032
2692
  {
2033
2693
  className: cn(
2034
- "ph-skeleton-card animate-[skeleton-stagger_0.5s_ease-out_both] rounded-2xl border border-ph-border/70 bg-ph-surface p-6 shadow-sm",
2694
+ "ph-skeleton-card animate-[skeleton-stagger_0.5s_ease-out_both] rounded-2xl border border-ph-border bg-ph-surface p-6 shadow-sm",
2035
2695
  className
2036
2696
  ),
2037
2697
  "aria-hidden": true,
2038
2698
  children: [
2039
- header ? /* @__PURE__ */ jsxs15(
2699
+ header ? /* @__PURE__ */ jsxs16(
2040
2700
  "div",
2041
2701
  {
2042
2702
  className: "mb-5 flex items-center gap-3 animate-[skeleton-stagger_0.5s_ease-out_both]",
2043
2703
  style: { animationDelay: "100ms" },
2044
2704
  children: [
2045
- /* @__PURE__ */ jsx19("div", { className: "ph-skeleton h-10 w-10 rounded-xl" }),
2046
- /* @__PURE__ */ jsxs15("div", { className: "flex-1 space-y-2", children: [
2047
- /* @__PURE__ */ jsx19("div", { className: "ph-skeleton h-4 w-1/3" }),
2048
- /* @__PURE__ */ jsx19("div", { className: "ph-skeleton h-3 w-1/2" })
2705
+ /* @__PURE__ */ jsx20("div", { className: "ph-skeleton h-10 w-10 rounded-xl" }),
2706
+ /* @__PURE__ */ jsxs16("div", { className: "flex-1 space-y-2", children: [
2707
+ /* @__PURE__ */ jsx20("div", { className: "ph-skeleton h-4 w-1/3" }),
2708
+ /* @__PURE__ */ jsx20("div", { className: "ph-skeleton h-3 w-1/2" })
2049
2709
  ] })
2050
2710
  ]
2051
2711
  }
2052
2712
  ) : null,
2053
- /* @__PURE__ */ jsx19("div", { className: "space-y-3", children: Array.from({ length: rows }).map((_, index) => /* @__PURE__ */ jsx19(
2713
+ /* @__PURE__ */ jsx20("div", { className: "space-y-3", children: Array.from({ length: rows }).map((_, index) => /* @__PURE__ */ jsx20(
2054
2714
  "div",
2055
2715
  {
2056
2716
  className: "animate-[skeleton-stagger_0.5s_ease-out_both]",
2057
2717
  style: { animationDelay: `${200 + index * 80}ms` },
2058
- children: /* @__PURE__ */ jsx19(
2718
+ children: /* @__PURE__ */ jsx20(
2059
2719
  "div",
2060
2720
  {
2061
2721
  className: "ph-skeleton h-3",
@@ -2073,16 +2733,16 @@ function SuiteSkeletonList({
2073
2733
  className,
2074
2734
  items = 4
2075
2735
  }) {
2076
- return /* @__PURE__ */ jsx19("div", { className: cn("space-y-3", className), "aria-hidden": true, children: Array.from({ length: items }).map((_, index) => /* @__PURE__ */ jsx19(
2736
+ return /* @__PURE__ */ jsx20("div", { className: cn("space-y-3", className), "aria-hidden": true, children: Array.from({ length: items }).map((_, index) => /* @__PURE__ */ jsx20(
2077
2737
  "div",
2078
2738
  {
2079
2739
  className: "animate-[skeleton-stagger_0.6s_ease-out_both]",
2080
2740
  style: { animationDelay: `${index * 100}ms` },
2081
- children: /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-3 rounded-xl border border-ph-border/60 bg-ph-surface p-4", children: [
2082
- /* @__PURE__ */ jsx19("div", { className: "ph-skeleton h-10 w-10 rounded-lg" }),
2083
- /* @__PURE__ */ jsxs15("div", { className: "min-w-0 flex-1 space-y-2", children: [
2084
- /* @__PURE__ */ jsx19("div", { className: "ph-skeleton h-3.5 w-3/4" }),
2085
- /* @__PURE__ */ jsx19("div", { className: "ph-skeleton h-3 w-1/2" })
2741
+ children: /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-3 rounded-xl border border-ph-border bg-ph-surface p-4", children: [
2742
+ /* @__PURE__ */ jsx20("div", { className: "ph-skeleton h-10 w-10 rounded-lg" }),
2743
+ /* @__PURE__ */ jsxs16("div", { className: "min-w-0 flex-1 space-y-2", children: [
2744
+ /* @__PURE__ */ jsx20("div", { className: "ph-skeleton h-3.5 w-3/4" }),
2745
+ /* @__PURE__ */ jsx20("div", { className: "ph-skeleton h-3 w-1/2" })
2086
2746
  ] })
2087
2747
  ] })
2088
2748
  },
@@ -2097,22 +2757,22 @@ function SuiteEmptyState({
2097
2757
  className,
2098
2758
  size = "md"
2099
2759
  }) {
2100
- return /* @__PURE__ */ jsxs15(
2760
+ return /* @__PURE__ */ jsxs16(
2101
2761
  "div",
2102
2762
  {
2103
2763
  className: cn(
2104
- "relative overflow-hidden rounded-2xl border border-ph-border/80 bg-ph-surface text-center",
2764
+ "relative overflow-hidden rounded-2xl border border-ph-border bg-ph-surface text-center",
2105
2765
  size === "sm" ? "px-5 py-8" : "px-6 py-12 sm:px-10 sm:py-16",
2106
2766
  className
2107
2767
  ),
2108
2768
  children: [
2109
- /* @__PURE__ */ jsxs15("div", { className: "pointer-events-none absolute inset-0 overflow-hidden opacity-60", "aria-hidden": true, children: [
2110
- /* @__PURE__ */ jsx19("div", { className: "absolute -left-10 -top-10 h-40 w-40 rounded-full bg-ph-brand/10 blur-3xl" }),
2111
- /* @__PURE__ */ jsx19("div", { className: "absolute -bottom-10 -right-10 h-40 w-40 rounded-full bg-ph-accent/10 blur-3xl" })
2769
+ /* @__PURE__ */ jsxs16("div", { className: "pointer-events-none absolute inset-0 overflow-hidden opacity-60", "aria-hidden": true, children: [
2770
+ /* @__PURE__ */ jsx20("div", { className: "absolute -left-10 -top-10 h-40 w-40 rounded-full bg-ph-brand/10 blur-3xl" }),
2771
+ /* @__PURE__ */ jsx20("div", { className: "absolute -bottom-10 -right-10 h-40 w-40 rounded-full bg-ph-accent/10 blur-3xl" })
2112
2772
  ] }),
2113
- /* @__PURE__ */ jsxs15("div", { className: "relative", children: [
2114
- icon ? /* @__PURE__ */ jsx19("div", { className: "mx-auto mb-4 inline-flex items-center justify-center rounded-2xl bg-ph-muted p-3 shadow-sm ring-1 ring-black/[0.06]", children: icon }) : null,
2115
- /* @__PURE__ */ jsx19(
2773
+ /* @__PURE__ */ jsxs16("div", { className: "relative", children: [
2774
+ icon ? /* @__PURE__ */ jsx20("div", { className: "mx-auto mb-4 inline-flex items-center justify-center rounded-2xl bg-ph-muted p-3 shadow-sm ring-1 ring-black/[0.06]", children: icon }) : null,
2775
+ /* @__PURE__ */ jsx20(
2116
2776
  "h3",
2117
2777
  {
2118
2778
  className: cn(
@@ -2122,8 +2782,8 @@ function SuiteEmptyState({
2122
2782
  children: title
2123
2783
  }
2124
2784
  ),
2125
- description ? /* @__PURE__ */ jsx19("p", { className: "mx-auto mt-2 max-w-sm text-sm leading-relaxed text-ph-subtle", children: description }) : null,
2126
- action ? /* @__PURE__ */ jsx19("div", { className: "mt-5 flex flex-wrap items-center justify-center gap-2", children: action }) : null
2785
+ description ? /* @__PURE__ */ jsx20("p", { className: "mx-auto mt-2 max-w-sm text-sm leading-relaxed text-ph-subtle", children: description }) : null,
2786
+ action ? /* @__PURE__ */ jsx20("div", { className: "mt-5 flex flex-wrap items-center justify-center gap-2", children: action }) : null
2127
2787
  ] })
2128
2788
  ]
2129
2789
  }
@@ -2132,23 +2792,25 @@ function SuiteEmptyState({
2132
2792
 
2133
2793
  // src/suite/components/suite-settings-mobile-nav.tsx
2134
2794
  import { isValidElement } from "react";
2135
- import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
2795
+ import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
2136
2796
  function SuiteSettingsMobileNav({
2137
2797
  items,
2138
2798
  activeHref,
2139
2799
  onSelect,
2140
- dataTest
2800
+ dataTest,
2801
+ ariaLabel = "Settings sections",
2802
+ className
2141
2803
  }) {
2142
- return /* @__PURE__ */ jsx20(
2804
+ return /* @__PURE__ */ jsx21(
2143
2805
  "nav",
2144
2806
  {
2145
2807
  "data-test": dataTest,
2146
- "aria-label": "Settings sections",
2147
- className: "-mx-4 px-4 lg:hidden",
2148
- children: /* @__PURE__ */ jsx20("div", { className: "flex snap-x gap-2 overflow-x-auto pb-3 pt-1 scrollbar-hide [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", children: items.map((item) => {
2808
+ "aria-label": ariaLabel,
2809
+ className: cn("suite-settings-mobile-nav lg:hidden", className),
2810
+ children: /* @__PURE__ */ jsx21("div", { className: "flex snap-x gap-2 overflow-x-auto pb-3 pt-1 scrollbar-hide [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", children: items.map((item) => {
2149
2811
  const Icon = item.icon;
2150
2812
  const active = activeHref === item.href || activeHref.startsWith(`${item.href}/`);
2151
- const iconNode = isValidElement(item.icon) ? item.icon : /* @__PURE__ */ jsx20(
2813
+ const iconNode = isValidElement(item.icon) ? item.icon : /* @__PURE__ */ jsx21(
2152
2814
  Icon,
2153
2815
  {
2154
2816
  className: cn(
@@ -2158,7 +2820,7 @@ function SuiteSettingsMobileNav({
2158
2820
  "aria-hidden": true
2159
2821
  }
2160
2822
  );
2161
- return /* @__PURE__ */ jsxs16(
2823
+ return /* @__PURE__ */ jsxs17(
2162
2824
  "button",
2163
2825
  {
2164
2826
  type: "button",
@@ -2166,14 +2828,14 @@ function SuiteSettingsMobileNav({
2166
2828
  onClick: () => onSelect(item.href),
2167
2829
  "aria-current": active ? "page" : void 0,
2168
2830
  className: cn(
2169
- "group relative flex shrink-0 snap-start items-center gap-2 rounded-full px-3.5 py-2.5 text-sm font-medium transition",
2831
+ "group relative flex shrink-0 snap-start items-center gap-2 rounded-full px-3.5 py-2.5 text-sm font-medium transition-colors",
2170
2832
  "min-h-[44px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
2171
2833
  active ? "bg-ph-ink text-ph-surface shadow-sm" : "bg-ph-surface text-ph-subtle ring-1 ring-ph-border hover:bg-ph-muted hover:text-ph-ink"
2172
2834
  ),
2173
2835
  children: [
2174
2836
  iconNode,
2175
- /* @__PURE__ */ jsx20("span", { className: "whitespace-nowrap", children: item.label }),
2176
- active ? /* @__PURE__ */ jsx20("span", { className: "absolute inset-x-0 -bottom-3 h-0.5 rounded-full bg-ph-ink lg:hidden" }) : null
2837
+ /* @__PURE__ */ jsx21("span", { className: "whitespace-nowrap", children: item.label }),
2838
+ active ? /* @__PURE__ */ jsx21("span", { className: "absolute inset-x-0 -bottom-3 h-0.5 rounded-full bg-ph-ink lg:hidden" }) : null
2177
2839
  ]
2178
2840
  },
2179
2841
  item.href
@@ -2185,7 +2847,7 @@ function SuiteSettingsMobileNav({
2185
2847
 
2186
2848
  // src/suite/components/suite-sidebar.tsx
2187
2849
  import Link2 from "next/link";
2188
- import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
2850
+ import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
2189
2851
  function renderNode(node, collapsed) {
2190
2852
  if (typeof node === "function") return node(collapsed);
2191
2853
  return node;
@@ -2202,7 +2864,7 @@ function SuiteSidebar({
2202
2864
  className,
2203
2865
  surface = false
2204
2866
  }) {
2205
- return /* @__PURE__ */ jsxs17(
2867
+ return /* @__PURE__ */ jsxs18(
2206
2868
  "div",
2207
2869
  {
2208
2870
  className: cn(
@@ -2211,7 +2873,7 @@ function SuiteSidebar({
2211
2873
  className
2212
2874
  ),
2213
2875
  children: [
2214
- /* @__PURE__ */ jsxs17(
2876
+ /* @__PURE__ */ jsxs18(
2215
2877
  "div",
2216
2878
  {
2217
2879
  className: cn(
@@ -2220,12 +2882,12 @@ function SuiteSidebar({
2220
2882
  collapsed ? "flex-col items-center gap-1 px-1 py-2" : "h-14 items-center justify-between gap-2 px-3"
2221
2883
  ),
2222
2884
  children: [
2223
- /* @__PURE__ */ jsx21("div", { className: cn("min-w-0", collapsed ? "shrink-0" : "flex-1"), children: renderNode(appSwitcher, collapsed) }),
2224
- collapsed ? null : /* @__PURE__ */ jsx21("div", { className: "shrink-0", children: renderNode(notificationBell, collapsed) })
2885
+ /* @__PURE__ */ jsx22("div", { className: cn("min-w-0", collapsed ? "shrink-0" : "flex-1"), children: renderNode(appSwitcher, collapsed) }),
2886
+ collapsed ? null : /* @__PURE__ */ jsx22("div", { className: "shrink-0", children: renderNode(notificationBell, collapsed) })
2225
2887
  ]
2226
2888
  }
2227
2889
  ),
2228
- /* @__PURE__ */ jsxs17(
2890
+ /* @__PURE__ */ jsxs18(
2229
2891
  "div",
2230
2892
  {
2231
2893
  className: cn(
@@ -2234,17 +2896,17 @@ function SuiteSidebar({
2234
2896
  collapsed ? "px-1.5 py-2" : "p-2"
2235
2897
  ),
2236
2898
  children: [
2237
- /* @__PURE__ */ jsxs17(
2899
+ /* @__PURE__ */ jsxs18(
2238
2900
  "nav",
2239
2901
  {
2240
2902
  "aria-label": "Pages",
2241
2903
  className: "shrink-0 space-y-0.5",
2242
2904
  children: [
2243
- /* @__PURE__ */ jsx21("div", { className: cn("mb-3", collapsed && "flex justify-center"), children: renderNode(contextSwitcher, collapsed) }),
2244
- /* @__PURE__ */ jsx21("div", { className: "space-y-0.5", children: navItems.map((item) => {
2905
+ /* @__PURE__ */ jsx22("div", { className: cn("mb-3", collapsed && "flex justify-center"), children: renderNode(contextSwitcher, collapsed) }),
2906
+ /* @__PURE__ */ jsx22("div", { className: "space-y-0.5", children: navItems.map((item) => {
2245
2907
  const Icon = item.icon;
2246
2908
  const active = Boolean(item.active);
2247
- const link = /* @__PURE__ */ jsxs17(
2909
+ const link = /* @__PURE__ */ jsxs18(
2248
2910
  Link2,
2249
2911
  {
2250
2912
  href: item.href,
@@ -2257,7 +2919,7 @@ function SuiteSidebar({
2257
2919
  collapsed ? "mx-auto size-10 shrink-0 justify-center gap-0 px-0 py-0" : "w-full px-2.5 py-2"
2258
2920
  ),
2259
2921
  children: [
2260
- /* @__PURE__ */ jsx21(
2922
+ /* @__PURE__ */ jsx22(
2261
2923
  "span",
2262
2924
  {
2263
2925
  className: cn(
@@ -2265,7 +2927,7 @@ function SuiteSidebar({
2265
2927
  item.iconClassName
2266
2928
  ),
2267
2929
  "aria-hidden": true,
2268
- children: /* @__PURE__ */ jsx21(
2930
+ children: /* @__PURE__ */ jsx22(
2269
2931
  Icon,
2270
2932
  {
2271
2933
  className: "h-full w-full",
@@ -2274,7 +2936,7 @@ function SuiteSidebar({
2274
2936
  )
2275
2937
  }
2276
2938
  ),
2277
- /* @__PURE__ */ jsx21(
2939
+ /* @__PURE__ */ jsx22(
2278
2940
  "span",
2279
2941
  {
2280
2942
  className: cn(
@@ -2285,17 +2947,17 @@ function SuiteSidebar({
2285
2947
  children: item.label
2286
2948
  }
2287
2949
  ),
2288
- !collapsed && item.badge ? /* @__PURE__ */ jsx21("span", { className: "relative ml-auto shrink-0", children: item.badge }) : null
2950
+ !collapsed && item.badge ? /* @__PURE__ */ jsx22("span", { className: "relative ml-auto shrink-0", children: item.badge }) : null
2289
2951
  ]
2290
2952
  },
2291
2953
  item.label
2292
2954
  );
2293
- return collapsed ? /* @__PURE__ */ jsx21(Tooltip, { content: item.label, side: "right", children: link }, item.label) : link;
2955
+ return collapsed ? /* @__PURE__ */ jsx22(Tooltip, { content: item.label, side: "right", children: link }, item.label) : link;
2294
2956
  }) })
2295
2957
  ]
2296
2958
  }
2297
2959
  ),
2298
- secondaryNav ? /* @__PURE__ */ jsx21(
2960
+ secondaryNav ? /* @__PURE__ */ jsx22(
2299
2961
  "div",
2300
2962
  {
2301
2963
  className: "min-h-0 flex-1",
@@ -2306,7 +2968,7 @@ function SuiteSidebar({
2306
2968
  ]
2307
2969
  }
2308
2970
  ),
2309
- /* @__PURE__ */ jsx21(
2971
+ /* @__PURE__ */ jsx22(
2310
2972
  "div",
2311
2973
  {
2312
2974
  className: cn(
@@ -2314,7 +2976,7 @@ function SuiteSidebar({
2314
2976
  surface ? "bg-ph-surface" : "bg-ph-canvas",
2315
2977
  collapsed ? "p-1.5" : "p-3"
2316
2978
  ),
2317
- children: /* @__PURE__ */ jsxs17(
2979
+ children: /* @__PURE__ */ jsxs18(
2318
2980
  "div",
2319
2981
  {
2320
2982
  className: cn(
@@ -2323,7 +2985,7 @@ function SuiteSidebar({
2323
2985
  ),
2324
2986
  children: [
2325
2987
  renderNode(userMenu, collapsed),
2326
- !collapsed && footerExtras ? /* @__PURE__ */ jsx21("div", { className: "ml-auto shrink-0", children: renderNode(footerExtras, collapsed) }) : null
2988
+ !collapsed && footerExtras ? /* @__PURE__ */ jsx22("div", { className: "ml-auto shrink-0", children: renderNode(footerExtras, collapsed) }) : null
2327
2989
  ]
2328
2990
  }
2329
2991
  )
@@ -2335,7 +2997,7 @@ function SuiteSidebar({
2335
2997
  }
2336
2998
 
2337
2999
  // src/suite/components/suite-app-layout.tsx
2338
- import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
3000
+ import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
2339
3001
  function SuiteAppLayout({
2340
3002
  sidebar,
2341
3003
  children,
@@ -2343,41 +3005,59 @@ function SuiteAppLayout({
2343
3005
  bottomNav,
2344
3006
  sidebarWidth = 240,
2345
3007
  collapsed = false,
3008
+ lockMainScroll = false,
2346
3009
  onStartResize,
2347
3010
  onResetWidth,
2348
3011
  className
2349
3012
  }) {
2350
- return /* @__PURE__ */ jsxs18("div", { "data-test": "app-shell", className: cn("min-h-screen", className), children: [
2351
- /* @__PURE__ */ jsxs18("div", { className: "hidden lg:flex h-screen overflow-hidden", children: [
2352
- /* @__PURE__ */ jsxs18(
2353
- "aside",
2354
- {
2355
- className: "relative flex shrink-0 flex-col border-r border-ph-border transition-[width] duration-200 ease-out",
2356
- style: { width: `${sidebarWidth}px` },
2357
- children: [
2358
- sidebar,
2359
- onStartResize ? /* @__PURE__ */ jsx22(
2360
- "div",
2361
- {
2362
- role: "separator",
2363
- "aria-orientation": "vertical",
2364
- "aria-label": "Resize sidebar",
2365
- onPointerDown: onStartResize,
2366
- onDoubleClick: onResetWidth,
2367
- className: "absolute inset-y-0 right-0 z-10 w-1 cursor-col-resize transition-colors hover:bg-[color-mix(in_oklab,var(--ph-accent)_30%,transparent)]"
2368
- }
2369
- ) : null
2370
- ]
2371
- }
2372
- ),
2373
- /* @__PURE__ */ jsx22("main", { className: "min-w-0 flex-1 overflow-y-auto bg-ph-canvas", children })
2374
- ] }),
2375
- /* @__PURE__ */ jsxs18("div", { className: "flex min-h-screen flex-col lg:hidden", children: [
2376
- mobileHeader ? /* @__PURE__ */ jsx22("div", { className: "shrink-0", children: mobileHeader }) : null,
2377
- /* @__PURE__ */ jsx22("main", { className: "min-w-0 flex-1 overflow-y-auto bg-ph-canvas", children }),
2378
- bottomNav ? /* @__PURE__ */ jsx22("div", { className: "shrink-0", children: bottomNav }) : null
2379
- ] })
2380
- ] });
3013
+ return /* @__PURE__ */ jsxs19(
3014
+ "div",
3015
+ {
3016
+ "data-test": "app-shell",
3017
+ className: cn("flex min-h-dvh lg:h-screen lg:overflow-hidden", className),
3018
+ children: [
3019
+ /* @__PURE__ */ jsxs19(
3020
+ "aside",
3021
+ {
3022
+ className: "relative hidden shrink-0 flex-col overflow-hidden border-r border-ph-border transition-[width] duration-200 ease-out lg:flex",
3023
+ style: { width: `${sidebarWidth}px` },
3024
+ "data-collapsed": collapsed ? "true" : "false",
3025
+ children: [
3026
+ sidebar,
3027
+ onStartResize ? /* @__PURE__ */ jsx23(
3028
+ "div",
3029
+ {
3030
+ role: "separator",
3031
+ "aria-orientation": "vertical",
3032
+ "aria-label": "Resize sidebar",
3033
+ onPointerDown: onStartResize,
3034
+ onDoubleClick: onResetWidth,
3035
+ className: "absolute inset-y-0 right-0 z-10 w-1 cursor-col-resize transition-colors hover:bg-[color-mix(in_oklab,var(--ph-accent)_30%,transparent)]"
3036
+ }
3037
+ ) : null
3038
+ ]
3039
+ }
3040
+ ),
3041
+ /* @__PURE__ */ jsxs19("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [
3042
+ mobileHeader ? /* @__PURE__ */ jsx23("div", { className: "shrink-0 lg:hidden", children: mobileHeader }) : null,
3043
+ /* @__PURE__ */ jsx23(
3044
+ "main",
3045
+ {
3046
+ id: "main-content",
3047
+ tabIndex: -1,
3048
+ "data-lock-scroll": lockMainScroll ? "true" : void 0,
3049
+ className: cn(
3050
+ "flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden overscroll-contain bg-ph-canvas focus:outline-none",
3051
+ lockMainScroll ? "overflow-hidden" : "overflow-y-auto"
3052
+ ),
3053
+ children
3054
+ }
3055
+ ),
3056
+ bottomNav ? /* @__PURE__ */ jsx23("div", { className: "shrink-0 lg:hidden", children: bottomNav }) : null
3057
+ ] })
3058
+ ]
3059
+ }
3060
+ );
2381
3061
  }
2382
3062
  export {
2383
3063
  APP_ACCENTS,
@@ -2389,10 +3069,16 @@ export {
2389
3069
  AppSwitcherMark,
2390
3070
  CommandPaletteHost,
2391
3071
  DeferredChrome,
3072
+ SIDEBAR_COLLAPSE_THRESHOLD,
3073
+ SIDEBAR_DEFAULT_WIDTH,
3074
+ SIDEBAR_MAX_WIDTH,
3075
+ SIDEBAR_MIN_EXPANDED_WIDTH,
3076
+ SIDEBAR_RAIL_WIDTH,
2392
3077
  SUITE_APPS2 as SUITE_APPS,
2393
3078
  SUITE_APP_MAP,
2394
3079
  SUITE_GLYPHS,
2395
3080
  SUITE_ICON_NAMES,
3081
+ SUITE_OPEN_ASK_AI_EVENT,
2396
3082
  SUITE_SPRINGS,
2397
3083
  SourceAppGlyph,
2398
3084
  SuiteAiPanel,
@@ -2410,6 +3096,7 @@ export {
2410
3096
  SuitePage,
2411
3097
  SuitePageHeader,
2412
3098
  SuiteSectionHeader,
3099
+ SuiteSettingsLayout,
2413
3100
  SuiteSettingsMobileNav,
2414
3101
  SuiteSidebar,
2415
3102
  SuiteSkeleton,
@@ -2419,11 +3106,15 @@ export {
2419
3106
  SuiteThemeProvider,
2420
3107
  SuiteToolbar,
2421
3108
  SuiteUserMenu,
3109
+ TodayAskAiCard,
2422
3110
  TodayCalibrating,
3111
+ TodayEmptyAction,
2423
3112
  TodayEmptyState,
2424
3113
  TodayHero,
3114
+ TodayLayout,
2425
3115
  TodayPageFrame,
2426
3116
  TodayPanel,
3117
+ TodayPrimaryAction,
2427
3118
  TodaySection,
2428
3119
  TodayTimeIcon,
2429
3120
  TodayTopBar,
@@ -2433,9 +3124,12 @@ export {
2433
3124
  cn,
2434
3125
  getTodayGreeting,
2435
3126
  m,
3127
+ openSuiteAskAi,
3128
+ resolveSuiteNotificationHref,
2436
3129
  sourceToSuiteApp,
2437
3130
  suiteAppBaseUrl,
2438
3131
  suiteAppLabel,
2439
3132
  useIdleMount,
3133
+ useSidebarWidth,
2440
3134
  useSuiteTheme
2441
3135
  };