@xenide-io/the-old-ui-theme 0.9.5 → 0.9.7

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.
@@ -0,0 +1,65 @@
1
+ "use client";
2
+ import "./chunk-FWCSY2DS.mjs";
3
+
4
+ // src/suite/components/ai-message-markdown.tsx
5
+ import ReactMarkdown from "react-markdown";
6
+ import remarkGfm from "remark-gfm";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var WORD_STEP_MS = 18;
9
+ var MAX_DELAY_MS = 600;
10
+ var SKIP_TAGS = /* @__PURE__ */ new Set(["code", "pre", "style", "script"]);
11
+ function rehypeStaggerWords() {
12
+ return (tree) => {
13
+ let wordIndex = 0;
14
+ const visit = (node) => {
15
+ if (!node.children) return;
16
+ if (node.tagName && SKIP_TAGS.has(node.tagName)) return;
17
+ const next = [];
18
+ for (const child of node.children) {
19
+ const isText = child.type === "text" && typeof child.value === "string" && /\S/.test(child.value);
20
+ if (!isText) {
21
+ visit(child);
22
+ next.push(child);
23
+ continue;
24
+ }
25
+ for (const part of child.value.split(/(\s+)/)) {
26
+ if (!part) continue;
27
+ if (/^\s+$/.test(part)) {
28
+ next.push({ type: "text", value: part });
29
+ continue;
30
+ }
31
+ const delay = Math.min(wordIndex * WORD_STEP_MS, MAX_DELAY_MS);
32
+ wordIndex += 1;
33
+ next.push({
34
+ type: "element",
35
+ tagName: "span",
36
+ properties: {
37
+ className: ["suite-word-in"],
38
+ style: `animation-delay:${delay}ms`
39
+ },
40
+ children: [{ type: "text", value: part }]
41
+ });
42
+ }
43
+ }
44
+ node.children = next;
45
+ };
46
+ visit(tree);
47
+ };
48
+ }
49
+ function SuiteAiMarkdownMessage({
50
+ markdown,
51
+ animate = false
52
+ }) {
53
+ return /* @__PURE__ */ jsx("div", { className: "suite-ai-markdown", children: /* @__PURE__ */ jsx(
54
+ ReactMarkdown,
55
+ {
56
+ remarkPlugins: [remarkGfm],
57
+ rehypePlugins: animate ? [rehypeStaggerWords] : void 0,
58
+ components: { img: () => null },
59
+ children: markdown
60
+ }
61
+ ) });
62
+ }
63
+ export {
64
+ SuiteAiMarkdownMessage as default
65
+ };
package/dist/suite.d.mts CHANGED
@@ -339,6 +339,10 @@ interface SuiteUserMenuProps {
339
339
  onSignOut: () => void;
340
340
  /** Fallback letter(s) when there is no avatar image. */
341
341
  fallbackInitials?: string;
342
+ /** Second line under the name, e.g. the workspace subscription ("Plus"). */
343
+ subtitle?: string | null;
344
+ /** Show the name + subtitle beside the avatar, for the sidebar footer. */
345
+ showDetails?: boolean;
342
346
  /** Show the old desktop sidebar sign-out action beside the avatar. */
343
347
  showSignOutAction?: boolean;
344
348
  dataTest?: string;
@@ -351,7 +355,7 @@ interface SuiteUserMenuProps {
351
355
  * profile settings and sign out. Desktop sidebars can also expose the legacy
352
356
  * adjacent sign-out action without adding it to mobile chrome.
353
357
  */
354
- declare function SuiteUserMenu({ name, email, image, settingsHref, onSignOut, fallbackInitials, showSignOutAction, dataTest, triggerId, triggerDataTest, className, }: SuiteUserMenuProps): react.JSX.Element;
358
+ declare function SuiteUserMenu({ name, email, image, settingsHref, onSignOut, fallbackInitials, subtitle, showDetails, showSignOutAction, dataTest, triggerId, triggerDataTest, className, }: SuiteUserMenuProps): react.JSX.Element;
355
359
 
356
360
  /** Any icon component that accepts sizing/stroke props (Lucide, theme icons). */
357
361
  type SuiteNavIcon = ComponentType<{
package/dist/suite.d.ts CHANGED
@@ -339,6 +339,10 @@ interface SuiteUserMenuProps {
339
339
  onSignOut: () => void;
340
340
  /** Fallback letter(s) when there is no avatar image. */
341
341
  fallbackInitials?: string;
342
+ /** Second line under the name, e.g. the workspace subscription ("Plus"). */
343
+ subtitle?: string | null;
344
+ /** Show the name + subtitle beside the avatar, for the sidebar footer. */
345
+ showDetails?: boolean;
342
346
  /** Show the old desktop sidebar sign-out action beside the avatar. */
343
347
  showSignOutAction?: boolean;
344
348
  dataTest?: string;
@@ -351,7 +355,7 @@ interface SuiteUserMenuProps {
351
355
  * profile settings and sign out. Desktop sidebars can also expose the legacy
352
356
  * adjacent sign-out action without adding it to mobile chrome.
353
357
  */
354
- declare function SuiteUserMenu({ name, email, image, settingsHref, onSignOut, fallbackInitials, showSignOutAction, dataTest, triggerId, triggerDataTest, className, }: SuiteUserMenuProps): react.JSX.Element;
358
+ declare function SuiteUserMenu({ name, email, image, settingsHref, onSignOut, fallbackInitials, subtitle, showDetails, showSignOutAction, dataTest, triggerId, triggerDataTest, className, }: SuiteUserMenuProps): react.JSX.Element;
355
359
 
356
360
  /** Any icon component that accepts sizing/stroke props (Lucide, theme icons). */
357
361
  type SuiteNavIcon = ComponentType<{
package/dist/suite.js CHANGED
@@ -64,19 +64,59 @@ var ai_message_markdown_exports = {};
64
64
  __export(ai_message_markdown_exports, {
65
65
  default: () => SuiteAiMarkdownMessage
66
66
  });
67
+ function rehypeStaggerWords() {
68
+ return (tree) => {
69
+ let wordIndex = 0;
70
+ const visit = (node) => {
71
+ if (!node.children) return;
72
+ if (node.tagName && SKIP_TAGS.has(node.tagName)) return;
73
+ const next = [];
74
+ for (const child of node.children) {
75
+ const isText = child.type === "text" && typeof child.value === "string" && /\S/.test(child.value);
76
+ if (!isText) {
77
+ visit(child);
78
+ next.push(child);
79
+ continue;
80
+ }
81
+ for (const part of child.value.split(/(\s+)/)) {
82
+ if (!part) continue;
83
+ if (/^\s+$/.test(part)) {
84
+ next.push({ type: "text", value: part });
85
+ continue;
86
+ }
87
+ const delay = Math.min(wordIndex * WORD_STEP_MS, MAX_DELAY_MS);
88
+ wordIndex += 1;
89
+ next.push({
90
+ type: "element",
91
+ tagName: "span",
92
+ properties: {
93
+ className: ["suite-word-in"],
94
+ style: `animation-delay:${delay}ms`
95
+ },
96
+ children: [{ type: "text", value: part }]
97
+ });
98
+ }
99
+ }
100
+ node.children = next;
101
+ };
102
+ visit(tree);
103
+ };
104
+ }
67
105
  function SuiteAiMarkdownMessage({
68
- markdown
106
+ markdown,
107
+ animate = false
69
108
  }) {
70
109
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "suite-ai-markdown", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
71
110
  import_react_markdown.default,
72
111
  {
73
112
  remarkPlugins: [import_remark_gfm.default],
113
+ rehypePlugins: animate ? [rehypeStaggerWords] : void 0,
74
114
  components: { img: () => null },
75
115
  children: markdown
76
116
  }
77
117
  ) });
78
118
  }
79
- var import_react_markdown, import_remark_gfm, import_jsx_runtime23;
119
+ var import_react_markdown, import_remark_gfm, import_jsx_runtime23, WORD_STEP_MS, MAX_DELAY_MS, SKIP_TAGS;
80
120
  var init_ai_message_markdown = __esm({
81
121
  "src/suite/components/ai-message-markdown.tsx"() {
82
122
  "use strict";
@@ -84,6 +124,9 @@ var init_ai_message_markdown = __esm({
84
124
  import_react_markdown = __toESM(require("react-markdown"));
85
125
  import_remark_gfm = __toESM(require("remark-gfm"));
86
126
  import_jsx_runtime23 = require("react/jsx-runtime");
127
+ WORD_STEP_MS = 18;
128
+ MAX_DELAY_MS = 600;
129
+ SKIP_TAGS = /* @__PURE__ */ new Set(["code", "pre", "style", "script"]);
87
130
  }
88
131
  });
89
132
 
@@ -2741,6 +2784,8 @@ function SuiteUserMenu({
2741
2784
  settingsHref,
2742
2785
  onSignOut,
2743
2786
  fallbackInitials,
2787
+ subtitle,
2788
+ showDetails = false,
2744
2789
  showSignOutAction = false,
2745
2790
  dataTest = "suite-user-menu",
2746
2791
  triggerId,
@@ -2770,10 +2815,17 @@ function SuiteUserMenu({
2770
2815
  children: initials
2771
2816
  }
2772
2817
  ) }) });
2818
+ const trigger = showDetails ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "flex min-w-0 items-center gap-2", children: [
2819
+ avatar,
2820
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "flex min-w-0 flex-col text-left", children: [
2821
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate text-sm font-medium text-ph-ink", children: name || email || "Account" }),
2822
+ subtitle ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate text-xs text-ph-mutedtext", children: subtitle }) : null
2823
+ ] })
2824
+ ] }) : avatar;
2773
2825
  const accountMenu = /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
2774
2826
  DropdownMenu,
2775
2827
  {
2776
- trigger: avatar,
2828
+ trigger,
2777
2829
  triggerId: triggerId != null ? triggerId : `${dataTest}-trigger`,
2778
2830
  triggerDataTest: triggerDataTest != null ? triggerDataTest : `${dataTest}-trigger`,
2779
2831
  "aria-label": "Account menu",
@@ -2784,6 +2836,7 @@ function SuiteUserMenu({
2784
2836
  modal: false,
2785
2837
  className: cn(
2786
2838
  "[&_.ph-dropdown-trigger]:rounded-full",
2839
+ showDetails && "min-w-0 flex-1 [&_.ph-dropdown-trigger]:w-full [&_.ph-dropdown-trigger]:justify-start [&_.ph-dropdown-trigger]:rounded-lg",
2787
2840
  showSignOutAction ? void 0 : className
2788
2841
  ),
2789
2842
  "data-test": dataTest,
@@ -3541,6 +3594,11 @@ function SuiteAiPanel({
3541
3594
  var _a;
3542
3595
  if (open && !hydrating) (_a = textareaRef.current) == null ? void 0 : _a.focus();
3543
3596
  }, [open, hydrating]);
3597
+ (0, import_react12.useEffect)(() => {
3598
+ if (!open) return;
3599
+ void Promise.resolve().then(() => (init_ai_message_markdown(), ai_message_markdown_exports)).catch(() => {
3600
+ });
3601
+ }, [open]);
3544
3602
  (0, import_react12.useLayoutEffect)(() => {
3545
3603
  const el = textareaRef.current;
3546
3604
  if (!el) return;
@@ -3665,6 +3723,10 @@ function SuiteAiPanel({
3665
3723
  else window.location.assign(new URL(openPath, href).toString());
3666
3724
  }
3667
3725
  if (!open) return null;
3726
+ const lastAssistantIndex = messages.reduce(
3727
+ (found, message, index) => message.role === "assistant" ? index : found,
3728
+ -1
3729
+ );
3668
3730
  const Icon = BrandIcon != null ? BrandIcon : import_iconoir_react8.Sparks;
3669
3731
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
3670
3732
  "aside",
@@ -3733,7 +3795,7 @@ function SuiteAiPanel({
3733
3795
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3734
3796
  "div",
3735
3797
  {
3736
- className: "flex justify-end",
3798
+ className: "suite-msg-in suite-msg-in--user flex justify-end",
3737
3799
  "data-test": "ask-ai-user",
3738
3800
  children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "w-fit max-w-[85%] whitespace-pre-wrap break-words rounded-2xl rounded-br-md bg-ph-brand px-3 py-2 text-sm leading-relaxed text-[var(--ph-on-accent)] [overflow-wrap:anywhere]", children: message.content })
3739
3801
  },
@@ -3749,30 +3811,28 @@ function SuiteAiPanel({
3749
3811
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3750
3812
  "span",
3751
3813
  {
3752
- className: "mb-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-ph-brand/10 text-ph-brand",
3814
+ className: "suite-msg-avatar mb-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-ph-brand/10 text-ph-brand",
3753
3815
  "aria-hidden": true,
3754
3816
  children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Icon, { className: "h-3.5 w-3.5" })
3755
3817
  }
3756
3818
  ),
3757
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "min-w-0 max-w-[85%] space-y-2", children: [
3819
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "suite-msg-bubble min-w-0 max-w-[85%] space-y-2", children: [
3758
3820
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "rounded-2xl rounded-bl-md bg-ph-muted px-3 py-2 text-sm leading-relaxed text-ph-ink", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3759
3821
  import_react12.Suspense,
3760
3822
  {
3761
- fallback: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
3762
- "div",
3823
+ fallback: (
3824
+ // The chunk is pre-warmed above, so this only shows on a
3825
+ // cold cache. Keep it invisible — a skeleton here reads
3826
+ // as if it were part of the answer.
3827
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "sr-only", role: "status", children: "Rendering response\u2026" })
3828
+ ),
3829
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3830
+ SuiteAiMarkdownMessage2,
3763
3831
  {
3764
- className: "space-y-2 py-0.5",
3765
- role: "status",
3766
- "aria-label": "Rendering response",
3767
- children: [
3768
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "sr-only", children: "Rendering response\u2026" }),
3769
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "h-3 w-11/12 rounded bg-ph-mutedtext/20 motion-safe:animate-pulse" }),
3770
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "h-3 w-full rounded bg-ph-mutedtext/20 motion-safe:animate-pulse" }),
3771
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "h-3 w-3/5 rounded bg-ph-mutedtext/20 motion-safe:animate-pulse" })
3772
- ]
3832
+ markdown: message.content,
3833
+ animate: index === lastAssistantIndex
3773
3834
  }
3774
- ),
3775
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SuiteAiMarkdownMessage2, { markdown: message.content })
3835
+ )
3776
3836
  }
3777
3837
  ) }),
3778
3838
  ((_a = message.actions) != null ? _a : []).map((action) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
@@ -3836,9 +3896,9 @@ function SuiteAiPanel({
3836
3896
  waiting ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex items-end gap-2", "aria-hidden": true, children: [
3837
3897
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "mb-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-ph-brand/10 text-ph-brand", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Icon, { className: "h-3.5 w-3.5" }) }),
3838
3898
  /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex items-center gap-1.5 rounded-2xl rounded-bl-md bg-ph-muted px-3 py-2.5", children: [
3839
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "h-1.5 w-1.5 rounded-full bg-ph-mutedtext motion-safe:animate-bounce" }),
3840
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "h-1.5 w-1.5 rounded-full bg-ph-mutedtext motion-safe:animate-bounce [animation-delay:0.15s]" }),
3841
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "h-1.5 w-1.5 rounded-full bg-ph-mutedtext motion-safe:animate-bounce [animation-delay:0.3s]" })
3899
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "suite-think-dot h-1.5 w-1.5 rounded-full bg-ph-mutedtext" }),
3900
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "suite-think-dot suite-think-dot--2 h-1.5 w-1.5 rounded-full bg-ph-mutedtext" }),
3901
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "suite-think-dot suite-think-dot--3 h-1.5 w-1.5 rounded-full bg-ph-mutedtext" })
3842
3902
  ] })
3843
3903
  ] }) : null,
3844
3904
  waiting ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "sr-only", "aria-live": "polite", children: "Assistant is responding\u2026" }) : null,
@@ -4730,9 +4790,7 @@ function SuiteSidebar({
4730
4790
  className: cn(
4731
4791
  "shrink-0 border-t border-ph-border",
4732
4792
  surface ? "bg-ph-surface" : "bg-ph-canvas",
4733
- // Trim only the bottom inset: the 32px avatar sits inside a 40px
4734
- // trigger, so even padding read top-heavy once the avatar shrank.
4735
- collapsed ? "px-1.5 pt-1.5 pb-1" : "px-3 pt-3 pb-2"
4793
+ collapsed ? "p-1.5" : "p-3"
4736
4794
  ),
4737
4795
  children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
4738
4796
  "div",
package/dist/suite.mjs CHANGED
@@ -1897,6 +1897,8 @@ function SuiteUserMenu({
1897
1897
  settingsHref,
1898
1898
  onSignOut,
1899
1899
  fallbackInitials,
1900
+ subtitle,
1901
+ showDetails = false,
1900
1902
  showSignOutAction = false,
1901
1903
  dataTest = "suite-user-menu",
1902
1904
  triggerId,
@@ -1926,10 +1928,17 @@ function SuiteUserMenu({
1926
1928
  children: initials
1927
1929
  }
1928
1930
  ) }) });
1931
+ const trigger = showDetails ? /* @__PURE__ */ jsxs11("span", { className: "flex min-w-0 items-center gap-2", children: [
1932
+ avatar,
1933
+ /* @__PURE__ */ jsxs11("span", { className: "flex min-w-0 flex-col text-left", children: [
1934
+ /* @__PURE__ */ jsx13("span", { className: "truncate text-sm font-medium text-ph-ink", children: name || email || "Account" }),
1935
+ subtitle ? /* @__PURE__ */ jsx13("span", { className: "truncate text-xs text-ph-mutedtext", children: subtitle }) : null
1936
+ ] })
1937
+ ] }) : avatar;
1929
1938
  const accountMenu = /* @__PURE__ */ jsxs11(
1930
1939
  DropdownMenu,
1931
1940
  {
1932
- trigger: avatar,
1941
+ trigger,
1933
1942
  triggerId: triggerId != null ? triggerId : `${dataTest}-trigger`,
1934
1943
  triggerDataTest: triggerDataTest != null ? triggerDataTest : `${dataTest}-trigger`,
1935
1944
  "aria-label": "Account menu",
@@ -1940,6 +1949,7 @@ function SuiteUserMenu({
1940
1949
  modal: false,
1941
1950
  className: cn2(
1942
1951
  "[&_.ph-dropdown-trigger]:rounded-full",
1952
+ showDetails && "min-w-0 flex-1 [&_.ph-dropdown-trigger]:w-full [&_.ph-dropdown-trigger]:justify-start [&_.ph-dropdown-trigger]:rounded-lg",
1943
1953
  showSignOutAction ? void 0 : className
1944
1954
  ),
1945
1955
  "data-test": dataTest,
@@ -2386,7 +2396,7 @@ import {
2386
2396
  WarningTriangle
2387
2397
  } from "iconoir-react";
2388
2398
  import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
2389
- var SuiteAiMarkdownMessage = lazy(() => import("./ai-message-markdown-TSPYON2F.mjs"));
2399
+ var SuiteAiMarkdownMessage = lazy(() => import("./ai-message-markdown-AUEMCO7K.mjs"));
2390
2400
  var SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
2391
2401
  var SUITE_ASK_AI_OPEN_KEY = "shellstack:shelly-open";
2392
2402
  var SUITE_ASK_AI_OPEN_QUERY = "shelly";
@@ -2561,6 +2571,11 @@ function SuiteAiPanel({
2561
2571
  var _a;
2562
2572
  if (open && !hydrating) (_a = textareaRef.current) == null ? void 0 : _a.focus();
2563
2573
  }, [open, hydrating]);
2574
+ useEffect7(() => {
2575
+ if (!open) return;
2576
+ void import("./ai-message-markdown-AUEMCO7K.mjs").catch(() => {
2577
+ });
2578
+ }, [open]);
2564
2579
  useLayoutEffect(() => {
2565
2580
  const el = textareaRef.current;
2566
2581
  if (!el) return;
@@ -2685,6 +2700,10 @@ function SuiteAiPanel({
2685
2700
  else window.location.assign(new URL(openPath, href).toString());
2686
2701
  }
2687
2702
  if (!open) return null;
2703
+ const lastAssistantIndex = messages.reduce(
2704
+ (found, message, index) => message.role === "assistant" ? index : found,
2705
+ -1
2706
+ );
2688
2707
  const Icon = BrandIcon != null ? BrandIcon : Sparks;
2689
2708
  return /* @__PURE__ */ jsxs14(
2690
2709
  "aside",
@@ -2753,7 +2772,7 @@ function SuiteAiPanel({
2753
2772
  return /* @__PURE__ */ jsx17(
2754
2773
  "div",
2755
2774
  {
2756
- className: "flex justify-end",
2775
+ className: "suite-msg-in suite-msg-in--user flex justify-end",
2757
2776
  "data-test": "ask-ai-user",
2758
2777
  children: /* @__PURE__ */ jsx17("div", { className: "w-fit max-w-[85%] whitespace-pre-wrap break-words rounded-2xl rounded-br-md bg-ph-brand px-3 py-2 text-sm leading-relaxed text-[var(--ph-on-accent)] [overflow-wrap:anywhere]", children: message.content })
2759
2778
  },
@@ -2769,30 +2788,28 @@ function SuiteAiPanel({
2769
2788
  /* @__PURE__ */ jsx17(
2770
2789
  "span",
2771
2790
  {
2772
- className: "mb-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-ph-brand/10 text-ph-brand",
2791
+ className: "suite-msg-avatar mb-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-ph-brand/10 text-ph-brand",
2773
2792
  "aria-hidden": true,
2774
2793
  children: /* @__PURE__ */ jsx17(Icon, { className: "h-3.5 w-3.5" })
2775
2794
  }
2776
2795
  ),
2777
- /* @__PURE__ */ jsxs14("div", { className: "min-w-0 max-w-[85%] space-y-2", children: [
2796
+ /* @__PURE__ */ jsxs14("div", { className: "suite-msg-bubble min-w-0 max-w-[85%] space-y-2", children: [
2778
2797
  /* @__PURE__ */ jsx17("div", { className: "rounded-2xl rounded-bl-md bg-ph-muted px-3 py-2 text-sm leading-relaxed text-ph-ink", children: /* @__PURE__ */ jsx17(
2779
2798
  Suspense,
2780
2799
  {
2781
- fallback: /* @__PURE__ */ jsxs14(
2782
- "div",
2800
+ fallback: (
2801
+ // The chunk is pre-warmed above, so this only shows on a
2802
+ // cold cache. Keep it invisible — a skeleton here reads
2803
+ // as if it were part of the answer.
2804
+ /* @__PURE__ */ jsx17("span", { className: "sr-only", role: "status", children: "Rendering response\u2026" })
2805
+ ),
2806
+ children: /* @__PURE__ */ jsx17(
2807
+ SuiteAiMarkdownMessage,
2783
2808
  {
2784
- className: "space-y-2 py-0.5",
2785
- role: "status",
2786
- "aria-label": "Rendering response",
2787
- children: [
2788
- /* @__PURE__ */ jsx17("span", { className: "sr-only", children: "Rendering response\u2026" }),
2789
- /* @__PURE__ */ jsx17("div", { className: "h-3 w-11/12 rounded bg-ph-mutedtext/20 motion-safe:animate-pulse" }),
2790
- /* @__PURE__ */ jsx17("div", { className: "h-3 w-full rounded bg-ph-mutedtext/20 motion-safe:animate-pulse" }),
2791
- /* @__PURE__ */ jsx17("div", { className: "h-3 w-3/5 rounded bg-ph-mutedtext/20 motion-safe:animate-pulse" })
2792
- ]
2809
+ markdown: message.content,
2810
+ animate: index === lastAssistantIndex
2793
2811
  }
2794
- ),
2795
- children: /* @__PURE__ */ jsx17(SuiteAiMarkdownMessage, { markdown: message.content })
2812
+ )
2796
2813
  }
2797
2814
  ) }),
2798
2815
  ((_a = message.actions) != null ? _a : []).map((action) => /* @__PURE__ */ jsxs14(
@@ -2856,9 +2873,9 @@ function SuiteAiPanel({
2856
2873
  waiting ? /* @__PURE__ */ jsxs14("div", { className: "flex items-end gap-2", "aria-hidden": true, children: [
2857
2874
  /* @__PURE__ */ jsx17("span", { className: "mb-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-ph-brand/10 text-ph-brand", children: /* @__PURE__ */ jsx17(Icon, { className: "h-3.5 w-3.5" }) }),
2858
2875
  /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1.5 rounded-2xl rounded-bl-md bg-ph-muted px-3 py-2.5", children: [
2859
- /* @__PURE__ */ jsx17("span", { className: "h-1.5 w-1.5 rounded-full bg-ph-mutedtext motion-safe:animate-bounce" }),
2860
- /* @__PURE__ */ jsx17("span", { className: "h-1.5 w-1.5 rounded-full bg-ph-mutedtext motion-safe:animate-bounce [animation-delay:0.15s]" }),
2861
- /* @__PURE__ */ jsx17("span", { className: "h-1.5 w-1.5 rounded-full bg-ph-mutedtext motion-safe:animate-bounce [animation-delay:0.3s]" })
2876
+ /* @__PURE__ */ jsx17("span", { className: "suite-think-dot h-1.5 w-1.5 rounded-full bg-ph-mutedtext" }),
2877
+ /* @__PURE__ */ jsx17("span", { className: "suite-think-dot suite-think-dot--2 h-1.5 w-1.5 rounded-full bg-ph-mutedtext" }),
2878
+ /* @__PURE__ */ jsx17("span", { className: "suite-think-dot suite-think-dot--3 h-1.5 w-1.5 rounded-full bg-ph-mutedtext" })
2862
2879
  ] })
2863
2880
  ] }) : null,
2864
2881
  waiting ? /* @__PURE__ */ jsx17("span", { className: "sr-only", "aria-live": "polite", children: "Assistant is responding\u2026" }) : null,
@@ -3754,9 +3771,7 @@ function SuiteSidebar({
3754
3771
  className: cn2(
3755
3772
  "shrink-0 border-t border-ph-border",
3756
3773
  surface ? "bg-ph-surface" : "bg-ph-canvas",
3757
- // Trim only the bottom inset: the 32px avatar sits inside a 40px
3758
- // trigger, so even padding read top-heavy once the avatar shrank.
3759
- collapsed ? "px-1.5 pt-1.5 pb-1" : "px-3 pt-3 pb-2"
3774
+ collapsed ? "p-1.5" : "p-3"
3760
3775
  ),
3761
3776
  children: /* @__PURE__ */ jsxs19(
3762
3777
  "div",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenide-io/the-old-ui-theme",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "description": "Props-driven React components and theme tokens inspired by classic interface design — optimized for Next.js apps and internal tools.",
5
5
  "author": "Xenide",
6
6
  "license": "MIT",
@@ -797,6 +797,164 @@ button:hover > .ph-card {
797
797
  border-top: 1px solid var(--ph-border-subtle);
798
798
  }
799
799
 
800
+ /* ── Ask AI conversation motion ─────────────────────────────────────────
801
+ Replies arrive whole (the suite chat is polled, not streamed), so the
802
+ motion carries the "alive" signal instead: a soft one-shot entrance per
803
+ message, a breathing thinking indicator, and a shimmer for the skeleton.
804
+ Only opacity + transform animate — height/margin would fight the
805
+ scroller. All of it collapses under prefers-reduced-motion. */
806
+ @keyframes suite-msg-in {
807
+ from {
808
+ opacity: 0;
809
+ transform: translateY(8px) scale(0.97);
810
+ filter: blur(4px);
811
+ }
812
+ 60% {
813
+ opacity: 1;
814
+ }
815
+ to {
816
+ opacity: 1;
817
+ transform: none;
818
+ filter: blur(0);
819
+ }
820
+ }
821
+ @keyframes suite-msg-in-user {
822
+ /* The user's own turn grows out of the composer corner, per the chat
823
+ scroller guidance, so sending feels anchored to where they typed. */
824
+ from {
825
+ opacity: 0;
826
+ transform: translateY(12px) scale(0.96);
827
+ filter: blur(4px);
828
+ }
829
+ 60% {
830
+ opacity: 1;
831
+ }
832
+ to {
833
+ opacity: 1;
834
+ transform: none;
835
+ filter: blur(0);
836
+ }
837
+ }
838
+ .suite-msg-in {
839
+ /* Slight overshoot rather than a flat ease-out: the bubble reads as
840
+ landing rather than fading up. Origin sits at the tail corner so it
841
+ appears to grow from the avatar (assistant) / composer (user). */
842
+ animation: suite-msg-in 280ms cubic-bezier(0.34, 1.4, 0.64, 1) both;
843
+ transform-origin: 0 100%;
844
+ }
845
+ .suite-msg-in--user {
846
+ animation-name: suite-msg-in-user;
847
+ animation-duration: 240ms;
848
+ transform-origin: 100% 100%;
849
+ }
850
+ /* Avatar lands first, bubble follows — a small layer separation. The bubble
851
+ only settles (no blur) because the words themselves carry the reveal. */
852
+ @keyframes suite-msg-settle {
853
+ from {
854
+ opacity: 0;
855
+ transform: translateY(4px);
856
+ }
857
+ to {
858
+ opacity: 1;
859
+ transform: none;
860
+ }
861
+ }
862
+ .suite-msg-avatar {
863
+ animation: suite-msg-in 260ms cubic-bezier(0.34, 1.4, 0.64, 1) both;
864
+ }
865
+ .suite-msg-bubble {
866
+ animation: suite-msg-settle 200ms ease-out both;
867
+ }
868
+
869
+ /* ── Answer reveal ──────────────────────────────────────────────────────
870
+ Replies land in one payload (the suite chat is polled), so the words
871
+ themselves are staggered to read as if they were being written. Delay is
872
+ applied per word inline by the rehype plugin in ai-message-markdown.tsx
873
+ and capped so a long answer still finishes settling in well under a
874
+ second. `both` fill keeps each word hidden until its turn. */
875
+ @keyframes suite-word-in {
876
+ from {
877
+ opacity: 0;
878
+ filter: blur(2px);
879
+ }
880
+ to {
881
+ opacity: 1;
882
+ filter: blur(0);
883
+ }
884
+ }
885
+ .suite-word-in {
886
+ animation: suite-word-in 240ms ease-out both;
887
+ }
888
+
889
+ @keyframes suite-think-breathe {
890
+ 0%,
891
+ 100% {
892
+ opacity: 0.3;
893
+ transform: scale(0.8);
894
+ }
895
+ 50% {
896
+ opacity: 1;
897
+ transform: scale(1);
898
+ }
899
+ }
900
+ .suite-think-dot {
901
+ animation: suite-think-breathe 1.4s ease-in-out infinite;
902
+ }
903
+ .suite-think-dot--2 {
904
+ animation-delay: 0.16s;
905
+ }
906
+ .suite-think-dot--3 {
907
+ animation-delay: 0.32s;
908
+ }
909
+
910
+ /* Sweep a translateX band rather than background-position: the latter
911
+ repaints every frame, the former stays on the compositor. */
912
+ @keyframes suite-shimmer {
913
+ from {
914
+ transform: translateX(-100%);
915
+ }
916
+ to {
917
+ transform: translateX(200%);
918
+ }
919
+ }
920
+ .suite-shimmer {
921
+ position: relative;
922
+ overflow: hidden;
923
+ }
924
+ .suite-shimmer::after {
925
+ content: "";
926
+ position: absolute;
927
+ inset: 0 auto 0 0;
928
+ width: 60%;
929
+ background-image: linear-gradient(
930
+ 90deg,
931
+ transparent,
932
+ color-mix(in oklab, var(--ph-text-primary) 9%, transparent),
933
+ transparent
934
+ );
935
+ animation: suite-shimmer 1.6s linear infinite;
936
+ }
937
+
938
+ @media (prefers-reduced-motion: reduce) {
939
+ .suite-msg-in,
940
+ .suite-msg-in--user,
941
+ .suite-msg-avatar,
942
+ .suite-msg-bubble,
943
+ .suite-word-in {
944
+ animation: none;
945
+ filter: none;
946
+ }
947
+ .suite-think-dot {
948
+ animation: none;
949
+ opacity: 0.7;
950
+ transform: none;
951
+ }
952
+ .suite-shimmer::after {
953
+ animation: none;
954
+ opacity: 0;
955
+ }
956
+ }
957
+
800
958
  /* ── Auth / OAuth / launch handoff ──────────────────────────────────────
801
959
  One calm opacity fade — no stacked entrance reveals on bridge pages. */
802
960
  .suite-auth-handoff {
@@ -38,4 +38,44 @@ describe("SuiteAiMarkdownMessage", () => {
38
38
  expect(container.querySelector('[contenteditable]')).toBeNull();
39
39
  expect(container.querySelector("img")).toBeNull();
40
40
  });
41
+
42
+ it("leaves words untouched unless animate is set", () => {
43
+ const { container } = render(
44
+ <SuiteThemeProvider config={themeConfig}>
45
+ <SuiteAiMarkdownMessage markdown={"Hello there world"} />
46
+ </SuiteThemeProvider>,
47
+ );
48
+
49
+ expect(container.querySelectorAll(".suite-word-in")).toHaveLength(0);
50
+ expect(container.textContent).toContain("Hello there world");
51
+ });
52
+
53
+ it("staggers each word when animate is set", () => {
54
+ const { container } = render(
55
+ <SuiteThemeProvider config={themeConfig}>
56
+ <SuiteAiMarkdownMessage markdown={"Hello there world"} animate />
57
+ </SuiteThemeProvider>,
58
+ );
59
+
60
+ const words = container.querySelectorAll<HTMLElement>(".suite-word-in");
61
+ expect(words).toHaveLength(3);
62
+ expect(words[0].textContent).toBe("Hello");
63
+ expect(words[0].style.animationDelay).toBe("0ms");
64
+ expect(words[1].style.animationDelay).toBe("18ms");
65
+ expect(words[2].style.animationDelay).toBe("36ms");
66
+ // Copy still yields the original prose.
67
+ expect(container.textContent).toBe("Hello there world");
68
+ });
69
+
70
+ it("does not stagger code spans", () => {
71
+ const { container } = render(
72
+ <SuiteThemeProvider config={themeConfig}>
73
+ <SuiteAiMarkdownMessage markdown={"Run `npm run dev` now"} animate />
74
+ </SuiteThemeProvider>,
75
+ );
76
+
77
+ const code = container.querySelector("code");
78
+ expect(code).toBeTruthy();
79
+ expect(code?.querySelector(".suite-word-in")).toBeNull();
80
+ });
41
81
  });
@@ -1,17 +1,91 @@
1
1
  "use client";
2
2
 
3
- import ReactMarkdown from "react-markdown";
3
+ import ReactMarkdown, { type Options } from "react-markdown";
4
4
  import remarkGfm from "remark-gfm";
5
5
 
6
+ /** Per-word stagger step, and the ceiling so a long reply still settles fast. */
7
+ const WORD_STEP_MS = 18;
8
+ const MAX_DELAY_MS = 600;
9
+
10
+ /** Tags whose text is left alone — animating code word by word reads badly. */
11
+ const SKIP_TAGS = new Set(["code", "pre", "style", "script"]);
12
+
13
+ interface HastNode {
14
+ type: string;
15
+ tagName?: string;
16
+ value?: string;
17
+ properties?: Record<string, unknown>;
18
+ children?: HastNode[];
19
+ }
20
+
21
+ /**
22
+ * Wrap each word of prose in a span that fades in on a stagger, so a reply
23
+ * that arrived in one payload reads as if it were being written.
24
+ *
25
+ * Delay lives inline on the span, so React re-rendering the same tree reuses
26
+ * the same elements and the animation does not replay.
27
+ */
28
+ function rehypeStaggerWords() {
29
+ return (tree: HastNode) => {
30
+ let wordIndex = 0;
31
+
32
+ const visit = (node: HastNode) => {
33
+ if (!node.children) return;
34
+ if (node.tagName && SKIP_TAGS.has(node.tagName)) return;
35
+
36
+ const next: HastNode[] = [];
37
+ for (const child of node.children) {
38
+ const isText =
39
+ child.type === "text" &&
40
+ typeof child.value === "string" &&
41
+ /\S/.test(child.value);
42
+
43
+ if (!isText) {
44
+ visit(child);
45
+ next.push(child);
46
+ continue;
47
+ }
48
+
49
+ for (const part of child.value!.split(/(\s+)/)) {
50
+ if (!part) continue;
51
+ if (/^\s+$/.test(part)) {
52
+ next.push({ type: "text", value: part });
53
+ continue;
54
+ }
55
+ const delay = Math.min(wordIndex * WORD_STEP_MS, MAX_DELAY_MS);
56
+ wordIndex += 1;
57
+ next.push({
58
+ type: "element",
59
+ tagName: "span",
60
+ properties: {
61
+ className: ["suite-word-in"],
62
+ style: `animation-delay:${delay}ms`,
63
+ },
64
+ children: [{ type: "text", value: part }],
65
+ });
66
+ }
67
+ }
68
+ node.children = next;
69
+ };
70
+
71
+ visit(tree);
72
+ };
73
+ }
74
+
6
75
  export default function SuiteAiMarkdownMessage({
7
76
  markdown,
77
+ animate = false,
8
78
  }: {
9
79
  markdown: string;
80
+ animate?: boolean;
10
81
  }) {
11
82
  return (
12
83
  <div className="suite-ai-markdown">
13
84
  <ReactMarkdown
14
85
  remarkPlugins={[remarkGfm]}
86
+ rehypePlugins={
87
+ (animate ? [rehypeStaggerWords] : undefined) as Options["rehypePlugins"]
88
+ }
15
89
  components={{ img: () => null }}
16
90
  >
17
91
  {markdown}
@@ -297,6 +297,13 @@ export function SuiteAiPanel({
297
297
  if (open && !hydrating) textareaRef.current?.focus();
298
298
  }, [open, hydrating]);
299
299
 
300
+ // Warm the lazily-imported Markdown renderer as soon as the panel opens, so a
301
+ // reply never flashes a placeholder while its chunk is still downloading.
302
+ useEffect(() => {
303
+ if (!open) return;
304
+ void import("./ai-message-markdown").catch(() => {});
305
+ }, [open]);
306
+
300
307
  useLayoutEffect(() => {
301
308
  const el = textareaRef.current;
302
309
  if (!el) return;
@@ -425,6 +432,13 @@ export function SuiteAiPanel({
425
432
 
426
433
  if (!open) return null;
427
434
 
435
+ // Only the newest reply gets the staggered word reveal; animating the whole
436
+ // transcript would replay whenever the panel remounts mid-conversation.
437
+ const lastAssistantIndex = messages.reduce(
438
+ (found, message, index) => (message.role === "assistant" ? index : found),
439
+ -1,
440
+ );
441
+
428
442
  const Icon = BrandIcon ?? Sparks;
429
443
 
430
444
  return (
@@ -505,7 +519,7 @@ export function SuiteAiPanel({
505
519
  return (
506
520
  <div
507
521
  key={`user-${index}`}
508
- className="flex justify-end"
522
+ className="suite-msg-in suite-msg-in--user flex justify-end"
509
523
  data-test="ask-ai-user"
510
524
  >
511
525
  <div className="w-fit max-w-[85%] whitespace-pre-wrap break-words rounded-2xl rounded-br-md bg-ph-brand px-3 py-2 text-sm leading-relaxed text-[var(--ph-on-accent)] [overflow-wrap:anywhere]">
@@ -521,28 +535,27 @@ export function SuiteAiPanel({
521
535
  data-test="ask-ai-assistant"
522
536
  >
523
537
  <span
524
- className="mb-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-ph-brand/10 text-ph-brand"
538
+ className="suite-msg-avatar mb-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-ph-brand/10 text-ph-brand"
525
539
  aria-hidden
526
540
  >
527
541
  <Icon className="h-3.5 w-3.5" />
528
542
  </span>
529
- <div className="min-w-0 max-w-[85%] space-y-2">
543
+ <div className="suite-msg-bubble min-w-0 max-w-[85%] space-y-2">
530
544
  <div className="rounded-2xl rounded-bl-md bg-ph-muted px-3 py-2 text-sm leading-relaxed text-ph-ink">
531
545
  <Suspense
532
546
  fallback={
533
- <div
534
- className="space-y-2 py-0.5"
535
- role="status"
536
- aria-label="Rendering response"
537
- >
538
- <span className="sr-only">Rendering response…</span>
539
- <div className="h-3 w-11/12 rounded bg-ph-mutedtext/20 motion-safe:animate-pulse" />
540
- <div className="h-3 w-full rounded bg-ph-mutedtext/20 motion-safe:animate-pulse" />
541
- <div className="h-3 w-3/5 rounded bg-ph-mutedtext/20 motion-safe:animate-pulse" />
542
- </div>
547
+ // The chunk is pre-warmed above, so this only shows on a
548
+ // cold cache. Keep it invisible — a skeleton here reads
549
+ // as if it were part of the answer.
550
+ <span className="sr-only" role="status">
551
+ Rendering response…
552
+ </span>
543
553
  }
544
554
  >
545
- <SuiteAiMarkdownMessage markdown={message.content} />
555
+ <SuiteAiMarkdownMessage
556
+ markdown={message.content}
557
+ animate={index === lastAssistantIndex}
558
+ />
546
559
  </Suspense>
547
560
  </div>
548
561
  {(message.actions ?? []).map((action) => (
@@ -612,9 +625,9 @@ export function SuiteAiPanel({
612
625
  <Icon className="h-3.5 w-3.5" />
613
626
  </span>
614
627
  <div className="flex items-center gap-1.5 rounded-2xl rounded-bl-md bg-ph-muted px-3 py-2.5">
615
- <span className="h-1.5 w-1.5 rounded-full bg-ph-mutedtext motion-safe:animate-bounce" />
616
- <span className="h-1.5 w-1.5 rounded-full bg-ph-mutedtext motion-safe:animate-bounce [animation-delay:0.15s]" />
617
- <span className="h-1.5 w-1.5 rounded-full bg-ph-mutedtext motion-safe:animate-bounce [animation-delay:0.3s]" />
628
+ <span className="suite-think-dot h-1.5 w-1.5 rounded-full bg-ph-mutedtext" />
629
+ <span className="suite-think-dot suite-think-dot--2 h-1.5 w-1.5 rounded-full bg-ph-mutedtext" />
630
+ <span className="suite-think-dot suite-think-dot--3 h-1.5 w-1.5 rounded-full bg-ph-mutedtext" />
618
631
  </div>
619
632
  </div>
620
633
  ) : null}
@@ -320,9 +320,7 @@ export function SuiteSidebar({
320
320
  className={cn(
321
321
  "shrink-0 border-t border-ph-border",
322
322
  surface ? "bg-ph-surface" : "bg-ph-canvas",
323
- // Trim only the bottom inset: the 32px avatar sits inside a 40px
324
- // trigger, so even padding read top-heavy once the avatar shrank.
325
- collapsed ? "px-1.5 pt-1.5 pb-1" : "px-3 pt-3 pb-2",
323
+ collapsed ? "p-1.5" : "p-3",
326
324
  )}
327
325
  >
328
326
  <div
@@ -16,6 +16,10 @@ export interface SuiteUserMenuProps {
16
16
  onSignOut: () => void;
17
17
  /** Fallback letter(s) when there is no avatar image. */
18
18
  fallbackInitials?: string;
19
+ /** Second line under the name, e.g. the workspace subscription ("Plus"). */
20
+ subtitle?: string | null;
21
+ /** Show the name + subtitle beside the avatar, for the sidebar footer. */
22
+ showDetails?: boolean;
19
23
  /** Show the old desktop sidebar sign-out action beside the avatar. */
20
24
  showSignOutAction?: boolean;
21
25
  dataTest?: string;
@@ -72,6 +76,8 @@ export function SuiteUserMenu({
72
76
  settingsHref,
73
77
  onSignOut,
74
78
  fallbackInitials,
79
+ subtitle,
80
+ showDetails = false,
75
81
  showSignOutAction = false,
76
82
  dataTest = "suite-user-menu",
77
83
  triggerId,
@@ -110,9 +116,27 @@ export function SuiteUserMenu({
110
116
  </span>
111
117
  );
112
118
 
119
+ // Sidebar footer shows who is signed in and which plan the workspace is on;
120
+ // the mobile header keeps the bare avatar so its action row stays compact.
121
+ const trigger = showDetails ? (
122
+ <span className="flex min-w-0 items-center gap-2">
123
+ {avatar}
124
+ <span className="flex min-w-0 flex-col text-left">
125
+ <span className="truncate text-sm font-medium text-ph-ink">
126
+ {name || email || "Account"}
127
+ </span>
128
+ {subtitle ? (
129
+ <span className="truncate text-xs text-ph-mutedtext">{subtitle}</span>
130
+ ) : null}
131
+ </span>
132
+ </span>
133
+ ) : (
134
+ avatar
135
+ );
136
+
113
137
  const accountMenu = (
114
138
  <DropdownMenu
115
- trigger={avatar}
139
+ trigger={trigger}
116
140
  triggerId={triggerId ?? `${dataTest}-trigger`}
117
141
  triggerDataTest={triggerDataTest ?? `${dataTest}-trigger`}
118
142
  aria-label="Account menu"
@@ -123,6 +147,8 @@ export function SuiteUserMenu({
123
147
  modal={false}
124
148
  className={cn(
125
149
  "[&_.ph-dropdown-trigger]:rounded-full",
150
+ showDetails &&
151
+ "min-w-0 flex-1 [&_.ph-dropdown-trigger]:w-full [&_.ph-dropdown-trigger]:justify-start [&_.ph-dropdown-trigger]:rounded-lg",
126
152
  showSignOutAction ? undefined : className,
127
153
  )}
128
154
  data-test={dataTest}
@@ -1,22 +0,0 @@
1
- "use client";
2
- import "./chunk-FWCSY2DS.mjs";
3
-
4
- // src/suite/components/ai-message-markdown.tsx
5
- import ReactMarkdown from "react-markdown";
6
- import remarkGfm from "remark-gfm";
7
- import { jsx } from "react/jsx-runtime";
8
- function SuiteAiMarkdownMessage({
9
- markdown
10
- }) {
11
- return /* @__PURE__ */ jsx("div", { className: "suite-ai-markdown", children: /* @__PURE__ */ jsx(
12
- ReactMarkdown,
13
- {
14
- remarkPlugins: [remarkGfm],
15
- components: { img: () => null },
16
- children: markdown
17
- }
18
- ) });
19
- }
20
- export {
21
- SuiteAiMarkdownMessage as default
22
- };