@nextclaw/ui 0.15.20 → 0.15.22

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 (191) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/assets/{api-tWUa6l9J.js → api-B1yHQYs-.js} +6 -6
  3. package/dist/assets/app-presenter-provider-uP9waOzv.js +108 -0
  4. package/dist/assets/appearance-settings-page-C9Zq3hP9.js +1 -0
  5. package/dist/assets/{book-open-BQ29ZmDE.js → book-open-oSmyVXea.js} +1 -1
  6. package/dist/assets/{card-CtKueklV.js → card-CGvJZTZp.js} +1 -1
  7. package/dist/assets/{channels-list-page-C8y3rqYt.js → channels-list-page-5BTxpQHw.js} +2 -2
  8. package/dist/assets/chat-page-jTvli8Jp.js +1 -0
  9. package/dist/assets/config-split-page-BgBsUmp1.js +1 -0
  10. package/dist/assets/confirm-dialog-h849QRWO.js +5 -0
  11. package/dist/assets/desktop-update-config-BGGHxClR.js +1 -0
  12. package/dist/assets/{dist-rYHnBo8t.js → dist-CxUhurq9.js} +1 -1
  13. package/dist/assets/doc-browser-0eL8AX42.js +1 -0
  14. package/dist/assets/{doc-browser-D8ng4EUc.js → doc-browser-DEqE9lsh.js} +1 -1
  15. package/dist/assets/{doc-browser-context-CPHNzQ8m.js → doc-browser-context-DkzFIUoK.js} +1 -1
  16. package/dist/assets/{eye-CiJz3ODY.js → eye-gOWQGt8N.js} +1 -1
  17. package/dist/assets/{form-actions-BnZVzLZf.js → form-actions-CgjYZUzM.js} +1 -1
  18. package/dist/assets/index-DSAUWCfl.js +2 -0
  19. package/dist/assets/index-G7cBryJ4.css +1 -0
  20. package/dist/assets/loader-circle-BAuycGf6.js +1 -0
  21. package/dist/assets/{logo-badge-BXI-Kdhd.js → logo-badge-BNcsDsUM.js} +1 -1
  22. package/dist/assets/{mcp-marketplace-page-CxNe-Q9Q.js → mcp-marketplace-page-BX39MtPa.js} +3 -3
  23. package/dist/assets/mcp-marketplace-page-CkDtOEA4.js +1 -0
  24. package/dist/assets/{model-config-page-CR_mgEVp.js → model-config-page-7MIXg2fQ.js} +1 -1
  25. package/dist/assets/{navigation-link-C1cvB7y4.js → navigation-link-B3iFOdxx.js} +1 -1
  26. package/dist/assets/plus-yvhFgfal.js +1 -0
  27. package/dist/assets/{popover-ZoDwVwwd.js → popover-Cpep_rJe.js} +1 -1
  28. package/dist/assets/{provider-models-CXUgDxGR.js → provider-models-CUrptZ-o.js} +1 -1
  29. package/dist/assets/provider-scoped-model-input-CHmOzz7x.js +1 -0
  30. package/dist/assets/providers-config-page-A6J7Y0II.js +1 -0
  31. package/dist/assets/react-BCJGCjrP.js +8 -0
  32. package/dist/assets/{refresh-cw-DKewcdAl.js → refresh-cw-CkKwPztT.js} +1 -1
  33. package/dist/assets/remote-FD-ZfSIn.js +1 -0
  34. package/dist/assets/rotate-cw-DrJMfHhp.js +1 -0
  35. package/dist/assets/runtime-config-page-DwjO-Vs6.js +1 -0
  36. package/dist/assets/{save-BrckuAXY.js → save-BINMWate.js} +1 -1
  37. package/dist/assets/{search-BhXQB2TO.js → search-7M38azRp.js} +1 -1
  38. package/dist/assets/{search-config-page-DFOQ6SDJ.js → search-config-page-T6tMhXbP.js} +1 -1
  39. package/dist/assets/{secrets-config-page-BqqeClqE.js → secrets-config-page-2mVejXf2.js} +2 -2
  40. package/dist/assets/security-config-DD5RrjDe.js +1 -0
  41. package/dist/assets/{select-CVzGGT38.js → select-BvidP5Oa.js} +1 -1
  42. package/dist/assets/{settings-2-BP6L2enG.js → settings-2-DqZstREV.js} +1 -1
  43. package/dist/assets/skeleton-BEQW2Dva.js +1 -0
  44. package/dist/assets/{tabs-B-QWjKCL.js → tabs-BeLxVmkt.js} +1 -1
  45. package/dist/assets/{tag-chip-C88mxjwx.js → tag-chip-BoDaLnWt.js} +1 -1
  46. package/dist/assets/{trash-2-ck3t33I3.js → trash-2-CYUskMMN.js} +1 -1
  47. package/dist/assets/{use-config-BAr5bH2c.js → use-config-CxKRXXCl.js} +1 -1
  48. package/dist/assets/use-confirm-dialog-HASTvOP2.js +1 -0
  49. package/dist/assets/{use-viewport-layout-k1isXfNm.js → use-viewport-layout-CJxkjoRz.js} +1 -1
  50. package/dist/assets/x-DdUwjre5.js +1 -0
  51. package/dist/index.html +29 -29
  52. package/package.json +8 -8
  53. package/src/app/components/__tests__/app-notification-runtime.test.tsx +32 -0
  54. package/src/app/components/app-notification-runtime.tsx +15 -0
  55. package/src/app/components/layout/sidebar-items.tsx +23 -9
  56. package/src/app/components/layout/sidebar-rail.styles.ts +0 -2
  57. package/src/app/components/layout/sidebar.tsx +2 -2
  58. package/src/app/configs/app-navigation.config.ts +21 -0
  59. package/src/app/index.tsx +22 -2
  60. package/src/app/presenters/app.presenter.ts +8 -0
  61. package/src/app/styles/design-system.css +12 -0
  62. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +2 -2
  63. package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +19 -1
  64. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +1 -1
  65. package/src/features/chat/components/conversation/chat-conversation-content.tsx +5 -6
  66. package/src/features/chat/components/conversation/chat-conversation-header.tsx +1 -1
  67. package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +1 -1
  68. package/src/features/chat/components/layout/__tests__/chat-sidebar-session-area.test.tsx +52 -0
  69. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +11 -17
  70. package/src/features/chat/components/layout/chat-page-shell.tsx +9 -13
  71. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +40 -38
  72. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +13 -64
  73. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +1 -4
  74. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +1 -1
  75. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +15 -4
  76. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +7 -7
  77. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +4 -2
  78. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +1 -0
  79. package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +1 -0
  80. package/src/features/chat/features/conversation/utils/session-queued-input.utils.ts +1 -0
  81. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +3 -0
  82. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +0 -1
  83. package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +3 -0
  84. package/src/features/chat/features/message/utils/__tests__/chat-message-timeline.utils.test.ts +29 -0
  85. package/src/features/chat/features/message/utils/chat-message-timeline.utils.ts +4 -1
  86. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +10 -10
  87. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
  88. package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
  89. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +1 -1
  90. package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +1 -1
  91. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +1 -1
  92. package/src/features/chat/features/session/utils/__tests__/chat-session-display.utils.test.ts +26 -0
  93. package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +4 -2
  94. package/src/features/chat/features/session/utils/chat-session-display.utils.ts +48 -12
  95. package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +13 -4
  96. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +6 -0
  97. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +10 -7
  98. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +1 -1
  99. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +3 -14
  100. package/src/features/chat/features/welcome/components/chat-welcome-prompt-suggestions.tsx +37 -0
  101. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +1 -1
  102. package/src/features/chat/features/welcome/components/chat-welcome.tsx +33 -31
  103. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +1 -1
  104. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +1 -1
  105. package/src/features/chat/index.ts +1 -0
  106. package/src/features/chat/managers/__tests__/chat-completion-notification.manager.test.ts +177 -0
  107. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +15 -3
  108. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +3 -10
  109. package/src/features/chat/managers/chat-completion-notification.manager.ts +146 -0
  110. package/src/features/chat/managers/chat-session-list.manager.ts +3 -23
  111. package/src/features/chat/managers/chat-thread.manager.ts +2 -5
  112. package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +32 -11
  113. package/src/features/chat/pages/ncp-chat-page.tsx +9 -1
  114. package/src/features/chat/types/chat-input-bar.types.ts +1 -0
  115. package/src/features/cron/components/cron-task-discovery.tsx +1 -1
  116. package/src/features/inbox/components/inbox-delivery-content.test.tsx +57 -0
  117. package/src/features/inbox/components/inbox-delivery-content.tsx +118 -0
  118. package/src/features/inbox/components/inbox-reader-dialog.test.tsx +77 -0
  119. package/src/features/inbox/components/inbox-reader-dialog.tsx +224 -0
  120. package/src/features/inbox/components/inbox-runtime.tsx +14 -0
  121. package/src/features/inbox/hooks/use-inbox-deliveries.ts +14 -0
  122. package/src/features/inbox/index.ts +7 -0
  123. package/src/features/inbox/managers/inbox.manager.test.ts +86 -0
  124. package/src/features/inbox/managers/inbox.manager.ts +178 -0
  125. package/src/features/inbox/pages/inbox-page.test.ts +28 -0
  126. package/src/features/inbox/pages/inbox-page.tsx +385 -0
  127. package/src/features/inbox/stores/inbox.store.ts +23 -0
  128. package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
  129. package/src/features/notifications/components/__tests__/app-notification-toast.test.tsx +51 -0
  130. package/src/features/notifications/components/app-notification-toast.tsx +80 -0
  131. package/src/features/notifications/index.ts +8 -0
  132. package/src/features/notifications/managers/__tests__/app-notification.manager.test.ts +75 -0
  133. package/src/features/notifications/managers/app-notification.manager.ts +41 -0
  134. package/src/features/panel-apps/components/panel-app-list-item.tsx +1 -1
  135. package/src/features/panel-apps/components/panel-app-toolbar.tsx +2 -2
  136. package/src/features/panel-apps/components/panel-apps-list.tsx +4 -4
  137. package/src/features/service-apps/components/service-apps-panel.tsx +1 -1
  138. package/src/features/settings/components/config/provider-auth-section.tsx +90 -59
  139. package/src/features/settings/components/config/provider-form.tsx +10 -17
  140. package/src/features/settings/components/config/provider-status-badge.tsx +4 -2
  141. package/src/features/settings/pages/__tests__/providers-config-page.test.tsx +12 -0
  142. package/src/features/settings/pages/providers-config-page.tsx +2 -1
  143. package/src/platforms/mobile/components/__tests__/mobile-app-shell.test.tsx +13 -7
  144. package/src/platforms/mobile/components/__tests__/mobile-bottom-nav.test.tsx +18 -20
  145. package/src/platforms/mobile/components/mobile-bottom-nav.tsx +14 -7
  146. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +1 -1
  147. package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +1 -1
  148. package/src/shared/components/path-picker/server-path-picker-locations.tsx +1 -1
  149. package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +3 -3
  150. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +1 -1
  151. package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
  152. package/src/shared/components/ui/button.tsx +4 -4
  153. package/src/shared/components/ui/dialog.tsx +1 -1
  154. package/src/shared/components/ui/input.tsx +1 -1
  155. package/src/shared/components/ui/scroll-area.tsx +2 -0
  156. package/src/shared/components/ui/select.tsx +1 -1
  157. package/src/shared/components/ui/switch.tsx +1 -1
  158. package/src/shared/components/ui/tabs.tsx +1 -1
  159. package/src/shared/components/ui/textarea.tsx +1 -1
  160. package/src/shared/lib/api/ncp-session.types.ts +1 -0
  161. package/src/shared/lib/api/types.ts +2 -0
  162. package/src/shared/lib/i18n/index.ts +4 -0
  163. package/src/shared/lib/i18n/locales/en-US/chat.json +16 -14
  164. package/src/shared/lib/i18n/locales/en-US/inbox.json +30 -0
  165. package/src/shared/lib/i18n/locales/zh-CN/chat.json +16 -14
  166. package/src/shared/lib/i18n/locales/zh-CN/inbox.json +30 -0
  167. package/src/shared/lib/provider-models/__tests__/anonymous-provider-configuration.test.ts +40 -0
  168. package/src/shared/lib/provider-models/index.ts +4 -3
  169. package/dist/assets/app-presenter-provider-s-FrOinZ.js +0 -106
  170. package/dist/assets/appearance-settings-page-dMRBD7ni.js +0 -1
  171. package/dist/assets/chat-page-MXBltS3G.js +0 -1
  172. package/dist/assets/config-split-page-uC6d_r9c.js +0 -1
  173. package/dist/assets/confirm-dialog-C1pbqHZX.js +0 -5
  174. package/dist/assets/desktop-update-config-DssP8LCk.js +0 -1
  175. package/dist/assets/doc-browser-AXF1zRsM.js +0 -1
  176. package/dist/assets/index-CocHWEsI.js +0 -2
  177. package/dist/assets/index-DN6KM7WD.css +0 -1
  178. package/dist/assets/loader-circle-AOpK5kkW.js +0 -1
  179. package/dist/assets/mcp-marketplace-page-5ogeBHnl.js +0 -1
  180. package/dist/assets/plus-DI37QuHc.js +0 -1
  181. package/dist/assets/provider-scoped-model-input-DIL6Ex_d.js +0 -1
  182. package/dist/assets/providers-config-page-D2NXVctW.js +0 -1
  183. package/dist/assets/react-39CDNZlM.js +0 -8
  184. package/dist/assets/remote-DYOOO1YG.js +0 -1
  185. package/dist/assets/rotate-cw-PmqA8LQT.js +0 -1
  186. package/dist/assets/runtime-config-page-0lb5NyKc.js +0 -1
  187. package/dist/assets/security-config-13AtPIlm.js +0 -1
  188. package/dist/assets/skeleton-OwCNQJ7M.js +0 -1
  189. package/dist/assets/use-confirm-dialog-C4e0nG_4.js +0 -1
  190. package/dist/assets/x-C1ijNWcE.js +0 -1
  191. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +0 -59
@@ -12,14 +12,17 @@ import { t, type I18nLanguage } from "@/shared/lib/i18n";
12
12
  import type { UiTheme } from "@/shared/lib/theme";
13
13
  import { SidebarNavLinkItem } from "@/app/components/layout/sidebar-items";
14
14
  import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
15
+ import { SCROLL_BOTTOM_EDGE_FADE_CLASS } from "@/shared/components/ui/scroll-area";
15
16
  import {
16
17
  AlarmClock,
17
18
  Bot,
18
19
  BrainCircuit,
20
+ Inbox,
19
21
  PanelLeftClose,
20
22
  PanelLeftOpen,
21
- Plus,
23
+ FolderPlus,
22
24
  } from "lucide-react";
25
+ import { useInboxUnreadCount } from "@/features/inbox";
23
26
  import { ChatSidebarSessionList } from "@/features/chat/features/session/components/chat-sidebar-session-list";
24
27
  import { ChatSidebarUtilityMenu } from "@/features/chat/components/layout/chat-sidebar-utility-menu";
25
28
  import { isWindowsDesktopHost } from "@/platforms/desktop";
@@ -30,10 +33,14 @@ import {
30
33
  SIDEBAR_RAIL_PADDING_X_CLASS,
31
34
  SIDEBAR_RAIL_STACK_CLASS,
32
35
  SIDEBAR_RAIL_SURFACE_CLASS,
33
- SIDEBAR_SCROLL_EDGE_FADE_CLASS,
34
36
  } from "@/app/components/layout/sidebar-rail.styles";
35
37
 
36
38
  const navItems = [
39
+ {
40
+ target: "/inbox",
41
+ label: () => t("inboxTitle"),
42
+ icon: Inbox,
43
+ },
37
44
  {
38
45
  target: "/cron",
39
46
  label: () => t("chatSidebarScheduledTasks"),
@@ -79,7 +86,7 @@ export function ChatSidebarDesktopHeader({
79
86
  ? "justify-center px-2 py-1.5"
80
87
  : isWindowsHost
81
88
  ? "justify-end px-3 py-1.5"
82
- : "gap-2 px-5 py-2",
89
+ : "gap-2 px-4 py-2",
83
90
  isCollapsed && shouldReserveMacWindowControls ? "pt-8" : null,
84
91
  )}
85
92
  >
@@ -99,33 +106,31 @@ export function ChatSidebarDesktopNav({
99
106
  }: {
100
107
  isCollapsed: boolean;
101
108
  }) {
109
+ const unreadCount = useInboxUnreadCount();
102
110
  return (
103
- <>
104
- <div className={cn("pb-2", isCollapsed ? "px-0" : "px-3")}>
105
- <ul className={isCollapsed ? SIDEBAR_RAIL_STACK_CLASS : "space-y-0.5"}>
106
- {navItems.map((item) => (
107
- <li
108
- key={item.target}
109
- className={isCollapsed ? "flex justify-center" : undefined}
110
- >
111
- <SidebarNavLinkItem
112
- to={item.target}
113
- label={item.label()}
114
- icon={item.icon}
115
- density="compact"
116
- collapsed={isCollapsed}
117
- />
118
- </li>
119
- ))}
120
- </ul>
121
- </div>
122
- <div
123
- className={cn(
124
- "border-t border-border/70",
125
- isCollapsed ? "mx-2 my-1.5" : "mx-4",
126
- )}
127
- />
128
- </>
111
+ <div className={cn("pb-1", isCollapsed ? "px-0" : "px-3")}>
112
+ <ul className={isCollapsed ? SIDEBAR_RAIL_STACK_CLASS : "space-y-0.5"}>
113
+ {navItems.map((item) => (
114
+ <li
115
+ key={item.target}
116
+ className={isCollapsed ? "flex justify-center" : undefined}
117
+ >
118
+ <SidebarNavLinkItem
119
+ to={item.target}
120
+ label={item.label()}
121
+ icon={item.icon}
122
+ density="compact"
123
+ collapsed={isCollapsed}
124
+ indicator={item.target === "/inbox" && unreadCount > 0}
125
+ trailing={item.target === "/inbox" && unreadCount > 0
126
+ ? unreadCount > 99 ? "99+" : unreadCount
127
+ : undefined}
128
+ className={isCollapsed ? undefined : "rounded-lg px-2.5 py-1.5"}
129
+ />
130
+ </li>
131
+ ))}
132
+ </ul>
133
+ </div>
129
134
  );
130
135
  }
131
136
 
@@ -160,14 +165,11 @@ export function ChatSidebarSessionArea({
160
165
 
161
166
  return (
162
167
  <>
163
- <div className="flex items-center justify-between px-5 pb-2 pt-3">
164
- <div className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground/75">
165
- {t("chatSidebarTaskRecords")}
166
- </div>
167
- <div className="flex items-center gap-1">
168
+ <div className="flex items-center justify-end px-4 pb-1 pt-2">
169
+ <div className="flex h-7 items-center gap-1.5">
168
170
  {isProjectFirstView ? (
169
171
  <IconActionButton
170
- icon={<Plus className="h-3.5 w-3.5" />}
172
+ icon={<FolderPlus className="h-3.5 w-3.5" />}
171
173
  label={t("chatProjectAdd")}
172
174
  onClick={onAddProject}
173
175
  />
@@ -181,8 +183,8 @@ export function ChatSidebarSessionArea({
181
183
 
182
184
  <div
183
185
  className={cn(
184
- "custom-scrollbar min-h-0 flex-1 overflow-y-auto px-3 pb-7 pt-2",
185
- SIDEBAR_SCROLL_EDGE_FADE_CLASS,
186
+ "custom-scrollbar min-h-0 flex-1 overflow-y-auto px-3 pb-4 pt-1",
187
+ SCROLL_BOTTOM_EDGE_FADE_CLASS,
186
188
  )}
187
189
  >
188
190
  <ChatSidebarSessionList
@@ -234,7 +236,7 @@ export function ChatSidebarDesktopFooter({
234
236
  return (
235
237
  <div
236
238
  className={cn(
237
- "py-3",
239
+ "py-2",
238
240
  isCollapsed
239
241
  ? cn("flex justify-center", SIDEBAR_RAIL_PADDING_X_CLASS)
240
242
  : "px-3",
@@ -1,4 +1,4 @@
1
- import { Button, type ButtonProps } from "@/shared/components/ui/button";
1
+ import { Button } from "@/shared/components/ui/button";
2
2
  import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
3
3
  import { Input } from "@/shared/components/ui/input";
4
4
  import {
@@ -20,51 +20,6 @@ import {
20
20
  } from "@/app/components/layout/sidebar-rail.styles";
21
21
 
22
22
  type SessionTypeOption = ChatSessionTypeOption;
23
- type NewSessionActionStyleVariant =
24
- | "neutralSurface"
25
- | "brandSoft"
26
- | "brandTextSurface"
27
- | "brandSolid";
28
-
29
- const NEW_SESSION_ACTION_STYLE_VARIANT: NewSessionActionStyleVariant =
30
- "brandSoft";
31
-
32
- const NEW_SESSION_ACTION_STYLE_CLASSES: Record<
33
- NewSessionActionStyleVariant,
34
- {
35
- leftVariant: ButtonProps["variant"];
36
- leftClassName: string;
37
- rightClassName: string;
38
- }
39
- > = {
40
- neutralSurface: {
41
- leftVariant: "ghost",
42
- leftClassName:
43
- "bg-card text-foreground shadow-none transition-[background-color,color,box-shadow] hover:bg-accent hover:text-accent-foreground hover:shadow-sm active:bg-accent",
44
- rightClassName:
45
- "bg-card text-muted-foreground shadow-none transition-[background-color,color,box-shadow] hover:bg-accent hover:text-accent-foreground hover:shadow-sm active:bg-accent",
46
- },
47
- brandSoft: {
48
- leftVariant: "ghost",
49
- leftClassName:
50
- "bg-primary/10 text-primary shadow-none ring-1 ring-primary/10 transition-[background-color,color,box-shadow] hover:bg-primary/15 hover:text-primary-700 hover:shadow-sm active:bg-primary/20",
51
- rightClassName:
52
- "bg-primary/10 text-primary shadow-none ring-1 ring-primary/10 transition-[background-color,color,box-shadow] hover:bg-primary/15 hover:text-primary-700 hover:shadow-sm active:bg-primary/20",
53
- },
54
- brandTextSurface: {
55
- leftVariant: "ghost",
56
- leftClassName:
57
- "bg-card text-primary shadow-none transition-[background-color,color,box-shadow] hover:bg-accent hover:text-primary-700 hover:shadow-sm active:bg-primary/15",
58
- rightClassName:
59
- "bg-card text-primary shadow-none transition-[background-color,color,box-shadow] hover:bg-accent hover:text-primary-700 hover:shadow-sm active:bg-primary/15",
60
- },
61
- brandSolid: {
62
- leftVariant: "primary",
63
- leftClassName: "",
64
- rightClassName:
65
- "bg-primary text-primary-foreground shadow-sm hover:bg-primary-600 hover:text-primary-foreground active:bg-primary-700",
66
- },
67
- };
68
23
 
69
24
  type ChatSidebarToolbarProps = {
70
25
  query: string;
@@ -117,8 +72,6 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
117
72
  collapsed = false,
118
73
  } = props;
119
74
  const supportsSessionTypeSwitch = sessionTypeOptions.length > 1;
120
- const actionStyle =
121
- NEW_SESSION_ACTION_STYLE_CLASSES[NEW_SESSION_ACTION_STYLE_VARIANT];
122
75
 
123
76
  if (collapsed) {
124
77
  return (
@@ -183,21 +136,20 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
183
136
 
184
137
  return (
185
138
  <>
186
- <div className="px-4 pb-3">
187
- <div className="flex items-center gap-2">
139
+ <div className="px-4 pb-2">
140
+ <div className="flex overflow-hidden rounded-lg bg-primary/10 text-primary ring-1 ring-primary/10">
188
141
  <Button
189
- variant={actionStyle.leftVariant}
142
+ variant="ghost"
190
143
  className={cn(
191
- "min-w-0 rounded-xl",
192
- actionStyle.leftClassName,
193
- supportsSessionTypeSwitch ? "flex-1 rounded-r-md" : "w-full",
144
+ "min-w-0 rounded-none bg-transparent px-3 text-primary shadow-none hover:bg-primary/10 hover:text-primary-700 active:bg-primary/15",
145
+ supportsSessionTypeSwitch ? "flex-1" : "w-full",
194
146
  )}
195
147
  onClick={() => {
196
148
  onCreateMenuOpenChange(false);
197
149
  onCreateSession(selectedNewSessionType);
198
150
  }}
199
151
  >
200
- <Plus className="mr-2 h-4 w-4" />
152
+ <Plus className="mr-1.5 h-4 w-4" />
201
153
  {t("chatSidebarNewTask")}
202
154
  </Button>
203
155
  {supportsSessionTypeSwitch ? (
@@ -208,19 +160,16 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
208
160
  <PopoverTrigger asChild>
209
161
  <IconActionButton
210
162
  icon={
211
- <span className="inline-flex items-center gap-0.5">
163
+ <span className="inline-flex items-center gap-1">
212
164
  <SessionTypeTriggerIcon
213
165
  option={selectedNewSessionTypeOption}
214
166
  />
215
- <ChevronDown className="h-3 w-3 opacity-60" />
167
+ <ChevronDown className="h-3 w-3 opacity-50" />
216
168
  </span>
217
169
  }
218
170
  label={t("chatSessionTypeLabel")}
219
171
  tooltip={t("chatSessionTypeLabel")}
220
- className={cn(
221
- "h-9 w-12 shrink-0 rounded-xl rounded-l-md",
222
- actionStyle.rightClassName,
223
- )}
172
+ className="h-9 w-12 shrink-0 rounded-none border-l border-primary/10 bg-transparent text-primary shadow-none hover:bg-primary/10 hover:text-primary-700 active:bg-primary/15"
224
173
  />
225
174
  </PopoverTrigger>
226
175
  <ChatPopoverContent
@@ -241,14 +190,14 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
241
190
  </div>
242
191
  </div>
243
192
 
244
- <div className="px-4 pb-3">
193
+ <div className="px-4 pb-2">
245
194
  <div className="relative">
246
- <Search className="pointer-events-none absolute left-3 top-2.5 h-3.5 w-3.5 text-muted-foreground/70" />
195
+ <Search className="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground/60" />
247
196
  <Input
248
197
  value={query}
249
198
  onChange={(event) => onQueryChange(event.target.value)}
250
199
  placeholder={t("chatSidebarSearchPlaceholder")}
251
- className="h-9 rounded-lg pl-8 text-xs"
200
+ className="h-8 rounded-lg border-transparent bg-background/55 pl-8 text-xs shadow-none hover:bg-background/75"
252
201
  />
253
202
  </div>
254
203
  </div>
@@ -102,13 +102,10 @@ export function ChatSidebarUtilityMenu({
102
102
  <button
103
103
  type="button"
104
104
  aria-label={t("settingsMenu")}
105
- className="flex w-full items-center gap-2.5 rounded-xl px-3 py-2 text-[13px] font-medium text-muted-foreground transition-colors duration-base hover:bg-gray-200/60 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
105
+ className="flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-[13px] font-medium text-muted-foreground transition-colors duration-base hover:bg-background/70 hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
106
106
  >
107
107
  <Settings className="h-4 w-4 shrink-0 text-muted-foreground/70" />
108
108
  <span className="min-w-0 flex-1 text-left">{t("settings")}</span>
109
- <span className="max-w-[112px] truncate text-[13px] text-muted-foreground">
110
- {currentThemeLabel} / {currentLanguageLabel}
111
- </span>
112
109
  </button>
113
110
  </PopoverTrigger>
114
111
  )}
@@ -389,7 +389,7 @@ describe("SessionConversationArea input boundary", () => {
389
389
 
390
390
  expect(screen.getByTestId("conversation-bottom-slot")).toBeTruthy();
391
391
  expect(screen.getByText(/出错了|Something went wrong/)).toBeTruthy();
392
- expect(screen.getByText("Run failed: Invalid API Key")).toBeTruthy();
392
+ expect(screen.getByText(/Failed · Invalid API Key|失败 · Invalid API Key/)).toBeTruthy();
393
393
  });
394
394
 
395
395
  it("does not surface user-cancelled previews as conversation errors", () => {
@@ -3,9 +3,11 @@ import {
3
3
  useEffect,
4
4
  useMemo,
5
5
  useState,
6
+ type ReactElement,
6
7
  type MutableRefObject,
7
8
  } from 'react';
8
9
  import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
10
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
9
11
  import { afterEach, describe, expect, it, vi } from 'vitest';
10
12
  import {
11
13
  createChatComposerTextNode,
@@ -30,6 +32,15 @@ import { useChatMessageLayoutStore } from '@/features/chat/stores/chat-message-l
30
32
 
31
33
  const uploadNcpAssetsMock = vi.hoisted(() => vi.fn());
32
34
 
35
+ function renderInput(input: ReactElement) {
36
+ const queryClient = new QueryClient({
37
+ defaultOptions: { queries: { retry: false } },
38
+ });
39
+ return render(
40
+ <QueryClientProvider client={queryClient}>{input}</QueryClientProvider>,
41
+ );
42
+ }
43
+
33
44
  afterEach(() => {
34
45
  useChatMessageLayoutStore.getState().setLayout('card');
35
46
  });
@@ -254,7 +265,7 @@ describe('SessionConversationInput streaming stability', () => {
254
265
  useChatMessageLayoutStore.getState().setLayout('flat');
255
266
  const controlRef: MutableRefObject<StreamingInputControl | null> = { current: null };
256
267
 
257
- render(<StreamingSessionConversationInputHarness controlRef={controlRef} />);
268
+ renderInput(<StreamingSessionConversationInputHarness controlRef={controlRef} />);
258
269
 
259
270
  const track = document.querySelector(
260
271
  '[data-chat-conversation-track="flat"][data-chat-conversation-track-width="composer"]',
@@ -266,7 +277,7 @@ describe('SessionConversationInput streaming stability', () => {
266
277
 
267
278
  it('keeps a numbered IME candidate commit stable while streamed output rerenders the owner', async () => {
268
279
  const controlRef: MutableRefObject<StreamingInputControl | null> = { current: null };
269
- render(<StreamingSessionConversationInputHarness controlRef={controlRef} />);
280
+ renderInput(<StreamingSessionConversationInputHarness controlRef={controlRef} />);
270
281
 
271
282
  const textbox = screen.getByRole('textbox');
272
283
  fireEvent.focus(textbox);
@@ -300,7 +311,7 @@ describe('SessionConversationInput streaming stability', () => {
300
311
  const controlRef: MutableRefObject<StreamingInputControl | null> = { current: null };
301
312
  const deleteQueuedInput = vi.fn();
302
313
  const editQueuedInput = vi.fn();
303
- render(
314
+ renderInput(
304
315
  <StreamingSessionConversationInputHarness
305
316
  controlRef={controlRef}
306
317
  controllerOverride={{
@@ -427,7 +438,7 @@ describe('SessionConversationInput attachment submit', () => {
427
438
  ]);
428
439
  const send = vi.fn<AttachmentSubmitAgentSend>(async () => createAttachmentRunHandle());
429
440
 
430
- render(<AttachmentSubmitHarness send={send} />);
441
+ renderInput(<AttachmentSubmitHarness send={send} />);
431
442
 
432
443
  const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement | null;
433
444
  expect(fileInput).toBeTruthy();
@@ -13,6 +13,7 @@ import {
13
13
  resolveNcpChatSendErrorMessage,
14
14
  } from "@/features/chat/features/runtime/utils/ncp-chat-runtime-availability.utils";
15
15
  import { buildChatContextWindowIndicator } from "@/features/chat/features/session/utils/chat-context-window-indicator.utils";
16
+ import { sessionActivityPreviewText } from "@/features/chat/features/session/utils/chat-session-display.utils";
16
17
  import { readNcpContextWindowValue } from "@/features/chat/features/session/utils/ncp-session-context-metadata.utils";
17
18
  import { ChatConversationWelcome } from "@/features/chat/features/welcome/components/chat-conversation-welcome";
18
19
  import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
@@ -326,12 +327,10 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
326
327
  }),
327
328
  [inputSnapshot, lastSendError],
328
329
  );
329
- const sessionFailurePreview = inputQuery.selectedSession?.activityPreview;
330
+ const { selectedSession } = inputQuery;
330
331
  const sessionFailureMessage =
331
- sessionFailurePreview?.state === "failed"
332
- ? sessionFailurePreview.statusText?.trim() ||
333
- sessionFailurePreview.replyText?.trim() ||
334
- null
332
+ selectedSession?.activityPreview?.state === "failed"
333
+ ? sessionActivityPreviewText(selectedSession)
335
334
  : null;
336
335
  const sessionFailureSlot = sessionFailureMessage ? (
337
336
  <div className="rounded-lg border border-red-200/80 bg-red-50/80 px-3 py-2.5 shadow-sm">
@@ -348,7 +347,7 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
348
347
  applyPromptSuggestion: inputActions.applyPromptSuggestion,
349
348
  });
350
349
  const renderInput = useCallback(
351
- (surface: "default" | "embedded") => (
350
+ (surface: "default" | "embedded", placeholder?: string) => (
352
351
  <SessionConversationInput
353
352
  contextWindow={contextWindow}
354
353
  controller={inputController}
@@ -356,6 +355,7 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
356
355
  inputQuery={inputQuery}
357
356
  inputSnapshot={displayInputSnapshot}
358
357
  onContextCompactingChange={handleContextCompactingChange}
358
+ placeholder={placeholder}
359
359
  surface={surface}
360
360
  />
361
361
  ),
@@ -396,7 +396,7 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
396
396
  onLoadPreviousMessages={agent.loadPreviousMessages}
397
397
  welcomeSlot={
398
398
  <ChatConversationWelcome
399
- inputSlot={renderInput("embedded")}
399
+ inputSlot={renderInput("embedded", t("chatWelcomeInputPlaceholder"))}
400
400
  pendingProjectRoot={inputSnapshot.pendingProjectRoot}
401
401
  pendingSessionType={inputSnapshot.pendingSessionType}
402
402
  selectedSessionTypeValue={inputSnapshot.selectedSessionType}
@@ -193,6 +193,7 @@ type SessionConversationInputProps = {
193
193
  readonly inputQuery: SessionConversationInputQuery;
194
194
  readonly inputSnapshot: SessionConversationInputSnapshot;
195
195
  readonly onContextCompactingChange?: (sessionId: string, isCompacting: boolean) => void;
196
+ readonly placeholder?: string;
196
197
  readonly surface?: 'default' | 'embedded';
197
198
  };
198
199
 
@@ -204,6 +205,7 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
204
205
  inputQuery,
205
206
  inputSnapshot,
206
207
  onContextCompactingChange,
208
+ placeholder,
207
209
  surface = 'default',
208
210
  } = props;
209
211
  const presenter = usePresenter();
@@ -277,7 +279,7 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
277
279
  const attachmentSupported = true;
278
280
  const textareaPlaceholder = isModelOptionsEmpty
279
281
  ? t('chatModelNoOptions')
280
- : t(isMobile ? 'chatInputPlaceholderCompact' : 'chatInputPlaceholder');
282
+ : placeholder ?? t(isMobile ? 'chatInputPlaceholderCompact' : 'chatInputPlaceholder');
281
283
  const selectedModelOption = modelRecords.find((option) => option.value === selectedModel);
282
284
  const thinkingSupportedLevels = selectedModelOption?.thinkingCapability?.supported ?? [];
283
285
  const { handleFilesAdd, handleFileInputChange } = useSessionConversationInputAttachments({
@@ -426,6 +428,7 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
426
428
  },
427
429
  })}
428
430
  toolbar={{
431
+ addMenuLabel: t('chatInputAdd'),
429
432
  selects: [],
430
433
  trailingSelects: toolbarSelects,
431
434
  accessories: [
@@ -433,7 +436,6 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
433
436
  key: 'attach',
434
437
  label: t('chatInputAttach'),
435
438
  icon: 'paperclip' as const,
436
- iconOnly: true,
437
439
  disabled: !attachmentSupported || inputDisabled,
438
440
  onClick: () => fileInputRef.current?.click(),
439
441
  },
@@ -46,6 +46,7 @@ export function useSessionConversationInputAttachments(params: UseSessionConvers
46
46
  insertedAttachments.map((attachment) => ({
47
47
  tokenKey: attachment.id,
48
48
  label: attachment.name,
49
+ previewUrl: attachment.mimeType.startsWith('image/') ? attachment.url : undefined,
49
50
  })),
50
51
  );
51
52
  }
@@ -82,6 +82,7 @@ export function buildSessionConversationToolbarSelects(params: ToolbarSelectBuil
82
82
  favoriteModelsLabel,
83
83
  favoriteModelLabel,
84
84
  unfavoriteModelLabel,
85
+ manageModelsLabel: t('chatManageModels'),
85
86
  recentModelsLabel,
86
87
  allModelsLabel,
87
88
  },
@@ -115,6 +115,7 @@ export function buildSessionQueuedInputComposerSnapshot(
115
115
  tokenKind: 'file',
116
116
  tokenKey: id,
117
117
  label: attachment.name,
118
+ previewUrl: attachment.mimeType.startsWith('image/') ? attachment.url : undefined,
118
119
  }));
119
120
  });
120
121
 
@@ -22,6 +22,7 @@ function createModelTexts() {
22
22
  favoriteModelsLabel: 'Favorites',
23
23
  favoriteModelLabel: 'Favorite model',
24
24
  unfavoriteModelLabel: 'Remove favorite',
25
+ manageModelsLabel: 'Manage models and providers',
25
26
  recentModelsLabel: 'Recent',
26
27
  allModelsLabel: 'All models'
27
28
  };
@@ -262,6 +263,8 @@ describe('buildModelToolbarSelect', () => {
262
263
  value: 'minimax/MiniMax-M2.7',
263
264
  label: 'MiniMax/MiniMax-M2.7'
264
265
  });
266
+ expect(select.manageLabel).toBe('Manage models and providers');
267
+ expect(select.manageHref).toBe('/providers');
265
268
  });
266
269
 
267
270
  it('keeps the full provider/model label in shared state while exposing a compact mobile label', () => {
@@ -278,7 +278,6 @@ export function buildSkillPickerModel(params: {
278
278
  const recentKeySet = new Set(groupedRecentSkillValues ?? []);
279
279
  return {
280
280
  title: texts.title,
281
- allGroupsLabel: texts.allSkillsLabel,
282
281
  searchPlaceholder: texts.searchPlaceholder,
283
282
  emptyLabel: texts.emptyLabel,
284
283
  loadingLabel: texts.loadingLabel,
@@ -86,6 +86,7 @@ export function buildModelToolbarSelect({
86
86
  | "favoriteModelsLabel"
87
87
  | "favoriteModelLabel"
88
88
  | "unfavoriteModelLabel"
89
+ | "manageModelsLabel"
89
90
  | "recentModelsLabel"
90
91
  | "allModelsLabel"
91
92
  >;
@@ -172,6 +173,8 @@ export function buildModelToolbarSelect({
172
173
  onToggle: onFavoriteToggle,
173
174
  }
174
175
  : undefined,
176
+ manageLabel: texts.manageModelsLabel,
177
+ manageHref: "/providers",
175
178
  onValueChange,
176
179
  };
177
180
  }
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ NCP_INTERNAL_VISIBILITY_METADATA_KEY,
4
+ type NcpMessage,
5
+ } from "@nextclaw/ncp";
6
+ import { isVisibleChatMessage } from "@/features/chat/features/message/utils/chat-message-timeline.utils";
7
+
8
+ const visibleMessage = {
9
+ id: "assistant-visible",
10
+ sessionId: "session-1",
11
+ role: "assistant",
12
+ status: "final",
13
+ timestamp: "2026-08-07T00:00:00.000Z",
14
+ parts: [{ type: "text", text: "visible" }],
15
+ } satisfies NcpMessage;
16
+
17
+ describe("chat message timeline visibility", () => {
18
+ it("hides internal and legacy silent messages", () => {
19
+ expect(isVisibleChatMessage({
20
+ ...visibleMessage,
21
+ metadata: { [NCP_INTERNAL_VISIBILITY_METADATA_KEY]: "hidden" },
22
+ })).toBe(false);
23
+ expect(isVisibleChatMessage({
24
+ ...visibleMessage,
25
+ parts: [{ type: "text", text: "\\n\\n<noreply/>" }],
26
+ })).toBe(false);
27
+ expect(isVisibleChatMessage(visibleMessage)).toBe(true);
28
+ });
29
+ });
@@ -1,4 +1,5 @@
1
- import type { NcpMessage } from "@nextclaw/ncp";
1
+ import { isHiddenNcpMessage, type NcpMessage } from "@nextclaw/ncp";
2
+ import { isSilentReplyNcpMessage } from "@nextclaw/shared";
2
3
  import type { ChatMessageViewModel } from "@nextclaw/agent-chat-ui";
3
4
  import {
4
5
  readContextCompactionTimeline,
@@ -48,6 +49,8 @@ function readInheritedSourceSessionId(message: NcpMessage): string | null {
48
49
 
49
50
  export function isVisibleChatMessage(message: NcpMessage): boolean {
50
51
  return (
52
+ !isHiddenNcpMessage(message) &&
53
+ !isSilentReplyNcpMessage(message) &&
51
54
  !readContextCompactionTimeline(message) &&
52
55
  !readInheritedSourceSessionId(message)
53
56
  );
@@ -63,12 +63,12 @@ function ChatSidebarSessionEditingView({
63
63
  }
64
64
  }}
65
65
  placeholder={t('sessionsLabelPlaceholder')}
66
- className="h-8 rounded-lg border-gray-300 bg-white text-xs"
66
+ className="h-8 rounded-lg border-border bg-background text-xs"
67
67
  autoFocus
68
68
  disabled={isSaving}
69
69
  />
70
70
  <div className="flex items-center justify-between gap-2">
71
- <div className="min-w-0 text-[11px] text-gray-400 truncate">{sessionKey}</div>
71
+ <div className="min-w-0 truncate text-[11px] text-muted-foreground/65">{sessionKey}</div>
72
72
  <div className="flex items-center gap-1">
73
73
  <IconActionButton
74
74
  icon={<Check className="h-3.5 w-3.5" />}
@@ -134,8 +134,8 @@ function ChatSidebarSessionDisplayView({
134
134
  className={cn(
135
135
  'shrink-0 rounded-full border px-1.5 py-0.5 text-[10px] font-semibold leading-none',
136
136
  active
137
- ? 'border-gray-300 bg-white/80 text-gray-700'
138
- : 'border-gray-200 bg-gray-100 text-gray-500'
137
+ ? 'border-border/70 bg-background/80 text-foreground/75'
138
+ : 'border-transparent bg-muted/70 text-muted-foreground'
139
139
  )}
140
140
  >
141
141
  {context.label}
@@ -143,12 +143,12 @@ function ChatSidebarSessionDisplayView({
143
143
  ) : null}
144
144
  {context.icon ? (
145
145
  <span className="inline-flex h-4 w-4 shrink-0 items-center justify-center">
146
- <SessionContextIconNode icon={context.icon} className={cn('h-[13px] w-[13px]', active ? 'text-gray-700' : 'text-gray-500')} />
146
+ <SessionContextIconNode icon={context.icon} className={cn('h-[13px] w-[13px]', active ? 'text-foreground/75' : 'text-muted-foreground')} />
147
147
  </span>
148
148
  ) : null}
149
149
  </span>
150
150
  </div>
151
- <div className="mt-1 flex items-center gap-2 text-[11px] text-gray-400">
151
+ <div className="mt-0.5 flex items-center gap-2 text-[11px] text-muted-foreground/65">
152
152
  <span className="min-w-0 truncate">
153
153
  {previewText}
154
154
  </span>
@@ -169,7 +169,7 @@ function ChatSidebarSessionDisplayView({
169
169
  event.stopPropagation();
170
170
  onOpenChildSessions();
171
171
  }}
172
- className="pointer-events-none absolute right-12 top-0 inline-flex h-6 items-center gap-1 rounded-md px-1.5 text-[10px] font-medium text-muted-foreground opacity-0 transition-colors hover:bg-black/10 hover:text-foreground group-hover/session:pointer-events-auto group-hover/session:opacity-100 focus-visible:pointer-events-auto focus-visible:opacity-100"
172
+ className="pointer-events-none absolute right-12 top-0 inline-flex h-6 items-center gap-1 rounded-md px-1.5 text-[10px] font-medium text-muted-foreground opacity-0 transition-colors hover:bg-background/80 hover:text-foreground group-hover/session:pointer-events-auto group-hover/session:opacity-100 focus-visible:pointer-events-auto focus-visible:opacity-100"
173
173
  aria-label={t('chatSessionOpenChildSessions')}
174
174
  title={t('chatSessionOpenChildSessions')}
175
175
  >
@@ -245,10 +245,10 @@ export function ChatSidebarSessionItem({
245
245
  return (
246
246
  <div
247
247
  className={cn(
248
- 'w-full rounded-xl px-3 py-2.5 text-left transition-all text-[13px]',
248
+ 'w-full rounded-lg px-2.5 py-2 text-left text-[13px] transition-colors',
249
249
  active
250
- ? 'bg-gray-200/80 text-gray-900 font-semibold shadow-sm'
251
- : 'text-gray-700 hover:bg-gray-200/60 hover:text-gray-900'
250
+ ? 'bg-background/90 font-medium text-foreground'
251
+ : 'text-foreground/80 hover:bg-background/65 hover:text-foreground'
252
252
  )}
253
253
  >
254
254
  {isEditing ? (
@@ -61,10 +61,10 @@ export function ChatSidebarSessionList({
61
61
  }
62
62
 
63
63
  return (
64
- <div className="space-y-3">
64
+ <div className="space-y-2">
65
65
  {groups.map((group) => (
66
66
  <div key={group.label}>
67
- <div className="px-2 py-1 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70">
67
+ <div className="px-2 pb-1 pt-0.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/65">
68
68
  {group.label}
69
69
  </div>
70
70
  <div className="space-y-0.5">{group.items.map(renderSessionItem)}</div>