@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
@@ -7,7 +7,7 @@ import { type SessionContextView } from '@/features/chat/features/session/utils/
7
7
  import type { SessionRunStatus } from '@/features/chat/types/session-run-status.types';
8
8
  import { cn } from '@/shared/lib/utils';
9
9
  import { t } from '@/shared/lib/i18n';
10
- import { Check, GitBranch, Pencil, X } from 'lucide-react';
10
+ import { Check, GitBranch, Pencil, Pin, X } from 'lucide-react';
11
11
 
12
12
  type ChatSidebarSessionItemProps = {
13
13
  sessionKey: string;
@@ -15,6 +15,7 @@ type ChatSidebarSessionItemProps = {
15
15
  showUnreadDot: boolean;
16
16
  runStatus?: SessionRunStatus;
17
17
  context: SessionContextView;
18
+ isPinned: boolean;
18
19
  title: string;
19
20
  previewText: string;
20
21
  trailingText: string;
@@ -31,6 +32,7 @@ type ChatSidebarSessionItemProps = {
31
32
  onDraftLabelChange: (value: string) => void;
32
33
  onSave: () => void | Promise<void>;
33
34
  onCancel: () => void;
35
+ onTogglePinned: () => void;
34
36
  };
35
37
 
36
38
  type ChatSidebarSessionEditingViewProps = Pick<
@@ -72,7 +74,6 @@ function ChatSidebarSessionEditingView({
72
74
  icon={<Check className="h-3.5 w-3.5" />}
73
75
  label={t('save')}
74
76
  tooltip={false}
75
- className="h-7 w-7 rounded-lg text-gray-500 hover:bg-white hover:text-gray-900"
76
77
  onClick={() => void onSave()}
77
78
  disabled={isSaving}
78
79
  />
@@ -80,7 +81,6 @@ function ChatSidebarSessionEditingView({
80
81
  icon={<X className="h-3.5 w-3.5" />}
81
82
  label={t('cancel')}
82
83
  tooltip={false}
83
- className="h-7 w-7 rounded-lg text-gray-500 hover:bg-white hover:text-gray-900"
84
84
  onClick={onCancel}
85
85
  disabled={isSaving}
86
86
  />
@@ -100,6 +100,7 @@ function ChatSidebarSessionDisplayView({
100
100
  showUnreadDot,
101
101
  runStatus,
102
102
  context,
103
+ isPinned,
103
104
  title,
104
105
  previewText,
105
106
  trailingText,
@@ -109,14 +110,15 @@ function ChatSidebarSessionDisplayView({
109
110
  childSessionCount = 0,
110
111
  onSelect,
111
112
  onOpenChildSessions,
112
- onStartEditing
113
+ onStartEditing,
114
+ onTogglePinned
113
115
  }: ChatSidebarSessionDisplayViewProps) {
114
- const trailingControlsClassName = childSessionCount > 0 && onOpenChildSessions ? 'pr-14' : 'pr-6';
116
+ const trailingControlsClassName = childSessionCount > 0 && onOpenChildSessions ? 'pr-20' : 'pr-12';
115
117
 
116
118
  return (
117
119
  <div className="group/session relative">
118
120
  <button type="button" onClick={onSelect} className="w-full text-left">
119
- <div className={cn('flex min-w-0 items-start', trailingControlsClassName)}>
121
+ <div className={cn('flex min-h-6 min-w-0 items-center', trailingControlsClassName)}>
120
122
  <span className="flex min-w-0 items-center gap-1.5">
121
123
  {agentId?.trim() && agentId.trim().toLowerCase() !== 'main' ? (
122
124
  <AgentAvatar
@@ -126,7 +128,7 @@ function ChatSidebarSessionDisplayView({
126
128
  className="h-5 w-5 shrink-0"
127
129
  />
128
130
  ) : null}
129
- <span className="truncate font-medium">{title}</span>
131
+ <span className="truncate leading-6 font-medium">{title}</span>
130
132
  {context.label ? (
131
133
  <span
132
134
  className={cn(
@@ -140,7 +142,7 @@ function ChatSidebarSessionDisplayView({
140
142
  </span>
141
143
  ) : null}
142
144
  {context.icon ? (
143
- <span className="inline-flex h-[1.125rem] w-[1.125rem] shrink-0 items-center justify-center">
145
+ <span className="inline-flex h-5 w-5 shrink-0 items-center justify-center">
144
146
  <SessionContextIconNode icon={context.icon} className={active ? 'text-gray-700' : 'text-gray-500'} />
145
147
  </span>
146
148
  ) : null}
@@ -168,7 +170,7 @@ function ChatSidebarSessionDisplayView({
168
170
  onOpenChildSessions();
169
171
  }}
170
172
  className={cn(
171
- 'absolute right-6 top-0 inline-flex h-5 items-center gap-1 rounded-md px-1.5 text-[10px] font-medium text-gray-400 transition-all hover:bg-white hover:text-gray-900',
173
+ 'absolute right-12 top-0 inline-flex h-6 items-center gap-1 rounded-md px-1.5 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-black/10 hover:text-foreground',
172
174
  active
173
175
  ? 'opacity-100'
174
176
  : 'opacity-0 group-hover/session:opacity-100 group-focus-within/session:opacity-100'
@@ -181,23 +183,41 @@ function ChatSidebarSessionDisplayView({
181
183
  </button>
182
184
  ) : null}
183
185
  {runStatus ? (
184
- <span className="absolute right-0 top-0 inline-flex h-5 w-5 items-center justify-center transition-opacity group-hover/session:opacity-0 group-focus-within/session:opacity-0">
186
+ <span className="absolute right-0 top-0 inline-flex h-6 w-6 items-center justify-center transition-opacity group-hover/session:opacity-0 group-focus-within/session:opacity-0">
185
187
  <SessionRunBadge status={runStatus} />
186
188
  </span>
187
189
  ) : null}
188
- <IconActionButton
189
- icon={<Pencil className="h-3 w-3" />}
190
- label={t('edit')}
191
- tooltip={false}
192
- onClick={(event) => {
193
- event.stopPropagation();
194
- onStartEditing();
195
- }}
196
- className={cn(
197
- 'absolute right-0 top-0 inline-flex h-5 w-5 items-center justify-center rounded-md text-gray-400 transition-all hover:bg-white hover:text-gray-900',
198
- 'opacity-0 group-hover/session:opacity-100 group-focus-within/session:opacity-100'
199
- )}
200
- />
190
+ <div className="absolute right-0 top-0 flex h-6 items-center gap-0.5 opacity-0 transition-opacity group-hover/session:opacity-100 group-focus-within/session:opacity-100">
191
+ <IconActionButton
192
+ size="sm"
193
+ tone="strong"
194
+ icon={
195
+ <Pin
196
+ className={cn(
197
+ 'h-3.5 w-3.5',
198
+ isPinned && 'fill-current text-foreground',
199
+ )}
200
+ />
201
+ }
202
+ label={t(isPinned ? 'chatSidebarUnpinSession' : 'chatSidebarPinSession')}
203
+ tooltipSide="right"
204
+ onClick={(event) => {
205
+ event.stopPropagation();
206
+ onTogglePinned();
207
+ }}
208
+ />
209
+ <IconActionButton
210
+ size="sm"
211
+ tone="strong"
212
+ icon={<Pencil className="h-3.5 w-3.5" />}
213
+ label={t('edit')}
214
+ tooltipSide="right"
215
+ onClick={(event) => {
216
+ event.stopPropagation();
217
+ onStartEditing();
218
+ }}
219
+ />
220
+ </div>
201
221
  </div>
202
222
  );
203
223
  }
@@ -208,6 +228,7 @@ export function ChatSidebarSessionItem({
208
228
  showUnreadDot,
209
229
  runStatus,
210
230
  context,
231
+ isPinned,
211
232
  title,
212
233
  previewText,
213
234
  trailingText,
@@ -223,7 +244,8 @@ export function ChatSidebarSessionItem({
223
244
  onStartEditing,
224
245
  onDraftLabelChange,
225
246
  onSave,
226
- onCancel
247
+ onCancel,
248
+ onTogglePinned
227
249
  }: ChatSidebarSessionItemProps) {
228
250
  return (
229
251
  <div
@@ -249,6 +271,7 @@ export function ChatSidebarSessionItem({
249
271
  showUnreadDot={showUnreadDot}
250
272
  runStatus={runStatus}
251
273
  context={context}
274
+ isPinned={isPinned}
252
275
  title={title}
253
276
  previewText={previewText}
254
277
  trailingText={trailingText}
@@ -259,6 +282,7 @@ export function ChatSidebarSessionItem({
259
282
  childSessionCount={childSessionCount}
260
283
  onOpenChildSessions={onOpenChildSessions}
261
284
  onStartEditing={onStartEditing}
285
+ onTogglePinned={onTogglePinned}
262
286
  />
263
287
  )}
264
288
  </div>
@@ -19,7 +19,6 @@ type ChatSidebarSessionListProps = {
19
19
  defaultSessionType: string;
20
20
  sessionTypeOptions: SessionTypeOption[];
21
21
  renderSessionItem: (item: NcpSessionListItemView) => ReactNode;
22
- onCreateSession: (sessionType: string, projectRoot?: string | null) => void;
23
22
  };
24
23
 
25
24
  function ChatSidebarEmptyState({ label }: { label: string }) {
@@ -36,7 +35,6 @@ export function ChatSidebarSessionList({
36
35
  groups,
37
36
  isLoading,
38
37
  isProjectFirstView,
39
- onCreateSession,
40
38
  projectGroups,
41
39
  renderSessionItem,
42
40
  sessionTypeOptions,
@@ -54,7 +52,6 @@ export function ChatSidebarSessionList({
54
52
  defaultSessionType={defaultSessionType}
55
53
  sessionTypeOptions={sessionTypeOptions}
56
54
  renderSessionItem={renderSessionItem}
57
- onCreateSession={onCreateSession}
58
55
  />
59
56
  );
60
57
  }
@@ -9,7 +9,6 @@ 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 shrink-0 rounded-lg text-muted-foreground/70 hover:bg-accent hover:text-accent-foreground';
13
12
 
14
13
  type ChatSessionHeaderActionsProps = {
15
14
  sessionKey: string;
@@ -65,7 +64,6 @@ export function ChatSessionHeaderActions({
65
64
  <IconActionButton
66
65
  icon={<GitBranch className="h-4 w-4" />}
67
66
  label={t('chatSessionOpenChildSessions')}
68
- className={SESSION_HEADER_ACTION_BUTTON_CLASS}
69
67
  onClick={onOpenChildSessions}
70
68
  disabled={isBusy}
71
69
  />
@@ -74,7 +72,6 @@ export function ChatSessionHeaderActions({
74
72
  <IconActionButton
75
73
  icon={<AlarmClock className="h-4 w-4" />}
76
74
  label={t('chatSessionOpenCronJobs')}
77
- className={SESSION_HEADER_ACTION_BUTTON_CLASS}
78
75
  onClick={onOpenSessionCronJobs}
79
76
  disabled={isBusy}
80
77
  />
@@ -85,7 +82,6 @@ export function ChatSessionHeaderActions({
85
82
  icon={<MoreVertical className="h-4 w-4" />}
86
83
  label={t('chatSessionMoreActions')}
87
84
  tooltip={false}
88
- className={SESSION_HEADER_ACTION_BUTTON_CLASS}
89
85
  disabled={isBusy}
90
86
  />
91
87
  </PopoverTrigger>
@@ -40,7 +40,7 @@ import { t } from "@/shared/lib/i18n";
40
40
  import { cn } from "@/shared/lib/utils";
41
41
 
42
42
  const SWITCHER_TRIGGER_CLASS =
43
- "group inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-md px-1.5 py-1 text-sm font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40";
43
+ "group inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-md px-1.5 py-1 text-sm font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border";
44
44
 
45
45
  function ChatSessionTitle({ title }: { title: string }) {
46
46
  return (
@@ -111,7 +111,7 @@ function ChatSessionSwitchItem({
111
111
  type="button"
112
112
  aria-current={active ? "page" : undefined}
113
113
  className={cn(
114
- "flex w-full min-w-0 items-start gap-2 rounded-lg px-2.5 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40",
114
+ "flex w-full min-w-0 items-start gap-2 rounded-lg px-2.5 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
115
115
  active
116
116
  ? "bg-accent text-accent-foreground"
117
117
  : "text-popover-foreground hover:bg-accent/70",
@@ -13,6 +13,7 @@ export type ChatSidebarProjectGroup = {
13
13
  projectName: string;
14
14
  items: NcpSessionListItemView[];
15
15
  latestUpdatedAt: number;
16
+ isPinned: boolean;
16
17
  };
17
18
 
18
19
  export function getSessionActivityAtTimestamp(
@@ -33,8 +34,19 @@ export function sortSessionItemsByActivityAtDesc(
33
34
  );
34
35
  }
35
36
 
37
+ function sortSessionItemsByPinnedActivityAtDesc(
38
+ items: NcpSessionListItemView[],
39
+ pinnedSessionKeys: ReadonlySet<string>,
40
+ ): NcpSessionListItemView[] {
41
+ return [...items].sort((left, right) => {
42
+ const pinnedDifference = Number(pinnedSessionKeys.has(right.session.key)) - Number(pinnedSessionKeys.has(left.session.key));
43
+ return pinnedDifference || getSessionActivityAtTimestamp(right) - getSessionActivityAtTimestamp(left);
44
+ });
45
+ }
46
+
36
47
  export function groupSessionsByDate(
37
48
  items: NcpSessionListItemView[],
49
+ pinnedSessionKeys: ReadonlySet<string> = new Set(),
38
50
  ): ChatSidebarDateGroup[] {
39
51
  const now = new Date();
40
52
  const todayStart = new Date(
@@ -50,7 +62,13 @@ export function groupSessionsByDate(
50
62
  const previous7: NcpSessionListItemView[] = [];
51
63
  const older: NcpSessionListItemView[] = [];
52
64
 
53
- for (const item of items) {
65
+ const unpinnedItems = items.filter((item) => !pinnedSessionKeys.has(item.session.key));
66
+ const pinnedItems = sortSessionItemsByPinnedActivityAtDesc(
67
+ items.filter((item) => pinnedSessionKeys.has(item.session.key)),
68
+ pinnedSessionKeys,
69
+ );
70
+
71
+ for (const item of unpinnedItems) {
54
72
  const ts = getSessionActivityAtTimestamp(item);
55
73
  if (ts >= todayStart) {
56
74
  today.push(item);
@@ -64,6 +82,7 @@ export function groupSessionsByDate(
64
82
  }
65
83
 
66
84
  const groups: ChatSidebarDateGroup[] = [];
85
+ if (pinnedItems.length > 0) groups.push({ label: t("chatSidebarPinned"), items: pinnedItems });
67
86
  if (today.length > 0) groups.push({ label: t("chatSidebarToday"), items: today });
68
87
  if (yesterday.length > 0)
69
88
  groups.push({ label: t("chatSidebarYesterday"), items: yesterday });
@@ -75,6 +94,8 @@ export function groupSessionsByDate(
75
94
 
76
95
  export function groupSessionsByProject(
77
96
  items: NcpSessionListItemView[],
97
+ pinnedSessionKeys: ReadonlySet<string> = new Set(),
98
+ pinnedProjectRoots: ReadonlySet<string> = new Set(),
78
99
  ): ChatSidebarProjectGroup[] {
79
100
  const grouped = new Map<string, ChatSidebarProjectGroup>();
80
101
 
@@ -101,15 +122,16 @@ export function groupSessionsByProject(
101
122
  projectRoot,
102
123
  items: [item],
103
124
  latestUpdatedAt: updatedAt,
125
+ isPinned: pinnedProjectRoots.has(projectRoot),
104
126
  });
105
127
  }
106
128
 
107
129
  return [...grouped.values()]
108
130
  .map((group) => ({
109
131
  ...group,
110
- items: sortSessionItemsByActivityAtDesc(group.items),
132
+ items: sortSessionItemsByPinnedActivityAtDesc(group.items, pinnedSessionKeys),
111
133
  }))
112
- .sort((left, right) => right.latestUpdatedAt - left.latestUpdatedAt);
134
+ .sort((left, right) => Number(right.isPinned) - Number(left.isPinned) || right.latestUpdatedAt - left.latestUpdatedAt);
113
135
  }
114
136
 
115
137
  export function groupChildSessionsByParentKey(
@@ -1,4 +1,5 @@
1
1
  import { AlarmClock, BrainCircuit, MessageCircle } from 'lucide-react';
2
+ import { cn } from '@/shared/lib/utils';
2
3
  import { t } from '@/shared/lib/i18n';
3
4
 
4
5
  const WELCOME_CAPABILITIES = [
@@ -34,13 +35,17 @@ export function ChatWelcomeCapabilityGrid({
34
35
  return (
35
36
  <button
36
37
  key={capability.titleKey}
38
+ type="button"
37
39
  onClick={() => onSelectPrompt(t(capability.promptKey))}
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"
40
+ className={cn(
41
+ 'min-w-0 rounded-xl border border-border/75 bg-card p-3 text-left text-card-foreground shadow-none transition-colors sm:p-3.5',
42
+ 'hover:bg-muted/40',
43
+ )}
39
44
  >
40
- <div className="mb-3 flex h-9 w-9 items-center justify-center rounded-xl bg-accent">
41
- <Icon className="h-4.5 w-4.5 text-primary" />
45
+ <div className="mb-2.5 flex h-8 w-8 items-center justify-center rounded-lg bg-muted text-muted-foreground">
46
+ <Icon className="h-4 w-4" />
42
47
  </div>
43
- <div className="mb-1 text-sm font-semibold text-foreground">
48
+ <div className="mb-1 text-sm font-medium text-foreground">
44
49
  {t(capability.titleKey)}
45
50
  </div>
46
51
  <div className="text-[11px] leading-relaxed text-muted-foreground">
@@ -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-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground 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-1 focus-visible:ring-border"
53
53
  title={projectRoot ?? undefined}
54
54
  aria-label={t('chatWelcomeProjectPickerLabel')}
55
55
  disabled={!selectable || isSaving}
@@ -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-2 focus-visible:ring-primary/25"
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"
43
43
  aria-label={t('chatWelcomeSessionTypePickerLabel')}
44
44
  >
45
45
  {selectedOption.icon?.src ? (
@@ -1,6 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import { useState } from 'react';
3
- import { Bot } from 'lucide-react';
4
3
  import { ChatSessionProjectDialog } from '@/features/chat/features/session/components/session-header/chat-session-project-dialog';
5
4
  import { ChatWelcomeAgentPicker } from '@/features/chat/features/welcome/components/chat-welcome-agent-picker';
6
5
  import { ChatWelcomeCapabilityGrid } from '@/features/chat/features/welcome/components/chat-welcome-capability-grid';
@@ -64,10 +63,6 @@ export function ChatWelcome({
64
63
  return (
65
64
  <div className="flex min-h-full items-center justify-center px-4 py-8 sm:p-8">
66
65
  <div className="min-w-0 w-full max-w-[min(760px,100%)]">
67
- <div className="mx-auto mb-5 flex h-14 w-14 items-center justify-center rounded-2xl border border-border bg-accent shadow-card">
68
- <Bot className="h-8 w-8 text-primary" />
69
- </div>
70
-
71
66
  <div className="text-center">
72
67
  <h2 className="text-2xl font-semibold text-foreground sm:text-3xl">
73
68
  {t('chatWelcomeTitle')}