({
setViewState: setParentViewState,
setResult,
onManageComplete,
onSearchModeChange,
targetPlugin,
targetMarketplace,
action
}: Props)
| 395 | }); |
| 396 | } |
| 397 | export function ManagePlugins({ |
| 398 | setViewState: setParentViewState, |
| 399 | setResult, |
| 400 | onManageComplete, |
| 401 | onSearchModeChange, |
| 402 | targetPlugin, |
| 403 | targetMarketplace, |
| 404 | action |
| 405 | }: Props): React.ReactNode { |
| 406 | // App state for MCP access |
| 407 | const mcpClients = useAppState(s => s.mcp.clients); |
| 408 | const mcpTools = useAppState(s_0 => s_0.mcp.tools); |
| 409 | const pluginErrors = useAppState(s_1 => s_1.plugins.errors); |
| 410 | const flaggedPlugins = getFlaggedPlugins(); |
| 411 | |
| 412 | // Search state |
| 413 | const [isSearchMode, setIsSearchModeRaw] = useState(false); |
| 414 | const setIsSearchMode = useCallback((active: boolean) => { |
| 415 | setIsSearchModeRaw(active); |
| 416 | onSearchModeChange?.(active); |
| 417 | }, [onSearchModeChange]); |
| 418 | const isTerminalFocused = useTerminalFocus(); |
| 419 | const { |
| 420 | columns: terminalWidth |
| 421 | } = useTerminalSize(); |
| 422 | |
| 423 | // View state |
| 424 | const [viewState, setViewState] = useState<ViewState>('plugin-list'); |
| 425 | const { |
| 426 | query: searchQuery, |
| 427 | setQuery: setSearchQuery, |
| 428 | cursorOffset: searchCursorOffset |
| 429 | } = useSearchInput({ |
| 430 | isActive: viewState === 'plugin-list' && isSearchMode, |
| 431 | onExit: () => { |
| 432 | setIsSearchMode(false); |
| 433 | } |
| 434 | }); |
| 435 | const [selectedPlugin, setSelectedPlugin] = useState<PluginState | null>(null); |
| 436 | |
| 437 | // Data state |
| 438 | const [marketplaces, setMarketplaces] = useState<MarketplaceInfo[]>([]); |
| 439 | const [pluginStates, setPluginStates] = useState<PluginState[]>([]); |
| 440 | const [loading, setLoading] = useState(true); |
| 441 | const [pendingToggles, setPendingToggles] = useState<Map<string, 'will-enable' | 'will-disable'>>(new Map()); |
| 442 | |
| 443 | // Guard to prevent auto-navigation from re-triggering after the user |
| 444 | // navigates away (targetPlugin is never cleared by the parent). |
| 445 | const hasAutoNavigated = useRef(false); |
| 446 | // Auto-action (enable/disable/uninstall) to fire after auto-navigation lands. |
| 447 | // Ref, not state: it's consumed by a one-shot effect that already re-runs on |
| 448 | // viewState/selectedPlugin, so a render-triggering state var would be redundant. |
| 449 | const pendingAutoActionRef = useRef<'enable' | 'disable' | 'uninstall' | undefined>(undefined); |
| 450 | |
| 451 | // MCP toggle hook |
| 452 | const toggleMcpServer = useMcpToggleEnabled(); |
| 453 | |
| 454 | // Handle escape to go back - viewState-dependent navigation |
nothing calls this directly
no test coverage detected