@nextclaw/ui 0.14.1-beta.1 → 0.14.2

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 (179) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/assets/{api-dQ6uQSZG.js → api-a14tR_z1.js} +7 -7
  3. package/dist/assets/channels-list-page-CFGkm4DT.js +8 -0
  4. package/dist/assets/chat-page-CEdavJXc.js +105 -0
  5. package/dist/assets/{config-split-page-CeFfbaqc.js → config-split-page-B5b9B0uE.js} +1 -1
  6. package/dist/assets/{confirm-dialog-C95Y4QPC.js → confirm-dialog-ClH_Ec7n.js} +1 -1
  7. package/dist/assets/{desktop-update-config-CcK6jiEb.js → desktop-update-config-DPNDnVpk.js} +1 -1
  8. package/dist/assets/{dist-DTGzeil6.js → dist-BtCS2Ucq.js} +1 -1
  9. package/dist/assets/{dist-fGogBmPA.js → dist-CLGGLm8b.js} +1 -1
  10. package/dist/assets/doc-browser-5nMWYKBD.js +1 -0
  11. package/dist/assets/doc-browser-BJRLhztv.js +1 -0
  12. package/dist/assets/doc-browser-context-D4JwMMxK.js +1 -0
  13. package/dist/assets/{ellipsis-D2F4KMiU.js → ellipsis-CtJXjIol.js} +1 -1
  14. package/dist/assets/{external-link-CcgtBK26.js → external-link-CdJiG13p.js} +1 -1
  15. package/dist/assets/index-BDN1zzMQ.js +105 -0
  16. package/dist/assets/index-g3uSZXoc.css +1 -0
  17. package/dist/assets/mcp-marketplace-page-BU0kIjhR.js +40 -0
  18. package/dist/assets/mcp-marketplace-page-sMNFSq_T.js +1 -0
  19. package/dist/assets/middleware-CkFT3R4v.js +1 -0
  20. package/dist/assets/model-config-page-D6hYXROd.js +1 -0
  21. package/dist/assets/plus-DPjb0piO.js +1 -0
  22. package/dist/assets/provider-scoped-model-input-DAX2HgmR.js +1 -0
  23. package/dist/assets/providers-config-page-BzAeaACA.js +1 -0
  24. package/dist/assets/{react-CTqdhFMk.js → react-C6cZgP44.js} +1 -1
  25. package/dist/assets/remote-B0G80uPr.js +1 -0
  26. package/dist/assets/rotate-cw-DnEB1Rju.js +1 -0
  27. package/dist/assets/runtime-config-page-C5xKaV3i.js +1 -0
  28. package/dist/assets/{save-C9fKWfVB.js → save-DKA0x4zk.js} +1 -1
  29. package/dist/assets/{search-D5FQ5q0X.js → search-BNlcoj5c.js} +1 -1
  30. package/dist/assets/{search-config-page-AMEJJNIS.js → search-config-page-Cq9uJ-N8.js} +1 -1
  31. package/dist/assets/{secrets-config-page-CcgJnvBO.js → secrets-config-page-CxSbZLBo.js} +2 -2
  32. package/dist/assets/{select-Cu5n0E-3.js → select-CvTlDZTQ.js} +1 -1
  33. package/dist/assets/{tag-chip-C5aOtjxu.js → tag-chip-8ffF2y2C.js} +1 -1
  34. package/dist/assets/use-config-DmT0o1gv.js +1 -0
  35. package/dist/assets/x-BiSSpgqB.js +1 -0
  36. package/dist/index.html +15 -14
  37. package/package.json +9 -9
  38. package/src/app/components/app-presenter-provider.tsx +11 -2
  39. package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
  40. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
  41. package/src/app/components/layout/app-layout.tsx +18 -4
  42. package/src/app/components/layout/sidebar-items.tsx +194 -71
  43. package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
  44. package/src/app/components/layout/sidebar.tsx +340 -83
  45. package/src/app/managers/viewport-layout.manager.ts +10 -0
  46. package/src/app/stores/viewport-layout.store.ts +39 -3
  47. package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
  48. package/src/features/agents/components/agents-page.tsx +10 -6
  49. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
  50. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
  51. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
  52. package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
  53. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +14 -15
  54. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +18 -18
  55. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
  56. package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
  57. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +77 -12
  58. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
  59. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +182 -21
  60. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +82 -25
  61. package/src/features/chat/components/layout/chat-sidebar.tsx +79 -93
  62. package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
  63. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +289 -0
  64. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +427 -0
  65. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +204 -0
  66. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
  67. package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +32 -17
  68. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
  69. package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
  70. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
  71. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
  72. package/src/features/chat/features/input/hooks/use-chat-model-favorites.ts +2 -1
  73. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
  74. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
  75. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
  76. package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
  77. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
  78. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
  79. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
  80. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
  81. package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
  82. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
  83. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
  84. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
  85. package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
  86. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
  87. package/src/features/chat/features/message/components/chat-message-list.container.tsx +93 -12
  88. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
  89. package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
  90. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
  91. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +5 -4
  92. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
  93. package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
  94. package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
  95. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
  96. package/src/features/chat/features/session-type/components/__tests__/{chat-sidebar-create-menu.test.tsx → chat-session-type-menu.test.tsx} +5 -5
  97. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +29 -1
  98. package/src/features/chat/features/session-type/components/{chat-sidebar-create-menu.tsx → chat-session-type-menu.tsx} +13 -9
  99. package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +17 -5
  100. package/src/features/chat/features/session-type/hooks/__tests__/use-chat-new-session-type-preference.test.tsx +152 -0
  101. package/src/features/chat/features/session-type/hooks/use-chat-new-session-type-preference.ts +159 -0
  102. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
  103. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -2
  104. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
  105. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +2 -2
  106. package/src/features/chat/features/welcome/components/chat-welcome.tsx +2 -2
  107. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
  108. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
  109. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  110. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
  111. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
  112. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
  113. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
  114. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
  115. package/src/features/chat/index.ts +0 -1
  116. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
  117. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
  118. package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
  119. package/src/features/chat/managers/chat-thread.manager.ts +108 -34
  120. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
  121. package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
  122. package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
  123. package/src/features/chat/presenters/chat.presenter.ts +2 -19
  124. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
  125. package/src/features/chat/stores/chat-thread.store.ts +42 -26
  126. package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +24 -0
  127. package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +8 -3
  128. package/src/features/marketplace/components/marketplace-item-list-view.tsx +2 -2
  129. package/src/features/marketplace/components/marketplace-list-card.tsx +124 -98
  130. package/src/features/marketplace/components/marketplace-page-parts.tsx +11 -6
  131. package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
  132. package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
  133. package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
  134. package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
  135. package/src/shared/components/common/__tests__/agent-avatar.test.tsx +3 -1
  136. package/src/shared/components/common/agent-avatar.tsx +5 -2
  137. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
  138. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
  139. package/src/shared/lib/api/index.ts +1 -0
  140. package/src/shared/lib/api/preferences/preference-keys.config.ts +9 -0
  141. package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
  142. package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
  143. package/src/shared/lib/i18n/locales/en-US/marketplace.json +2 -0
  144. package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
  145. package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
  146. package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +2 -0
  147. package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
  148. package/src/shared/lib/ui-document-title/index.ts +50 -21
  149. package/dist/assets/channels-list-page-DfpjB8XB.js +0 -8
  150. package/dist/assets/chat-page-D3QaEqjs.js +0 -105
  151. package/dist/assets/doc-browser-CuBTcBEZ.js +0 -1
  152. package/dist/assets/doc-browser-DVHhEb96.js +0 -1
  153. package/dist/assets/doc-browser-context-BfMXwnrz.js +0 -1
  154. package/dist/assets/index-ByJCbQqr.css +0 -1
  155. package/dist/assets/index-DjXqiloY.js +0 -103
  156. package/dist/assets/mcp-marketplace-page-98-V__3u.js +0 -1
  157. package/dist/assets/mcp-marketplace-page-BNq_PNmO.js +0 -40
  158. package/dist/assets/model-config-page-aAUkZkSX.js +0 -1
  159. package/dist/assets/plus-1Rxl5FlV.js +0 -1
  160. package/dist/assets/provider-scoped-model-input-we4_Z4gP.js +0 -1
  161. package/dist/assets/providers-config-page-CtMkPSyu.js +0 -1
  162. package/dist/assets/remote-fQ7N_r8u.js +0 -1
  163. package/dist/assets/rotate-cw-CzfIeAJj.js +0 -1
  164. package/dist/assets/runtime-config-page-TD1UrTbG.js +0 -1
  165. package/dist/assets/use-config-D8TESeo6.js +0 -1
  166. package/dist/assets/x-1qETdQ70.js +0 -1
  167. package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
  168. package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
  169. package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
  170. package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
  171. package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
  172. package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
  173. package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
  174. package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
  175. package/src/features/chat/managers/chat-input.manager.ts +0 -430
  176. package/src/features/chat/managers/chat-run.manager.ts +0 -110
  177. package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
  178. package/src/features/chat/stores/chat-input.store.ts +0 -104
  179. /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
@@ -11,41 +11,14 @@ import {
11
11
  } from "@/features/chat/components/layout/chat-page-shell";
12
12
  import { parseSessionKeyFromRoute } from "@/features/chat/features/session/utils/chat-session-route.utils";
13
13
  import { ChatPresenter } from "@/features/chat/presenters/chat.presenter";
14
- import {
15
- isNcpAgentStartupUnavailableErrorMessage,
16
- useNcpSessionConversation,
17
- } from "@/features/chat/features/ncp/hooks/use-ncp-session-conversation";
18
- import {
19
- useNcpChatSelectedSession,
20
- } from "@/features/chat/features/ncp/hooks/use-ncp-chat-derived-state";
21
14
  import { useChatQueryStoreSync } from "@/features/chat/features/ncp/hooks/use-ncp-chat-query-store-sync";
22
15
  import {
23
16
  ChatPresenterProvider,
24
17
  usePresenter,
25
18
  } from "@/features/chat/components/providers/chat-presenter.provider";
26
19
  import { useConfirmDialog } from "@/shared/hooks/use-confirm-dialog";
27
- import { useSystemStatus } from "@/features/system-status";
28
20
  import { useAppPresenter } from "@/app/components/app-presenter-provider";
29
- import { isNcpChatRuntimeBlocked, resolveNcpChatSendErrorMessage } from "@/features/chat/features/runtime/utils/ncp-chat-runtime-availability.utils";
30
21
  import { useUiShowContentEvent } from "@/features/chat/features/ncp/hooks/use-ui-show-content-event";
31
- import { readNcpContextWindowValue } from "@/features/chat/features/session/utils/ncp-session-context-metadata.utils";
32
-
33
- function useChatDraftIntentConsumer() {
34
- const appPresenter = useAppPresenter();
35
- const presenter = usePresenter();
36
- useEffect(() => {
37
- const applyIntent = (intent: { id: number; prompt: string }) => {
38
- presenter.startAgentCreationDraft(intent.prompt);
39
- appPresenter.chatDraftIntentManager.markConsumed(intent.id);
40
- };
41
- const unsubscribe = appPresenter.chatDraftIntentManager.subscribe(applyIntent);
42
- const pendingIntent = appPresenter.chatDraftIntentManager.consumePending();
43
- if (pendingIntent) {
44
- applyIntent(pendingIntent);
45
- }
46
- return unsubscribe;
47
- }, [appPresenter, presenter]);
48
- }
49
22
 
50
23
  function useNcpChatRouteSelection() {
51
24
  const { sessionId: routeSessionIdParam } = useParams<{ sessionId?: string }>();
@@ -59,31 +32,6 @@ function useNcpChatRouteSelection() {
59
32
  };
60
33
  }
61
34
 
62
- type NcpChatConversation = ReturnType<typeof useNcpSessionConversation>;
63
-
64
- function useChatRunRuntimeConnection(params: {
65
- agent: NcpChatConversation;
66
- sessionKey: string | undefined;
67
- }) {
68
- const presenter = usePresenter();
69
- const { agent, sessionKey } = params;
70
- useEffect(() => {
71
- presenter.chatRunManager.setActiveRuntime({
72
- sessionKey: sessionKey ?? null,
73
- sendEnvelope: (envelope) => agent.send(envelope),
74
- abortCurrentRun: () => agent.abort(),
75
- resumeCurrentSessionRun: () => agent.streamRun(),
76
- });
77
- return () => {
78
- presenter.chatRunManager.setActiveRuntime(null);
79
- };
80
- }, [
81
- agent,
82
- presenter,
83
- sessionKey,
84
- ]);
85
- }
86
-
87
35
  function useNcpChatUiBindings() {
88
36
  const presenter = usePresenter();
89
37
  const { confirm, ConfirmDialog } = useConfirmDialog();
@@ -101,42 +49,6 @@ function useNcpChatUiBindings() {
101
49
  return <ConfirmDialog />;
102
50
  }
103
51
 
104
- function useChatRunSnapshotSync(params: {
105
- agent: NcpChatConversation;
106
- isSending: boolean;
107
- isRunning: boolean;
108
- lastSendError: string | null;
109
- routeSessionKey: string | null;
110
- }) {
111
- const presenter = usePresenter();
112
- const { agent, isRunning, isSending, lastSendError, routeSessionKey } = params;
113
- useEffect(() => {
114
- presenter.chatRunManager.applyRunSnapshot({
115
- routeSessionKey,
116
- isHydrating: agent.isHydrating,
117
- isSending,
118
- isRunning,
119
- visibleMessages: agent.visibleMessages,
120
- contextWindow: readNcpContextWindowValue(agent.snapshot.contextWindow),
121
- sendErrorMessage: lastSendError,
122
- materializedSessionKey:
123
- agent.snapshot.activeRun?.sessionId ??
124
- agent.visibleMessages.find((message) => message.sessionId.trim())?.sessionId ??
125
- null,
126
- });
127
- }, [
128
- agent.isHydrating,
129
- agent.snapshot.activeRun?.sessionId,
130
- agent.snapshot.contextWindow,
131
- agent.visibleMessages,
132
- isRunning,
133
- isSending,
134
- lastSendError,
135
- presenter,
136
- routeSessionKey,
137
- ]);
138
- }
139
-
140
52
  export function NcpChatPage({ view }: ChatPageProps) {
141
53
  const appPresenter = useAppPresenter();
142
54
  const [presenter] = useState(() => new ChatPresenter(appPresenter));
@@ -149,52 +61,18 @@ export function NcpChatPage({ view }: ChatPageProps) {
149
61
 
150
62
  function NcpChatPageContent({ view }: ChatPageProps) {
151
63
  const presenter = usePresenter();
152
- const systemStatus = useSystemStatus();
153
- const isRuntimeBlocked = isNcpChatRuntimeBlocked(systemStatus);
154
64
  const confirmDialog = useNcpChatUiBindings();
155
65
  const routeSelection = useNcpChatRouteSelection();
156
66
  const { routeSessionKey, sessionKey } = routeSelection;
157
67
  useChatQueryStoreSync({
158
68
  sessionKey: sessionKey ?? null,
159
69
  });
160
- const selectedSession = useNcpChatSelectedSession(sessionKey ?? null);
161
- const agent = useNcpSessionConversation(sessionKey);
162
- const rawLastSendError =
163
- agent.hydrateError?.message ?? agent.snapshot.error?.message ?? null;
164
- const filteredLastSendError =
165
- systemStatus.phase === "ready" &&
166
- isNcpAgentStartupUnavailableErrorMessage(rawLastSendError)
167
- ? null
168
- : rawLastSendError;
169
- const currentSessionRunning = agent.isRunning || selectedSession?.status === "running";
170
- const isSending = agent.isSending || currentSessionRunning;
171
- const lastSendError =
172
- isRuntimeBlocked
173
- ? null
174
- : systemStatus.phase === "ready"
175
- ? filteredLastSendError
176
- : resolveNcpChatSendErrorMessage({
177
- message: filteredLastSendError,
178
- status: systemStatus,
179
- });
180
- useChatRunRuntimeConnection({
181
- agent,
182
- sessionKey,
183
- });
184
70
  useChatSessionSync({
185
71
  view,
186
72
  routeSessionKey,
187
73
  syncRouteSessionSelection:
188
74
  presenter.chatSessionListManager.syncRouteSessionSelection,
189
75
  });
190
- useChatDraftIntentConsumer();
191
76
  useUiShowContentEvent();
192
- useChatRunSnapshotSync({
193
- agent,
194
- isRunning: currentSessionRunning,
195
- routeSessionKey,
196
- lastSendError,
197
- isSending,
198
- });
199
77
  return <ChatPageLayout view={view} confirmDialog={confirmDialog} />;
200
78
  }
@@ -1,39 +1,22 @@
1
1
  import { ChatSessionListManager } from '@/features/chat/managers/chat-session-list.manager';
2
- import { ChatRunManager } from '@/features/chat/managers/chat-run.manager';
3
2
  import { ChatUiManager } from '@/features/chat/managers/chat-ui.manager';
4
- import { ChatInputManager } from '@/features/chat/managers/chat-input.manager';
5
3
  import { ChatQueryManager } from '@/features/chat/managers/chat-query.manager';
6
4
  import { ChatThreadManager } from '@/features/chat/managers/chat-thread.manager';
7
5
  import type { AppPresenter } from '@/app/presenters/app.presenter';
8
6
 
9
7
  export class ChatPresenter {
10
8
  readonly chatUiManager: ChatUiManager;
11
- readonly chatRunManager: ChatRunManager;
12
9
  readonly chatSessionListManager: ChatSessionListManager;
13
- readonly chatInputManager: ChatInputManager;
14
10
  readonly chatQueryManager: ChatQueryManager;
15
11
  readonly chatThreadManager: ChatThreadManager;
16
12
 
17
13
  constructor(appPresenter: AppPresenter) {
18
14
  this.chatUiManager = new ChatUiManager(appPresenter.docBrowserManager);
19
- this.chatRunManager = new ChatRunManager(this.chatUiManager);
20
- this.chatSessionListManager = new ChatSessionListManager(this.chatUiManager, this.chatRunManager);
15
+ this.chatSessionListManager = new ChatSessionListManager(this.chatUiManager);
21
16
  this.chatQueryManager = new ChatQueryManager();
22
- this.chatInputManager = new ChatInputManager(
23
- this.chatRunManager,
24
- this.chatSessionListManager
25
- );
26
17
  this.chatThreadManager = new ChatThreadManager(
27
18
  this.chatUiManager,
28
- this.chatSessionListManager,
29
- this.chatRunManager
19
+ this.chatSessionListManager
30
20
  );
31
21
  }
32
-
33
- startAgentCreationDraft = (prompt: string) => {
34
- this.chatSessionListManager.createSession();
35
- this.chatSessionListManager.setSelectedAgentId('main');
36
- this.chatInputManager.setDraft(prompt);
37
- this.chatInputManager.requestComposerFocusAtEnd();
38
- };
39
22
  }
@@ -22,6 +22,7 @@ function resetChatThreadStore() {
22
22
  activeWorkspacePanelKind: null,
23
23
  childSessionTabs: [],
24
24
  activeChildSessionKey: null,
25
+ activeSideChatDraft: null,
25
26
  workspaceFileTabs: [],
26
27
  activeWorkspaceFileKey: null,
27
28
  workspaceNavigationHistory: [],
@@ -85,6 +86,36 @@ describe('chat thread workspace panel persistence', () => {
85
86
  expect(persisted.state.snapshot.workspaceFileTabs[0].fullLines).toBeUndefined();
86
87
  });
87
88
 
89
+ it('does not persist an unmaterialized side chat draft', () => {
90
+ useChatThreadStore.getState().setSnapshot({
91
+ workspacePanelParentKey: 'session-1',
92
+ activeWorkspacePanelKind: 'side-chat-draft',
93
+ activeSideChatDraft: {
94
+ draftKey: 'draft-1',
95
+ parentSessionKey: 'session-1',
96
+ },
97
+ workspaceNavigationHistory: [
98
+ { kind: 'child-session', key: 'child-session-1' },
99
+ { kind: 'side-chat-draft', key: 'draft-1' },
100
+ ],
101
+ workspaceNavigationHistoryIndex: 1,
102
+ });
103
+
104
+ const persisted = JSON.parse(
105
+ window.localStorage.getItem(chatThreadWorkspaceStorageKey) ?? '{}',
106
+ );
107
+
108
+ expect(persisted.state.snapshot).toMatchObject({
109
+ workspacePanelParentKey: 'session-1',
110
+ activeWorkspacePanelKind: null,
111
+ workspaceNavigationHistory: [
112
+ { kind: 'child-session', key: 'child-session-1' },
113
+ ],
114
+ workspaceNavigationHistoryIndex: 0,
115
+ });
116
+ expect(persisted.state.snapshot.activeSideChatDraft).toBeUndefined();
117
+ });
118
+
88
119
  it('hydrates the workspace panel state and repairs stale active file keys', async () => {
89
120
  window.localStorage.setItem(
90
121
  chatThreadWorkspaceStorageKey,
@@ -14,6 +14,11 @@ export type ChatChildSessionTab = {
14
14
  agentId?: string | null;
15
15
  };
16
16
 
17
+ export type ChatWorkspaceSideChatDraft = {
18
+ draftKey: string;
19
+ parentSessionKey: string;
20
+ };
21
+
17
22
  export type ChatWorkspaceFileTab = {
18
23
  key: string;
19
24
  parentSessionKey: string | null;
@@ -32,17 +37,10 @@ export type ChatWorkspaceFileTab = {
32
37
  };
33
38
 
34
39
  export type ChatWorkspaceNavigationEntry =
35
- | {
36
- kind: 'child-session';
37
- key: string;
38
- }
39
- | {
40
- kind: 'file';
41
- key: string;
42
- }
43
- | {
44
- kind: 'cron';
45
- };
40
+ | { kind: 'child-session'; key: string }
41
+ | { kind: 'side-chat-draft'; key: string }
42
+ | { kind: 'file'; key: string }
43
+ | { kind: 'cron' };
46
44
 
47
45
  export type ChatThreadSnapshot = {
48
46
  sessionTypeLabel?: string | null;
@@ -63,9 +61,10 @@ export type ChatThreadSnapshot = {
63
61
  parentSessionKey?: string | null;
64
62
  parentSessionLabel?: string | null;
65
63
  workspacePanelParentKey?: string | null;
66
- activeWorkspacePanelKind?: "child-session" | "file" | "cron" | null;
64
+ activeWorkspacePanelKind?: "child-session" | "side-chat-draft" | "file" | "cron" | null;
67
65
  childSessionTabs: ChatChildSessionTab[];
68
66
  activeChildSessionKey?: string | null;
67
+ activeSideChatDraft?: ChatWorkspaceSideChatDraft | null;
69
68
  workspaceFileTabs: ChatWorkspaceFileTab[];
70
69
  activeWorkspaceFileKey?: string | null;
71
70
  workspaceNavigationHistory: ChatWorkspaceNavigationEntry[];
@@ -105,6 +104,8 @@ type PersistedChatWorkspaceSnapshot = Pick<
105
104
  | 'workspaceNavigationHistoryIndex'
106
105
  >;
107
106
 
107
+ type PersistedWorkspacePanelKind = 'child-session' | 'file' | 'cron';
108
+
108
109
  const initialSnapshot: ChatThreadSnapshot = {
109
110
  sessionTypeLabel: null,
110
111
  sessionTypeIcon: null,
@@ -127,6 +128,7 @@ const initialSnapshot: ChatThreadSnapshot = {
127
128
  activeWorkspacePanelKind: null,
128
129
  childSessionTabs: [],
129
130
  activeChildSessionKey: null,
131
+ activeSideChatDraft: null,
130
132
  workspaceFileTabs: [],
131
133
  activeWorkspaceFileKey: null,
132
134
  workspaceNavigationHistory: [],
@@ -158,7 +160,7 @@ function normalizeHistoryIndex(value: unknown, maxIndex: number): number {
158
160
 
159
161
  function isWorkspacePanelKind(
160
162
  value: unknown,
161
- ): value is NonNullable<ChatThreadSnapshot['activeWorkspacePanelKind']> {
163
+ ): value is PersistedWorkspacePanelKind {
162
164
  return value === 'child-session' || value === 'file' || value === 'cron';
163
165
  }
164
166
 
@@ -344,19 +346,33 @@ export const useChatThreadStore = create<ChatThreadStore>()(
344
346
  name: CHAT_THREAD_WORKSPACE_STORAGE_KEY,
345
347
  version: CHAT_THREAD_WORKSPACE_STORAGE_VERSION,
346
348
  storage: createJSONStorage(() => window.localStorage),
347
- partialize: (state): PersistedChatThreadStore => ({
348
- snapshot: {
349
- workspacePanelParentKey: state.snapshot.workspacePanelParentKey,
350
- activeWorkspacePanelKind: state.snapshot.activeWorkspacePanelKind,
351
- activeChildSessionKey: state.snapshot.activeChildSessionKey,
352
- workspaceFileTabs: state.snapshot.workspaceFileTabs
353
- .slice(-CHAT_THREAD_MAX_PERSISTED_WORKSPACE_FILE_TABS)
354
- .map(toPersistedWorkspaceFileTab),
355
- activeWorkspaceFileKey: state.snapshot.activeWorkspaceFileKey,
356
- workspaceNavigationHistory: state.snapshot.workspaceNavigationHistory,
357
- workspaceNavigationHistoryIndex: state.snapshot.workspaceNavigationHistoryIndex,
358
- }
359
- }),
349
+ partialize: (state): PersistedChatThreadStore => {
350
+ const workspaceNavigationHistory = state.snapshot.workspaceNavigationHistory
351
+ .filter((entry) => entry.kind !== 'side-chat-draft');
352
+ const workspaceNavigationHistoryIndex =
353
+ workspaceNavigationHistory.length > 0
354
+ ? Math.min(
355
+ state.snapshot.workspaceNavigationHistoryIndex,
356
+ workspaceNavigationHistory.length - 1,
357
+ )
358
+ : 0;
359
+ return {
360
+ snapshot: {
361
+ workspacePanelParentKey: state.snapshot.workspacePanelParentKey,
362
+ activeWorkspacePanelKind:
363
+ state.snapshot.activeWorkspacePanelKind === 'side-chat-draft'
364
+ ? null
365
+ : state.snapshot.activeWorkspacePanelKind,
366
+ activeChildSessionKey: state.snapshot.activeChildSessionKey,
367
+ workspaceFileTabs: state.snapshot.workspaceFileTabs
368
+ .slice(-CHAT_THREAD_MAX_PERSISTED_WORKSPACE_FILE_TABS)
369
+ .map(toPersistedWorkspaceFileTab),
370
+ activeWorkspaceFileKey: state.snapshot.activeWorkspaceFileKey,
371
+ workspaceNavigationHistory,
372
+ workspaceNavigationHistoryIndex,
373
+ }
374
+ };
375
+ },
360
376
  merge: (persistedState, currentState) => {
361
377
  const persistedSnapshot = isRecord(persistedState)
362
378
  ? persistedState.snapshot
@@ -236,6 +236,30 @@ describe("MarketplacePage", () => {
236
236
  expect(card?.textContent).not.toContain("Skill");
237
237
  });
238
238
 
239
+ it("hides redundant skill tags while keeping useful tags", () => {
240
+ mocks.itemsQuery = createItemsQuery({
241
+ data: {
242
+ total: 1,
243
+ page: 1,
244
+ pageSize: 20,
245
+ totalPages: 1,
246
+ sort: "relevance",
247
+ items: [
248
+ createMarketplaceItem({
249
+ tags: ["skill", "automation", "Skill", "ops"],
250
+ }),
251
+ ],
252
+ } satisfies MarketplaceListView,
253
+ });
254
+
255
+ render(<MarketplacePage forcedType="skills" />);
256
+
257
+ expect(screen.queryByText("skill")).toBeNull();
258
+ expect(screen.queryByText("Skill")).toBeNull();
259
+ expect(screen.getAllByText("automation").length).toBeGreaterThan(0);
260
+ expect(screen.getAllByText("ops").length).toBeGreaterThan(0);
261
+ });
262
+
239
263
  it("does not dim the loaded list during background refresh", () => {
240
264
  mocks.itemsQuery = createItemsQuery({
241
265
  data: {
@@ -46,7 +46,7 @@ export function SkillShelfCard(props: {
46
46
  layout === "rail" ? "w-[260px] shrink-0" : "w-full min-w-0",
47
47
  )}
48
48
  >
49
- <div>
49
+ <div className="flex min-h-0 flex-1 flex-col">
50
50
  <div className="mb-2.5 flex min-w-0 items-start gap-2.5">
51
51
  <MarketplaceItemIcon name={item.name} fallback={item.install.spec} />
52
52
  <div className="min-w-0 flex-1 pt-0.5">
@@ -97,17 +97,22 @@ function formatShelfMeta(item: MarketplaceItemSummary) {
97
97
  }
98
98
 
99
99
  function TagLine({ tags }: { tags: string[] }) {
100
- const visibleTags = tags.slice(0, 2);
100
+ const visibleTags = tags.filter(isVisibleShelfTag).slice(0, 2);
101
101
  if (visibleTags.length === 0) {
102
102
  return null;
103
103
  }
104
104
  return (
105
- <div className="mt-2 truncate text-[11px] font-medium text-gray-400">
105
+ <div className="mt-auto truncate pt-2 text-[11px] font-medium text-gray-400">
106
106
  {visibleTags.join(" / ")}
107
107
  </div>
108
108
  );
109
109
  }
110
110
 
111
+ function isVisibleShelfTag(tag: string) {
112
+ const normalized = tag.trim().toLowerCase();
113
+ return normalized.length > 0 && normalized !== "skill";
114
+ }
115
+
111
116
  function formatUpdatedAt(value: string) {
112
117
  const date = value.slice(0, 10);
113
118
  return date || value;
@@ -127,8 +127,8 @@ export function MarketplaceItemListView({
127
127
  <div
128
128
  data-testid={showSkeleton ? "marketplace-list-skeleton" : undefined}
129
129
  className={cn(
130
- "grid grid-cols-1 gap-3 lg:grid-cols-2 2xl:grid-cols-3",
131
- showSkeleton && "min-h-0 flex-1 auto-rows-[104px] content-start",
130
+ "grid grid-cols-1 gap-2.5 md:grid-cols-2 xl:grid-cols-3",
131
+ showSkeleton && "min-h-0 flex-1 auto-rows-[156px] content-start",
132
132
  )}
133
133
  >
134
134
  {showSkeleton && <MarketplaceListSkeleton count={36} />}