diff --git a/desktop/src/__tests__/pluginsSettings.test.tsx b/desktop/src/__tests__/pluginsSettings.test.tsx index 3a1e9d7a..11e20e49 100644 --- a/desktop/src/__tests__/pluginsSettings.test.tsx +++ b/desktop/src/__tests__/pluginsSettings.test.tsx @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { fireEvent, render, screen } from '@testing-library/react' +import { act, fireEvent, render, screen } from '@testing-library/react' import '@testing-library/jest-dom' import { Settings } from '../pages/Settings' @@ -338,6 +338,59 @@ describe('Settings > Plugins tab', () => { expect(screen.getByText('Uninstall')).toBeInTheDocument() }) + it('keeps plugin detail hook order stable while the selected plugin reloads', () => { + usePluginStore.setState({ + selectedPlugin: { + id: 'github@claude-plugins-official', + name: 'github', + marketplace: 'claude-plugins-official', + scope: 'user', + enabled: false, + hasErrors: false, + isBuiltin: false, + description: 'GitHub integration', + componentCounts: { + commands: 1, + agents: 0, + skills: 0, + hooks: 0, + mcpServers: 0, + lspServers: 0, + }, + capabilities: { + commands: ['review-pr'], + agents: [], + skills: [], + hooks: [], + mcpServers: [], + lspServers: [], + }, + commandEntries: [ + { + name: 'review-pr', + description: 'Review the current pull request.', + }, + ], + agentEntries: [], + hookEntries: [], + skillEntries: [], + mcpServerEntries: [], + errors: [], + }, + }) + + const { container } = render() + switchToPluginsTab() + + expect(screen.getByText('GitHub integration')).toBeInTheDocument() + + act(() => { + usePluginStore.setState({ isDetailLoading: true }) + }) + + expect(container.querySelector('.animate-spin')).toBeInTheDocument() + }) + it('navigates plugin skills into the shared Skills page flow', () => { usePluginStore.setState({ selectedPlugin: { diff --git a/desktop/src/components/plugins/PluginDetail.tsx b/desktop/src/components/plugins/PluginDetail.tsx index ab91091f..14c0d897 100644 --- a/desktop/src/components/plugins/PluginDetail.tsx +++ b/desktop/src/components/plugins/PluginDetail.tsx @@ -42,6 +42,15 @@ export function PluginDetail() { const activeSession = sessions.find((session) => session.id === activeSessionId) const currentWorkDir = activeSession?.workDir || undefined + const otherCapabilityItems = useMemo( + () => + CAPABILITY_ORDER.map((key) => ({ + key, + items: selectedPlugin?.capabilities[key] ?? [], + })), + [selectedPlugin], + ) + if (isDetailLoading) { return (
@@ -54,14 +63,6 @@ export function PluginDetail() { const canMutate = selectedPlugin.scope !== 'managed' && selectedPlugin.scope !== 'builtin' const canNavigateSharedCapabilities = selectedPlugin.enabled - const otherCapabilityItems = useMemo( - () => - CAPABILITY_ORDER.map((key) => ({ - key, - items: selectedPlugin.capabilities[key], - })), - [selectedPlugin], - ) const runAction = async (key: string, fn: () => Promise) => { setActionKey(key)