@nextclaw/ui 0.14.2 → 0.14.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 (129) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/assets/{api-a14tR_z1.js → api-BP_8IaxB.js} +3 -3
  3. package/dist/assets/{channels-list-page-CFGkm4DT.js → channels-list-page-DEh5Jjqx.js} +1 -1
  4. package/dist/assets/chat-page-BRB0N5WW.js +105 -0
  5. package/dist/assets/config-split-page-CRhlZ_pH.js +1 -0
  6. package/dist/assets/{confirm-dialog-ClH_Ec7n.js → confirm-dialog-DsivFJ4r.js} +2 -2
  7. package/dist/assets/{desktop-update-config-DPNDnVpk.js → desktop-update-config-B0w9p2ep.js} +1 -1
  8. package/dist/assets/{dist-BtCS2Ucq.js → dist-CikkhOlq.js} +1 -1
  9. package/dist/assets/{dist-CLGGLm8b.js → dist-PVA_xL1D.js} +1 -1
  10. package/dist/assets/doc-browser-BoNJ7-tE.js +1 -0
  11. package/dist/assets/doc-browser-CdJ_d4ik.js +1 -0
  12. package/dist/assets/{doc-browser-context-D4JwMMxK.js → doc-browser-context-MTw4EvKd.js} +1 -1
  13. package/dist/assets/{ellipsis-CtJXjIol.js → ellipsis-y6vDtq4M.js} +1 -1
  14. package/dist/assets/{external-link-CdJiG13p.js → external-link-DVJIQf_G.js} +1 -1
  15. package/dist/assets/index-BiE-K8-q.css +1 -0
  16. package/dist/assets/index-D9BAE_L_.js +105 -0
  17. package/dist/assets/{mcp-marketplace-page-BU0kIjhR.js → mcp-marketplace-page-B1SZNY5m.js} +1 -1
  18. package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +1 -0
  19. package/dist/assets/{model-config-page-D6hYXROd.js → model-config-page-DotaTUHb.js} +1 -1
  20. package/dist/assets/plus-BbZqquJe.js +1 -0
  21. package/dist/assets/{provider-scoped-model-input-DAX2HgmR.js → provider-scoped-model-input-DsFRuWJH.js} +1 -1
  22. package/dist/assets/providers-config-page-DW4tQSLN.js +1 -0
  23. package/dist/assets/{react-C6cZgP44.js → react-VNqT5Zp7.js} +1 -1
  24. package/dist/assets/remote-B8TiUfwW.js +1 -0
  25. package/dist/assets/rotate-cw-DjHDTkCv.js +1 -0
  26. package/dist/assets/{runtime-config-page-C5xKaV3i.js → runtime-config-page-S6ErRHR8.js} +1 -1
  27. package/dist/assets/{save-DKA0x4zk.js → save-BOJ477H-.js} +1 -1
  28. package/dist/assets/{search-BNlcoj5c.js → search-BEti5beA.js} +1 -1
  29. package/dist/assets/{search-config-page-Cq9uJ-N8.js → search-config-page-DGj5am03.js} +1 -1
  30. package/dist/assets/{secrets-config-page-CxSbZLBo.js → secrets-config-page--IzrIOid.js} +1 -1
  31. package/dist/assets/{select-CvTlDZTQ.js → select-hoj8dK4K.js} +2 -2
  32. package/dist/assets/{tag-chip-8ffF2y2C.js → tag-chip-T8-h-die.js} +1 -1
  33. package/dist/assets/{use-config-DmT0o1gv.js → use-config-CjRV2hwL.js} +1 -1
  34. package/dist/assets/x-D9PXs_xP.js +1 -0
  35. package/dist/index.html +69 -19
  36. package/index.html +55 -5
  37. package/package.json +7 -7
  38. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +3 -3
  39. package/src/app/components/layout/header.tsx +3 -3
  40. package/src/app/components/layout/page-layout.tsx +2 -2
  41. package/src/app/components/layout/runtime-status-entry.tsx +7 -7
  42. package/src/app/components/layout/sidebar-items.tsx +10 -10
  43. package/src/app/components/layout/sidebar-rail.styles.ts +2 -2
  44. package/src/app/components/layout/sidebar.tsx +6 -6
  45. package/src/app/styles/design-system.css +388 -26
  46. package/src/app/test/vitest-setup.ts +31 -0
  47. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +3 -3
  48. package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +4 -2
  49. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +1 -1
  50. package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -6
  51. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -1
  52. package/src/features/chat/components/conversation/chat-conversation-skeleton.tsx +13 -13
  53. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +2 -2
  54. package/src/features/chat/components/layout/chat-page-shell.tsx +1 -1
  55. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +3 -3
  56. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +14 -14
  57. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +15 -15
  58. package/src/features/chat/components/layout/chat-sidebar.tsx +3 -3
  59. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +225 -0
  60. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +199 -0
  61. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +28 -9
  62. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +18 -6
  63. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +3 -13
  64. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-query.ts +19 -3
  65. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +7 -7
  66. package/src/features/chat/features/message/components/chat-message-list.container.tsx +6 -6
  67. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +5 -5
  68. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +4 -4
  69. package/src/features/chat/features/session/components/session-context-icon.tsx +2 -2
  70. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +2 -2
  71. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +1 -1
  72. package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
  73. package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
  74. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
  75. package/src/features/chat/features/session/components/session-run-badge.tsx +1 -1
  76. package/src/features/chat/features/session-type/hooks/use-chat-session-type-state.ts +10 -2
  77. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +0 -7
  78. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +3 -3
  79. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +4 -4
  80. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +14 -19
  81. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +3 -3
  82. package/src/features/chat/features/welcome/components/chat-welcome.tsx +4 -4
  83. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +6 -8
  84. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +1 -16
  85. package/src/features/panel-apps/components/panel-app-list-item.tsx +8 -8
  86. package/src/features/panel-apps/components/panel-app-toolbar.tsx +4 -4
  87. package/src/features/panel-apps/components/panel-apps-list.tsx +20 -20
  88. package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +70 -6
  89. package/src/features/pwa/managers/pwa-shell-theme.manager.ts +10 -5
  90. package/src/features/service-apps/components/service-action-authorization-dialog.tsx +8 -8
  91. package/src/features/service-apps/components/service-apps-panel.tsx +24 -24
  92. package/src/features/settings/components/config/provider-advanced-settings-section.tsx +5 -5
  93. package/src/features/settings/components/config/provider-auth-section.tsx +7 -7
  94. package/src/features/settings/components/config/provider-form.tsx +10 -10
  95. package/src/features/settings/components/config/provider-models-section.tsx +16 -16
  96. package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
  97. package/src/features/settings/pages/providers-config-page.tsx +17 -17
  98. package/src/shared/components/config-split-page.tsx +11 -11
  99. package/src/shared/components/doc-browser/doc-browser-home-page.tsx +2 -2
  100. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +4 -4
  101. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +4 -4
  102. package/src/shared/components/doc-browser/doc-browser.tsx +3 -3
  103. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +1 -1
  104. package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
  105. package/src/shared/components/ui/button.tsx +4 -4
  106. package/src/shared/components/ui/card.tsx +4 -4
  107. package/src/shared/components/ui/dialog.tsx +5 -5
  108. package/src/shared/components/ui/input.tsx +1 -1
  109. package/src/shared/components/ui/label.tsx +1 -1
  110. package/src/shared/components/ui/popover.tsx +1 -1
  111. package/src/shared/components/ui/select.tsx +3 -3
  112. package/src/shared/components/ui/switch.tsx +3 -3
  113. package/src/shared/components/ui/tabs.tsx +4 -4
  114. package/src/shared/components/ui/textarea.tsx +1 -1
  115. package/src/shared/lib/i18n/locales/en-US/core.json +7 -2
  116. package/src/shared/lib/i18n/locales/zh-CN/core.json +7 -2
  117. package/src/shared/lib/theme/index.ts +94 -52
  118. package/dist/assets/chat-page-CEdavJXc.js +0 -105
  119. package/dist/assets/config-split-page-B5b9B0uE.js +0 -1
  120. package/dist/assets/doc-browser-5nMWYKBD.js +0 -1
  121. package/dist/assets/doc-browser-BJRLhztv.js +0 -1
  122. package/dist/assets/index-BDN1zzMQ.js +0 -105
  123. package/dist/assets/index-g3uSZXoc.css +0 -1
  124. package/dist/assets/mcp-marketplace-page-sMNFSq_T.js +0 -1
  125. package/dist/assets/plus-DPjb0piO.js +0 -1
  126. package/dist/assets/providers-config-page-BzAeaACA.js +0 -1
  127. package/dist/assets/remote-B0G80uPr.js +0 -1
  128. package/dist/assets/rotate-cw-DnEB1Rju.js +0 -1
  129. package/dist/assets/x-BiSSpgqB.js +0 -1
@@ -22,7 +22,10 @@ import {
22
22
  } from '@/features/chat/features/conversation/hooks/use-session-conversation-controller';
23
23
  import { useSessionConversationInputQuery } from '@/features/chat/features/conversation/hooks/use-session-conversation-input-query';
24
24
  import { useSessionConversationInputState } from '@/features/chat/features/conversation/hooks/use-session-conversation-input-state';
25
- import { SessionConversationInput } from './session-conversation-input';
25
+ import {
26
+ SessionConversationInput,
27
+ type SessionConversationInputController,
28
+ } from './session-conversation-input';
26
29
 
27
30
  type SessionConversationAreaProps = {
28
31
  readonly consumeDraftIntent?: boolean;
@@ -226,14 +229,31 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
226
229
  restoreComposer: inputActions.restoreComposer,
227
230
  setSendError: inputActions.setSendError,
228
231
  });
229
- const displayInputSnapshot = useMemo(() => ({
230
- ...inputSnapshot,
231
- sendError: lastSendError ?? inputSnapshot.sendError,
232
- }), [inputSnapshot, lastSendError]);
232
+ const controllerRef = useRef(controller);
233
+ useEffect(() => {
234
+ controllerRef.current = controller;
235
+ }, [controller]);
236
+ const inputController = useMemo<SessionConversationInputController>(() => ({
237
+ canStopGeneration: controller.canStopGeneration,
238
+ isSending: controller.isSending,
239
+ send: () => controllerRef.current.send(),
240
+ sendDisabled: controller.sendDisabled,
241
+ stop: () => controllerRef.current.stop(),
242
+ stopDisabled: controller.stopDisabled,
243
+ }), [
244
+ controller.canStopGeneration,
245
+ controller.isSending,
246
+ controller.sendDisabled,
247
+ controller.stopDisabled,
248
+ ]);
233
249
  const contextWindow = useMemo(
234
250
  () => buildChatContextWindowIndicator(readNcpContextWindowValue(agent.snapshot.contextWindow)),
235
251
  [agent.snapshot.contextWindow],
236
252
  );
253
+ const displayInputSnapshot = useMemo(() => ({
254
+ ...inputSnapshot,
255
+ sendError: lastSendError ?? inputSnapshot.sendError,
256
+ }), [inputSnapshot, lastSendError]);
237
257
  useSessionConversationDraftIntent({
238
258
  consumeDraftIntent,
239
259
  applyPromptSuggestion: inputActions.applyPromptSuggestion,
@@ -241,7 +261,7 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
241
261
  const renderInput = useCallback((surface: 'default' | 'embedded') => (
242
262
  <SessionConversationInput
243
263
  contextWindow={contextWindow}
244
- controller={controller}
264
+ controller={inputController}
245
265
  inputActions={inputActions}
246
266
  inputQuery={inputQuery}
247
267
  inputSnapshot={displayInputSnapshot}
@@ -249,8 +269,8 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
249
269
  />
250
270
  ), [
251
271
  contextWindow,
252
- controller,
253
272
  displayInputSnapshot,
273
+ inputController,
254
274
  inputActions,
255
275
  inputQuery,
256
276
  ]);
@@ -258,8 +278,7 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
258
278
  showWelcomeForDraft &&
259
279
  !sessionKey &&
260
280
  agent.visibleMessages.length === 0 &&
261
- !agent.isHydrating &&
262
- !controller.isSending;
281
+ !agent.isHydrating;
263
282
 
264
283
  return (
265
284
  <>
@@ -1,10 +1,15 @@
1
1
  import {
2
+ memo,
2
3
  useCallback,
3
4
  useEffect,
4
5
  useMemo,
5
6
  useRef,
6
7
  } from 'react';
7
- import { ChatInputBar, type ChatInputBarHandle, type ChatContextWindowIndicator } from '@nextclaw/agent-chat-ui';
8
+ import {
9
+ ChatInputBar,
10
+ type ChatContextWindowIndicator,
11
+ type ChatInputBarHandle,
12
+ } from '@nextclaw/agent-chat-ui';
8
13
  import { isRuntimeDefaultModelValue } from '@nextclaw/shared';
9
14
 
10
15
  import { useI18n } from '@/app/components/i18n-provider';
@@ -43,7 +48,6 @@ import {
43
48
  CHAT_RECENT_SKILLS_MIN_OPTIONS,
44
49
  } from '@/features/chat/managers/chat-recent-skills.manager';
45
50
 
46
- import type { useSessionConversationController } from '@/features/chat/features/conversation/hooks/use-session-conversation-controller';
47
51
  import { useSessionConversationInputAttachments } from '@/features/chat/features/conversation/hooks/use-session-conversation-input-attachments';
48
52
  import type { useSessionConversationInputQuery } from '@/features/chat/features/conversation/hooks/use-session-conversation-input-query';
49
53
  import type {
@@ -57,7 +61,15 @@ import {
57
61
  } from '@/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils';
58
62
 
59
63
  type SessionConversationInputQuery = ReturnType<typeof useSessionConversationInputQuery>;
60
- type SessionConversationController = ReturnType<typeof useSessionConversationController>;
64
+
65
+ export type SessionConversationInputController = {
66
+ readonly canStopGeneration: boolean;
67
+ readonly isSending: boolean;
68
+ readonly sendDisabled: boolean;
69
+ readonly stopDisabled: boolean;
70
+ readonly send: () => Promise<void> | void;
71
+ readonly stop: () => Promise<void> | void;
72
+ };
61
73
 
62
74
  function toSkillRecords(
63
75
  snapshotRecords: SessionSkillEntryView[],
@@ -150,14 +162,14 @@ function useSessionConversationInputCollections(params: {
150
162
 
151
163
  type SessionConversationInputProps = {
152
164
  readonly contextWindow: ChatContextWindowIndicator | null;
153
- readonly controller: SessionConversationController;
165
+ readonly controller: SessionConversationInputController;
154
166
  readonly inputActions: SessionConversationInputActions;
155
167
  readonly inputQuery: SessionConversationInputQuery;
156
168
  readonly inputSnapshot: SessionConversationInputSnapshot;
157
169
  readonly surface?: 'default' | 'embedded';
158
170
  };
159
171
 
160
- export function SessionConversationInput(props: SessionConversationInputProps) {
172
+ export const SessionConversationInput = memo(function SessionConversationInput(props: SessionConversationInputProps) {
161
173
  const {
162
174
  contextWindow,
163
175
  controller,
@@ -424,4 +436,4 @@ export function SessionConversationInput(props: SessionConversationInputProps) {
424
436
  />
425
437
  </>
426
438
  );
427
- }
439
+ });
@@ -55,18 +55,6 @@ const resolveModelForSend = (value: string | null | undefined): string | undefin
55
55
  return trimmed || undefined;
56
56
  };
57
57
 
58
- const resolveProjectRootForSend = (params: {
59
- readonly inputSnapshot: SessionConversationInputSnapshot;
60
- readonly inputQuery: SessionConversationInputQuery;
61
- readonly sessionKey: string | null;
62
- }): string | null => {
63
- const { inputSnapshot, inputQuery, sessionKey } = params;
64
- if (!sessionKey) {
65
- return inputSnapshot.pendingProjectRoot ?? inputQuery.defaultProjectRoot ?? null;
66
- }
67
- return inputQuery.selectedSession?.projectRoot ?? null;
68
- };
69
-
70
58
  export function useSessionConversationController(params: UseSessionConversationControllerParams) {
71
59
  const {
72
60
  agent,
@@ -135,7 +123,9 @@ export function useSessionConversationController(params: UseSessionConversationC
135
123
  : inputQuery.sessionTypeState.selectedSessionType,
136
124
  projectRoot: materializationContext
137
125
  ? inputSnapshot.pendingProjectRoot
138
- : resolveProjectRootForSend({ inputSnapshot, inputQuery, sessionKey }),
126
+ : sessionKey
127
+ ? inputQuery.selectedSession?.projectRoot ?? null
128
+ : inputSnapshot.pendingProjectRoot,
139
129
  requestedSkills: [...inputSnapshot.selectedSkills],
140
130
  composerNodes: [...inputSnapshot.nodes],
141
131
  sessionMaterialization: materializationContext
@@ -119,9 +119,25 @@ export function useSessionConversationInputQuery(params: UseSessionConversationI
119
119
  [selectedSessionKey, sessionTypeState.selectedSessionType, sessions],
120
120
  );
121
121
 
122
- return {
122
+ const defaultProjectRoot = normalizeSessionProjectRootValue(
123
+ config?.agents.defaults.workspace,
124
+ );
125
+
126
+ return useMemo(() => ({
123
127
  defaultModel: config?.agents.defaults.model,
124
- defaultProjectRoot: normalizeSessionProjectRootValue(config?.agents.defaults.workspace),
128
+ defaultProjectRoot,
129
+ fallbackPreferredModel,
130
+ fallbackPreferredThinking,
131
+ isProviderStateResolved,
132
+ isSkillsLoading,
133
+ modelOptions,
134
+ selectedSession,
135
+ selectedSessionKey,
136
+ sessionTypeState,
137
+ skillRecords,
138
+ }), [
139
+ config?.agents.defaults.model,
140
+ defaultProjectRoot,
125
141
  fallbackPreferredModel,
126
142
  fallbackPreferredThinking,
127
143
  isProviderStateResolved,
@@ -131,5 +147,5 @@ export function useSessionConversationInputQuery(params: UseSessionConversationI
131
147
  selectedSessionKey,
132
148
  sessionTypeState,
133
149
  skillRecords,
134
- };
150
+ ]);
135
151
  }
@@ -77,28 +77,28 @@ export function ChatInlinePanelAppCard({
77
77
  if (!url) {
78
78
  return (
79
79
  <div
80
- className="h-[320px] min-h-[220px] w-full animate-pulse rounded-lg border border-[#eee3d1] bg-gradient-to-b from-white to-amber-50/60"
80
+ className="h-[320px] min-h-[220px] w-full animate-pulse rounded-lg border border-border bg-muted/45"
81
81
  style={{ maxHeight: PANEL_APP_INLINE_CARD_MAX_HEIGHT_PX }}
82
82
  />
83
83
  );
84
84
  }
85
85
 
86
86
  return (
87
- <div className="w-full max-w-[42rem] overflow-hidden rounded-lg border border-[#eee3d1] bg-white shadow-[0_1px_2px_rgba(30,20,10,0.04)]">
88
- <div className="flex h-9 items-center justify-between gap-2 border-b border-[#f1e7d4] bg-[#fffaf2] px-2.5">
89
- <div className="flex min-w-0 items-center gap-2 text-xs font-medium text-[#5c4933]">
87
+ <div className="w-full max-w-[42rem] overflow-hidden rounded-lg border border-border bg-card shadow-sm">
88
+ <div className="flex h-9 items-center justify-between gap-2 border-b border-border bg-muted/45 px-2.5">
89
+ <div className="flex min-w-0 items-center gap-2 text-xs font-medium text-foreground">
90
90
  <AppWindow className="h-3.5 w-3.5 shrink-0 text-primary" />
91
91
  <span className="truncate">{title}</span>
92
92
  </div>
93
93
  <IconActionButton
94
- className="h-7 w-7 rounded-md text-[#7a6145] hover:bg-white hover:text-[#2f2212]"
94
+ className="h-7 w-7 rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground"
95
95
  icon={<Maximize2 className="h-3.5 w-3.5" />}
96
96
  label={t('chatPanelCardExpand')}
97
97
  onClick={openExpanded}
98
98
  />
99
99
  </div>
100
100
  <div
101
- className="relative h-[420px] min-h-[220px] max-h-[min(60vh,420px)] overflow-hidden bg-white"
101
+ className="relative h-[420px] min-h-[220px] max-h-[min(60vh,420px)] overflow-hidden bg-card"
102
102
  >
103
103
  <iframe
104
104
  ref={iframeRef}
@@ -107,7 +107,7 @@ export function ChatInlinePanelAppCard({
107
107
  title={title}
108
108
  sandbox={PANEL_APP_INLINE_IFRAME_SANDBOX}
109
109
  scrolling="auto"
110
- className="h-full w-full border-0 bg-white"
110
+ className="h-full w-full border-0 bg-card"
111
111
  />
112
112
  </div>
113
113
  </div>
@@ -126,13 +126,13 @@ function ChatContextCompactionDivider({
126
126
  `${t("chatContextCompactionProjectedTokens")}: ${checkpoint.projectedEstimatedTokens}`,
127
127
  ].join("\n");
128
128
  return (
129
- <div className="my-4 flex items-center gap-3 text-[11px] text-gray-500" title={title}>
130
- <div className="h-px flex-1 bg-gray-200" />
131
- <div className="inline-flex items-center gap-2 rounded-full border border-gray-200 bg-gray-50 px-3 py-1">
129
+ <div className="my-4 flex items-center gap-3 text-[11px] text-muted-foreground" title={title}>
130
+ <div className="h-px flex-1 bg-border" />
131
+ <div className="inline-flex items-center gap-2 rounded-full border border-border bg-muted px-3 py-1">
132
132
  {checkpoint.status === "compressing" ? (
133
- <span className="h-1.5 w-1.5 rounded-full bg-gray-400 animate-pulse" />
133
+ <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-muted-foreground" />
134
134
  ) : (
135
- <span className="h-1.5 w-1.5 rounded-full bg-gray-300" />
135
+ <span className="h-1.5 w-1.5 rounded-full bg-muted-foreground/45" />
136
136
  )}
137
137
  <span>
138
138
  {checkpoint.status === "compressing"
@@ -140,7 +140,7 @@ function ChatContextCompactionDivider({
140
140
  : t("chatContextCompactionCompressed")}
141
141
  </span>
142
142
  </div>
143
- <div className="h-px flex-1 bg-gray-200" />
143
+ <div className="h-px flex-1 bg-border" />
144
144
  </div>
145
145
  );
146
146
  }
@@ -49,12 +49,12 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
49
49
  <div className="flex items-center justify-between gap-2 px-2 py-0.5">
50
50
  <div className="flex min-w-0 items-center gap-1.5">
51
51
  <div
52
- className="truncate text-[11px] font-medium uppercase tracking-wider text-gray-500"
52
+ className="truncate text-[11px] font-medium uppercase tracking-wider text-muted-foreground"
53
53
  title={group.projectRoot}
54
54
  >
55
55
  {group.projectName}
56
56
  </div>
57
- <span className="shrink-0 text-[10px] text-gray-400">
57
+ <span className="shrink-0 text-[10px] text-muted-foreground/70">
58
58
  {group.items.length}
59
59
  </span>
60
60
  </div>
@@ -70,12 +70,12 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
70
70
  icon={<Plus className="h-3.5 w-3.5" />}
71
71
  label={actionLabel}
72
72
  tooltip={false}
73
- className="h-7 w-7 shrink-0 rounded-lg text-gray-400 hover:bg-white hover:text-gray-900"
73
+ className="h-7 w-7 shrink-0 rounded-lg text-muted-foreground hover:bg-accent hover:text-accent-foreground"
74
74
  />
75
75
  </PopoverTrigger>
76
76
  <PopoverContent
77
77
  align="end"
78
- className="w-56 rounded-2xl border border-gray-200/80 bg-white p-1.5 shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
78
+ className="w-56 rounded-2xl border border-border bg-popover p-1.5 text-popover-foreground shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
79
79
  >
80
80
  <ChatSessionTypeMenu
81
81
  options={sessionTypeOptions}
@@ -91,7 +91,7 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
91
91
  <IconActionButton
92
92
  icon={<Plus className="h-3.5 w-3.5" />}
93
93
  label={actionLabel}
94
- className="h-7 w-7 shrink-0 rounded-lg text-gray-400 hover:bg-white hover:text-gray-900"
94
+ className="h-7 w-7 shrink-0 rounded-lg text-muted-foreground hover:bg-accent hover:text-accent-foreground"
95
95
  onClick={() => onCreateSession(preferredSessionType, group.projectRoot)}
96
96
  />
97
97
  )}
@@ -25,8 +25,8 @@ type ChatSidebarSessionListProps = {
25
25
  function ChatSidebarEmptyState({ label }: { label: string }) {
26
26
  return (
27
27
  <div className="p-4 text-center">
28
- <MessageSquareText className="mx-auto mb-2 h-6 w-6 text-gray-300" />
29
- <div className="text-xs text-gray-500">{label}</div>
28
+ <MessageSquareText className="mx-auto mb-2 h-6 w-6 text-muted-foreground/45" />
29
+ <div className="text-xs text-muted-foreground">{label}</div>
30
30
  </div>
31
31
  );
32
32
  }
@@ -42,7 +42,7 @@ export function ChatSidebarSessionList({
42
42
  sessionTypeOptions,
43
43
  }: ChatSidebarSessionListProps) {
44
44
  if (isLoading) {
45
- return <div className="p-3 text-xs text-gray-500">{t("sessionsLoading")}</div>;
45
+ return <div className="p-3 text-xs text-muted-foreground">{t("sessionsLoading")}</div>;
46
46
  }
47
47
 
48
48
  if (isProjectFirstView) {
@@ -67,7 +67,7 @@ export function ChatSidebarSessionList({
67
67
  <div className="space-y-3">
68
68
  {groups.map((group) => (
69
69
  <div key={group.label}>
70
- <div className="px-2 py-1 text-[11px] font-medium uppercase tracking-wider text-gray-400">
70
+ <div className="px-2 py-1 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70">
71
71
  {group.label}
72
72
  </div>
73
73
  <div className="space-y-0.5">{group.items.map(renderSessionItem)}</div>
@@ -17,7 +17,7 @@ export function SessionContextIconNode({ icon, className }: { icon: SessionConte
17
17
  src={runtimeIconSrc ?? undefined}
18
18
  className={cn('h-[1.125rem] w-[1.125rem]', className)}
19
19
  imgClassName="h-full w-full object-contain"
20
- fallback={<Bot className={cn('h-3 w-3 text-gray-500', className)} />}
20
+ fallback={<Bot className={cn('h-3 w-3 text-muted-foreground', className)} />}
21
21
  />
22
22
  );
23
23
  }
@@ -34,7 +34,7 @@ function ChannelLogoIcon(
34
34
  src={logoSrc}
35
35
  className={cn('h-[1.125rem] w-[1.125rem]', className)}
36
36
  imgClassName="h-full w-full object-contain"
37
- fallback={<Bot className="h-3 w-3 text-gray-500" />}
37
+ fallback={<Bot className="h-3 w-3 text-muted-foreground" />}
38
38
  />
39
39
  );
40
40
  }
@@ -58,8 +58,8 @@ describe('ChatSessionProjectBadge', () => {
58
58
  renderProjectBadge();
59
59
 
60
60
  const trigger = screen.getByRole('button', { name: 'Set Project Directory' });
61
- expect(trigger.className).toContain('border-gray-200');
62
- expect(trigger.className).toContain('text-gray-600');
61
+ expect(trigger.className).toContain('border-border');
62
+ expect(trigger.className).toContain('text-muted-foreground');
63
63
  expect(trigger.className).not.toContain('emerald');
64
64
  });
65
65
 
@@ -9,7 +9,7 @@ import { ChatSessionProjectDialog } from './chat-session-project-dialog';
9
9
  import { t } from '@/shared/lib/i18n';
10
10
 
11
11
  const SESSION_HEADER_ACTION_GROUP_CLASS = 'flex shrink-0 items-center gap-1.5';
12
- const SESSION_HEADER_ACTION_BUTTON_CLASS = 'h-7 w-7 rounded-lg shrink-0 text-gray-400 hover:text-gray-700';
12
+ const SESSION_HEADER_ACTION_BUTTON_CLASS = 'h-7 w-7 shrink-0 rounded-lg text-muted-foreground/70 hover:bg-accent hover:text-accent-foreground';
13
13
 
14
14
  type ChatSessionHeaderActionsProps = {
15
15
  sessionKey: string;
@@ -23,7 +23,7 @@ export function ChatSessionHeaderMenuItem({
23
23
  'flex w-full items-center gap-2 rounded-xl px-3 py-2 text-left text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50',
24
24
  destructive
25
25
  ? 'text-destructive hover:bg-destructive/10'
26
- : 'text-gray-700 hover:bg-gray-100'
26
+ : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
27
27
  )}
28
28
  onClick={onClick}
29
29
  disabled={disabled}
@@ -29,7 +29,7 @@ export function ChatSessionMetadataDialog({
29
29
  return (
30
30
  <Dialog open={open} onOpenChange={onOpenChange}>
31
31
  <DialogContent className="max-w-2xl gap-0 overflow-hidden p-0">
32
- <DialogHeader className="border-b border-gray-200 px-5 py-4 pr-12">
32
+ <DialogHeader className="border-b border-border px-5 py-4 pr-12">
33
33
  <DialogTitle className="text-base">{t('chatSessionMetadataDialogTitle')}</DialogTitle>
34
34
  <DialogDescription className="sr-only">
35
35
  {t('chatSessionMetadataDialogDescription')}
@@ -55,7 +55,7 @@ export function ChatSessionProjectBadge({
55
55
  <button
56
56
  type="button"
57
57
  title={projectRoot ?? undefined}
58
- className="min-w-0 max-w-[320px] shrink rounded-full border border-gray-200 bg-gray-100/90 px-2 py-0.5 text-[11px] font-medium text-gray-600 transition-colors hover:border-gray-300 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-60"
58
+ className="min-w-0 max-w-[320px] shrink rounded-full border border-border bg-muted px-2 py-0.5 text-[11px] font-medium text-muted-foreground transition-colors hover:border-primary/35 hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-60"
59
59
  aria-label={t('chatSessionSetProject')}
60
60
  disabled={isProjectPending}
61
61
  >
@@ -72,11 +72,11 @@ export function ChatSessionProjectBadge({
72
72
  style={SESSION_PROJECT_MENU_STYLE}
73
73
  >
74
74
  <div className="px-3 pb-2 pt-1">
75
- <div className="text-[11px] font-medium uppercase tracking-wider text-gray-500">
75
+ <div className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
76
76
  {projectName}
77
77
  </div>
78
78
  {projectRoot ? (
79
- <div className="mt-1 break-all text-xs text-gray-500">
79
+ <div className="mt-1 break-all text-xs text-muted-foreground">
80
80
  {projectRoot}
81
81
  </div>
82
82
  ) : null}
@@ -12,7 +12,7 @@ export function SessionRunBadge({ status, className }: SessionRunBadgeProps) {
12
12
  const label = status === 'running' ? t('sessionsRunStatusRunning') : t('sessionsRunStatusQueued');
13
13
  return (
14
14
  <span
15
- className={cn('inline-flex h-3.5 w-3.5 items-center justify-center text-gray-400', className)}
15
+ className={cn('inline-flex h-3.5 w-3.5 items-center justify-center text-muted-foreground/70', className)}
16
16
  title={label}
17
17
  aria-label={label}
18
18
  >
@@ -128,7 +128,7 @@ export function useChatSessionTypeState(params: UseChatSessionTypeStateParams):
128
128
  return null;
129
129
  }, [availableSessionTypeSet, selectedSession, selectedSessionType, selectedSessionTypeOption]);
130
130
 
131
- return {
131
+ return useMemo(() => ({
132
132
  sessionTypeOptions,
133
133
  selectedSessionTypeOption,
134
134
  defaultSessionType,
@@ -136,5 +136,13 @@ export function useChatSessionTypeState(params: UseChatSessionTypeStateParams):
136
136
  canEditSessionType,
137
137
  sessionTypeUnavailable,
138
138
  sessionTypeUnavailableMessage
139
- };
139
+ }), [
140
+ canEditSessionType,
141
+ defaultSessionType,
142
+ selectedSessionType,
143
+ selectedSessionTypeOption,
144
+ sessionTypeOptions,
145
+ sessionTypeUnavailable,
146
+ sessionTypeUnavailableMessage
147
+ ]);
140
148
  }
@@ -89,17 +89,10 @@ describe('ChatWelcome', () => {
89
89
  defaultProjectRoot: '/Users/demo/.nextclaw/workspace',
90
90
  onSelectProjectRoot,
91
91
  projectOptions: [
92
- {
93
- projectRoot: '/Users/demo/.nextclaw/workspace',
94
- projectName: 'workspace',
95
- sessionCount: 0,
96
- isDefault: true,
97
- },
98
92
  {
99
93
  projectRoot: '/tmp/project-alpha',
100
94
  projectName: 'project-alpha',
101
95
  sessionCount: 3,
102
- isDefault: false,
103
96
  },
104
97
  ],
105
98
  });
@@ -28,7 +28,7 @@ export function ChatWelcomeAgentPicker({
28
28
  <Select value={selectedAgentId} onValueChange={onSelectAgent}>
29
29
  <SelectTrigger
30
30
  aria-label={t('chatDraftAgentTitle')}
31
- className="h-auto w-auto min-w-0 gap-1 rounded-lg border-0 bg-transparent px-2 py-1.5 text-gray-500 shadow-none hover:bg-gray-100 hover:text-gray-800 focus:ring-0"
31
+ className="h-auto w-auto min-w-0 gap-1 rounded-lg border-0 bg-transparent px-2 py-1.5 text-muted-foreground shadow-none hover:bg-accent hover:text-accent-foreground focus:ring-0"
32
32
  >
33
33
  <span className="sr-only">{t('chatDraftAgentTitle')}</span>
34
34
  <div className="flex min-w-0 items-center gap-1.5">
@@ -48,7 +48,7 @@ export function ChatWelcomeAgentPicker({
48
48
  </div>
49
49
  </SelectTrigger>
50
50
  <SelectContent
51
- className="rounded-xl border-gray-200/80 shadow-lg"
51
+ className="rounded-xl border-border shadow-lg"
52
52
  style={{ maxHeight: AGENT_PICKER_MAX_HEIGHT }}
53
53
  >
54
54
  {agents.map((agent) => (
@@ -60,7 +60,7 @@ export function ChatWelcomeAgentPicker({
60
60
  avatarUrl={agent.avatarUrl}
61
61
  className="h-5 w-5 shrink-0"
62
62
  />
63
- <span className="truncate text-sm font-medium text-gray-700">
63
+ <span className="truncate text-sm font-medium text-foreground">
64
64
  {agent.displayName?.trim() || agent.id}
65
65
  </span>
66
66
  </div>
@@ -35,15 +35,15 @@ export function ChatWelcomeCapabilityGrid({
35
35
  <button
36
36
  key={capability.titleKey}
37
37
  onClick={() => onSelectPrompt(t(capability.promptKey))}
38
- className="min-w-0 rounded-2xl border border-gray-200 bg-white p-3 text-left shadow-card transition-shadow hover:shadow-card-hover sm:p-4"
38
+ className="min-w-0 rounded-2xl border border-border bg-card p-3 text-left text-card-foreground shadow-card transition-[border-color,box-shadow,transform] hover:border-primary/30 hover:shadow-card-hover sm:p-4"
39
39
  >
40
- <div className="mb-3 flex h-9 w-9 items-center justify-center rounded-xl bg-primary/8">
40
+ <div className="mb-3 flex h-9 w-9 items-center justify-center rounded-xl bg-accent">
41
41
  <Icon className="h-4.5 w-4.5 text-primary" />
42
42
  </div>
43
- <div className="mb-1 text-sm font-semibold text-gray-900">
43
+ <div className="mb-1 text-sm font-semibold text-foreground">
44
44
  {t(capability.titleKey)}
45
45
  </div>
46
- <div className="text-[11px] leading-relaxed text-gray-500">
46
+ <div className="text-[11px] leading-relaxed text-muted-foreground">
47
47
  {t(capability.descKey)}
48
48
  </div>
49
49
  </button>
@@ -49,7 +49,7 @@ export function ChatWelcomeProjectPicker({
49
49
  <PopoverTrigger asChild>
50
50
  <button
51
51
  type="button"
52
- className="inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left font-medium text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/25"
52
+ className="inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/25"
53
53
  title={projectRoot ?? undefined}
54
54
  aria-label={t('chatWelcomeProjectPickerLabel')}
55
55
  disabled={!selectable || isSaving}
@@ -57,19 +57,19 @@ export function ChatWelcomeProjectPicker({
57
57
  <FolderOpen className="h-4 w-4 shrink-0" />
58
58
  <span className="truncate">{projectLabel}</span>
59
59
  {isDefaultProject ? (
60
- <span className="shrink-0 text-xs font-normal text-gray-400">
60
+ <span className="shrink-0 text-xs font-normal text-muted-foreground/70">
61
61
  {t('chatWelcomeProjectDefaultBadge')}
62
62
  </span>
63
63
  ) : null}
64
- <ChevronDown className="h-3.5 w-3.5 shrink-0 text-gray-400" />
64
+ <ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground/70" />
65
65
  </button>
66
66
  </PopoverTrigger>
67
67
  <PopoverContent
68
68
  align="start"
69
- className="flex w-[min(20rem,calc(100vw-1rem))] flex-col overflow-hidden rounded-2xl border border-gray-200/80 bg-white p-0 shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
69
+ className="flex w-[min(20rem,calc(100vw-1rem))] flex-col overflow-hidden rounded-2xl border border-border bg-popover p-0 text-popover-foreground shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
70
70
  style={{ maxHeight: PROJECT_PICKER_MAX_HEIGHT }}
71
71
  >
72
- <div className="shrink-0 px-3 pb-2 pt-3 text-[10px] font-semibold uppercase tracking-[0.18em] text-gray-400">
72
+ <div className="shrink-0 px-3 pb-2 pt-3 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
73
73
  {t('chatWelcomeProjectRecentTitle')}
74
74
  </div>
75
75
  <div className="min-h-0 flex-1 overflow-y-auto px-1.5 pb-1.5">
@@ -78,51 +78,46 @@ export function ChatWelcomeProjectPicker({
78
78
  <button
79
79
  key={option.projectRoot}
80
80
  type="button"
81
- className="flex w-full min-w-0 items-start gap-2 rounded-xl px-2 py-2 text-left transition-colors hover:bg-gray-50"
81
+ className="flex w-full min-w-0 items-start gap-2 rounded-xl px-2 py-2 text-left transition-colors hover:bg-accent"
82
82
  title={option.projectRoot}
83
83
  onClick={() => {
84
84
  void selectProjectRoot(option.projectRoot);
85
85
  }}
86
86
  >
87
- <FolderOpen className="mt-0.5 h-4 w-4 shrink-0 text-gray-400" />
87
+ <FolderOpen className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground/70" />
88
88
  <span className="min-w-0 flex-1">
89
89
  <span className="flex min-w-0 items-center gap-1.5">
90
- <span className="truncate text-[13px] font-semibold text-gray-800">
90
+ <span className="truncate text-[13px] font-semibold text-foreground">
91
91
  {option.projectName}
92
92
  </span>
93
- {option.isDefault ? (
94
- <span className="shrink-0 text-[10px] font-medium text-gray-400">
95
- {t('chatWelcomeProjectDefaultBadge')}
96
- </span>
97
- ) : null}
98
93
  </span>
99
- <span className="block truncate text-[11px] leading-4 text-gray-500">
94
+ <span className="block truncate text-[11px] leading-4 text-muted-foreground">
100
95
  {option.projectRoot}
101
96
  </span>
102
97
  </span>
103
98
  {option.sessionCount > 0 ? (
104
- <span className="mt-0.5 shrink-0 rounded-full bg-gray-100 px-1.5 py-0.5 text-[10px] font-medium text-gray-500">
99
+ <span className="mt-0.5 shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
105
100
  {option.sessionCount}
106
101
  </span>
107
102
  ) : null}
108
103
  </button>
109
104
  ))
110
105
  ) : (
111
- <div className="px-2 py-5 text-center text-xs text-gray-400">
106
+ <div className="px-2 py-5 text-center text-xs text-muted-foreground">
112
107
  {t('chatWelcomeProjectNoRecent')}
113
108
  </div>
114
109
  )}
115
110
  </div>
116
- <div className="shrink-0 border-t border-gray-100 p-1.5">
111
+ <div className="shrink-0 border-t border-border p-1.5">
117
112
  <button
118
113
  type="button"
119
- className="flex w-full items-center gap-2 rounded-xl px-2.5 py-2 text-left text-[13px] font-semibold text-gray-700 transition-colors hover:bg-gray-50"
114
+ className="flex w-full items-center gap-2 rounded-xl px-2.5 py-2 text-left text-[13px] font-semibold text-foreground transition-colors hover:bg-accent"
120
115
  onClick={() => {
121
116
  setIsOpen(false);
122
117
  onOpenProjectDialog();
123
118
  }}
124
119
  >
125
- <FolderOpen className="h-4 w-4 shrink-0 text-gray-400" />
120
+ <FolderOpen className="h-4 w-4 shrink-0 text-muted-foreground/70" />
126
121
  <span>{t('chatWelcomeProjectOpenFolder')}</span>
127
122
  </button>
128
123
  </div>