@nextclaw/ui 0.20.3 → 0.22.0
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.
- package/CHANGELOG.md +45 -0
- package/dist/assets/{api-iMkU_o5P.js → api-m33O1Kb0.js} +6 -6
- package/dist/assets/{appearance-settings-page-CU5k4b-M.js → appearance-settings-page-x4Vjnfmz.js} +1 -1
- package/dist/assets/{book-open-nD8gdMhA.js → book-open-BHiasMCg.js} +1 -1
- package/dist/assets/{channels-list-page-Drlt62kY.js → channels-list-page-DOxJZeXq.js} +2 -2
- package/dist/assets/{config-split-page-BtJZp1E5.js → config-split-page-DBnMEP1o.js} +1 -1
- package/dist/assets/{confirm-dialog-k2-Jq_x3.js → confirm-dialog-f5NKLh5I.js} +2 -2
- package/dist/assets/{createLucideIcon-DxlwKLr-.js → createLucideIcon-CHALGA42.js} +1 -1
- package/dist/assets/desktop-capabilities-page-DixayAe6.js +1 -0
- package/dist/assets/desktop-update-config-ChhfeOHj.js +1 -0
- package/dist/assets/{dist-Cy_e4kNb.js → dist-_UMsWSY0.js} +1 -1
- package/dist/assets/{dist-m08DUswB.js → dist-sD6V4nHX.js} +1 -1
- package/dist/assets/doc-browser-Bq7LJsQY.js +1 -0
- package/dist/assets/doc-browser-BuDgpY2e.js +1 -0
- package/dist/assets/{doc-browser-context-jiy-O4F8.js → doc-browser-context-Capim3og.js} +1 -1
- package/dist/assets/{extensions-CIb17__o.js → extensions-Bdclr3Wr.js} +1 -1
- package/dist/assets/{form-actions-DGf5zUg_.js → form-actions-CH0kWhEt.js} +1 -1
- package/dist/assets/index-BjxnLYE3.js +109 -0
- package/dist/assets/index-CBmwJjwE.css +1 -0
- package/dist/assets/loader-circle-CH_0iPLy.js +1 -0
- package/dist/assets/mcp-marketplace-page-BUsxLVB5.js +10 -0
- package/dist/assets/mcp-marketplace-page-GPRV3fB0.js +1 -0
- package/dist/assets/model-config-page-CIevwqp4.js +1 -0
- package/dist/assets/{navigation-link-DAfwFCj4.js → navigation-link-CAUiM3zS.js} +1 -1
- package/dist/assets/plus-B3obD55V.js +1 -0
- package/dist/assets/privacy-settings-page-CNKfZsYH.js +1 -0
- package/dist/assets/providers-config-page-DAah2NIS.js +1 -0
- package/dist/assets/react-Co6JiC1n.js +8 -0
- package/dist/assets/{refresh-cw-D_Eibznw.js → refresh-cw-BG_LX3SS.js} +1 -1
- package/dist/assets/remote-DuscgfvR.js +1 -0
- package/dist/assets/{rotate-cw-QQjmoWuq.js → rotate-cw-Cz7I_m7O.js} +1 -1
- package/dist/assets/runtime-config-page-Cl1RpmSC.js +1 -0
- package/dist/assets/{save-W2xthJJ2.js → save-BiHnb8ps.js} +1 -1
- package/dist/assets/{search-Vih1R2BD.js → search-C47TyRg0.js} +1 -1
- package/dist/assets/{search-config-page-CtBcL2uc.js → search-config-page-BAIzd6gN.js} +1 -1
- package/dist/assets/{secrets-config-page-Q6XfIj0h.js → secrets-config-page-Ddi-n43R.js} +2 -2
- package/dist/assets/security-config-C8MHnE1r.js +1 -0
- package/dist/assets/{select-lNdozU4o.js → select-BzQKfW1e.js} +2 -2
- package/dist/assets/skeleton-BXFAE9SE.js +1 -0
- package/dist/assets/{tag-chip-gdaU9bE7.js → tag-chip-C3K814HZ.js} +1 -1
- package/dist/assets/x-C1r9IXet.js +1 -0
- package/dist/index.html +29 -21
- package/dist/themes/island/island-atmosphere.webp +0 -0
- package/index.html +8 -0
- package/package.json +8 -8
- package/public/themes/island/island-atmosphere.webp +0 -0
- package/src/app/components/layout/header.tsx +4 -1
- package/src/app/components/layout/page-layout.tsx +4 -1
- package/src/app/components/layout/sidebar.tsx +6 -1
- package/src/app/configs/app-navigation.config.ts +17 -2
- package/src/app/hooks/use-feature-controls.ts +11 -0
- package/src/app/index.tsx +10 -0
- package/src/app/styles/island-theme.css +542 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +1 -0
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +5 -1
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +4 -1
- package/src/features/chat/components/layout/__tests__/chat-sidebar-toolbar.test.tsx +10 -0
- package/src/features/chat/components/layout/chat-page-shell.tsx +4 -1
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +2 -0
- package/src/features/chat/components/layout/chat-sidebar.tsx +2 -0
- package/src/features/chat/features/session/components/__tests__/chat-sidebar-session-item.test.tsx +12 -0
- package/src/features/chat/features/session/components/chat-sidebar-session-entry.tsx +3 -0
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +14 -1
- package/src/features/chat/features/session/utils/chat-session-deletion.utils.ts +25 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +1 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +1 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +1 -0
- package/src/features/chat/managers/__tests__/chat-thread-deletion.manager.test.ts +125 -0
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +0 -37
- package/src/features/chat/managers/chat-thread.manager.ts +17 -28
- package/src/features/desktop-capabilities/components/desktop-authorization-dialog.test.tsx +58 -0
- package/src/features/desktop-capabilities/components/desktop-authorization-dialog.tsx +161 -0
- package/src/features/desktop-capabilities/hooks/use-desktop-capabilities.test.ts +10 -0
- package/src/features/desktop-capabilities/hooks/use-desktop-capabilities.ts +55 -0
- package/src/features/desktop-capabilities/index.ts +1 -0
- package/src/features/desktop-capabilities/managers/desktop-capability.manager.ts +25 -0
- package/src/features/desktop-capabilities/pages/desktop-capabilities-page.test.tsx +44 -0
- package/src/features/desktop-capabilities/pages/desktop-capabilities-page.tsx +171 -0
- package/src/features/desktop-capabilities/stores/desktop-authorization.store.ts +21 -0
- package/src/features/marketplace/components/mcp/__tests__/mcp-connection-dialog.test.tsx +23 -0
- package/src/features/marketplace/components/mcp/mcp-connection-dialog.tsx +35 -0
- package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +20 -2
- package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +9 -0
- package/src/features/pwa/managers/pwa-shell-theme.manager.ts +1 -0
- package/src/features/service-apps/components/__tests__/service-apps-panel.test.tsx +35 -1
- package/src/features/service-apps/components/service-action-row.tsx +130 -0
- package/src/features/service-apps/components/service-app-list-item.tsx +11 -66
- package/src/features/service-apps/components/service-apps-panel.tsx +12 -0
- package/src/features/service-apps/hooks/use-service-apps.ts +20 -1
- package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +2 -2
- package/src/features/side-dock/components/side-dock.tsx +1 -0
- package/src/index.css +1 -0
- package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +8 -0
- package/src/platforms/desktop/components/desktop-app-shell.tsx +56 -2
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +1 -0
- package/src/platforms/mobile/components/mobile-settings-shell.tsx +5 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +3 -0
- package/src/shared/components/doc-browser/doc-browser.tsx +2 -0
- package/src/shared/components/ui/__tests__/confirm-dialog.test.tsx +31 -0
- package/src/shared/components/ui/confirm-dialog.tsx +11 -1
- package/src/shared/components/ui/context-menu/context-menu.tsx +1 -0
- package/src/shared/components/ui/dialog.tsx +2 -0
- package/src/shared/components/ui/overlays/sheet.tsx +2 -0
- package/src/shared/components/ui/popover.tsx +1 -0
- package/src/shared/components/ui/select.tsx +1 -0
- package/src/shared/components/ui/tooltip.tsx +1 -0
- package/src/shared/lib/api/index.ts +1 -0
- package/src/shared/lib/api/utils/mcp-connection.utils.ts +10 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +2 -0
- package/src/shared/lib/i18n/locales/en-US/core.json +44 -0
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +1 -0
- package/src/shared/lib/i18n/locales/en-US/marketplace.json +29 -1
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +2 -0
- package/src/shared/lib/i18n/locales/zh-CN/core.json +44 -0
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +1 -0
- package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +29 -1
- package/src/shared/lib/theme/index.test.ts +9 -0
- package/src/shared/lib/theme/index.ts +2 -0
- package/dist/assets/desktop-update-config-BiZmy7FC.js +0 -1
- package/dist/assets/doc-browser-CPsPHh12.js +0 -1
- package/dist/assets/doc-browser-npOvWtgf.js +0 -1
- package/dist/assets/index-C86hxgv5.css +0 -1
- package/dist/assets/index-CYBXR7zN.js +0 -109
- package/dist/assets/loader-circle-BlLuRFVW.js +0 -1
- package/dist/assets/mcp-marketplace-page-B12JNh7Q.js +0 -1
- package/dist/assets/mcp-marketplace-page-kLWW6kzt.js +0 -9
- package/dist/assets/model-config-page-DSeAjVT_.js +0 -1
- package/dist/assets/plus-D4LtWtTk.js +0 -1
- package/dist/assets/privacy-settings-page-tN0Mt15x.js +0 -1
- package/dist/assets/providers-config-page-CgPrXR1u.js +0 -1
- package/dist/assets/react-8iBFECZE.js +0 -8
- package/dist/assets/remote-Derr_AQp.js +0 -1
- package/dist/assets/runtime-config-page-Crq96Jjq.js +0 -1
- package/dist/assets/security-config-CE9rkQvq.js +0 -1
- package/dist/assets/skeleton-B__aTcly.js +0 -1
- package/dist/assets/x-CWdWBu_U.js +0 -1
|
@@ -40,6 +40,11 @@ describe("ChatSidebarToolbar", () => {
|
|
|
40
40
|
expect(
|
|
41
41
|
screen.getByPlaceholderText("Search conversations...").className,
|
|
42
42
|
).toContain("border-0");
|
|
43
|
+
expect(
|
|
44
|
+
screen
|
|
45
|
+
.getByPlaceholderText("Search conversations...")
|
|
46
|
+
.getAttribute("data-theme-control"),
|
|
47
|
+
).toBe("chat-search");
|
|
43
48
|
});
|
|
44
49
|
|
|
45
50
|
it("keeps mobile search icon transparent to pointer input", () => {
|
|
@@ -49,5 +54,10 @@ describe("ChatSidebarToolbar", () => {
|
|
|
49
54
|
expect(
|
|
50
55
|
screen.getByPlaceholderText("Search conversations...").className,
|
|
51
56
|
).toContain("border-0");
|
|
57
|
+
expect(
|
|
58
|
+
screen
|
|
59
|
+
.getByPlaceholderText("Search conversations...")
|
|
60
|
+
.getAttribute("data-theme-control"),
|
|
61
|
+
).toBe("chat-search");
|
|
52
62
|
});
|
|
53
63
|
});
|
|
@@ -53,7 +53,10 @@ export function ChatPageLayout({ view, confirmDialog }: ChatPageLayoutProps) {
|
|
|
53
53
|
{view === "chat" ? (
|
|
54
54
|
isMobile ? <ChatMobileShell /> : <ChatConversationPanel />
|
|
55
55
|
) : (
|
|
56
|
-
<section
|
|
56
|
+
<section
|
|
57
|
+
data-theme-surface="workspace"
|
|
58
|
+
className="flex-1 min-h-0 overflow-hidden bg-background"
|
|
59
|
+
>
|
|
57
60
|
{view === "inbox" ? (
|
|
58
61
|
<div className={`mx-auto flex h-full min-h-0 w-full flex-col py-4 sm:px-6 sm:py-5 ${MANAGEMENT_PAGE_CANVAS_WIDTH_CLASS}`}>
|
|
59
62
|
<InboxPage />
|
|
@@ -194,6 +194,7 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
|
|
|
194
194
|
<div className="relative">
|
|
195
195
|
<Search className="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground/60" />
|
|
196
196
|
<Input
|
|
197
|
+
data-theme-control="chat-search"
|
|
197
198
|
value={query}
|
|
198
199
|
onChange={(event) => onQueryChange(event.target.value)}
|
|
199
200
|
placeholder={t("chatSidebarSearchPlaceholder")}
|
|
@@ -223,6 +224,7 @@ export function ChatSidebarMobileToolbar(props: ChatSidebarToolbarProps) {
|
|
|
223
224
|
<div className="relative min-w-0 flex-1">
|
|
224
225
|
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground/70" />
|
|
225
226
|
<Input
|
|
227
|
+
data-theme-control="chat-search"
|
|
226
228
|
value={query}
|
|
227
229
|
onChange={(event) => onQueryChange(event.target.value)}
|
|
228
230
|
placeholder={t("chatSidebarSearchPlaceholder")}
|
|
@@ -247,6 +247,7 @@ export function ChatSidebar({
|
|
|
247
247
|
onTogglePinned={() =>
|
|
248
248
|
presenter.chatSessionListManager.toggleSessionPinned(item.session.key)
|
|
249
249
|
}
|
|
250
|
+
onDeleteSession={presenter.chatThreadManager.deleteSession}
|
|
250
251
|
/>
|
|
251
252
|
);
|
|
252
253
|
const createSessionAndOpenIfNeeded = (
|
|
@@ -278,6 +279,7 @@ export function ChatSidebar({
|
|
|
278
279
|
|
|
279
280
|
return (
|
|
280
281
|
<aside
|
|
282
|
+
data-theme-surface="navigation"
|
|
281
283
|
className={cn(
|
|
282
284
|
"flex h-full min-h-0 flex-col bg-secondary transition-[width] duration-200 ease-out",
|
|
283
285
|
isMobileVariant
|
package/src/features/chat/features/session/components/__tests__/chat-sidebar-session-item.test.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { ChatSidebarSessionItem } from "@/features/chat/features/session/compone
|
|
|
5
5
|
|
|
6
6
|
const mocks = vi.hoisted(() => ({
|
|
7
7
|
copyText: vi.fn(),
|
|
8
|
+
deleteSession: vi.fn(),
|
|
8
9
|
}));
|
|
9
10
|
|
|
10
11
|
vi.mock("@nextclaw/agent-chat-ui", () => ({
|
|
@@ -21,6 +22,7 @@ vi.mock("sonner", () => ({
|
|
|
21
22
|
beforeEach(() => {
|
|
22
23
|
mocks.copyText.mockReset();
|
|
23
24
|
mocks.copyText.mockResolvedValue(true);
|
|
25
|
+
mocks.deleteSession.mockReset();
|
|
24
26
|
render(
|
|
25
27
|
<ChatSidebarSessionItem
|
|
26
28
|
sessionKey="session:current"
|
|
@@ -50,6 +52,7 @@ beforeEach(() => {
|
|
|
50
52
|
onSave={vi.fn()}
|
|
51
53
|
onCancel={vi.fn()}
|
|
52
54
|
onTogglePinned={vi.fn()}
|
|
55
|
+
onDelete={mocks.deleteSession}
|
|
53
56
|
/>,
|
|
54
57
|
);
|
|
55
58
|
});
|
|
@@ -87,6 +90,15 @@ it("copies the sidebar session ID from the more-actions menu", async () => {
|
|
|
87
90
|
expect(mocks.copyText).toHaveBeenCalledWith("session:current");
|
|
88
91
|
});
|
|
89
92
|
|
|
93
|
+
it("deletes the sidebar session from the more-actions menu", async () => {
|
|
94
|
+
const user = userEvent.setup();
|
|
95
|
+
|
|
96
|
+
await user.click(screen.getByRole("button", { name: "More actions" }));
|
|
97
|
+
await user.click(screen.getByRole("button", { name: "Delete Session" }));
|
|
98
|
+
|
|
99
|
+
expect(mocks.deleteSession).toHaveBeenCalledOnce();
|
|
100
|
+
});
|
|
101
|
+
|
|
90
102
|
it("sizes the runtime icon to the session title text", () => {
|
|
91
103
|
const runtimeIcon = screen.getByRole("img", { name: "Codex logo" });
|
|
92
104
|
|
|
@@ -32,6 +32,7 @@ export function ChatSidebarSessionEntry(props: {
|
|
|
32
32
|
onSaveSessionLabel: (session: SessionEntryView) => void;
|
|
33
33
|
onCancelEditingSessionLabel: () => void;
|
|
34
34
|
onTogglePinned: () => void;
|
|
35
|
+
onDeleteSession: (sessionKey: string) => void;
|
|
35
36
|
}) {
|
|
36
37
|
const {
|
|
37
38
|
item,
|
|
@@ -52,6 +53,7 @@ export function ChatSidebarSessionEntry(props: {
|
|
|
52
53
|
onSaveSessionLabel,
|
|
53
54
|
onCancelEditingSessionLabel,
|
|
54
55
|
onTogglePinned,
|
|
56
|
+
onDeleteSession,
|
|
55
57
|
} = props;
|
|
56
58
|
const { session, runStatus } = item;
|
|
57
59
|
const active = selectedSessionKey === session.key;
|
|
@@ -111,6 +113,7 @@ export function ChatSidebarSessionEntry(props: {
|
|
|
111
113
|
onSave={() => onSaveSessionLabel(session)}
|
|
112
114
|
onCancel={onCancelEditingSessionLabel}
|
|
113
115
|
onTogglePinned={onTogglePinned}
|
|
116
|
+
onDelete={() => onDeleteSession(session.key)}
|
|
114
117
|
/>
|
|
115
118
|
);
|
|
116
119
|
}
|
|
@@ -15,9 +15,11 @@ import {
|
|
|
15
15
|
GitBranch,
|
|
16
16
|
Pencil,
|
|
17
17
|
Pin,
|
|
18
|
+
Trash2,
|
|
18
19
|
X,
|
|
19
20
|
} from "lucide-react";
|
|
20
21
|
import { ChatSidebarContextCard } from "@/features/chat/features/session/components/chat-sidebar-context-card";
|
|
22
|
+
import { ChatSessionHeaderMenuItem } from "@/features/chat/features/session/components/session-header/chat-session-header-menu-item";
|
|
21
23
|
|
|
22
24
|
type ChatSidebarSessionItemProps = {
|
|
23
25
|
sessionKey: string;
|
|
@@ -44,6 +46,7 @@ type ChatSidebarSessionItemProps = {
|
|
|
44
46
|
onSave: () => void | Promise<void>;
|
|
45
47
|
onCancel: () => void;
|
|
46
48
|
onTogglePinned: () => void;
|
|
49
|
+
onDelete: () => void;
|
|
47
50
|
};
|
|
48
51
|
|
|
49
52
|
type ChatSidebarSessionEditingViewProps = Pick<
|
|
@@ -139,6 +142,7 @@ function ChatSidebarSessionDisplayView({
|
|
|
139
142
|
onSelect,
|
|
140
143
|
onStartEditing,
|
|
141
144
|
onTogglePinned,
|
|
145
|
+
onDelete,
|
|
142
146
|
}: ChatSidebarSessionDisplayViewProps) {
|
|
143
147
|
return (
|
|
144
148
|
<div className="group/session relative">
|
|
@@ -262,7 +266,14 @@ function ChatSidebarSessionDisplayView({
|
|
|
262
266
|
sessionKey={sessionKey}
|
|
263
267
|
triggerSize="sm"
|
|
264
268
|
triggerTone="strong"
|
|
265
|
-
|
|
269
|
+
>
|
|
270
|
+
<ChatSessionHeaderMenuItem
|
|
271
|
+
icon={Trash2}
|
|
272
|
+
label={t("chatDeleteSession")}
|
|
273
|
+
onClick={onDelete}
|
|
274
|
+
destructive
|
|
275
|
+
/>
|
|
276
|
+
</ChatSessionMoreActionsMenu>
|
|
266
277
|
</div>
|
|
267
278
|
</div>
|
|
268
279
|
);
|
|
@@ -293,6 +304,7 @@ export function ChatSidebarSessionItem({
|
|
|
293
304
|
onSave,
|
|
294
305
|
onCancel,
|
|
295
306
|
onTogglePinned,
|
|
307
|
+
onDelete,
|
|
296
308
|
}: ChatSidebarSessionItemProps) {
|
|
297
309
|
return (
|
|
298
310
|
<div
|
|
@@ -332,6 +344,7 @@ export function ChatSidebarSessionItem({
|
|
|
332
344
|
projectName={projectName}
|
|
333
345
|
onStartEditing={onStartEditing}
|
|
334
346
|
onTogglePinned={onTogglePinned}
|
|
347
|
+
onDelete={onDelete}
|
|
335
348
|
/>
|
|
336
349
|
)}
|
|
337
350
|
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ChatThreadSnapshot } from "@/features/chat/stores/chat-thread.store";
|
|
2
|
+
|
|
3
|
+
export function createDeletedChatThreadStatePatch(): Partial<ChatThreadSnapshot> {
|
|
4
|
+
return {
|
|
5
|
+
sessionKey: null,
|
|
6
|
+
sessionTypeLabel: null,
|
|
7
|
+
agentId: null,
|
|
8
|
+
sessionDisplayName: undefined,
|
|
9
|
+
draftProjectRoot: null,
|
|
10
|
+
sessionProjectName: null,
|
|
11
|
+
canDeleteSession: false,
|
|
12
|
+
parentSessionKey: null,
|
|
13
|
+
parentSessionLabel: null,
|
|
14
|
+
workspacePanelParentKey: null,
|
|
15
|
+
activeWorkspacePanelKind: null,
|
|
16
|
+
childSessionTabs: [],
|
|
17
|
+
activeChildSessionKey: null,
|
|
18
|
+
activeSideChatDraft: null,
|
|
19
|
+
workspaceFileTabs: [],
|
|
20
|
+
activeWorkspaceFileKey: null,
|
|
21
|
+
closedWorkspaceTabEntries: [],
|
|
22
|
+
workspaceNavigationHistory: [],
|
|
23
|
+
workspaceNavigationHistoryIndex: 0,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -163,6 +163,7 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
163
163
|
|
|
164
164
|
const panel = screen.getByTestId("chat-session-workspace-panel");
|
|
165
165
|
expect(panel.className).not.toContain("absolute");
|
|
166
|
+
expect(panel.getAttribute("data-theme-surface")).toBe("workspace-panel");
|
|
166
167
|
|
|
167
168
|
await user.click(
|
|
168
169
|
screen.getByRole("button", { name: "Maximize workspace panel" }),
|
package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx
CHANGED
|
@@ -363,6 +363,7 @@ export function ChatSessionWorkspacePanelContent({
|
|
|
363
363
|
) : null}
|
|
364
364
|
<div
|
|
365
365
|
data-testid="workspace-shared-explorer"
|
|
366
|
+
data-theme-surface="workspace-explorer"
|
|
366
367
|
data-mode={projectFilesActive ? 'full' : explorerOverlay ? 'overlay' : 'side'}
|
|
367
368
|
className={cn(
|
|
368
369
|
'relative min-h-0 shrink-0 flex-col border-r border-gray-200/80 bg-white',
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { appQueryClient } from "@/app-query-client";
|
|
3
|
+
import { ChatThreadManager } from "@/features/chat/managers/chat-thread.manager";
|
|
4
|
+
import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
|
|
5
|
+
import { useChatThreadStore } from "@/features/chat/stores/chat-thread.store";
|
|
6
|
+
import type * as SharedApi from "@/shared/lib/api";
|
|
7
|
+
|
|
8
|
+
const { deleteNcpSessionMock, deleteSummaryMock, toast } = vi.hoisted(() => ({
|
|
9
|
+
deleteNcpSessionMock: vi.fn(),
|
|
10
|
+
deleteSummaryMock: vi.fn(),
|
|
11
|
+
toast: { success: vi.fn(), error: vi.fn() },
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock("@/shared/lib/api", async (importOriginal) => {
|
|
15
|
+
const actual = await importOriginal<typeof SharedApi>();
|
|
16
|
+
return {
|
|
17
|
+
...actual,
|
|
18
|
+
deleteNcpSession: deleteNcpSessionMock,
|
|
19
|
+
deleteNcpSessionSummaryInQueryClient: deleteSummaryMock,
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
vi.mock("sonner", () => ({ toast }));
|
|
24
|
+
|
|
25
|
+
function createUiManager() {
|
|
26
|
+
return {
|
|
27
|
+
confirm: vi.fn(async () => true),
|
|
28
|
+
goToChatRoot: vi.fn(),
|
|
29
|
+
} as unknown as ConstructorParameters<typeof ChatThreadManager>[0];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
deleteNcpSessionMock.mockReset();
|
|
34
|
+
deleteNcpSessionMock.mockResolvedValue({
|
|
35
|
+
deleted: true,
|
|
36
|
+
sessionId: "parent-session-1",
|
|
37
|
+
});
|
|
38
|
+
deleteSummaryMock.mockReset();
|
|
39
|
+
toast.success.mockReset();
|
|
40
|
+
toast.error.mockReset();
|
|
41
|
+
useChatSessionListStore.setState({
|
|
42
|
+
snapshot: {
|
|
43
|
+
...useChatSessionListStore.getInitialState().snapshot,
|
|
44
|
+
selectedSessionKey: "parent-session-1",
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
useChatThreadStore.setState({
|
|
48
|
+
snapshot: {
|
|
49
|
+
...useChatThreadStore.getInitialState().snapshot,
|
|
50
|
+
sessionKey: "parent-session-1",
|
|
51
|
+
canDeleteSession: true,
|
|
52
|
+
childSessionTabs: [
|
|
53
|
+
{
|
|
54
|
+
sessionKey: "child-session-1",
|
|
55
|
+
parentSessionKey: "parent-session-1",
|
|
56
|
+
label: "Child Session 1",
|
|
57
|
+
agentId: "reviewer",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("ChatThreadManager deletion", () => {
|
|
65
|
+
it("clears the current thread and confirms the deletion", async () => {
|
|
66
|
+
const uiManager = createUiManager();
|
|
67
|
+
const removeQueries = vi
|
|
68
|
+
.spyOn(appQueryClient, "removeQueries")
|
|
69
|
+
.mockImplementation(async () => undefined);
|
|
70
|
+
const manager = new ChatThreadManager(
|
|
71
|
+
uiManager,
|
|
72
|
+
{} as ConstructorParameters<typeof ChatThreadManager>[1],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
await manager.deleteSession();
|
|
76
|
+
|
|
77
|
+
expect(useChatThreadStore.getState().snapshot.sessionKey).toBeNull();
|
|
78
|
+
expect(deleteSummaryMock).toHaveBeenCalledWith(
|
|
79
|
+
appQueryClient,
|
|
80
|
+
"parent-session-1",
|
|
81
|
+
);
|
|
82
|
+
expect(removeQueries).toHaveBeenCalledWith({
|
|
83
|
+
queryKey: ["ncp-session-messages", "parent-session-1"],
|
|
84
|
+
});
|
|
85
|
+
expect(uiManager.goToChatRoot).toHaveBeenCalledWith({ replace: true });
|
|
86
|
+
expect(toast.success).toHaveBeenCalledWith("Session deleted.");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("preserves the current thread when deleting another session", async () => {
|
|
90
|
+
const uiManager = createUiManager();
|
|
91
|
+
const manager = new ChatThreadManager(
|
|
92
|
+
uiManager,
|
|
93
|
+
{} as ConstructorParameters<typeof ChatThreadManager>[1],
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
await manager.deleteSession("other-session-1");
|
|
97
|
+
|
|
98
|
+
expect(deleteNcpSessionMock).toHaveBeenCalledWith("other-session-1");
|
|
99
|
+
expect(useChatThreadStore.getState().snapshot.sessionKey).toBe(
|
|
100
|
+
"parent-session-1",
|
|
101
|
+
);
|
|
102
|
+
expect(uiManager.goToChatRoot).not.toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("keeps the session visible and reports a deletion error", async () => {
|
|
106
|
+
deleteNcpSessionMock.mockRejectedValueOnce(
|
|
107
|
+
new Error("network unavailable"),
|
|
108
|
+
);
|
|
109
|
+
const uiManager = createUiManager();
|
|
110
|
+
const manager = new ChatThreadManager(
|
|
111
|
+
uiManager,
|
|
112
|
+
{} as ConstructorParameters<typeof ChatThreadManager>[1],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
await manager.deleteSession();
|
|
116
|
+
|
|
117
|
+
expect(useChatThreadStore.getState().snapshot.sessionKey).toBe(
|
|
118
|
+
"parent-session-1",
|
|
119
|
+
);
|
|
120
|
+
expect(uiManager.goToChatRoot).not.toHaveBeenCalled();
|
|
121
|
+
expect(toast.error).toHaveBeenCalledWith(
|
|
122
|
+
"Could not delete session: network unavailable",
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { appQueryClient } from '@/app-query-client';
|
|
3
2
|
import { ChatThreadManager } from '@/features/chat/managers/chat-thread.manager';
|
|
4
3
|
import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
|
|
5
4
|
import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
|
|
@@ -841,39 +840,3 @@ describe('ChatThreadManager visible workspace selection', () => {
|
|
|
841
840
|
expect(sessionListManager.markVisibleWorkspaceChildRead).toHaveBeenCalledWith(tab);
|
|
842
841
|
});
|
|
843
842
|
});
|
|
844
|
-
|
|
845
|
-
describe('ChatThreadManager deletion', () => {
|
|
846
|
-
it('clears thread state while leaving route-derived selection to navigation', async () => {
|
|
847
|
-
const removeQueries = vi.spyOn(appQueryClient, 'removeQueries').mockImplementation(async () => undefined);
|
|
848
|
-
const uiManager = {
|
|
849
|
-
goToSession: vi.fn(),
|
|
850
|
-
goToChatRoot: vi.fn(),
|
|
851
|
-
goToProviders: vi.fn(),
|
|
852
|
-
confirm: vi.fn(async () => true),
|
|
853
|
-
} as unknown as ConstructorParameters<typeof ChatThreadManager>[0];
|
|
854
|
-
const sessionListManager = {} as ConstructorParameters<typeof ChatThreadManager>[1];
|
|
855
|
-
const manager = new ChatThreadManager(uiManager, sessionListManager);
|
|
856
|
-
|
|
857
|
-
await manager.deleteSession();
|
|
858
|
-
|
|
859
|
-
expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBe('parent-session-1');
|
|
860
|
-
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
861
|
-
sessionKey: null,
|
|
862
|
-
canDeleteSession: false,
|
|
863
|
-
workspacePanelParentKey: null,
|
|
864
|
-
childSessionTabs: [],
|
|
865
|
-
activeChildSessionKey: null,
|
|
866
|
-
workspaceFileTabs: [],
|
|
867
|
-
activeWorkspaceFileKey: null,
|
|
868
|
-
workspaceNavigationHistory: [],
|
|
869
|
-
workspaceNavigationHistoryIndex: 0,
|
|
870
|
-
});
|
|
871
|
-
expect(deleteSummaryMock).toHaveBeenCalledWith(appQueryClient, 'parent-session-1');
|
|
872
|
-
expect(removeQueries).toHaveBeenCalledWith({
|
|
873
|
-
queryKey: ['ncp-session-messages', 'parent-session-1'],
|
|
874
|
-
});
|
|
875
|
-
expect(uiManager.goToChatRoot).toHaveBeenCalledWith({ replace: true });
|
|
876
|
-
|
|
877
|
-
removeQueries.mockRestore();
|
|
878
|
-
});
|
|
879
|
-
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { appQueryClient } from '@/app-query-client';
|
|
2
2
|
import { deleteNcpSession as deleteNcpSessionApi, deleteNcpSessionSummaryInQueryClient } from '@/shared/lib/api';
|
|
3
|
+
import { toast } from 'sonner';
|
|
3
4
|
import type {
|
|
4
5
|
ChatFileOpenActionViewModel,
|
|
5
6
|
ChatUiShowContentRequest,
|
|
@@ -17,6 +18,7 @@ import type {
|
|
|
17
18
|
ChatWorkspaceFileTab,
|
|
18
19
|
} from '@/features/chat/stores/chat-thread.store';
|
|
19
20
|
import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
|
|
21
|
+
import { createDeletedChatThreadStatePatch } from '@/features/chat/features/session/utils/chat-session-deletion.utils';
|
|
20
22
|
import {
|
|
21
23
|
normalizeChatWorkspaceExplorerWidth,
|
|
22
24
|
normalizeChatWorkspacePanelWidth,
|
|
@@ -80,27 +82,7 @@ export class ChatThreadManager {
|
|
|
80
82
|
};
|
|
81
83
|
|
|
82
84
|
private clearDeletedSessionState = () => {
|
|
83
|
-
useChatThreadStore.getState().setSnapshot(
|
|
84
|
-
sessionKey: null,
|
|
85
|
-
sessionTypeLabel: null,
|
|
86
|
-
agentId: null,
|
|
87
|
-
sessionDisplayName: undefined,
|
|
88
|
-
draftProjectRoot: null,
|
|
89
|
-
sessionProjectName: null,
|
|
90
|
-
canDeleteSession: false,
|
|
91
|
-
parentSessionKey: null,
|
|
92
|
-
parentSessionLabel: null,
|
|
93
|
-
workspacePanelParentKey: null,
|
|
94
|
-
activeWorkspacePanelKind: null,
|
|
95
|
-
childSessionTabs: [],
|
|
96
|
-
activeChildSessionKey: null,
|
|
97
|
-
activeSideChatDraft: null,
|
|
98
|
-
workspaceFileTabs: [],
|
|
99
|
-
activeWorkspaceFileKey: null,
|
|
100
|
-
closedWorkspaceTabEntries: [],
|
|
101
|
-
workspaceNavigationHistory: [],
|
|
102
|
-
workspaceNavigationHistoryIndex: 0,
|
|
103
|
-
});
|
|
85
|
+
useChatThreadStore.getState().setSnapshot(createDeletedChatThreadStatePatch());
|
|
104
86
|
};
|
|
105
87
|
|
|
106
88
|
private resolveWorkspaceParentSessionKey = (): string | null => {
|
|
@@ -565,11 +547,12 @@ export class ChatThreadManager {
|
|
|
565
547
|
this.uiManager.goToSession(resolvedParentSessionKey);
|
|
566
548
|
};
|
|
567
549
|
|
|
568
|
-
deleteSession = async () => {
|
|
550
|
+
deleteSession = async (rawSessionKey?: string) => {
|
|
569
551
|
const {
|
|
570
552
|
snapshot: { selectedSessionKey },
|
|
571
553
|
} = useChatSessionListStore.getState();
|
|
572
|
-
|
|
554
|
+
const sessionKey = rawSessionKey?.trim() || selectedSessionKey;
|
|
555
|
+
if (!sessionKey) {
|
|
573
556
|
return;
|
|
574
557
|
}
|
|
575
558
|
const confirmed = await this.uiManager.confirm({
|
|
@@ -582,13 +565,19 @@ export class ChatThreadManager {
|
|
|
582
565
|
}
|
|
583
566
|
useChatThreadStore.getState().setSnapshot({ isDeletePending: true });
|
|
584
567
|
try {
|
|
585
|
-
await deleteNcpSessionApi(
|
|
586
|
-
deleteNcpSessionSummaryInQueryClient(appQueryClient,
|
|
568
|
+
await deleteNcpSessionApi(sessionKey);
|
|
569
|
+
deleteNcpSessionSummaryInQueryClient(appQueryClient, sessionKey);
|
|
587
570
|
appQueryClient.removeQueries({
|
|
588
|
-
queryKey: ['ncp-session-messages',
|
|
571
|
+
queryKey: ['ncp-session-messages', sessionKey],
|
|
589
572
|
});
|
|
590
|
-
|
|
591
|
-
|
|
573
|
+
toast.success(t('chatDeleteSessionSucceeded'));
|
|
574
|
+
if (sessionKey === selectedSessionKey) {
|
|
575
|
+
this.clearDeletedSessionState();
|
|
576
|
+
this.uiManager.goToChatRoot({ replace: true });
|
|
577
|
+
}
|
|
578
|
+
} catch (error) {
|
|
579
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
580
|
+
toast.error(`${t('chatDeleteSessionFailed')}: ${message}`);
|
|
592
581
|
} finally {
|
|
593
582
|
useChatThreadStore.getState().setSnapshot({ isDeletePending: false });
|
|
594
583
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { DesktopAuthorizationDialog } from "./desktop-authorization-dialog";
|
|
4
|
+
import { useDesktopAuthorizationStore } from "../stores/desktop-authorization.store";
|
|
5
|
+
|
|
6
|
+
const mocks = vi.hoisted(() => ({ grantAccess: vi.fn() }));
|
|
7
|
+
|
|
8
|
+
vi.mock("../managers/desktop-capability.manager", () => ({
|
|
9
|
+
desktopCapabilityManager: {
|
|
10
|
+
grantAccess: mocks.grantAccess,
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe("DesktopAuthorizationDialog", () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mocks.grantAccess.mockReset();
|
|
17
|
+
mocks.grantAccess.mockResolvedValue({});
|
|
18
|
+
useDesktopAuthorizationStore.getState().clear();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("shows the trusted subject and grants only after explicit approval", async () => {
|
|
22
|
+
useDesktopAuthorizationStore.getState().present({
|
|
23
|
+
applicationId: "wechat",
|
|
24
|
+
request: {
|
|
25
|
+
subject: { type: "agent", id: "main" },
|
|
26
|
+
resource: {
|
|
27
|
+
type: "desktop.application",
|
|
28
|
+
target: { applicationId: "wechat", platform: "darwin", bundleId: "com.tencent.xinWeChat" },
|
|
29
|
+
},
|
|
30
|
+
access: ["ui.write"],
|
|
31
|
+
declarationFingerprint: "fingerprint",
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
render(<DesktopAuthorizationDialog />);
|
|
36
|
+
expect(screen.getByText("AI · main")).toBeTruthy();
|
|
37
|
+
expect(screen.getByText("wechat")).toBeTruthy();
|
|
38
|
+
expect(mocks.grantAccess).not.toHaveBeenCalled();
|
|
39
|
+
|
|
40
|
+
fireEvent.click(screen.getByRole("button", { name: "Allow" }));
|
|
41
|
+
await waitFor(() => expect(mocks.grantAccess).toHaveBeenCalledOnce());
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("explains the bounded pointer permission in user terms", () => {
|
|
45
|
+
useDesktopAuthorizationStore.getState().present({
|
|
46
|
+
applicationId: "wechat",
|
|
47
|
+
request: {
|
|
48
|
+
subject: { type: "agent", id: "main" },
|
|
49
|
+
resource: { type: "desktop.application", target: { applicationId: "wechat", platform: "darwin", bundleId: "com.tencent.xinWeChat" } },
|
|
50
|
+
access: ["input.pointer"],
|
|
51
|
+
declarationFingerprint: "fingerprint",
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
render(<DesktopAuthorizationDialog />);
|
|
55
|
+
expect(screen.getByText("Click inside the app window")).toBeTruthy();
|
|
56
|
+
expect(screen.getByText(/cannot use keys, scroll, drag, send, or confirm/i)).toBeTruthy();
|
|
57
|
+
});
|
|
58
|
+
});
|