@nextclaw/ui 0.15.1 → 0.15.3

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 (192) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/assets/{api-b1R9VdeH.js → api-iNBpppzj.js} +3 -3
  3. package/dist/assets/{appearance-settings-page-D2O9pch9.js → appearance-settings-page-DMffQFR3.js} +1 -1
  4. package/dist/assets/{book-open-B77lQ2Tg.js → book-open-C3mJGwiy.js} +1 -1
  5. package/dist/assets/channels-list-page-CCtp0BRS.js +8 -0
  6. package/dist/assets/chat-page-H5a0dWvO.js +1 -0
  7. package/dist/assets/config-split-page-CrOJgB-Y.js +1 -0
  8. package/dist/assets/{confirm-dialog-C-NXSioX.js → confirm-dialog-Chv50USy.js} +2 -2
  9. package/dist/assets/{desktop-update-config-usHpXugP.js → desktop-update-config-KzTuXRf_.js} +1 -1
  10. package/dist/assets/{dist-jdGmbb6w.js → dist-BunzmC4B.js} +1 -1
  11. package/dist/assets/{dist-YtoqU-AT.js → dist-ChmGwvtK.js} +1 -1
  12. package/dist/assets/doc-browser-DNRL4Zvd.js +1 -0
  13. package/dist/assets/doc-browser-Dg0pmkOg.js +1 -0
  14. package/dist/assets/doc-browser-context-BHY-0FSD.js +1 -0
  15. package/dist/assets/{es2015-bNpL2LKy.js → es2015-DQZ5JWwK.js} +1 -1
  16. package/dist/assets/{external-link-BM6fk8q-.js → external-link-CU0kzzIP.js} +1 -1
  17. package/dist/assets/form-actions-BvCp_0lS.js +1 -0
  18. package/dist/assets/{i18n-provider-CntWolRA.js → i18n-provider-Cu8XyZb_.js} +1 -1
  19. package/dist/assets/index-CzfBdvK1.css +1 -0
  20. package/dist/assets/index-DGqHne1Q.js +104 -0
  21. package/dist/assets/loader-circle-CDSFuzmk.js +1 -0
  22. package/dist/assets/mcp-marketplace-page-BeEQK0DL.js +9 -0
  23. package/dist/assets/mcp-marketplace-page-D6idOTLp.js +1 -0
  24. package/dist/assets/model-config-page-bNNo-XyG.js +1 -0
  25. package/dist/assets/plus-D4DsdLN-.js +1 -0
  26. package/dist/assets/{provider-scoped-model-input-Cm_a4GHf.js → provider-scoped-model-input-IlgQCsoT.js} +1 -1
  27. package/dist/assets/providers-config-page-wLo6CAJw.js +1 -0
  28. package/dist/assets/{react-BN8uzPnp.js → react-BPIsQIlO.js} +1 -1
  29. package/dist/assets/{refresh-cw-BpVcFPmJ.js → refresh-cw-CILd1vAP.js} +1 -1
  30. package/dist/assets/remote-uKEYp7AR.js +1 -0
  31. package/dist/assets/rotate-cw-wPFPg1kH.js +1 -0
  32. package/dist/assets/runtime-config-page-DDn1sP4G.js +1 -0
  33. package/dist/assets/{save-BpFW5fpk.js → save-ByJqcPTP.js} +1 -1
  34. package/dist/assets/{search-D0uO7enf.js → search-DVIV6pfD.js} +1 -1
  35. package/dist/assets/search-config-page-B-Cf4iiB.js +1 -0
  36. package/dist/assets/secrets-config-page-DPKsY8Iw.js +3 -0
  37. package/dist/assets/security-config-e9SSBlKr.js +1 -0
  38. package/dist/assets/{select-Bh-CrA4p.js → select-BdgczSNw.js} +1 -1
  39. package/dist/assets/{setting-row-BJfCHRcb.js → setting-row-BkTVUTKx.js} +1 -1
  40. package/dist/assets/{settings-2-Blzi1vr6.js → settings-2-CH5shGg_.js} +1 -1
  41. package/dist/assets/{side-dock-ZdaSrQVB.js → side-dock-BsBvcJ9i.js} +2 -2
  42. package/dist/assets/{skeleton-BLlEzebZ.js → skeleton-DeX1FKw7.js} +1 -1
  43. package/dist/assets/switch-Cv8Ekb3x.js +1 -0
  44. package/dist/assets/{tag-chip-CzGihiQx.js → tag-chip-RB9uKcoC.js} +1 -1
  45. package/dist/assets/{tooltip-CdcrBjrC.js → tooltip-oQlEN-l6.js} +1 -1
  46. package/dist/assets/{use-config-CVPgsBmU.js → use-config-l3W7y8Qz.js} +1 -1
  47. package/dist/assets/{use-confirm-dialog-CSeBk9SF.js → use-confirm-dialog-BAxaVOJI.js} +1 -1
  48. package/dist/assets/x-BBMffEN0.js +1 -0
  49. package/dist/index.html +38 -30
  50. package/index.html +12 -4
  51. package/package.json +9 -9
  52. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +29 -1
  53. package/src/app/components/layout/sidebar-items.tsx +46 -7
  54. package/src/app/components/layout/sidebar-rail.styles.ts +2 -2
  55. package/src/app/components/layout/sidebar.tsx +84 -62
  56. package/src/app/configs/app-navigation.config.ts +49 -0
  57. package/src/app/styles/design-system.css +99 -4
  58. package/src/features/channels/components/config/channel-form.tsx +14 -11
  59. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +40 -0
  60. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +3 -1
  61. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -1
  62. package/src/features/chat/components/layout/__tests__/chat-sidebar-toolbar.test.tsx +48 -0
  63. package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +55 -0
  64. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +0 -3
  65. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +3 -4
  66. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +30 -31
  67. package/src/features/chat/components/layout/chat-sidebar.tsx +21 -7
  68. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +2 -1
  69. package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +1 -1
  70. package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +22 -1
  71. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +62 -1
  72. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +10 -5
  73. package/src/features/chat/features/message/components/chat-message-list.container.tsx +47 -1
  74. package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +11 -1
  75. package/src/features/chat/features/message/utils/__tests__/chat-message-texts.utils.test.ts +23 -0
  76. package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -11
  77. package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +5 -0
  78. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +138 -0
  79. package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx +116 -0
  80. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +108 -46
  81. package/src/features/chat/features/session/components/chat-sidebar-session-entry.tsx +6 -0
  82. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +48 -24
  83. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +0 -3
  84. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +0 -4
  85. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +2 -2
  86. package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +25 -3
  87. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +9 -4
  88. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +1 -1
  89. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +1 -1
  90. package/src/features/chat/features/welcome/components/chat-welcome.tsx +0 -5
  91. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +226 -56
  92. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +1 -0
  93. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +144 -0
  94. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +99 -0
  95. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +2 -2
  96. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +4 -2
  97. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +232 -21
  98. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +3 -0
  99. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +44 -1
  100. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +45 -3
  101. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +4 -0
  102. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +33 -2
  103. package/src/features/chat/managers/chat-session-list.manager.ts +32 -0
  104. package/src/features/chat/managers/chat-thread.manager.ts +2 -0
  105. package/src/features/chat/stores/chat-session-list.store.ts +28 -5
  106. package/src/features/chat/stores/chat-thread.store.ts +3 -0
  107. package/src/features/cron/components/cron-config.tsx +63 -80
  108. package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +44 -6
  109. package/src/features/marketplace/components/marketplace-detail-doc.tsx +8 -4
  110. package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
  111. package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +5 -12
  112. package/src/features/marketplace/hooks/use-marketplace-detail-doc-entry.ts +84 -0
  113. package/src/features/panel-apps/components/panel-app-list-item.tsx +6 -6
  114. package/src/features/panel-apps/components/panel-apps-list.tsx +2 -2
  115. package/src/features/panel-apps/index.ts +1 -0
  116. package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +2 -10
  117. package/src/features/panel-apps/utils/panel-app-iframe.utils.ts +6 -0
  118. package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +11 -3
  119. package/src/features/pwa/managers/pwa-shell-theme.manager.ts +3 -1
  120. package/src/features/right-panel-resources/components/right-panel-resource-home-page.tsx +1 -1
  121. package/src/features/service-apps/components/service-apps-panel.tsx +145 -35
  122. package/src/features/settings/components/config/provider-auth-section.tsx +1 -1
  123. package/src/features/settings/components/config/provider-form.tsx +11 -8
  124. package/src/features/settings/components/config/provider-models-section.tsx +7 -7
  125. package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
  126. package/src/features/settings/components/config/secrets-config-form.tsx +5 -4
  127. package/src/features/settings/pages/model-config-page.tsx +26 -25
  128. package/src/features/settings/pages/providers-config-page.tsx +10 -13
  129. package/src/features/settings/pages/search-config-page.tsx +8 -5
  130. package/src/features/side-dock/components/side-dock.tsx +3 -3
  131. package/src/features/system-status/components/config/runtime-config-editor.tsx +5 -4
  132. package/src/index.css +257 -13
  133. package/src/shared/components/actions/action-link.tsx +1 -1
  134. package/src/shared/components/config-split-page.tsx +6 -6
  135. package/src/shared/components/doc-browser/__tests__/doc-browser-url.utils.test.ts +73 -0
  136. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +48 -0
  137. package/src/shared/components/doc-browser/doc-browser-context.tsx +4 -0
  138. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +6 -2
  139. package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +2 -1
  140. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +10 -10
  141. package/src/shared/components/doc-browser/doc-browser.tsx +6 -4
  142. package/src/shared/components/doc-browser/utils/doc-browser-url.utils.ts +62 -4
  143. package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +18 -0
  144. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +28 -3
  145. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +36 -0
  146. package/src/shared/components/ui/actions/form-actions.tsx +25 -0
  147. package/src/shared/components/ui/actions/icon-action-button.tsx +25 -2
  148. package/src/shared/components/ui/button.tsx +1 -1
  149. package/src/shared/components/ui/card.tsx +12 -4
  150. package/src/shared/components/ui/dialog.tsx +1 -1
  151. package/src/shared/components/ui/input.tsx +1 -1
  152. package/src/shared/components/ui/select.tsx +1 -1
  153. package/src/shared/components/ui/switch.tsx +1 -1
  154. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +2 -1
  155. package/src/shared/components/ui/tabs.tsx +1 -1
  156. package/src/shared/components/ui/textarea.tsx +1 -1
  157. package/src/shared/hooks/use-server-path-browse.ts +26 -4
  158. package/src/shared/hooks/use-server-path-read.ts +11 -1
  159. package/src/shared/lib/api/utils/server-path.utils.ts +1 -0
  160. package/src/shared/lib/i18n/locales/en-US/chat.json +99 -0
  161. package/src/shared/lib/i18n/locales/en-US/core.json +3 -0
  162. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +5 -0
  163. package/src/shared/lib/i18n/locales/zh-CN/chat.json +99 -0
  164. package/src/shared/lib/i18n/locales/zh-CN/core.json +3 -0
  165. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +5 -0
  166. package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +9 -5
  167. package/src/shared/lib/theme/index.test.ts +21 -0
  168. package/src/shared/lib/theme/index.ts +6 -4
  169. package/src/vite-env.d.ts +2 -0
  170. package/dist/assets/channels-list-page-DcsIQc6A.js +0 -8
  171. package/dist/assets/chat-page-CZ7WxPir.js +0 -1
  172. package/dist/assets/config-split-page-BWQ6_Raq.js +0 -1
  173. package/dist/assets/doc-browser-B_Tf9n70.js +0 -1
  174. package/dist/assets/doc-browser-BdZPFOVa.js +0 -1
  175. package/dist/assets/doc-browser-context-DwokemUn.js +0 -1
  176. package/dist/assets/index-BIcEw90u.js +0 -104
  177. package/dist/assets/index-BiNSTKTU.css +0 -1
  178. package/dist/assets/loader-circle-MBgNSVIH.js +0 -1
  179. package/dist/assets/mcp-marketplace-page-CLmUstLN.js +0 -9
  180. package/dist/assets/mcp-marketplace-page-rZkmWPMy.js +0 -1
  181. package/dist/assets/model-config-page-Di0ySJD2.js +0 -1
  182. package/dist/assets/plus-vDqW5Kxp.js +0 -1
  183. package/dist/assets/providers-config-page-BAf2W0MV.js +0 -1
  184. package/dist/assets/remote-CLQ0IBM8.js +0 -1
  185. package/dist/assets/rotate-cw-CvNBv7_w.js +0 -1
  186. package/dist/assets/runtime-config-page-CflvvoDc.js +0 -1
  187. package/dist/assets/search-config-page-D0klFUXd.js +0 -1
  188. package/dist/assets/secrets-config-page-dVZ631Wp.js +0 -3
  189. package/dist/assets/security-config-C5gnlkuj.js +0 -1
  190. package/dist/assets/switch-CFLJFNSd.js +0 -1
  191. package/dist/assets/x-BF2KQ--V.js +0 -1
  192. /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-C_iP1zl_.js} +0 -0
@@ -2,34 +2,35 @@ import { cn } from "@/shared/lib/utils";
2
2
  import { t } from "@/shared/lib/i18n";
3
3
  import { THEME_OPTIONS, type UiTheme } from "@/shared/lib/theme";
4
4
  import {
5
- MessageCircle,
6
5
  BookOpen,
7
- BrainCircuit,
8
- AlarmClock,
9
6
  Languages,
10
7
  Palette,
11
8
  KeyRound,
12
9
  Settings,
13
10
  ArrowLeft,
14
- Bot,
15
11
  PanelLeftClose,
16
12
  PanelLeftOpen,
17
- type LucideIcon,
18
13
  } from "lucide-react";
19
14
  import { NavLink } from "react-router-dom";
20
15
  import { useDocBrowser } from "@/shared/components/doc-browser";
21
16
  import { BrandHeader } from "@/shared/components/common/brand-header";
22
17
  import {
23
18
  SidebarActionItem,
19
+ SidebarNavigationList,
24
20
  SidebarNavLinkItem,
25
21
  SidebarSelectItem,
22
+ type SidebarItemDensity,
23
+ type SidebarNavListItem,
26
24
  } from "@/app/components/layout/sidebar-items";
27
25
  import { useTheme } from "@/app/components/theme-provider";
28
26
  import { SelectItem } from "@/shared/components/ui/select";
29
27
  import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
30
28
  import { useAppPresenter } from "@/app/components/app-presenter-provider";
31
29
  import { useRemoteStatus } from "@/features/remote";
32
- import { getSettingsNavItems } from "@/app/configs/app-navigation.config";
30
+ import {
31
+ getMainSidebarNavItems,
32
+ getSettingsNavSections,
33
+ } from "@/app/configs/app-navigation.config";
33
34
  import { useLanguagePreference } from "@/features/settings";
34
35
  import { viewportLayoutManager } from "@/app/managers/viewport-layout.manager";
35
36
  import { useViewportLayoutStore } from "@/app/stores/viewport-layout.store";
@@ -50,12 +51,38 @@ type SidebarProps = {
50
51
  mode: SidebarMode;
51
52
  };
52
53
 
53
- type SidebarNavItem = {
54
- target: string;
54
+ type SidebarNavSection = {
55
55
  label: string;
56
- icon: LucideIcon;
56
+ items: SidebarNavListItem[];
57
+ };
58
+
59
+ type SidebarNavigationModel = {
60
+ items: SidebarNavListItem[];
61
+ sections?: SidebarNavSection[];
62
+ density: SidebarItemDensity;
57
63
  };
58
64
 
65
+ type SidebarTranslate = (key: string) => string;
66
+
67
+ function resolveSidebarNavigation(
68
+ isSettingsMode: boolean,
69
+ translate: SidebarTranslate,
70
+ ): SidebarNavigationModel {
71
+ if (!isSettingsMode) {
72
+ return {
73
+ items: getMainSidebarNavItems(translate),
74
+ density: "default",
75
+ };
76
+ }
77
+
78
+ const sections = getSettingsNavSections(translate);
79
+ return {
80
+ items: sections.flatMap((section) => section.items),
81
+ sections,
82
+ density: "compact",
83
+ };
84
+ }
85
+
59
86
  function SidebarCollapseToggle({
60
87
  isCollapsed,
61
88
  onToggle,
@@ -106,7 +133,7 @@ function SettingsSidebarHeader({
106
133
  to="/chat"
107
134
  aria-label={t("backToMain")}
108
135
  className={cn(
109
- "group inline-flex min-w-0 items-center rounded-lg text-[12px] font-medium text-muted-foreground transition-colors hover:bg-gray-200/60 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35",
136
+ "group inline-flex min-w-0 items-center rounded-lg text-[12px] font-medium text-muted-foreground transition-colors hover:bg-gray-200/60 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
110
137
  isCollapsed
111
138
  ? cn(
112
139
  SIDEBAR_RAIL_CONTROL_CLASS,
@@ -197,13 +224,23 @@ function SidebarNavigation({
197
224
  isSettingsMode,
198
225
  isCollapsed,
199
226
  items,
227
+ sections,
200
228
  density,
201
229
  }: {
202
230
  isSettingsMode: boolean;
203
231
  isCollapsed: boolean;
204
- items: SidebarNavItem[];
205
- density: "default" | "compact";
232
+ items: SidebarNavListItem[];
233
+ sections?: SidebarNavSection[];
234
+ density: SidebarItemDensity;
206
235
  }) {
236
+ const listClassName = cn(
237
+ isCollapsed
238
+ ? cn(SIDEBAR_RAIL_STACK_CLASS, "pb-3")
239
+ : isSettingsMode
240
+ ? "space-y-0.5 pb-3"
241
+ : "space-y-1 pb-4",
242
+ );
243
+
207
244
  return (
208
245
  <nav
209
246
  className={cn(
@@ -212,30 +249,34 @@ function SidebarNavigation({
212
249
  SIDEBAR_SCROLL_EDGE_FADE_CLASS,
213
250
  )}
214
251
  >
215
- <ul
216
- className={cn(
217
- isCollapsed
218
- ? cn(SIDEBAR_RAIL_STACK_CLASS, "pb-3")
219
- : isSettingsMode
220
- ? "space-y-0.5 pb-3"
221
- : "space-y-1 pb-4",
222
- )}
223
- >
224
- {items.map((item) => (
225
- <li
226
- key={item.target}
227
- className={isCollapsed ? "flex justify-center" : undefined}
228
- >
229
- <SidebarNavLinkItem
230
- to={item.target}
231
- label={item.label}
232
- icon={item.icon}
233
- density={density}
234
- collapsed={isCollapsed}
235
- />
236
- </li>
237
- ))}
238
- </ul>
252
+ {sections && !isCollapsed ? (
253
+ <div className="space-y-3 pb-3">
254
+ {sections.map((section) => (
255
+ <section
256
+ key={section.label}
257
+ aria-label={section.label}
258
+ className="space-y-1"
259
+ >
260
+ <h2 className="px-3 text-[11px] font-semibold text-muted-foreground/70">
261
+ {section.label}
262
+ </h2>
263
+ <SidebarNavigationList
264
+ isCollapsed={isCollapsed}
265
+ items={section.items}
266
+ density={density}
267
+ className="space-y-0.5"
268
+ />
269
+ </section>
270
+ ))}
271
+ </div>
272
+ ) : (
273
+ <SidebarNavigationList
274
+ isCollapsed={isCollapsed}
275
+ items={items}
276
+ density={density}
277
+ className={listClassName}
278
+ />
279
+ )}
239
280
  </nav>
240
281
  );
241
282
  }
@@ -258,7 +299,7 @@ export function Sidebar({ mode }: SidebarProps) {
258
299
  const isSettingsMode = mode === "settings";
259
300
  const currentThemeLabel = t(
260
301
  THEME_OPTIONS.find((option) => option.value === theme)?.labelKey ??
261
- "themeNatural",
302
+ "themeWork",
262
303
  );
263
304
  const accountEmail = remoteStatus.data?.account.email?.trim();
264
305
  const accountConnected = Boolean(remoteStatus.data?.account.loggedIn);
@@ -267,31 +308,11 @@ export function Sidebar({ mode }: SidebarProps) {
267
308
  if (theme !== nextTheme) setTheme(nextTheme);
268
309
  };
269
310
 
270
- const mainNavItems: SidebarNavItem[] = [
271
- {
272
- target: "/chat",
273
- label: t("chat"),
274
- icon: MessageCircle,
275
- },
276
- {
277
- target: "/chat/cron",
278
- label: t("cron"),
279
- icon: AlarmClock,
280
- },
281
- {
282
- target: "/chat/skills",
283
- label: t("marketplaceFilterSkills"),
284
- icon: BrainCircuit,
285
- },
286
- {
287
- target: "/agents",
288
- label: t("agentsPageTitle"),
289
- icon: Bot,
290
- },
291
- ];
292
-
293
- const navItems = isSettingsMode ? getSettingsNavItems(t) : mainNavItems;
294
- const sidebarDensity = isSettingsMode ? "compact" : "default";
311
+ const {
312
+ items: navItems,
313
+ sections: settingsNavSections,
314
+ density: sidebarDensity,
315
+ } = resolveSidebarNavigation(isSettingsMode, t);
295
316
 
296
317
  return (
297
318
  <aside
@@ -314,6 +335,7 @@ export function Sidebar({ mode }: SidebarProps) {
314
335
  isSettingsMode={isSettingsMode}
315
336
  isCollapsed={isCollapsed}
316
337
  items={navItems}
338
+ sections={settingsNavSections}
317
339
  density={sidebarDensity}
318
340
  />
319
341
 
@@ -2,6 +2,7 @@ import type { LucideIcon } from "lucide-react";
2
2
  import {
3
3
  Bot,
4
4
  BrainCircuit,
5
+ AlarmClock,
5
6
  Cpu,
6
7
  Download,
7
8
  KeyRound,
@@ -25,6 +26,11 @@ export type AppNavigationItem = {
25
26
  icon: LucideIcon;
26
27
  };
27
28
 
29
+ export type AppNavigationSection = {
30
+ label: string;
31
+ items: AppNavigationItem[];
32
+ };
33
+
28
34
  function matchesRouteTarget(pathname: string, target: string): boolean {
29
35
  const normalizedPath = pathname.toLowerCase();
30
36
  const normalizedTarget = target.toLowerCase();
@@ -80,6 +86,33 @@ export function getMobileBottomNavItems(
80
86
  ];
81
87
  }
82
88
 
89
+ export function getMainSidebarNavItems(
90
+ translate: Translate,
91
+ ): AppNavigationItem[] {
92
+ return [
93
+ {
94
+ target: "/chat",
95
+ label: translate("chat"),
96
+ icon: MessageCircle,
97
+ },
98
+ {
99
+ target: "/chat/cron",
100
+ label: translate("cron"),
101
+ icon: AlarmClock,
102
+ },
103
+ {
104
+ target: "/chat/skills",
105
+ label: translate("marketplaceFilterSkills"),
106
+ icon: BrainCircuit,
107
+ },
108
+ {
109
+ target: "/agents",
110
+ label: translate("agentsPageTitle"),
111
+ icon: Bot,
112
+ },
113
+ ];
114
+ }
115
+
83
116
  export function getSettingsNavItems(
84
117
  translate: Translate,
85
118
  ): AppNavigationItem[] {
@@ -142,6 +175,22 @@ export function getSettingsNavItems(
142
175
  ];
143
176
  }
144
177
 
178
+ export function getSettingsNavSections(
179
+ translate: Translate,
180
+ ): AppNavigationSection[] {
181
+ const items = getSettingsNavItems(translate);
182
+ return [
183
+ {
184
+ label: translate("settingsGroupBasic"),
185
+ items: items.slice(0, 3),
186
+ },
187
+ {
188
+ label: translate("settingsGroupAdvanced"),
189
+ items: items.slice(3),
190
+ },
191
+ ];
192
+ }
193
+
145
194
  export function getMobileSettingsNavItems(
146
195
  translate: Translate,
147
196
  ): AppNavigationItem[] {
@@ -284,6 +284,101 @@
284
284
  }
285
285
  }
286
286
 
287
+ @layer base {
288
+ :root[data-theme="work"] {
289
+ /* ========================================
290
+ BRAND COLORS (Work White Ink)
291
+ ======================================== */
292
+ --brand-50: 0 0% 97%;
293
+ --brand-100: 0 0% 94%;
294
+ --brand-200: 0 0% 88%;
295
+ --brand-300: 0 0% 78%;
296
+ --brand-400: 0 0% 58%;
297
+ --brand-500: 0 0% 23%;
298
+ --brand-600: 0 0% 18%;
299
+ --brand-700: 0 0% 12%;
300
+ --brand-800: 0 0% 7%;
301
+ --brand-900: 0 0% 4%;
302
+
303
+ /* ========================================
304
+ NEUTRAL COLORS (White Canvas / Soft Gray Rail)
305
+ ======================================== */
306
+ --gray-50: 0 0% 100%;
307
+ --gray-100: 0 0% 95%;
308
+ --gray-200: 0 0% 90%;
309
+ --gray-300: 0 0% 84%;
310
+ --gray-400: 0 0% 70%;
311
+ --gray-500: 0 0% 46%;
312
+ --gray-600: 0 0% 34%;
313
+ --gray-700: 0 0% 28%;
314
+ --gray-800: 0 0% 13%;
315
+ --gray-900: 0 0% 7%;
316
+
317
+ /* Semantic Colors */
318
+ --background: var(--gray-50);
319
+ --background-secondary: var(--gray-100);
320
+ --background-tertiary: var(--gray-200);
321
+ --foreground: var(--gray-800);
322
+ --foreground-secondary: var(--gray-700);
323
+ --foreground-tertiary: var(--gray-500);
324
+ --foreground-muted: var(--gray-400);
325
+ --primary: var(--brand-500);
326
+ --primary-hover: var(--brand-600);
327
+ --primary-active: var(--brand-700);
328
+ --primary-foreground: 0 0% 100%;
329
+ --secondary: var(--gray-100);
330
+ --secondary-hover: var(--gray-200);
331
+ --secondary-foreground: var(--gray-800);
332
+ --accent: var(--gray-200);
333
+ --accent-foreground: var(--gray-800);
334
+ --muted: var(--gray-100);
335
+ --muted-foreground: var(--gray-500);
336
+ --card: 0 0% 100%;
337
+ --card-foreground: var(--gray-800);
338
+ --card-border: var(--gray-200);
339
+ --popover: 0 0% 100%;
340
+ --popover-foreground: var(--gray-800);
341
+ --border: var(--gray-200);
342
+ --border-hover: var(--gray-300);
343
+ --border-active: var(--gray-600);
344
+ --input: 0 0% 100%;
345
+ --input-border: var(--gray-200);
346
+ --input-focus: var(--gray-600);
347
+ --ring: var(--gray-600);
348
+ --ring-offset: var(--gray-50);
349
+
350
+ /* Shadows (bright neutral tint) */
351
+ --shadow-xs: 0 1px 2px 0 rgb(20 24 28 / 0.03);
352
+ --shadow-sm: 0 1px 3px 0 rgb(20 24 28 / 0.05), 0 1px 2px -1px rgb(20 24 28 / 0.04);
353
+ --shadow-md: 0 4px 6px -1px rgb(20 24 28 / 0.05), 0 2px 4px -2px rgb(20 24 28 / 0.04);
354
+ --shadow-lg: 0 10px 15px -3px rgb(20 24 28 / 0.06), 0 4px 6px -4px rgb(20 24 28 / 0.04);
355
+ --shadow-xl: 0 20px 25px -5px rgb(20 24 28 / 0.08), 0 8px 10px -6px rgb(20 24 28 / 0.04);
356
+ --shadow-2xl: 0 25px 50px -12px rgb(20 24 28 / 0.14);
357
+ --shadow-card: 0 0 8px 0 rgba(20, 24, 28, 0.04);
358
+ --shadow-card-hover: 0 0 16px 0 rgba(20, 24, 28, 0.06);
359
+ }
360
+
361
+ :root[data-theme="work"] .nextclaw-chat-message-user {
362
+ background-color: hsl(var(--gray-100));
363
+ border-color: transparent;
364
+ color: hsl(var(--gray-800));
365
+ }
366
+
367
+ /* User bubble is light in work theme; keep clickable skill tokens as link-colored text. */
368
+ :root[data-theme="work"] .nextclaw-chat-message-user .nextclaw-chat-inline-token {
369
+ border-color: transparent;
370
+ background-color: transparent;
371
+ color: var(--md-link, #1f73c9);
372
+ box-shadow: none;
373
+ }
374
+
375
+ :root[data-theme="work"] .nextclaw-chat-message-user .nextclaw-chat-inline-token > span:first-child {
376
+ color: currentColor;
377
+ opacity: 0.8;
378
+ }
379
+
380
+ }
381
+
287
382
  @layer base {
288
383
  :root[data-theme="warm"] {
289
384
  /* ========================================
@@ -812,10 +907,10 @@
812
907
  -webkit-backdrop-filter: blur(12px);
813
908
  }
814
909
 
815
- /* Focus Ring */
910
+ /* Focus Ring — light inset-style ring, no offset box */
816
911
  .focus-ring {
817
- @apply focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2;
818
- --tw-ring-color: hsl(var(--ring));
819
- --tw-ring-offset-color: hsl(var(--ring-offset));
912
+ @apply focus:outline-none focus-visible:ring-1 focus-visible:ring-offset-0;
913
+ --tw-ring-color: hsl(var(--border));
914
+ --tw-ring-offset-color: transparent;
820
915
  }
821
916
  }
@@ -5,6 +5,7 @@ import { BookOpen, ChevronDown } from 'lucide-react';
5
5
  import { toast } from 'sonner';
6
6
  import { LogoBadge } from '@/shared/components/common/logo-badge';
7
7
  import { Button } from '@/shared/components/ui/button';
8
+ import { FormActions } from '@/shared/components/ui/actions/form-actions';
8
9
  import { StatusDot } from '@/shared/components/status/status-dot';
9
10
  import { nextclawClient } from '@/shared/lib/api';
10
11
  import { useConfig, useConfigMeta, useConfigSchema, useExecuteConfigAction, useUpdateChannel } from '@/shared/hooks/use-config';
@@ -363,17 +364,19 @@ function ChannelFormEditor({
363
364
  />
364
365
  </ConfigSplitPaneBody>
365
366
 
366
- <ConfigSplitPaneFooter className="flex flex-wrap items-center justify-between gap-3 px-6 py-4">
367
- <div className="flex flex-wrap items-center gap-2">
368
- {actions.filter((action) => action.trigger === 'manual').map((action) => (
369
- <Button key={action.id} type="button" onClick={() => void handleManualAction(action)} disabled={disabled} variant="secondary">
370
- {runningActionId === action.id ? t('connecting') : action.title}
371
- </Button>
372
- ))}
373
- </div>
374
- <Button type="submit" disabled={disabled}>
375
- {updateChannel.isPending ? t('saving') : t('save')}
376
- </Button>
367
+ <ConfigSplitPaneFooter>
368
+ <FormActions align="between">
369
+ <div className="flex flex-wrap items-center gap-2">
370
+ {actions.filter((action) => action.trigger === 'manual').map((action) => (
371
+ <Button key={action.id} type="button" size="sm" onClick={() => void handleManualAction(action)} disabled={disabled} variant="secondary">
372
+ {runningActionId === action.id ? t('connecting') : action.title}
373
+ </Button>
374
+ ))}
375
+ </div>
376
+ <Button type="submit" size="sm" disabled={disabled}>
377
+ {updateChannel.isPending ? t('saving') : t('save')}
378
+ </Button>
379
+ </FormActions>
377
380
  </ConfigSplitPaneFooter>
378
381
  </form>
379
382
  </ConfigSplitDetailPane>
@@ -224,6 +224,46 @@ describe("ChatConversationHeaderSection", () => {
224
224
 
225
225
  expect(screen.queryByRole("button", { name: /Switch session/ })).toBeNull();
226
226
  expect(screen.getByText("Parent Task")).toBeTruthy();
227
+ expect(screen.queryByText("Native")).toBeNull();
228
+ });
229
+
230
+ it("keeps the runtime badge for a non-native session", () => {
231
+ useChatQueryStore.setState({
232
+ snapshot: {
233
+ ...useChatQueryStore.getState().snapshot,
234
+ sessionsQuery: {
235
+ data: {
236
+ sessions: [
237
+ createSummary({
238
+ sessionId: "parent-session-1",
239
+ metadata: {
240
+ label: "Parent Task",
241
+ session_type: "codex",
242
+ },
243
+ }),
244
+ ],
245
+ total: 1,
246
+ },
247
+ } as never,
248
+ sessionTypesQuery: {
249
+ data: {
250
+ defaultType: "native",
251
+ options: [{ value: "codex", label: "Codex", ready: true }],
252
+ },
253
+ } as never,
254
+ },
255
+ });
256
+ mocks.sessionItems = [
257
+ createSessionListItem({
258
+ key: "parent-session-1",
259
+ label: "Parent Task",
260
+ sessionType: "codex",
261
+ }),
262
+ ];
263
+
264
+ renderHeaderSection();
265
+
266
+ expect(screen.getByText("Codex")).toBeTruthy();
227
267
  });
228
268
 
229
269
  it("keeps session switching available from a collapsed new-session header", async () => {
@@ -53,6 +53,8 @@ export function ChatConversationHeaderSection({
53
53
  sessionTypeOptions.find((option) => option.value === sessionType) ?? null;
54
54
  const sessionTypeLabel =
55
55
  sessionTypeOption?.label ?? resolveSessionTypeLabel(sessionType);
56
+ const shouldShowSessionTypeBadge =
57
+ sessionType !== DEFAULT_SESSION_TYPE && Boolean(sessionTypeLabel);
56
58
  const sessionProjectRoot = selectedSession?.projectRoot ?? null;
57
59
  const sessionProjectName =
58
60
  selectedSession?.projectName ?? getSessionProjectName(sessionProjectRoot);
@@ -110,7 +112,7 @@ export function ChatConversationHeaderSection({
110
112
  ) : null
111
113
  }
112
114
  sessionTypeBadge={
113
- sessionTypeLabel ? (
115
+ shouldShowSessionTypeBadge ? (
114
116
  <span className="inline-flex shrink-0 items-center gap-1.5 rounded-full border border-border bg-muted px-2 py-0.5 text-[11px] font-medium text-muted-foreground">
115
117
  {sessionTypeOption?.icon?.src ? (
116
118
  <span className="inline-flex h-[1.125rem] w-[1.125rem] items-center justify-center">
@@ -34,7 +34,7 @@ export function ChatConversationPanel({
34
34
  }
35
35
 
36
36
  return (
37
- <section className="flex-1 min-h-0 flex overflow-hidden bg-background">
37
+ <section className="relative flex-1 min-h-0 flex overflow-hidden bg-background">
38
38
  <div className="flex min-h-0 flex-1 flex-col overflow-hidden">
39
39
  <ChatConversationParentBanner />
40
40
  <ChatConversationHeaderSection
@@ -0,0 +1,48 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import {
4
+ ChatSidebarDesktopToolbar,
5
+ ChatSidebarMobileToolbar,
6
+ } from "@/features/chat/components/layout/chat-sidebar-toolbar";
7
+ import type { ChatSessionTypeOption } from "@/features/chat/features/session-type/utils/chat-session-type.utils";
8
+
9
+ const sessionTypeOptions: ChatSessionTypeOption[] = [
10
+ { value: "native", label: "Native", icon: null, ready: true },
11
+ { value: "codex", label: "Codex", icon: null, ready: true },
12
+ ];
13
+
14
+ const toolbarProps = {
15
+ query: "",
16
+ defaultSessionType: "native",
17
+ sessionTypeOptions,
18
+ nonDefaultSessionTypeOptions: [sessionTypeOptions[1]],
19
+ selectedNewSessionType: "native",
20
+ selectedNewSessionTypeOption: sessionTypeOptions[0],
21
+ isCreateMenuOpen: false,
22
+ onCreateMenuOpenChange: vi.fn(),
23
+ onCreateSession: vi.fn(),
24
+ onSelectNewSessionType: vi.fn(),
25
+ onQueryChange: vi.fn(),
26
+ };
27
+
28
+ function searchIconClassName() {
29
+ return (
30
+ screen
31
+ .getByPlaceholderText("Search conversations...")
32
+ .previousElementSibling?.getAttribute("class") ?? ""
33
+ );
34
+ }
35
+
36
+ describe("ChatSidebarToolbar", () => {
37
+ it("keeps desktop search icon transparent to pointer input", () => {
38
+ render(<ChatSidebarDesktopToolbar {...toolbarProps} />);
39
+
40
+ expect(searchIconClassName()).toContain("pointer-events-none");
41
+ });
42
+
43
+ it("keeps mobile search icon transparent to pointer input", () => {
44
+ render(<ChatSidebarMobileToolbar {...toolbarProps} />);
45
+
46
+ expect(searchIconClassName()).toContain("pointer-events-none");
47
+ });
48
+ });
@@ -0,0 +1,55 @@
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
+ import { MemoryRouter } from "react-router-dom";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { ChatSidebarUtilityMenu } from "@/features/chat/components/layout/chat-sidebar-utility-menu";
5
+
6
+ function renderUtilityMenu() {
7
+ return render(
8
+ <MemoryRouter>
9
+ <ChatSidebarUtilityMenu
10
+ isOpen
11
+ onOpenChange={vi.fn()}
12
+ currentTheme="warm"
13
+ currentThemeLabel="Warm"
14
+ themeOptions={[{ value: "warm", label: "Warm" }]}
15
+ onSelectTheme={vi.fn()}
16
+ currentLanguage="en"
17
+ currentLanguageLabel="English"
18
+ languageOptions={[{ value: "en", label: "English" }]}
19
+ onSelectLanguage={vi.fn()}
20
+ onOpenDocs={vi.fn()}
21
+ onOpenApps={vi.fn()}
22
+ />
23
+ </MemoryRouter>,
24
+ );
25
+ }
26
+
27
+ function appendComposerFocusTarget(): HTMLElement {
28
+ const composerShell = document.createElement("div");
29
+ composerShell.className = "nextclaw-chat-input-bar-shell";
30
+ const composer = document.createElement("div");
31
+ composer.setAttribute("contenteditable", "true");
32
+ composer.setAttribute("role", "textbox");
33
+ composer.tabIndex = 0;
34
+ composerShell.appendChild(composer);
35
+ document.body.appendChild(composerShell);
36
+ return composer;
37
+ }
38
+
39
+ describe("ChatSidebarUtilityMenu", () => {
40
+ it("stays open when the streaming composer restores focus", () => {
41
+ const composer = appendComposerFocusTarget();
42
+
43
+ try {
44
+ renderUtilityMenu();
45
+
46
+ expect(screen.getByRole("button", { name: "Help Docs" })).toBeTruthy();
47
+
48
+ fireEvent.focusIn(composer);
49
+
50
+ expect(screen.getByRole("button", { name: "Help Docs" })).toBeTruthy();
51
+ } finally {
52
+ composer.closest(".nextclaw-chat-input-bar-shell")?.remove();
53
+ }
54
+ });
55
+ });
@@ -134,7 +134,6 @@ export function ChatSidebarSessionArea({
134
134
  isCollapsed,
135
135
  isLoading,
136
136
  isProjectFirstView,
137
- onCreateSession,
138
137
  onSelectMode,
139
138
  projectGroups,
140
139
  renderSessionItem,
@@ -145,7 +144,6 @@ export function ChatSidebarSessionArea({
145
144
  isCollapsed: boolean;
146
145
  isLoading: boolean;
147
146
  isProjectFirstView: boolean;
148
- onCreateSession: (sessionType: string, projectRoot?: string | null) => void;
149
147
  onSelectMode: (mode: "time-first" | "project-first") => void;
150
148
  projectGroups: ReturnType<typeof groupSessionsByProject>;
151
149
  renderSessionItem: (item: NcpSessionListItemView) => JSX.Element;
@@ -183,7 +181,6 @@ export function ChatSidebarSessionArea({
183
181
  defaultSessionType={defaultSessionType}
184
182
  sessionTypeOptions={sessionTypeOptions}
185
183
  renderSessionItem={renderSessionItem}
186
- onCreateSession={onCreateSession}
187
184
  />
188
185
  </div>
189
186
  </>