@nextclaw/ui 0.12.8 → 0.12.10
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 +96 -0
- package/dist/assets/ChannelsList-M9FTK1Ak.js +8 -0
- package/dist/assets/DocBrowser-CH7-GxlL.js +1 -0
- package/dist/assets/{DocBrowser-BMxf9CIK.js → DocBrowser-DMfr0Oow.js} +1 -1
- package/dist/assets/{DocBrowserContext-Ce28gRXt.js → DocBrowserContext-BXydqby-.js} +1 -1
- package/dist/assets/{LogoBadge-o92MOA2L.js → LogoBadge-hO7tY7hE.js} +1 -1
- package/dist/assets/ModelConfig-CNIgLf0e.js +1 -0
- package/dist/assets/{ProviderScopedModelInput-CmTIzgI7.js → ProviderScopedModelInput-B3HWP4oz.js} +1 -1
- package/dist/assets/ProvidersList-CHjMnRhX.js +1 -0
- package/dist/assets/RuntimeConfig-psp8nMSG.js +1 -0
- package/dist/assets/SearchConfig-CSoKip1f.js +1 -0
- package/dist/assets/{SecretsConfig-Ba1RPJaG.js → SecretsConfig-MEt6MjuD.js} +2 -2
- package/dist/assets/SessionsConfig-DifCiXwR.js +2 -0
- package/dist/assets/{app-query-client-DniXoIN5.js → app-query-client-9jNewezV.js} +1 -1
- package/dist/assets/{book-open-DocgeQtR.js → book-open-DzdUViDm.js} +1 -1
- package/dist/assets/chat-page-CLp0UV0Y.js +58 -0
- package/dist/assets/chat-session-display-DsYHx0RZ.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-BvKvh1R8.js → chunk-JZWAC4HX-C5dEc8hV.js} +1 -1
- package/dist/assets/{client-CVqPF5ie.js → client-C-8fH7-c.js} +1 -1
- package/dist/assets/{config-Bop2oB18.js → config-CBScxsdV.js} +1 -1
- package/dist/assets/config-split-page-BUout_Ak.js +1 -0
- package/dist/assets/{createLucideIcon-DVv8taGY.js → createLucideIcon-dy5ie7Ox.js} +1 -1
- package/dist/assets/desktop-update-config-2BS6BMkW.js +1 -0
- package/dist/assets/{dist-DmAlInRu.js → dist-BruyLa92.js} +1 -1
- package/dist/assets/{dist-Da5Gm_pO.js → dist-Cy7_j6hA.js} +1 -1
- package/dist/assets/download-BD0ETkB-.js +1 -0
- package/dist/assets/{external-link-DFjw3x1B.js → external-link-kZSAO8nT.js} +1 -1
- package/dist/assets/{hash-DJtaCejM.js → hash-BHJC2Ovu.js} +1 -1
- package/dist/assets/i18n-CpTZLchQ.js +1 -0
- package/dist/assets/index-mW8W2FUu.css +1 -0
- package/dist/assets/index-zDZfXoI4.js +6 -0
- package/dist/assets/{infiniteQueryBehavior-DHSEQ3OH.js → infiniteQueryBehavior-CyER9hv0.js} +1 -1
- package/dist/assets/loader-circle-Bc2gCU33.js +1 -0
- package/dist/assets/{logos-DEFUIR12.js → logos-B7gRObP8.js} +1 -1
- package/dist/assets/marketplace-page-3qVMnF3d.js +1 -0
- package/dist/assets/marketplace-page-BhFIeQzI.js +49 -0
- package/dist/assets/mcp-marketplace-page-DYfteJ1D.js +40 -0
- package/dist/assets/{page-layout-Da3i3r6G.js → page-layout-0UcO9H9Z.js} +1 -1
- package/dist/assets/play-CKDjSQFL.js +1 -0
- package/dist/assets/plus-CG0QrVY_.js +1 -0
- package/dist/assets/{refresh-ccw-D6HkNtfz.js → refresh-ccw-COVhNHtN.js} +1 -1
- package/dist/assets/{refresh-cw-DRcvRrnc.js → refresh-cw-Bcv40SXy.js} +1 -1
- package/dist/assets/remote-access-page-CWHG-sug.js +1 -0
- package/dist/assets/{rotate-cw-BmDKfXtH.js → rotate-cw-oHMKJMC8.js} +1 -1
- package/dist/assets/{save-DHGmi2e9.js → save-EqJPOF0G.js} +1 -1
- package/dist/assets/search-BCAlB8nz.js +1 -0
- package/dist/assets/security-config-Slh0Mayz.js +1 -0
- package/dist/assets/select-CVz0t7MF.js +41 -0
- package/dist/assets/setting-row-CbVHAuQt.js +1 -0
- package/dist/assets/skeleton-D5rdKvzy.js +1 -0
- package/dist/assets/{status-dot-DurKKSwA.js → status-dot-DpPtVzQT.js} +1 -1
- package/dist/assets/{switch-0rmPBRKI.js → switch-CM29eCAR.js} +1 -1
- package/dist/assets/{tabs-custom-5JLVL6v8.js → tabs-custom-YcZUWn3o.js} +1 -1
- package/dist/assets/tag-chip-DMXdnLcj.js +1 -0
- package/dist/assets/{trash-2-C6caKPoz.js → trash-2-mJT6oWa2.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-dwnaa_qi.js → use-infinite-scroll-loader-DJ1L81Dz.js} +1 -1
- package/dist/assets/{useConfirmDialog-mMeWD_yo.js → useConfirmDialog-BsVuqu1x.js} +1 -1
- package/dist/assets/{useMutation-BmxxvCNf.js → useMutation-CNcz2fgt.js} +1 -1
- package/dist/assets/x-Czwxm82I.js +1 -0
- package/dist/index.html +95 -21
- package/dist/manifest.webmanifest +30 -0
- package/dist/offline.html +102 -0
- package/dist/pwa-192.png +0 -0
- package/dist/pwa-512.png +0 -0
- package/dist/runtime-icons/claude.ico +0 -0
- package/dist/runtime-icons/codex-openai.svg +6 -0
- package/dist/runtime-icons/hermes-agent.png +0 -0
- package/dist/sw.js +80 -0
- package/index.html +73 -1
- package/package.json +5 -5
- package/public/manifest.webmanifest +30 -0
- package/public/offline.html +102 -0
- package/public/pwa-192.png +0 -0
- package/public/pwa-512.png +0 -0
- package/public/runtime-icons/claude.ico +0 -0
- package/public/runtime-icons/codex-openai.svg +6 -0
- package/public/runtime-icons/hermes-agent.png +0 -0
- package/public/sw.js +80 -0
- package/src/account/components/account-panel.tsx +217 -97
- package/src/account/managers/account.manager.ts +3 -2
- package/src/api/chat-session-type.types.ts +7 -0
- package/src/api/runtime-control.types.ts +8 -0
- package/src/api/server-path.ts +27 -4
- package/src/api/types.ts +25 -10
- package/src/app.tsx +227 -54
- package/src/components/agents/agent-dialogs.tsx +499 -0
- package/src/components/agents/agents-page.test.tsx +238 -0
- package/src/components/agents/agents-page.tsx +435 -0
- package/src/components/chat/ChatSidebar.test.tsx +43 -1
- package/src/components/chat/ChatSidebar.tsx +35 -35
- package/src/components/chat/adapters/chat-message.summary-truncation.test.ts +66 -0
- package/src/components/chat/adapters/file-operation/card.ts +9 -0
- package/src/components/chat/adapters/file-operation/diff.ts +14 -0
- package/src/components/chat/{ChatConversationPanel.test.tsx → chat-conversation-panel.test.tsx} +127 -206
- package/src/components/chat/chat-conversation-panel.tsx +482 -0
- package/src/components/chat/chat-page-shell.tsx +19 -13
- package/src/components/chat/chat-session-type-option-item.test.tsx +46 -0
- package/src/components/chat/chat-session-type-option-item.tsx +68 -0
- package/src/components/chat/chat-session-workspace-file-preview.test.tsx +178 -0
- package/src/components/chat/chat-session-workspace-file-preview.tsx +278 -0
- package/src/components/chat/chat-session-workspace-panel-nav.tsx +203 -0
- package/src/components/chat/chat-session-workspace-panel.tsx +318 -0
- package/src/components/chat/chat-sidebar-project-groups.tsx +11 -36
- package/src/components/chat/chat-sidebar-session-item.tsx +32 -2
- package/src/components/chat/containers/chat-message-list.container.test.tsx +49 -0
- package/src/components/chat/containers/chat-message-list.container.tsx +4 -0
- package/src/components/chat/managers/chat-session-list.manager.test.ts +12 -0
- package/src/components/chat/managers/chat-session-list.manager.ts +7 -0
- package/src/components/chat/ncp/__tests__/ncp-session-adapter.cancelled-tool.test.ts +77 -0
- package/src/components/chat/ncp/ncp-chat-page.tsx +9 -7
- package/src/components/chat/ncp/ncp-chat-thread.manager.ts +179 -41
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +36 -1
- package/src/components/chat/ncp/ncp-session-adapter.ts +20 -0
- package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +62 -13
- package/src/components/chat/ncp/tests/ncp-chat-thread.manager.test.ts +189 -0
- package/src/components/chat/presenter/chat-presenter-context.tsx +13 -2
- package/src/components/chat/session-header/chat-session-header-actions.test.tsx +26 -0
- package/src/components/chat/session-header/chat-session-header-actions.tsx +19 -1
- package/src/components/chat/stores/chat-input.store.ts +2 -1
- package/src/components/chat/stores/chat-thread.store.ts +27 -1
- package/src/components/chat/useChatSessionTypeState.ts +10 -1
- package/src/components/chat/workspace/chat-session-workspace-file-breadcrumbs.tsx +86 -0
- package/src/components/common/BrandHeader.tsx +3 -1
- package/src/components/common/session-context-icon.tsx +15 -2
- package/src/components/common/{TagInput.tsx → tag-input.tsx} +25 -17
- package/src/components/config/ChannelForm.test.tsx +89 -3
- package/src/components/config/ChannelForm.tsx +157 -188
- package/src/components/config/ChannelsList.test.tsx +163 -119
- package/src/components/config/ChannelsList.tsx +90 -101
- package/src/components/config/ProviderForm.tsx +108 -146
- package/src/components/config/ProvidersList.tsx +100 -123
- package/src/components/config/RuntimeConfig.tsx +141 -2
- package/src/components/config/SearchConfig.tsx +423 -393
- package/src/components/config/channel-form-fields-section.tsx +70 -37
- package/src/components/config/config-split-page.tsx +109 -0
- package/src/components/config/provider-enabled-field.tsx +17 -10
- package/src/components/config/runtime-control-card.test.tsx +56 -0
- package/src/components/config/runtime-control-card.tsx +25 -0
- package/src/components/config/runtime-presence-card.tsx +93 -79
- package/src/components/layout/AppLayout.tsx +25 -37
- package/src/components/layout/app-layout.test.tsx +46 -14
- package/src/components/layout/runtime-status-entry.test.tsx +157 -0
- package/src/components/layout/runtime-status-entry.tsx +143 -0
- package/src/components/marketplace/marketplace-detail-doc.ts +93 -0
- package/src/components/marketplace/marketplace-list-card.tsx +288 -0
- package/src/components/marketplace/marketplace-page-data.ts +129 -0
- package/src/components/marketplace/marketplace-page.test.tsx +339 -0
- package/src/components/marketplace/marketplace-page.tsx +596 -0
- package/src/components/marketplace/mcp/mcp-marketplace-card.tsx +128 -0
- package/src/components/marketplace/mcp/mcp-marketplace-dialogs.tsx +191 -0
- package/src/components/marketplace/mcp/mcp-marketplace-doc.ts +152 -0
- package/src/components/marketplace/mcp/mcp-marketplace-page.test.tsx +223 -0
- package/src/components/marketplace/mcp/mcp-marketplace-page.tsx +414 -0
- package/src/components/providers/ThemeProvider.tsx +5 -0
- package/src/components/remote/remote-access-page.test.tsx +105 -0
- package/src/components/remote/remote-access-page.tsx +248 -0
- package/src/components/ui/notice-card.tsx +129 -0
- package/src/components/ui/setting-row.tsx +51 -0
- package/src/components/ui/tag-chip.tsx +39 -0
- package/src/components/ui/textarea.tsx +19 -0
- package/src/hooks/server-path/use-server-path-read.ts +20 -0
- package/src/hooks/useConfig.ts +2 -1
- package/src/index.css +24 -0
- package/src/lib/app-resource-uri.test.ts +20 -0
- package/src/lib/app-resource-uri.ts +29 -0
- package/src/lib/chat-message.ts +14 -3
- package/src/lib/i18n.chat.ts +12 -1
- package/src/lib/i18n.pwa.ts +62 -0
- package/src/lib/i18n.remote.ts +1 -1
- package/src/lib/i18n.runtime-control.ts +31 -0
- package/src/lib/i18n.ts +7 -10
- package/src/lib/session-context.utils.test.ts +71 -0
- package/src/lib/session-context.utils.ts +28 -3
- package/src/lib/session-project/workspace-file-breadcrumb.test.ts +83 -0
- package/src/lib/session-project/workspace-file-breadcrumb.ts +188 -0
- package/src/pwa/components/pwa-install-entry.test.tsx +110 -0
- package/src/pwa/components/pwa-install-entry.tsx +205 -0
- package/src/pwa/managers/pwa-install.manager.test.ts +160 -0
- package/src/pwa/managers/pwa-install.manager.ts +232 -0
- package/src/pwa/managers/pwa-runtime.manager.ts +196 -0
- package/src/pwa/managers/pwa-shell-theme.manager.test.ts +30 -0
- package/src/pwa/managers/pwa-shell-theme.manager.ts +46 -0
- package/src/pwa/pwa-install-banner.storage.ts +55 -0
- package/src/pwa/pwa.types.ts +22 -0
- package/src/pwa/register-pwa.ts +14 -0
- package/src/pwa/stores/pwa.store.ts +17 -0
- package/src/vite-env.d.ts +9 -0
- package/dist/assets/ChannelsList-KIQIxluX.js +0 -8
- package/dist/assets/DocBrowser-CyDgAtO9.js +0 -1
- package/dist/assets/MarketplacePage-BySqkYDh.js +0 -49
- package/dist/assets/MarketplacePage-C0olZaek.js +0 -1
- package/dist/assets/McpMarketplacePage-DqKaiXO9.js +0 -40
- package/dist/assets/ModelConfig-IrmzoslW.js +0 -1
- package/dist/assets/ProvidersList-8_Kalfwl.js +0 -1
- package/dist/assets/RemoteAccessPage-CyQlSjPf.js +0 -1
- package/dist/assets/RuntimeConfig-Bk0uYBhf.js +0 -1
- package/dist/assets/SearchConfig-DNBR-UbE.js +0 -1
- package/dist/assets/SessionsConfig-Doqp5ghH.js +0 -2
- package/dist/assets/chat-page-Bph8M5zo.js +0 -58
- package/dist/assets/chat-session-display-CoN3Wmn-.js +0 -1
- package/dist/assets/config-layout-DmlGaay2.js +0 -1
- package/dist/assets/desktop-update-config-1KBrqLBC.js +0 -1
- package/dist/assets/i18n-CwHZ-9vt.js +0 -1
- package/dist/assets/index-DafCdM4F.css +0 -1
- package/dist/assets/index-DdksE6U3.js +0 -6
- package/dist/assets/loader-circle-PsSP0H9n.js +0 -1
- package/dist/assets/play-DBQbBxTA.js +0 -1
- package/dist/assets/plus-DUOVbsyQ.js +0 -1
- package/dist/assets/popover-C_mWOFzI.js +0 -1
- package/dist/assets/search-MChQRYR1.js +0 -1
- package/dist/assets/security-config-CbXfPZzr.js +0 -1
- package/dist/assets/select-Caud8QvU.js +0 -41
- package/dist/assets/skeleton-B-4vRq_Z.js +0 -1
- package/dist/assets/x-DuMhMATD.js +0 -1
- package/src/components/agents/AgentDialogs.tsx +0 -400
- package/src/components/agents/AgentsPage.test.tsx +0 -217
- package/src/components/agents/AgentsPage.tsx +0 -352
- package/src/components/chat/ChatConversationPanel.tsx +0 -256
- package/src/components/chat/chat-child-session-panel.tsx +0 -270
- package/src/components/config/config-layout.ts +0 -10
- package/src/components/marketplace/MarketplacePage.test.tsx +0 -322
- package/src/components/marketplace/MarketplacePage.tsx +0 -827
- package/src/components/marketplace/mcp/McpMarketplacePage.test.tsx +0 -208
- package/src/components/marketplace/mcp/McpMarketplacePage.tsx +0 -580
- package/src/components/remote/RemoteAccessPage.test.tsx +0 -103
- package/src/components/remote/RemoteAccessPage.tsx +0 -144
- /package/dist/assets/{config-hints-BZoDjXye.js → config-hints-BhTmc9P1.js} +0 -0
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import { ArrowLeft, Loader2, X } from "lucide-react";
|
|
3
|
-
import { useStickyBottomScroll } from "@nextclaw/agent-chat-ui";
|
|
4
|
-
import { ChatMessageListContainer } from "@/components/chat/containers/chat-message-list.container";
|
|
5
|
-
import {
|
|
6
|
-
useNcpChildSessionTabsView,
|
|
7
|
-
type ResolvedChildSessionTab,
|
|
8
|
-
} from "@/components/chat/ncp/session-conversation/use-ncp-child-session-tabs-view";
|
|
9
|
-
import { useNcpSessionConversation } from "@/components/chat/ncp/session-conversation/use-ncp-session-conversation";
|
|
10
|
-
import {
|
|
11
|
-
shouldShowUnreadSessionIndicator,
|
|
12
|
-
useChatSessionListStore,
|
|
13
|
-
} from "@/components/chat/stores/chat-session-list.store";
|
|
14
|
-
import { usePresenter } from "@/components/chat/presenter/chat-presenter-context";
|
|
15
|
-
import type { ChatChildSessionTab } from "@/components/chat/stores/chat-thread.store";
|
|
16
|
-
import { AgentIdentityAvatar } from "@/components/common/agent-identity";
|
|
17
|
-
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
18
|
-
import { t } from "@/lib/i18n";
|
|
19
|
-
import { cn } from "@/lib/utils";
|
|
20
|
-
import type { ChatToolActionViewModel } from "@nextclaw/agent-chat-ui";
|
|
21
|
-
|
|
22
|
-
type ChatChildSessionPanelProps = {
|
|
23
|
-
tabs: readonly ChatChildSessionTab[];
|
|
24
|
-
activeSessionKey: string;
|
|
25
|
-
onSelectSession: (sessionKey: string) => void;
|
|
26
|
-
onClose: () => void;
|
|
27
|
-
onBackToParent: () => void;
|
|
28
|
-
onToolAction?: (action: ChatToolActionViewModel) => void;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
function ChildSessionPanelConversation({
|
|
32
|
-
sessionKey,
|
|
33
|
-
onToolAction,
|
|
34
|
-
}: {
|
|
35
|
-
sessionKey: string;
|
|
36
|
-
onToolAction?: (action: ChatToolActionViewModel) => void;
|
|
37
|
-
}) {
|
|
38
|
-
const agent = useNcpSessionConversation(sessionKey);
|
|
39
|
-
const messages = agent.visibleMessages;
|
|
40
|
-
const scrollRef = useRef<HTMLDivElement>(null);
|
|
41
|
-
const { onScroll } = useStickyBottomScroll({
|
|
42
|
-
scrollRef,
|
|
43
|
-
resetKey: sessionKey,
|
|
44
|
-
isLoading: agent.isHydrating,
|
|
45
|
-
hasContent: messages.length > 0,
|
|
46
|
-
contentVersion: messages[messages.length - 1] ?? null,
|
|
47
|
-
stickyThresholdPx: 20,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<div
|
|
52
|
-
ref={scrollRef}
|
|
53
|
-
onScroll={onScroll}
|
|
54
|
-
className="h-full overflow-y-auto custom-scrollbar"
|
|
55
|
-
>
|
|
56
|
-
{agent.isHydrating ? (
|
|
57
|
-
<div className="flex h-full items-center justify-center text-sm text-gray-500">
|
|
58
|
-
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
59
|
-
{t("chatChildSessionLoading")}
|
|
60
|
-
</div>
|
|
61
|
-
) : agent.hydrateError ? (
|
|
62
|
-
<div className="px-4 py-5 text-sm text-rose-600">
|
|
63
|
-
{agent.hydrateError.message}
|
|
64
|
-
</div>
|
|
65
|
-
) : messages.length === 0 && !agent.isRunning ? (
|
|
66
|
-
<div className="px-4 py-5 text-sm text-gray-500">
|
|
67
|
-
{t("chatChildSessionEmpty")}
|
|
68
|
-
</div>
|
|
69
|
-
) : (
|
|
70
|
-
<div className="px-4 py-5">
|
|
71
|
-
<ChatMessageListContainer
|
|
72
|
-
messages={messages}
|
|
73
|
-
isSending={agent.isRunning}
|
|
74
|
-
onToolAction={onToolAction}
|
|
75
|
-
/>
|
|
76
|
-
</div>
|
|
77
|
-
)}
|
|
78
|
-
</div>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function ChildSessionPanelMetaChip({ value }: { value: string }) {
|
|
83
|
-
return (
|
|
84
|
-
<span className="inline-flex max-w-full items-center rounded-full border border-gray-200/80 bg-gray-50/90 px-2.5 py-1 text-[11px] font-medium text-gray-600">
|
|
85
|
-
<span className="truncate">{value}</span>
|
|
86
|
-
</span>
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function ChildSessionPanelMetaStrip({ tab }: { tab: ResolvedChildSessionTab }) {
|
|
91
|
-
const metaItems = [
|
|
92
|
-
tab.sessionTypeLabel,
|
|
93
|
-
tab.preferredModel,
|
|
94
|
-
tab.projectName,
|
|
95
|
-
].filter((value): value is string => Boolean(value?.trim()));
|
|
96
|
-
|
|
97
|
-
if (metaItems.length === 0 && !tab.projectRoot) {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<div className="mt-3 space-y-2">
|
|
103
|
-
{metaItems.length > 0 ? (
|
|
104
|
-
<div className="flex flex-wrap gap-1.5">
|
|
105
|
-
{metaItems.map((item) => (
|
|
106
|
-
<ChildSessionPanelMetaChip key={item} value={item} />
|
|
107
|
-
))}
|
|
108
|
-
</div>
|
|
109
|
-
) : null}
|
|
110
|
-
{tab.projectRoot ? (
|
|
111
|
-
<div
|
|
112
|
-
title={tab.projectRoot}
|
|
113
|
-
className="truncate rounded-xl border border-gray-200/70 bg-gray-50/80 px-2.5 py-2 font-mono text-[11px] text-gray-500"
|
|
114
|
-
>
|
|
115
|
-
{tab.projectRoot}
|
|
116
|
-
</div>
|
|
117
|
-
) : null}
|
|
118
|
-
</div>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function ChatChildSessionPanel({
|
|
123
|
-
tabs,
|
|
124
|
-
activeSessionKey,
|
|
125
|
-
onSelectSession,
|
|
126
|
-
onClose,
|
|
127
|
-
onBackToParent,
|
|
128
|
-
onToolAction,
|
|
129
|
-
}: ChatChildSessionPanelProps) {
|
|
130
|
-
const presenter = usePresenter();
|
|
131
|
-
const resolvedTabs = useNcpChildSessionTabsView(tabs);
|
|
132
|
-
const optimisticReadAtBySessionKey = useChatSessionListStore(
|
|
133
|
-
(state) => state.optimisticReadAtBySessionKey,
|
|
134
|
-
);
|
|
135
|
-
const activeTab =
|
|
136
|
-
resolvedTabs.find((tab) => tab.sessionKey === activeSessionKey) ??
|
|
137
|
-
resolvedTabs[0] ??
|
|
138
|
-
null;
|
|
139
|
-
const activeTabSessionKey = activeTab?.sessionKey ?? null;
|
|
140
|
-
const activeTabReadAt = activeTab?.lastMessageAt?.trim() ?? null;
|
|
141
|
-
const hasParentSession = resolvedTabs.some((tab) =>
|
|
142
|
-
Boolean(tab.parentSessionKey),
|
|
143
|
-
);
|
|
144
|
-
const shouldShowTabs = resolvedTabs.length > 1;
|
|
145
|
-
|
|
146
|
-
useEffect(() => {
|
|
147
|
-
const syncActiveTabReadState = () => {
|
|
148
|
-
if (!activeTabSessionKey || !activeTabReadAt) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
presenter.chatSessionListManager.markSessionRead(
|
|
152
|
-
activeTabSessionKey,
|
|
153
|
-
activeTabReadAt,
|
|
154
|
-
activeTab?.readAt ?? null,
|
|
155
|
-
);
|
|
156
|
-
};
|
|
157
|
-
syncActiveTabReadState();
|
|
158
|
-
}, [activeTab?.readAt, activeTabReadAt, activeTabSessionKey, presenter]);
|
|
159
|
-
|
|
160
|
-
if (!activeTab) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return (
|
|
165
|
-
<aside className="hidden md:flex md:w-[24rem] lg:w-[28rem] shrink-0 border-l border-gray-200/70 bg-white/90 backdrop-blur-sm">
|
|
166
|
-
<div className="flex h-full min-h-0 w-full flex-col">
|
|
167
|
-
<div className="border-b border-gray-200/70 px-4 py-3">
|
|
168
|
-
<div className="flex items-center justify-between gap-3">
|
|
169
|
-
<button
|
|
170
|
-
type="button"
|
|
171
|
-
onClick={onBackToParent}
|
|
172
|
-
className={cn(
|
|
173
|
-
"inline-flex items-center gap-1 text-xs font-medium text-gray-600 transition-colors hover:text-gray-900",
|
|
174
|
-
!hasParentSession && "pointer-events-none opacity-0",
|
|
175
|
-
)}
|
|
176
|
-
>
|
|
177
|
-
<ArrowLeft className="h-3.5 w-3.5" />
|
|
178
|
-
<span>{t("chatBackToParent")}</span>
|
|
179
|
-
</button>
|
|
180
|
-
<button
|
|
181
|
-
type="button"
|
|
182
|
-
onClick={onClose}
|
|
183
|
-
className="rounded-full border border-gray-200/80 p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
|
184
|
-
aria-label={t("chatChildSessionClosePanel")}
|
|
185
|
-
>
|
|
186
|
-
<X className="h-4 w-4" />
|
|
187
|
-
</button>
|
|
188
|
-
</div>
|
|
189
|
-
{!shouldShowTabs ? (
|
|
190
|
-
<div className="mt-3 flex min-w-0 items-center gap-2 text-sm font-semibold text-gray-900">
|
|
191
|
-
{activeTab.agentId ? (
|
|
192
|
-
<AgentIdentityAvatar
|
|
193
|
-
agentId={activeTab.agentId}
|
|
194
|
-
className="h-5 w-5 shrink-0"
|
|
195
|
-
/>
|
|
196
|
-
) : null}
|
|
197
|
-
<span className="truncate" title={activeTab.sessionKey}>
|
|
198
|
-
{activeTab.title}
|
|
199
|
-
</span>
|
|
200
|
-
</div>
|
|
201
|
-
) : null}
|
|
202
|
-
{shouldShowTabs ? (
|
|
203
|
-
<div className="mt-3 overflow-x-auto custom-scrollbar">
|
|
204
|
-
<Tabs value={activeSessionKey} onValueChange={onSelectSession}>
|
|
205
|
-
<TabsList className="h-auto min-w-max justify-start gap-1.5 rounded-none bg-transparent p-0 text-gray-500">
|
|
206
|
-
{resolvedTabs.map((tab) => {
|
|
207
|
-
const optimisticReadAt = optimisticReadAtBySessionKey[tab.sessionKey];
|
|
208
|
-
const effectiveReadAt =
|
|
209
|
-
optimisticReadAt && tab.readAt
|
|
210
|
-
? (optimisticReadAt.localeCompare(tab.readAt) > 0
|
|
211
|
-
? optimisticReadAt
|
|
212
|
-
: tab.readAt)
|
|
213
|
-
: optimisticReadAt ?? tab.readAt;
|
|
214
|
-
const showUnreadDot = shouldShowUnreadSessionIndicator({
|
|
215
|
-
active: tab.sessionKey === activeSessionKey,
|
|
216
|
-
lastMessageAt: tab.lastMessageAt,
|
|
217
|
-
readAt: effectiveReadAt,
|
|
218
|
-
runStatus: tab.runStatus,
|
|
219
|
-
});
|
|
220
|
-
return (
|
|
221
|
-
<TabsTrigger
|
|
222
|
-
key={tab.sessionKey}
|
|
223
|
-
value={tab.sessionKey}
|
|
224
|
-
className="gap-2 rounded-full border border-gray-200/80 bg-white/85 px-2.5 py-1.5 text-xs font-medium text-gray-600 shadow-none hover:border-primary/30 hover:text-primary data-[state=active]:border-primary/30 data-[state=active]:bg-primary-50/70 data-[state=active]:text-primary data-[state=active]:shadow-sm"
|
|
225
|
-
>
|
|
226
|
-
{tab.agentId ? (
|
|
227
|
-
<AgentIdentityAvatar
|
|
228
|
-
agentId={tab.agentId}
|
|
229
|
-
className="h-4 w-4 shrink-0"
|
|
230
|
-
/>
|
|
231
|
-
) : null}
|
|
232
|
-
<span className="max-w-[132px] truncate">
|
|
233
|
-
{tab.title}
|
|
234
|
-
</span>
|
|
235
|
-
{showUnreadDot ? (
|
|
236
|
-
<span
|
|
237
|
-
aria-label={t("chatSessionUnread")}
|
|
238
|
-
className="h-2 w-2 shrink-0 rounded-full bg-primary"
|
|
239
|
-
/>
|
|
240
|
-
) : null}
|
|
241
|
-
</TabsTrigger>
|
|
242
|
-
);
|
|
243
|
-
})}
|
|
244
|
-
</TabsList>
|
|
245
|
-
</Tabs>
|
|
246
|
-
</div>
|
|
247
|
-
) : null}
|
|
248
|
-
<ChildSessionPanelMetaStrip tab={activeTab} />
|
|
249
|
-
</div>
|
|
250
|
-
|
|
251
|
-
<div className="flex-1 min-h-0">
|
|
252
|
-
{resolvedTabs.map((tab) => (
|
|
253
|
-
<div
|
|
254
|
-
key={tab.sessionKey}
|
|
255
|
-
className={cn(
|
|
256
|
-
"h-full",
|
|
257
|
-
tab.sessionKey === activeSessionKey ? "block" : "hidden",
|
|
258
|
-
)}
|
|
259
|
-
>
|
|
260
|
-
<ChildSessionPanelConversation
|
|
261
|
-
sessionKey={tab.sessionKey}
|
|
262
|
-
onToolAction={onToolAction}
|
|
263
|
-
/>
|
|
264
|
-
</div>
|
|
265
|
-
))}
|
|
266
|
-
</div>
|
|
267
|
-
</div>
|
|
268
|
-
</aside>
|
|
269
|
-
);
|
|
270
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export const CONFIG_SPLIT_GRID_CLASS = 'grid min-h-0 grid-cols-1 gap-5 xl:grid-cols-[340px_minmax(0,1fr)]';
|
|
2
|
-
|
|
3
|
-
export const CONFIG_SIDEBAR_CARD_CLASS =
|
|
4
|
-
'flex min-h-[520px] min-h-0 min-w-0 flex-col overflow-hidden rounded-2xl border border-gray-200/70 bg-white shadow-card xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]';
|
|
5
|
-
|
|
6
|
-
export const CONFIG_DETAIL_CARD_CLASS =
|
|
7
|
-
'flex min-h-[520px] min-h-0 min-w-0 flex-col overflow-hidden rounded-2xl border border-gray-200/70 bg-white shadow-card xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]';
|
|
8
|
-
|
|
9
|
-
export const CONFIG_EMPTY_DETAIL_CARD_CLASS =
|
|
10
|
-
'flex min-h-[520px] min-w-0 items-center justify-center overflow-hidden rounded-2xl border border-gray-200/70 bg-white px-6 text-center xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]';
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react';
|
|
2
|
-
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import { MarketplacePage } from '@/components/marketplace/MarketplacePage';
|
|
4
|
-
import type {
|
|
5
|
-
MarketplaceInstalledRecord,
|
|
6
|
-
MarketplaceInstalledView,
|
|
7
|
-
MarketplaceItemSummary,
|
|
8
|
-
MarketplaceListView
|
|
9
|
-
} from '@/api/types';
|
|
10
|
-
|
|
11
|
-
type ItemsQueryState = {
|
|
12
|
-
data?: MarketplaceListView;
|
|
13
|
-
isLoading: boolean;
|
|
14
|
-
isFetching: boolean;
|
|
15
|
-
isError: boolean;
|
|
16
|
-
error: Error | null;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type InstalledQueryState = {
|
|
20
|
-
data?: MarketplaceInstalledView;
|
|
21
|
-
isLoading: boolean;
|
|
22
|
-
isFetching: boolean;
|
|
23
|
-
isError: boolean;
|
|
24
|
-
error: Error | null;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const mocks = vi.hoisted(() => ({
|
|
28
|
-
navigate: vi.fn(),
|
|
29
|
-
docOpen: vi.fn(),
|
|
30
|
-
confirm: vi.fn(),
|
|
31
|
-
itemsQuery: null as unknown as ItemsQueryState,
|
|
32
|
-
installedQuery: null as unknown as InstalledQueryState,
|
|
33
|
-
installMutation: {
|
|
34
|
-
mutateAsync: vi.fn(),
|
|
35
|
-
isPending: false,
|
|
36
|
-
variables: undefined
|
|
37
|
-
},
|
|
38
|
-
manageMutation: {
|
|
39
|
-
mutate: vi.fn(),
|
|
40
|
-
mutateAsync: vi.fn(),
|
|
41
|
-
isPending: false,
|
|
42
|
-
variables: undefined
|
|
43
|
-
}
|
|
44
|
-
}));
|
|
45
|
-
|
|
46
|
-
vi.mock('react-router-dom', async () => {
|
|
47
|
-
const actual = await vi.importActual('react-router-dom');
|
|
48
|
-
return {
|
|
49
|
-
...(actual as object),
|
|
50
|
-
useNavigate: () => mocks.navigate,
|
|
51
|
-
useParams: () => ({})
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
vi.mock('@/components/doc-browser', () => ({
|
|
56
|
-
useDocBrowser: () => ({
|
|
57
|
-
open: mocks.docOpen
|
|
58
|
-
})
|
|
59
|
-
}));
|
|
60
|
-
|
|
61
|
-
vi.mock('@/components/providers/I18nProvider', () => ({
|
|
62
|
-
useI18n: () => ({
|
|
63
|
-
language: 'en'
|
|
64
|
-
})
|
|
65
|
-
}));
|
|
66
|
-
|
|
67
|
-
vi.mock('@/hooks/useConfirmDialog', () => ({
|
|
68
|
-
useConfirmDialog: () => ({
|
|
69
|
-
confirm: mocks.confirm,
|
|
70
|
-
ConfirmDialog: () => null
|
|
71
|
-
})
|
|
72
|
-
}));
|
|
73
|
-
|
|
74
|
-
vi.mock('@/hooks/useMarketplace', () => ({
|
|
75
|
-
useMarketplaceItems: () => mocks.itemsQuery,
|
|
76
|
-
useMarketplaceInstalled: () => mocks.installedQuery,
|
|
77
|
-
useInstallMarketplaceItem: () => mocks.installMutation,
|
|
78
|
-
useManageMarketplaceItem: () => mocks.manageMutation
|
|
79
|
-
}));
|
|
80
|
-
|
|
81
|
-
function createMarketplaceItem(overrides: Partial<MarketplaceItemSummary> = {}): MarketplaceItemSummary {
|
|
82
|
-
return {
|
|
83
|
-
id: 'skill-web-search',
|
|
84
|
-
slug: 'web-search',
|
|
85
|
-
type: 'skill',
|
|
86
|
-
name: 'Web Search',
|
|
87
|
-
summary: 'Search the web from the marketplace',
|
|
88
|
-
summaryI18n: { en: 'Search the web from the marketplace' },
|
|
89
|
-
tags: ['search'],
|
|
90
|
-
author: 'NextClaw',
|
|
91
|
-
install: {
|
|
92
|
-
kind: 'marketplace',
|
|
93
|
-
spec: '@nextclaw/web-search',
|
|
94
|
-
command: 'nextclaw skills install @nextclaw/web-search'
|
|
95
|
-
},
|
|
96
|
-
updatedAt: '2026-03-17T00:00:00.000Z',
|
|
97
|
-
...overrides
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function createPluginMarketplaceItem(overrides: Partial<MarketplaceItemSummary> = {}): MarketplaceItemSummary {
|
|
102
|
-
return createMarketplaceItem({
|
|
103
|
-
id: 'plugin-codex-runtime',
|
|
104
|
-
slug: 'codex-runtime',
|
|
105
|
-
type: 'plugin',
|
|
106
|
-
name: 'Codex SDK NCP Runtime',
|
|
107
|
-
summary: 'Optional Codex runtime for NextClaw',
|
|
108
|
-
summaryI18n: { en: 'Optional Codex runtime for NextClaw' },
|
|
109
|
-
install: {
|
|
110
|
-
kind: 'npm',
|
|
111
|
-
spec: '@nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk',
|
|
112
|
-
command: 'npm install @nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk'
|
|
113
|
-
},
|
|
114
|
-
...overrides
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function createInstalledRecord(overrides: Partial<MarketplaceInstalledRecord> = {}): MarketplaceInstalledRecord {
|
|
119
|
-
return {
|
|
120
|
-
type: 'plugin',
|
|
121
|
-
id: '@nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk',
|
|
122
|
-
spec: '@nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk',
|
|
123
|
-
label: 'Codex SDK NCP Runtime',
|
|
124
|
-
enabled: true,
|
|
125
|
-
origin: 'marketplace',
|
|
126
|
-
source: 'marketplace',
|
|
127
|
-
installedAt: '2026-03-19T00:00:00.000Z',
|
|
128
|
-
...overrides
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function createItemsQuery(overrides: Partial<Record<string, unknown>> = {}) {
|
|
133
|
-
return {
|
|
134
|
-
data: undefined as MarketplaceListView | undefined,
|
|
135
|
-
isLoading: false,
|
|
136
|
-
isFetching: false,
|
|
137
|
-
isError: false,
|
|
138
|
-
error: null,
|
|
139
|
-
...overrides
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function createInstalledQuery(overrides: Partial<Record<string, unknown>> = {}) {
|
|
144
|
-
return {
|
|
145
|
-
data: {
|
|
146
|
-
type: 'skill',
|
|
147
|
-
total: 0,
|
|
148
|
-
specs: [],
|
|
149
|
-
records: []
|
|
150
|
-
} satisfies MarketplaceInstalledView,
|
|
151
|
-
isLoading: false,
|
|
152
|
-
isFetching: false,
|
|
153
|
-
isError: false,
|
|
154
|
-
error: null,
|
|
155
|
-
...overrides
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
describe('MarketplacePage', () => {
|
|
160
|
-
beforeEach(() => {
|
|
161
|
-
mocks.navigate.mockReset();
|
|
162
|
-
mocks.docOpen.mockReset();
|
|
163
|
-
mocks.confirm.mockReset();
|
|
164
|
-
mocks.installMutation.mutateAsync.mockReset();
|
|
165
|
-
mocks.manageMutation.mutate.mockReset();
|
|
166
|
-
mocks.manageMutation.mutateAsync.mockReset();
|
|
167
|
-
mocks.installMutation.isPending = false;
|
|
168
|
-
mocks.installMutation.variables = undefined;
|
|
169
|
-
mocks.manageMutation.isPending = false;
|
|
170
|
-
mocks.manageMutation.variables = undefined;
|
|
171
|
-
mocks.itemsQuery = createItemsQuery();
|
|
172
|
-
mocks.installedQuery = createInstalledQuery();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('renders skeleton cards during initial skills loading', () => {
|
|
176
|
-
mocks.itemsQuery = createItemsQuery({
|
|
177
|
-
isLoading: true,
|
|
178
|
-
isFetching: true
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const { container } = render(<MarketplacePage forcedType="skills" />);
|
|
182
|
-
|
|
183
|
-
expect(screen.getByTestId('marketplace-list-skeleton')).toBeTruthy();
|
|
184
|
-
expect(container.querySelectorAll('[data-testid="marketplace-list-skeleton"] > article')).toHaveLength(12);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('keeps loaded cards visible during background refresh', () => {
|
|
188
|
-
mocks.itemsQuery = createItemsQuery({
|
|
189
|
-
data: {
|
|
190
|
-
total: 1,
|
|
191
|
-
page: 1,
|
|
192
|
-
pageSize: 12,
|
|
193
|
-
totalPages: 1,
|
|
194
|
-
sort: 'relevance',
|
|
195
|
-
items: [createMarketplaceItem()]
|
|
196
|
-
} satisfies MarketplaceListView,
|
|
197
|
-
isFetching: true
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
render(<MarketplacePage forcedType="skills" />);
|
|
201
|
-
|
|
202
|
-
expect(screen.queryByTestId('marketplace-list-skeleton')).toBeNull();
|
|
203
|
-
expect(screen.getByText('Web Search')).toBeTruthy();
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('does not render the redundant plugin type label in plugin cards', () => {
|
|
207
|
-
mocks.itemsQuery = createItemsQuery({
|
|
208
|
-
data: {
|
|
209
|
-
total: 1,
|
|
210
|
-
page: 1,
|
|
211
|
-
pageSize: 12,
|
|
212
|
-
totalPages: 1,
|
|
213
|
-
sort: 'relevance',
|
|
214
|
-
items: [
|
|
215
|
-
createMarketplaceItem({
|
|
216
|
-
id: 'plugin-codex-runtime',
|
|
217
|
-
slug: 'codex-runtime',
|
|
218
|
-
type: 'plugin',
|
|
219
|
-
name: 'Codex SDK NCP Runtime',
|
|
220
|
-
summary: 'Optional Codex runtime for NextClaw',
|
|
221
|
-
summaryI18n: { en: 'Optional Codex runtime for NextClaw' },
|
|
222
|
-
install: {
|
|
223
|
-
kind: 'npm',
|
|
224
|
-
spec: '@nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk',
|
|
225
|
-
command: 'npm install @nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk'
|
|
226
|
-
}
|
|
227
|
-
})
|
|
228
|
-
]
|
|
229
|
-
} satisfies MarketplaceListView
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
const { container } = render(<MarketplacePage forcedType="plugins" />);
|
|
233
|
-
const card = container.querySelector('article');
|
|
234
|
-
|
|
235
|
-
expect(card?.textContent).toContain('@nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk');
|
|
236
|
-
expect(card?.textContent).not.toContain('Plugin');
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it('does not dim the loaded list during background refresh', () => {
|
|
240
|
-
mocks.itemsQuery = createItemsQuery({
|
|
241
|
-
data: {
|
|
242
|
-
total: 1,
|
|
243
|
-
page: 1,
|
|
244
|
-
pageSize: 12,
|
|
245
|
-
totalPages: 1,
|
|
246
|
-
sort: 'relevance',
|
|
247
|
-
items: [createPluginMarketplaceItem()]
|
|
248
|
-
} satisfies MarketplaceListView,
|
|
249
|
-
isFetching: true
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
const { container } = render(<MarketplacePage forcedType="plugins" />);
|
|
253
|
-
|
|
254
|
-
expect(screen.getByText('Codex SDK NCP Runtime')).toBeTruthy();
|
|
255
|
-
expect(container.querySelector('.opacity-70')).toBeNull();
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('only disables the targeted plugin action while a manage request is pending', async () => {
|
|
259
|
-
const user = userEvent.setup();
|
|
260
|
-
let resolveMutation: (() => void) | undefined;
|
|
261
|
-
mocks.itemsQuery = createItemsQuery({
|
|
262
|
-
data: {
|
|
263
|
-
total: 2,
|
|
264
|
-
page: 1,
|
|
265
|
-
pageSize: 12,
|
|
266
|
-
totalPages: 1,
|
|
267
|
-
sort: 'relevance',
|
|
268
|
-
items: [
|
|
269
|
-
createPluginMarketplaceItem(),
|
|
270
|
-
createPluginMarketplaceItem({
|
|
271
|
-
id: 'plugin-claude-runtime',
|
|
272
|
-
slug: 'claude-runtime',
|
|
273
|
-
name: 'Claude Agent Runtime',
|
|
274
|
-
install: {
|
|
275
|
-
kind: 'npm',
|
|
276
|
-
spec: '@nextclaw/nextclaw-ncp-runtime-plugin-claude-code-sdk',
|
|
277
|
-
command: 'npm install @nextclaw/nextclaw-ncp-runtime-plugin-claude-code-sdk'
|
|
278
|
-
}
|
|
279
|
-
})
|
|
280
|
-
]
|
|
281
|
-
} satisfies MarketplaceListView
|
|
282
|
-
});
|
|
283
|
-
mocks.installedQuery = createInstalledQuery({
|
|
284
|
-
data: {
|
|
285
|
-
type: 'plugin',
|
|
286
|
-
total: 2,
|
|
287
|
-
specs: [
|
|
288
|
-
'@nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk',
|
|
289
|
-
'@nextclaw/nextclaw-ncp-runtime-plugin-claude-code-sdk'
|
|
290
|
-
],
|
|
291
|
-
records: [
|
|
292
|
-
createInstalledRecord(),
|
|
293
|
-
createInstalledRecord({
|
|
294
|
-
id: '@nextclaw/nextclaw-ncp-runtime-plugin-claude-code-sdk',
|
|
295
|
-
spec: '@nextclaw/nextclaw-ncp-runtime-plugin-claude-code-sdk',
|
|
296
|
-
label: 'Claude Agent Runtime'
|
|
297
|
-
})
|
|
298
|
-
]
|
|
299
|
-
} satisfies MarketplaceInstalledView
|
|
300
|
-
});
|
|
301
|
-
mocks.manageMutation.mutateAsync.mockImplementation(
|
|
302
|
-
() => new Promise<void>((resolve) => {
|
|
303
|
-
resolveMutation = resolve;
|
|
304
|
-
})
|
|
305
|
-
);
|
|
306
|
-
|
|
307
|
-
render(<MarketplacePage forcedType="plugins" />);
|
|
308
|
-
|
|
309
|
-
const disableButtons = screen.getAllByRole('button', { name: 'Disable' });
|
|
310
|
-
const firstDisableButton = disableButtons[0];
|
|
311
|
-
const secondDisableButton = disableButtons[1];
|
|
312
|
-
|
|
313
|
-
await user.click(firstDisableButton);
|
|
314
|
-
|
|
315
|
-
expect(mocks.manageMutation.mutateAsync).toHaveBeenCalledTimes(1);
|
|
316
|
-
expect(firstDisableButton.hasAttribute('disabled')).toBe(true);
|
|
317
|
-
expect(firstDisableButton.textContent).toContain('Disabling');
|
|
318
|
-
expect(secondDisableButton.hasAttribute('disabled')).toBe(false);
|
|
319
|
-
|
|
320
|
-
resolveMutation?.();
|
|
321
|
-
});
|
|
322
|
-
});
|