@nextclaw/ui 0.15.21 → 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 (161) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/assets/{api-CHo2gekJ.js → api-B1yHQYs-.js} +3 -3
  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-BrZL4o9L.js → book-open-oSmyVXea.js} +1 -1
  6. package/dist/assets/{card-B5rRLu_6.js → card-CGvJZTZp.js} +1 -1
  7. package/dist/assets/{channels-list-page-C1dnm68I.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-C9EX46wM.js → dist-CxUhurq9.js} +1 -1
  13. package/dist/assets/doc-browser-0eL8AX42.js +1 -0
  14. package/dist/assets/{doc-browser-DH-3hDnI.js → doc-browser-DEqE9lsh.js} +1 -1
  15. package/dist/assets/{doc-browser-context-TU2heon0.js → doc-browser-context-DkzFIUoK.js} +1 -1
  16. package/dist/assets/{eye-B-3dyZNA.js → eye-gOWQGt8N.js} +1 -1
  17. package/dist/assets/{form-actions-DvvxCpL4.js → form-actions-CgjYZUzM.js} +1 -1
  18. package/dist/assets/{index-k_eWScvN.js → index-DSAUWCfl.js} +2 -2
  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-DB51c0cS.js → logo-badge-BNcsDsUM.js} +1 -1
  22. package/dist/assets/{mcp-marketplace-page-DWtsIqml.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-D6cGNnMM.js → model-config-page-7MIXg2fQ.js} +1 -1
  25. package/dist/assets/{navigation-link-BSJEUiwD.js → navigation-link-B3iFOdxx.js} +1 -1
  26. package/dist/assets/plus-yvhFgfal.js +1 -0
  27. package/dist/assets/{popover-Bp9IpE_N.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-C2e94pmp.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-C57OLdPm.js → runtime-config-page-DwjO-Vs6.js} +1 -1
  36. package/dist/assets/{save-CkwDOgvt.js → save-BINMWate.js} +1 -1
  37. package/dist/assets/{search-CLXM2reO.js → search-7M38azRp.js} +1 -1
  38. package/dist/assets/{search-config-page-tnJXaaSr.js → search-config-page-T6tMhXbP.js} +1 -1
  39. package/dist/assets/{secrets-config-page-DQ_Curaj.js → secrets-config-page-2mVejXf2.js} +2 -2
  40. package/dist/assets/security-config-DD5RrjDe.js +1 -0
  41. package/dist/assets/{select-CNFBf00l.js → select-BvidP5Oa.js} +1 -1
  42. package/dist/assets/{settings-2-B9IT2HGd.js → settings-2-DqZstREV.js} +1 -1
  43. package/dist/assets/skeleton-BEQW2Dva.js +1 -0
  44. package/dist/assets/{tabs-BuNr8LSO.js → tabs-BeLxVmkt.js} +1 -1
  45. package/dist/assets/{tag-chip-CDkB5smU.js → tag-chip-BoDaLnWt.js} +1 -1
  46. package/dist/assets/{trash-2-GchaJuEM.js → trash-2-CYUskMMN.js} +1 -1
  47. package/dist/assets/{use-config-C69rR15i.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-BACNZyMY.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 +7 -7
  53. package/src/app/components/layout/sidebar-rail.styles.ts +0 -2
  54. package/src/app/components/layout/sidebar.tsx +2 -2
  55. package/src/app/styles/design-system.css +12 -0
  56. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +2 -2
  57. package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +19 -1
  58. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +1 -1
  59. package/src/features/chat/components/conversation/chat-conversation-content.tsx +5 -6
  60. package/src/features/chat/components/conversation/chat-conversation-header.tsx +1 -1
  61. package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +1 -1
  62. package/src/features/chat/components/layout/__tests__/chat-sidebar-session-area.test.tsx +52 -0
  63. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +11 -17
  64. package/src/features/chat/components/layout/chat-page-shell.tsx +2 -11
  65. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +32 -42
  66. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +13 -64
  67. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +1 -4
  68. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +1 -1
  69. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +15 -4
  70. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +7 -7
  71. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +4 -2
  72. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +1 -0
  73. package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +1 -0
  74. package/src/features/chat/features/conversation/utils/session-queued-input.utils.ts +1 -0
  75. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +3 -0
  76. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +0 -1
  77. package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +3 -0
  78. package/src/features/chat/features/message/utils/__tests__/chat-message-timeline.utils.test.ts +29 -0
  79. package/src/features/chat/features/message/utils/chat-message-timeline.utils.ts +4 -1
  80. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +10 -10
  81. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
  82. package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
  83. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +1 -1
  84. package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +1 -1
  85. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +1 -1
  86. package/src/features/chat/features/session/utils/__tests__/chat-session-display.utils.test.ts +26 -0
  87. package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +4 -2
  88. package/src/features/chat/features/session/utils/chat-session-display.utils.ts +48 -12
  89. package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +13 -4
  90. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +6 -0
  91. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +10 -7
  92. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +1 -1
  93. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +3 -14
  94. package/src/features/chat/features/welcome/components/chat-welcome-prompt-suggestions.tsx +37 -0
  95. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +1 -1
  96. package/src/features/chat/features/welcome/components/chat-welcome.tsx +33 -31
  97. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +1 -1
  98. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +1 -1
  99. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +15 -3
  100. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +3 -10
  101. package/src/features/chat/managers/chat-session-list.manager.ts +3 -23
  102. package/src/features/chat/managers/chat-thread.manager.ts +2 -5
  103. package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +3 -9
  104. package/src/features/chat/pages/ncp-chat-page.tsx +0 -1
  105. package/src/features/chat/types/chat-input-bar.types.ts +1 -0
  106. package/src/features/cron/components/cron-task-discovery.tsx +1 -1
  107. package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
  108. package/src/features/notifications/components/__tests__/app-notification-toast.test.tsx +3 -0
  109. package/src/features/notifications/components/app-notification-toast.tsx +1 -1
  110. package/src/features/panel-apps/components/panel-app-list-item.tsx +1 -1
  111. package/src/features/panel-apps/components/panel-app-toolbar.tsx +2 -2
  112. package/src/features/panel-apps/components/panel-apps-list.tsx +4 -4
  113. package/src/features/service-apps/components/service-apps-panel.tsx +1 -1
  114. package/src/features/settings/components/config/provider-auth-section.tsx +90 -59
  115. package/src/features/settings/components/config/provider-form.tsx +10 -17
  116. package/src/features/settings/components/config/provider-status-badge.tsx +4 -2
  117. package/src/features/settings/pages/__tests__/providers-config-page.test.tsx +12 -0
  118. package/src/features/settings/pages/providers-config-page.tsx +2 -1
  119. package/src/platforms/mobile/components/__tests__/mobile-app-shell.test.tsx +13 -7
  120. package/src/platforms/mobile/components/__tests__/mobile-bottom-nav.test.tsx +18 -20
  121. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +1 -1
  122. package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +1 -1
  123. package/src/shared/components/path-picker/server-path-picker-locations.tsx +1 -1
  124. package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +3 -3
  125. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +1 -1
  126. package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
  127. package/src/shared/components/ui/button.tsx +4 -4
  128. package/src/shared/components/ui/dialog.tsx +1 -1
  129. package/src/shared/components/ui/input.tsx +1 -1
  130. package/src/shared/components/ui/scroll-area.tsx +2 -0
  131. package/src/shared/components/ui/select.tsx +1 -1
  132. package/src/shared/components/ui/switch.tsx +1 -1
  133. package/src/shared/components/ui/tabs.tsx +1 -1
  134. package/src/shared/components/ui/textarea.tsx +1 -1
  135. package/src/shared/lib/api/ncp-session.types.ts +1 -0
  136. package/src/shared/lib/api/types.ts +2 -0
  137. package/src/shared/lib/i18n/locales/en-US/chat.json +13 -14
  138. package/src/shared/lib/i18n/locales/zh-CN/chat.json +13 -14
  139. package/src/shared/lib/provider-models/__tests__/anonymous-provider-configuration.test.ts +40 -0
  140. package/src/shared/lib/provider-models/index.ts +4 -3
  141. package/dist/assets/app-presenter-provider-2_DmWYT9.js +0 -108
  142. package/dist/assets/appearance-settings-page-BEy2UE7R.js +0 -1
  143. package/dist/assets/chat-page-Dz6tvZ91.js +0 -1
  144. package/dist/assets/config-split-page-6y6W6dz4.js +0 -1
  145. package/dist/assets/confirm-dialog-q1CpEmR8.js +0 -5
  146. package/dist/assets/desktop-update-config-DrdlwP4j.js +0 -1
  147. package/dist/assets/doc-browser-BfttDSti.js +0 -1
  148. package/dist/assets/index-CXDPLDFD.css +0 -1
  149. package/dist/assets/loader-circle-B35NlHTI.js +0 -1
  150. package/dist/assets/mcp-marketplace-page-CkPtwPBk.js +0 -1
  151. package/dist/assets/plus-BRVe5jSJ.js +0 -1
  152. package/dist/assets/provider-scoped-model-input-dk7fz9NL.js +0 -1
  153. package/dist/assets/providers-config-page-DTP2YfxA.js +0 -1
  154. package/dist/assets/react-D6ShNq95.js +0 -8
  155. package/dist/assets/remote-FllhXW3o.js +0 -1
  156. package/dist/assets/rotate-cw-CTC-Qszi.js +0 -1
  157. package/dist/assets/security-config-S00DjFfO.js +0 -1
  158. package/dist/assets/skeleton-Dc06gatK.js +0 -1
  159. package/dist/assets/use-confirm-dialog-DF_EgPYx.js +0 -1
  160. package/dist/assets/x-BXFquY9e.js +0 -1
  161. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +0 -59
@@ -11,7 +11,6 @@ import { t } from '@/shared/lib/i18n';
11
11
  import { getSessionProjectName } from '@/shared/lib/session-project';
12
12
 
13
13
  type ChatWelcomeProjectPickerProps = {
14
- defaultProjectRoot?: string | null;
15
14
  isSaving: boolean;
16
15
  projectOptions: readonly ChatWelcomeProjectOption[];
17
16
  projectRoot: string | null;
@@ -23,7 +22,6 @@ type ChatWelcomeProjectPickerProps = {
23
22
  const PROJECT_PICKER_MAX_HEIGHT = createPopoverAvailableHeightLimit('20rem');
24
23
 
25
24
  export function ChatWelcomeProjectPicker({
26
- defaultProjectRoot,
27
25
  isSaving,
28
26
  projectOptions,
29
27
  projectRoot,
@@ -34,10 +32,6 @@ export function ChatWelcomeProjectPicker({
34
32
  const [isOpen, setIsOpen] = useState(false);
35
33
  const projectLabel =
36
34
  getSessionProjectName(projectRoot) ?? t('chatWelcomeProjectPickerPlaceholder');
37
- const isDefaultProject =
38
- defaultProjectRoot !== null &&
39
- defaultProjectRoot !== undefined &&
40
- projectRoot === defaultProjectRoot;
41
35
 
42
36
  const selectProjectRoot = async (nextProjectRoot: string) => {
43
37
  setIsOpen(false);
@@ -49,18 +43,13 @@ export function ChatWelcomeProjectPicker({
49
43
  <PopoverTrigger asChild>
50
44
  <button
51
45
  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-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
46
+ 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-[var(--interaction-hover)] hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
53
47
  title={projectRoot ?? undefined}
54
48
  aria-label={t('chatWelcomeProjectPickerLabel')}
55
49
  disabled={!selectable || isSaving}
56
50
  >
57
51
  <FolderOpen className="h-4 w-4 shrink-0" />
58
52
  <span className="truncate">{projectLabel}</span>
59
- {isDefaultProject ? (
60
- <span className="shrink-0 text-xs font-normal text-muted-foreground/70">
61
- {t('chatWelcomeProjectDefaultBadge')}
62
- </span>
63
- ) : null}
64
53
  <ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground/70" />
65
54
  </button>
66
55
  </PopoverTrigger>
@@ -78,7 +67,7 @@ export function ChatWelcomeProjectPicker({
78
67
  <button
79
68
  key={option.projectRoot}
80
69
  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-accent"
70
+ className="flex w-full min-w-0 items-start gap-2 rounded-xl px-2 py-2 text-left transition-colors hover:bg-[var(--interaction-hover)]"
82
71
  title={option.projectRoot}
83
72
  onClick={() => {
84
73
  void selectProjectRoot(option.projectRoot);
@@ -111,7 +100,7 @@ export function ChatWelcomeProjectPicker({
111
100
  <div className="shrink-0 border-t border-border p-1.5">
112
101
  <button
113
102
  type="button"
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"
103
+ 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-[var(--interaction-hover)]"
115
104
  onClick={() => {
116
105
  setIsOpen(false);
117
106
  onOpenProjectDialog();
@@ -0,0 +1,37 @@
1
+ import { t } from '@/shared/lib/i18n';
2
+
3
+ const WELCOME_PROMPT_SUGGESTIONS = [
4
+ {
5
+ labelKey: 'chatWelcomeSuggestion1Label' as const,
6
+ promptKey: 'chatWelcomeSuggestion1Prompt' as const,
7
+ },
8
+ {
9
+ labelKey: 'chatWelcomeSuggestion2Label' as const,
10
+ promptKey: 'chatWelcomeSuggestion2Prompt' as const,
11
+ },
12
+ {
13
+ labelKey: 'chatWelcomeSuggestion3Label' as const,
14
+ promptKey: 'chatWelcomeSuggestion3Prompt' as const,
15
+ },
16
+ ];
17
+
18
+ export function ChatWelcomePromptSuggestions({
19
+ onSelectPrompt,
20
+ }: {
21
+ onSelectPrompt: (prompt: string) => void;
22
+ }) {
23
+ return (
24
+ <div className="mt-5 flex flex-wrap justify-center gap-2">
25
+ {WELCOME_PROMPT_SUGGESTIONS.map((suggestion) => (
26
+ <button
27
+ key={suggestion.labelKey}
28
+ type="button"
29
+ onClick={() => onSelectPrompt(t(suggestion.promptKey))}
30
+ className="rounded-full bg-muted/55 px-3.5 py-2 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
31
+ >
32
+ {t(suggestion.labelKey)}
33
+ </button>
34
+ ))}
35
+ </div>
36
+ );
37
+ }
@@ -39,7 +39,7 @@ export function ChatWelcomeSessionTypePicker({
39
39
  <PopoverTrigger asChild>
40
40
  <button
41
41
  type="button"
42
- 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-1 focus-visible:ring-border"
42
+ 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-[var(--interaction-hover)] hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
43
43
  aria-label={t('chatWelcomeSessionTypePickerLabel')}
44
44
  >
45
45
  {selectedOption.icon?.src ? (
@@ -2,7 +2,7 @@ import type { ReactNode } from 'react';
2
2
  import { useState } from 'react';
3
3
  import { ChatSessionProjectDialog } from '@/features/chat/features/session/components/session-header/chat-session-project-dialog';
4
4
  import { ChatWelcomeAgentPicker } from '@/features/chat/features/welcome/components/chat-welcome-agent-picker';
5
- import { ChatWelcomeCapabilityGrid } from '@/features/chat/features/welcome/components/chat-welcome-capability-grid';
5
+ import { ChatWelcomePromptSuggestions } from '@/features/chat/features/welcome/components/chat-welcome-prompt-suggestions';
6
6
  import { ChatWelcomeProjectPicker } from '@/features/chat/features/welcome/components/chat-welcome-project-picker';
7
7
  import { ChatWelcomeSessionTypePicker } from '@/features/chat/features/welcome/components/chat-welcome-session-type-picker';
8
8
  import type { ChatWelcomeProjectOption } from '@/features/chat/features/welcome/utils/chat-welcome-project-options.utils';
@@ -61,45 +61,47 @@ export function ChatWelcome({
61
61
  const selectableProjectRoot = Boolean(onSelectProjectRoot);
62
62
 
63
63
  return (
64
- <div className="flex min-h-full items-center justify-center px-4 py-8 sm:p-8">
65
- <div className="min-w-0 w-full max-w-[min(760px,100%)]">
64
+ <div className="flex min-h-full items-center justify-center px-4 py-10 sm:p-8">
65
+ <div className="w-full min-w-0 max-w-[min(680px,100%)]">
66
66
  <div className="text-center">
67
- <h2 className="text-2xl font-semibold text-foreground sm:text-3xl">
67
+ <h2 className="text-2xl font-semibold leading-tight text-foreground sm:text-[2rem]">
68
68
  {t('chatWelcomeTitle')}
69
69
  </h2>
70
- <p className="mt-3 text-sm font-medium text-muted-foreground sm:text-base">
71
- {t('chatWelcomeSubtitle')}
72
- </p>
73
70
  </div>
74
71
 
75
- {inputSlot ? <div className="mt-8">{inputSlot}</div> : null}
72
+ {inputSlot ? <div className="mt-6">{inputSlot}</div> : null}
76
73
 
77
- <div className="mt-3 flex flex-wrap items-center gap-2 px-1 text-sm text-muted-foreground">
78
- {onSelectProjectRoot ? (
79
- <ChatWelcomeProjectPicker
80
- defaultProjectRoot={defaultProjectRoot}
81
- isSaving={isProjectSaving}
82
- projectOptions={projectOptions}
83
- projectRoot={resolvedProjectRoot}
84
- selectable={selectableProjectRoot}
85
- onOpenProjectDialog={() => setIsProjectDialogOpen(true)}
86
- onSelectProjectRoot={saveProjectRoot}
74
+ <div className="mt-2 flex justify-center px-1">
75
+ <div
76
+ role="group"
77
+ aria-label={t('chatWelcomeContextLabel')}
78
+ className="flex min-w-0 max-w-full flex-wrap items-center justify-center gap-0.5 rounded-xl bg-muted/40 p-1 text-sm text-muted-foreground"
79
+ >
80
+ {onSelectProjectRoot ? (
81
+ <ChatWelcomeProjectPicker
82
+ isSaving={isProjectSaving}
83
+ projectOptions={projectOptions}
84
+ projectRoot={resolvedProjectRoot}
85
+ selectable={selectableProjectRoot}
86
+ onOpenProjectDialog={() => setIsProjectDialogOpen(true)}
87
+ onSelectProjectRoot={saveProjectRoot}
88
+ />
89
+ ) : null}
90
+ <ChatWelcomeAgentPicker
91
+ agents={agents}
92
+ selectedAgent={selectedAgent}
93
+ selectedAgentId={selectedAgentId}
94
+ onSelectAgent={onSelectAgent}
87
95
  />
88
- ) : null}
89
- <ChatWelcomeAgentPicker
90
- agents={agents}
91
- selectedAgent={selectedAgent}
92
- selectedAgentId={selectedAgentId}
93
- onSelectAgent={onSelectAgent}
94
- />
95
- <ChatWelcomeSessionTypePicker
96
- options={sessionTypeOptions}
97
- selectedSessionType={selectedSessionType}
98
- onSelectSessionType={onSelectSessionType}
99
- />
96
+ <ChatWelcomeSessionTypePicker
97
+ options={sessionTypeOptions}
98
+ selectedSessionType={selectedSessionType}
99
+ onSelectSessionType={onSelectSessionType}
100
+ />
101
+ </div>
100
102
  </div>
101
103
 
102
- <ChatWelcomeCapabilityGrid onSelectPrompt={onSelectPrompt} />
104
+ <ChatWelcomePromptSuggestions onSelectPrompt={onSelectPrompt} />
103
105
  </div>
104
106
 
105
107
  {onSelectProjectRoot ? (
@@ -113,7 +113,7 @@ it("shows all session workspace entries in the overview", async () => {
113
113
  name: /Child sessions/,
114
114
  });
115
115
  const cronJobsButton = screen.getByRole("button", {
116
- name: /Session cron jobs/,
116
+ name: /Scheduled tasks/,
117
117
  });
118
118
 
119
119
  expect((childSessionsButton as HTMLButtonElement).disabled).toBe(false);
@@ -96,7 +96,7 @@ describe("ChatSessionWorkspacePanel", () => {
96
96
  await user.click(screen.getByRole("button", { name: "Refresh preview" }));
97
97
 
98
98
  expect(mocks.invalidateQueries).toHaveBeenCalledWith({
99
- queryKey: ["server-path-read", "README.md", null],
99
+ queryKey: ["server-path-read", "README.md", null, null],
100
100
  });
101
101
  expect(mocks.invalidateQueries).toHaveBeenCalledWith({
102
102
  queryKey: ["server-path-browse", "README.md", "", true],
@@ -70,6 +70,18 @@ function resetChatSessionListManagerState() {
70
70
  describe("ChatSessionListManager draft and selection flow", () => {
71
71
  beforeEach(resetChatSessionListManagerState);
72
72
 
73
+ it("clears the route-derived session selection outside session routes", () => {
74
+ const manager = new ChatSessionListManager(
75
+ {} as ConstructorParameters<typeof ChatSessionListManager>[0],
76
+ );
77
+
78
+ manager.syncRouteSessionSelection(null);
79
+
80
+ expect(
81
+ useChatSessionListStore.getState().snapshot.selectedSessionKey,
82
+ ).toBeNull();
83
+ });
84
+
73
85
  it("applies the requested session type when creating a session", () => {
74
86
  const uiManager = {
75
87
  goToChatRoot: vi.fn(),
@@ -93,7 +105,7 @@ describe("ChatSessionListManager draft and selection flow", () => {
93
105
  });
94
106
  expect(
95
107
  useChatSessionListStore.getState().snapshot.selectedSessionKey,
96
- ).toBeNull();
108
+ ).toBe("session-1");
97
109
  expect(useChatThreadStore.getState().snapshot.sessionKey).toBeNull();
98
110
  expect(
99
111
  useChatThreadStore.getState().snapshot.hasSubmittedDraftMessage,
@@ -126,7 +138,7 @@ describe("ChatSessionListManager draft and selection flow", () => {
126
138
  );
127
139
  expect(
128
140
  useChatSessionListStore.getState().snapshot.selectedSessionKey,
129
- ).toBeNull();
141
+ ).toBe("session-1");
130
142
  expect(useChatThreadStore.getState().snapshot.sessionKey).toBeNull();
131
143
  expect(
132
144
  useChatThreadStore.getState().snapshot.hasSubmittedDraftMessage,
@@ -198,7 +210,7 @@ describe("ChatSessionListManager draft and selection flow", () => {
198
210
 
199
211
  expect(
200
212
  useChatSessionListStore.getState().snapshot.selectedSessionKey,
201
- ).toBeNull();
213
+ ).toBe("session-1");
202
214
  expect(uiManager.navigateTo).toHaveBeenCalledWith(
203
215
  "/chat/draft",
204
216
  expect.any(Object),
@@ -836,7 +836,7 @@ describe('ChatThreadManager visible workspace selection', () => {
836
836
  });
837
837
 
838
838
  describe('ChatThreadManager deletion', () => {
839
- it('clears the selected thread state after deleting the current session', async () => {
839
+ it('clears thread state while leaving route-derived selection to navigation', async () => {
840
840
  const removeQueries = vi.spyOn(appQueryClient, 'removeQueries').mockImplementation(async () => undefined);
841
841
  const uiManager = {
842
842
  goToSession: vi.fn(),
@@ -844,13 +844,7 @@ describe('ChatThreadManager deletion', () => {
844
844
  goToProviders: vi.fn(),
845
845
  confirm: vi.fn(async () => true),
846
846
  } as unknown as ConstructorParameters<typeof ChatThreadManager>[0];
847
- const sessionListManager = {
848
- setSelectedSessionKey: vi.fn((value: string | null) => {
849
- useChatSessionListStore.getState().setSnapshot({
850
- selectedSessionKey: value,
851
- });
852
- }),
853
- } as unknown as ConstructorParameters<typeof ChatThreadManager>[1];
847
+ const sessionListManager = {} as ConstructorParameters<typeof ChatThreadManager>[1];
854
848
  const manager = new ChatThreadManager(
855
849
  uiManager,
856
850
  sessionListManager,
@@ -858,8 +852,7 @@ describe('ChatThreadManager deletion', () => {
858
852
 
859
853
  await manager.deleteSession();
860
854
 
861
- expect(sessionListManager.setSelectedSessionKey).toHaveBeenCalledWith(null);
862
- expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBeNull();
855
+ expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBe('parent-session-1');
863
856
  expect(useChatThreadStore.getState().snapshot).toMatchObject({
864
857
  sessionKey: null,
865
858
  canDeleteSession: false,
@@ -100,9 +100,8 @@ export class ChatSessionListManager {
100
100
  this.setSelectedAgentId(normalizedAgentId);
101
101
  };
102
102
 
103
- setSelectedSessionKey = (next: SetStateAction<string | null>) => {
103
+ private setSelectedSessionKey = (value: string | null) => {
104
104
  const prev = useChatSessionListStore.getState().snapshot.selectedSessionKey;
105
- const value = this.resolveUpdateValue(prev, next);
106
105
  if (value === prev) {
107
106
  return;
108
107
  }
@@ -111,24 +110,8 @@ export class ChatSessionListManager {
111
110
  .setSnapshot({ selectedSessionKey: value });
112
111
  };
113
112
 
114
- syncRouteSessionSelection = (params: {
115
- isChatView: boolean;
116
- routeSessionKey: string | null;
117
- }) => {
118
- const { isChatView, routeSessionKey } = params;
119
- if (!isChatView) {
120
- return;
121
- }
122
- const { selectedSessionKey } = useChatSessionListStore.getState().snapshot;
123
- if (routeSessionKey) {
124
- if (selectedSessionKey !== routeSessionKey) {
125
- this.setSelectedSessionKey(routeSessionKey);
126
- }
127
- return;
128
- }
129
- if (selectedSessionKey !== null) {
130
- this.setSelectedSessionKey(null);
131
- }
113
+ syncRouteSessionSelection = (routeSessionKey: string | null) => {
114
+ this.setSelectedSessionKey(routeSessionKey);
132
115
  };
133
116
 
134
117
  setListMode = (next: SetStateAction<"time-first" | "project-first">) => {
@@ -207,9 +190,6 @@ export class ChatSessionListManager {
207
190
  );
208
191
  const normalizedProjectRoot = normalizeSessionProjectRootValue(projectRoot);
209
192
  const normalizedPrompt = prompt?.trim() || null;
210
- useChatSessionListStore.getState().setSnapshot({
211
- selectedSessionKey: null,
212
- });
213
193
  this.syncDraftThreadState();
214
194
  this.uiManager.navigateTo(CHAT_DRAFT_SESSION_PATH, {
215
195
  replace: this.uiManager.isAtChatRoot(),
@@ -75,10 +75,7 @@ export class ChatThreadManager {
75
75
  useChatThreadStore.getState().setSnapshot(patch);
76
76
  };
77
77
 
78
- private clearDeletedSessionState = (sessionKey: string) => {
79
- if (useChatSessionListStore.getState().snapshot.selectedSessionKey === sessionKey) {
80
- this.sessionListManager.setSelectedSessionKey(null);
81
- }
78
+ private clearDeletedSessionState = () => {
82
79
  useChatThreadStore.getState().setSnapshot({
83
80
  sessionKey: null,
84
81
  sessionTypeLabel: null,
@@ -569,7 +566,7 @@ export class ChatThreadManager {
569
566
  await deleteNcpSessionApi(selectedSessionKey);
570
567
  deleteNcpSessionSummaryInQueryClient(appQueryClient, selectedSessionKey);
571
568
  appQueryClient.removeQueries({ queryKey: ['ncp-session-messages', selectedSessionKey] });
572
- this.clearDeletedSessionState(selectedSessionKey);
569
+ this.clearDeletedSessionState();
573
570
  this.uiManager.goToChatRoot({ replace: true });
574
571
  } finally {
575
572
  useChatThreadStore.getState().setSnapshot({ isDeletePending: false });
@@ -39,17 +39,11 @@ vi.mock('@/shared/hooks/use-confirm-dialog', () => ({
39
39
  vi.mock('@/features/chat/components/layout/chat-page-shell', () => ({
40
40
  ChatPageLayout: () => <div data-testid="chat-page-layout" />,
41
41
  useChatSessionSync: (params: {
42
- syncRouteSessionSelection: (value: {
43
- isChatView: boolean;
44
- routeSessionKey: string | null;
45
- }) => void;
42
+ routeSessionKey: string | null;
43
+ syncRouteSessionSelection: (routeSessionKey: string | null) => void;
46
44
  }) => {
47
45
  mocks.useChatSessionSync(params);
48
- const { syncRouteSessionSelection } = params;
49
- syncRouteSessionSelection({
50
- isChatView: true,
51
- routeSessionKey: null,
52
- });
46
+ params.syncRouteSessionSelection(params.routeSessionKey);
53
47
  },
54
48
  }));
55
49
 
@@ -76,7 +76,6 @@ function NcpChatPageContent({ view }: ChatPageProps) {
76
76
  sessionKey: sessionKey ?? null,
77
77
  });
78
78
  useChatSessionSync({
79
- view,
80
79
  routeSessionKey,
81
80
  syncRouteSessionSelection:
82
81
  presenter.chatSessionListManager.syncRouteSessionSelection,
@@ -44,6 +44,7 @@ export type ChatInputBarAdapterTexts = {
44
44
  modelSearchEmptyLabel: string;
45
45
  favoriteModelLabel: string;
46
46
  unfavoriteModelLabel: string;
47
+ manageModelsLabel: string;
47
48
  thinkingLabels: Record<ChatThinkingLevel, string>;
48
49
  noModelOptionsLabel: string;
49
50
  configureProviderLabel: string;
@@ -94,7 +94,7 @@ export const CronTaskComposer = forwardRef<
94
94
  ref={ref}
95
95
  value={value}
96
96
  onChange={(event) => onChange(event.target.value)}
97
- className="pl-9 pr-11 focus:border-border/75"
97
+ className="pl-9 pr-11"
98
98
  placeholder={t("cronComposerPlaceholder")}
99
99
  aria-label={t("cronComposerLabel")}
100
100
  />
@@ -37,7 +37,7 @@ export function FilterPanel({
37
37
  value={searchText}
38
38
  onChange={(event) => onSearchTextChange(event.target.value)}
39
39
  placeholder={searchPlaceholder}
40
- className="h-9 w-full rounded-xl border border-border/75 bg-card pl-9 pr-9 text-sm transition-colors focus:outline-none focus:ring-0 focus:border-border"
40
+ className="h-9 w-full rounded-xl border border-border/75 bg-card pl-9 pr-9 text-sm transition-colors focus:outline-none focus:ring-0"
41
41
  />
42
42
  {isRefreshing && (
43
43
  <Loader2
@@ -24,6 +24,9 @@ describe("AppNotificationToast", () => {
24
24
  name: "Open the background task reply",
25
25
  });
26
26
  expect(notification.getAttribute("href")).toBe("/chat/session-1");
27
+ expect(notification.className).not.toMatch(
28
+ /(?:hover|active):bg-\S*\/\d+/,
29
+ );
27
30
  expect(screen.getByText("Background task")).toBeTruthy();
28
31
  expect(screen.getByText("The requested summary is ready.")).toBeTruthy();
29
32
 
@@ -10,7 +10,7 @@ export type AppNotificationToastProps = {
10
10
  };
11
11
 
12
12
  const NOTIFICATION_CARD_CLASS =
13
- "group ml-auto flex min-h-[74px] w-[320px] max-w-[calc(100vw-2rem)] items-center gap-3 rounded-[20px] border border-border/80 bg-background px-[18px] py-3 text-left text-foreground shadow-[0_8px_18px_rgba(0,0,0,0.12),0_2px_5px_rgba(0,0,0,0.06)] transition-colors duration-150 hover:bg-muted/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background active:bg-muted/20 motion-reduce:transition-none";
13
+ "group ml-auto flex min-h-[74px] w-[320px] max-w-[calc(100vw-2rem)] items-center gap-3 rounded-[20px] border border-border/80 bg-background px-[18px] py-3 text-left text-foreground shadow-[0_8px_18px_rgba(0,0,0,0.12),0_2px_5px_rgba(0,0,0,0.06)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background";
14
14
 
15
15
  function AppNotificationContent({
16
16
  title,
@@ -118,7 +118,7 @@ function PanelAppMenuItem({
118
118
  type="button"
119
119
  className={cn(
120
120
  'flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50',
121
- destructive ? 'text-destructive hover:bg-destructive/10' : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
121
+ destructive ? 'text-destructive hover:bg-destructive/10' : 'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
122
122
  )}
123
123
  disabled={disabled}
124
124
  onClick={onClick}
@@ -17,7 +17,7 @@ export function PanelAppToolbar({
17
17
  <button
18
18
  type="button"
19
19
  onClick={onOpenApps}
20
- className="inline-flex shrink-0 items-center rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
20
+ className="inline-flex shrink-0 items-center rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground"
21
21
  title={t('appsTitle')}
22
22
  aria-label={t('appsTitle')}
23
23
  >
@@ -32,7 +32,7 @@ export function PanelAppToolbar({
32
32
  <button
33
33
  type="button"
34
34
  onClick={onRefresh}
35
- className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
35
+ className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground"
36
36
  title={t('panelAppsRefreshCurrent')}
37
37
  aria-label={t('panelAppsRefreshCurrent')}
38
38
  >
@@ -104,7 +104,7 @@ export function PanelAppsList({
104
104
  <TooltipProvider delayDuration={250}>
105
105
  <Tooltip>
106
106
  <TooltipTrigger asChild>
107
- <button type="button" className="rounded p-0.5 text-muted-foreground/70 transition-colors hover:bg-accent hover:text-accent-foreground" aria-label={t('panelAppsTitle')}><HelpCircle className="h-3.5 w-3.5" /></button>
107
+ <button type="button" className="rounded p-0.5 text-muted-foreground/70 transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground" aria-label={t('panelAppsTitle')}><HelpCircle className="h-3.5 w-3.5" /></button>
108
108
  </TooltipTrigger>
109
109
  <TooltipContent side="bottom" className="max-w-[320px] break-all font-mono text-xs">{panelApps.data.panelsPath}</TooltipContent>
110
110
  </Tooltip>
@@ -114,7 +114,7 @@ export function PanelAppsList({
114
114
  <button
115
115
  type="button"
116
116
  onClick={() => void panelApps.refetch()}
117
- className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
117
+ className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground"
118
118
  title={t('panelAppsRefresh')}
119
119
  aria-label={t('panelAppsRefresh')}
120
120
  >
@@ -204,7 +204,7 @@ function PanelAppsEmptyGuide({
204
204
  <button
205
205
  type="button"
206
206
  onClick={onRefresh}
207
- className="mt-4 inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
207
+ className="mt-4 inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
208
208
  >
209
209
  <RefreshCw className="h-3.5 w-3.5" />
210
210
  {t('panelAppsRefresh')}
@@ -245,7 +245,7 @@ function PanelAppsEmptyGuideStep({
245
245
  <button
246
246
  type="button"
247
247
  onClick={onAction}
248
- className="flex w-full gap-2 rounded-md bg-muted/60 px-3 py-2.5 text-left transition-colors hover:bg-accent/70 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
248
+ className="flex w-full gap-2 rounded-md bg-muted/60 px-3 py-2.5 text-left transition-colors hover:bg-[var(--interaction-hover)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
249
249
  >
250
250
  {content}
251
251
  </button>
@@ -463,7 +463,7 @@ function ServiceAppMenuItem({
463
463
  type="button"
464
464
  className={cn(
465
465
  'flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50',
466
- destructive ? 'text-destructive hover:bg-destructive/10' : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
466
+ destructive ? 'text-destructive hover:bg-destructive/10' : 'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
467
467
  )}
468
468
  disabled={disabled}
469
469
  onClick={onClick}