From df0f98a1b7f2c7b8ff030a9cdd7d508d00f06422 Mon Sep 17 00:00:00 2001 From: OpenPets Dev Date: Fri, 19 Jun 2026 07:32:16 +0000 Subject: [PATCH] Extract integrations detail dialog seam --- apps/desktop/scripts/run-tests.mjs | 1 + apps/desktop/src/check-packaging-contract.ts | 6 +- .../renderer/src/control-center/codemap.md | 7 +- .../integrations-view-detail-dialog.tsx | 387 ++++++++++++++++++ .../src/control-center/integrations-view.tsx | 242 ++--------- .../integrations-view-detail-dialog.test.ts | 16 + .../tests/integrations-view-sections.test.ts | 14 +- 7 files changed, 453 insertions(+), 220 deletions(-) create mode 100644 apps/desktop/src/renderer/src/control-center/integrations-view-detail-dialog.tsx create mode 100644 apps/desktop/tests/integrations-view-detail-dialog.test.ts diff --git a/apps/desktop/scripts/run-tests.mjs b/apps/desktop/scripts/run-tests.mjs index 21808bc1..0271d651 100644 --- a/apps/desktop/scripts/run-tests.mjs +++ b/apps/desktop/scripts/run-tests.mjs @@ -73,6 +73,7 @@ const behaviorTests = [ ".test-dist/tests/mcp-toolkit-installer-seams.test.js", ".test-dist/tests/mcp-toolkit-catalog.test.js", ".test-dist/tests/integrations-view-toolkit-split.test.js", + ".test-dist/tests/integrations-view-detail-dialog.test.js", ".test-dist/tests/integrations-view-state.test.js", ".test-dist/tests/familiars-view-detail-dialog.test.js", ".test-dist/tests/familiars-view-state.test.js", diff --git a/apps/desktop/src/check-packaging-contract.ts b/apps/desktop/src/check-packaging-contract.ts index 826e508e..a49faa6f 100644 --- a/apps/desktop/src/check-packaging-contract.ts +++ b/apps/desktop/src/check-packaging-contract.ts @@ -129,6 +129,7 @@ const controlCenterFamiliarsStateSource = readFileSync(join(appDir, "src", "rend const controlCenterFamiliarsHelpersSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "familiars-view-helpers.ts"), "utf8"); const controlCenterFamiliarsEffectsSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "familiars-view-effects.ts"), "utf8"); const controlCenterIntegrationsSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "integrations-view.tsx"), "utf8"); +const controlCenterIntegrationsDetailDialogSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "integrations-view-detail-dialog.tsx"), "utf8"); const controlCenterIntegrationsStateSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "integrations-view-state.ts"), "utf8"); const controlCenterIntegrationsSharedSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "integrations-view-shared.tsx"), "utf8"); const controlCenterIntegrationsToolingSectionsSource = readFileSync(join(appDir, "src", "renderer", "src", "control-center", "integrations-view-tooling-sections.tsx"), "utf8"); @@ -234,6 +235,7 @@ const controlCenterInternalUiSource = [ const agentSetupCombinedSource = `${agentSetupSource}\n${agentSetupActionsSource}\n${agentSetupEditorToolsSource}`; const controlCenterIntegrationsCombinedSource = [ controlCenterIntegrationsSource, + controlCenterIntegrationsDetailDialogSource, controlCenterIntegrationsSharedSource, controlCenterIntegrationsToolingSectionsSource, controlCenterIntegrationsMcpToolkitSectionSource, @@ -599,9 +601,11 @@ assert.match(controlCenterFamiliarsEffectsSource, /export function useCatalogSea assert.match(controlCenterFamiliarsEffectsSource, /export function useCatalogSearchPrefetch/, "Control Center familiars effects seam must export catalog search prefetch behavior."); assert.match(controlCenterRouteDataLoadersSource, /getPetsState/, "Control Center shared route data loaders must bridge familiars state APIs."); assert.match(controlCenterIntegrationsSource, /export function IntegrationsView\(\)/, "Control Center must include the integrations page."); +assert.match(controlCenterIntegrationsSource, /from "\.\/integrations-view-detail-dialog"/, "Control Center integrations route must import the extracted detail dialog seam."); assert.match(controlCenterIntegrationsSource, /from "\.\/integrations-view-state"/, "Control Center integrations route must import the extracted integrations state seam."); assert.match(controlCenterIntegrationsSource, /from "\.\/integrations-view-shared"/, "Control Center integrations route must import the extracted integrations shared seam."); -assert.match(controlCenterIntegrationsSource, /from "\.\/integrations-view-tooling-sections"/, "Control Center integrations route must import the extracted integrations tooling seam."); +assert.match(controlCenterIntegrationsDetailDialogSource, /export function IntegrationsDetailDialog/, "Control Center integrations detail dialog seam must export the selected-integration overlay."); +assert.match(controlCenterIntegrationsDetailDialogSource, /from "\.\/integrations-view-tooling-sections"/, "Control Center integrations detail dialog seam must import the extracted integrations tooling seam."); assert.match(controlCenterIntegrationsStateSource, /export function useIntegrationsViewState/, "Control Center integrations state seam must export the route hook."); assert.match(controlCenterIntegrationsStateSource, /from "\.\/route-data-loaders"/, "Control Center integrations state seam must compose shared route data loaders."); assert.match(controlCenterIntegrationsStateSource, /from "\.\/integrations-view-shared"/, "Control Center integrations state seam must compose the extracted integrations shared helpers."); diff --git a/apps/desktop/src/renderer/src/control-center/codemap.md b/apps/desktop/src/renderer/src/control-center/codemap.md index fd293144..f36ed2a8 100644 --- a/apps/desktop/src/renderer/src/control-center/codemap.md +++ b/apps/desktop/src/renderer/src/control-center/codemap.md @@ -22,8 +22,11 @@ surface they depend on. card-level actions while composing the extracted detail overlay. - `familiars-view-detail-dialog.tsx` owns the selected-familiar detail overlay, animation previews, and detail actions. -- `integrations-view.tsx` owns integrations-route state, action callbacks, card - layout, and the remaining Claude/OpenCode/Cursor detail shell. +- `integrations-view.tsx` owns the integrations-route shell, card grid, and + message/loading presentation while composing the extracted detail overlay. +- `integrations-view-detail-dialog.tsx` owns the selected-integration overlay, + Claude/OpenCode/Cursor detail sections, and the remaining tooling dialog + composition. - `integrations-view-shared.tsx` owns route-local integration icons, command-path field UI, and status-tone helpers. - `integrations-view-tooling-sections.tsx` owns the MCP tool-server, diff --git a/apps/desktop/src/renderer/src/control-center/integrations-view-detail-dialog.tsx b/apps/desktop/src/renderer/src/control-center/integrations-view-detail-dialog.tsx new file mode 100644 index 00000000..f12fa1cc --- /dev/null +++ b/apps/desktop/src/renderer/src/control-center/integrations-view-detail-dialog.tsx @@ -0,0 +1,387 @@ +import type { ReactNode } from "react"; +import { useI18n } from "../i18n"; +import { + IntegrationIcon, + PathField, + claudeStatusTone, + cursorStatusTone, + opencodeStatusTone, +} from "./integrations-view-shared"; +import { + FamiliarOSMcpServerSections, + McpToolServersSection, + McpToolkitSection, + PiIntegrationSection, +} from "./integrations-view-tooling-sections"; +import * as Shared from "./shared"; +import type { + AgentSetupAction, + AgentSetupCommandPaths, + AgentSetupSnapshot, + FamiliarOSMcpServerHealth, + FamiliarOSMcpServerPreview, + McpToolkitInstallMode, + McpToolkitInstallResult, + McpToolkitPersistentTarget, +} from "./shared"; + +const { + Button, + CloseIcon, + HookIcon, + InstallIcon, + MemoryIcon, + NextIcon, + RefreshIcon, + RemoveIcon, + ReplaceIcon, + StatusPill, + commandModeLabelKeys, +} = Shared; + +type IntegrationsDetailDialogProps = { + snapshot: AgentSetupSnapshot; + selectedId: string; + selectedIntegrationName?: string; + selectedToolkitId: string; + onSelectedIdChange: (value: string | null) => void; + onSelectedToolkitIdChange: (value: string) => void; + toolkitInstallMode: McpToolkitInstallMode; + onToolkitInstallModeChange: (value: McpToolkitInstallMode) => void; + toolkitPersistentTarget: McpToolkitPersistentTarget; + onToolkitPersistentTargetChange: (value: McpToolkitPersistentTarget) => void; + toolkitLastInstall: McpToolkitInstallResult | null; + vanillaChatTools: string[]; + vanillaChatSaving: boolean; + familiarosMcpPreview: FamiliarOSMcpServerPreview | null; + familiarosMcpTest: { busy: boolean; result: FamiliarOSMcpServerHealth | null }; + isBusy: boolean; + loadIntegrations: (selectedPetId?: string, commandMode?: AgentSetupSnapshot["commandMode"]) => Promise; + runIntegrationAction: (label: string, action: AgentSetupAction) => Promise; + updatePath: (key: keyof AgentSetupCommandPaths, value: string) => Promise; + changeCommandMode: (mode: AgentSetupSnapshot["commandMode"]) => void; + copyText: (label: string, value: string) => Promise; + openDocs: (url: string) => Promise; + testFamiliarOSMcpServer: () => Promise; + copyFamiliarOSMcpJson: () => Promise; + saveVanillaChatTools: (toolIds: string[]) => Promise; + toggleVanillaChatTool: (id: string) => void; + installToolkitHost: () => Promise; +}; + +export function IntegrationsDetailDialog({ + snapshot, + selectedId, + selectedIntegrationName, + selectedToolkitId, + onSelectedIdChange, + onSelectedToolkitIdChange, + toolkitInstallMode, + onToolkitInstallModeChange, + toolkitPersistentTarget, + onToolkitPersistentTargetChange, + toolkitLastInstall, + vanillaChatTools, + vanillaChatSaving, + familiarosMcpPreview, + familiarosMcpTest, + isBusy, + loadIntegrations, + runIntegrationAction, + updatePath, + changeCommandMode, + copyText, + openDocs, + testFamiliarOSMcpServer, + copyFamiliarOSMcpJson, + saveVanillaChatTools, + toggleVanillaChatTool, + installToolkitHost, +}: IntegrationsDetailDialogProps) { + const { t } = useI18n(); + const integrationDialogTitleId = `integration-detail-title-${selectedId}`; + + return
+
; +} + +function GlassDialog({ + selectedId, + selectedIntegrationName, + integrationDialogTitleId, + onClose, + children, +}: { + selectedId: string; + selectedIntegrationName?: string; + integrationDialogTitleId: string; + onClose: () => void; + children: ReactNode; +}) { + const { t } = useI18n(); + return +
+
+ +
+
+

{t("integrations.detail")}

+

{selectedIntegrationName}

+
+ +
+
{children}
+
; +} + +function ClaudeIntegrationSections({ + snapshot, + isBusy, + onRunIntegrationAction, + onUpdatePath, + onReloadIntegrations, +}: { + snapshot: AgentSetupSnapshot; + isBusy: boolean; + onRunIntegrationAction: (label: string, action: AgentSetupAction) => Promise; + onUpdatePath: (key: keyof AgentSetupCommandPaths, value: string) => Promise; + onReloadIntegrations: () => Promise; +}) { + const { t } = useI18n(); + return <> +
+
{t("integrations.connection")}{t("integrations.statusRouting")}
+
+
+ {snapshot.status.label} + {snapshot.status.details} +
+ {snapshot.status.state} +
+
+ +
+
{t("integrations.configuration")}{t("integrations.commandPaths")}
+
+ void onUpdatePath("claude", value)} disabled={isBusy} /> +
+
+ +
+
+
{t("integrations.optional")}{t("integrations.claudeHooks")}
+
+ {snapshot.hookStatus.status} +
+
+ + +
+
+
+
{t("integrations.included")}{t("integrations.instructions")}
+
+ {snapshot.memoryStatus.state} +
+ +
+
+ +
+
{t("integrations.actions")}{t("integrations.management")}
+
+ {snapshot.status.canConfigure && } + {snapshot.status.canReplace && } + {snapshot.status.canRemove && } + +
+
+ ; +} + +function OpenCodeIntegrationSections({ + snapshot, + isBusy, + onRunIntegrationAction, + onUpdatePath, + onReloadIntegrations, +}: { + snapshot: AgentSetupSnapshot; + isBusy: boolean; + onRunIntegrationAction: (label: string, action: AgentSetupAction) => Promise; + onUpdatePath: (key: keyof AgentSetupCommandPaths, value: string) => Promise; + onReloadIntegrations: () => Promise; +}) { + const { t } = useI18n(); + return <> +
+
{t("integrations.connection")}{t("integrations.globalSetup")}
+
+
+ {snapshot.opencodeStatus.label} + {snapshot.opencodeStatus.details} +
+ {snapshot.opencodeStatus.state} +
+
+ +
+
{t("integrations.configuration")}{t("integrations.commandPaths")}
+
+ void onUpdatePath("opencode", value)} disabled={isBusy} /> +
+
+ +
+
{t("integrations.actions")}{t("integrations.management")}
+
+ {snapshot.opencodeStatus.canInstall && } + {snapshot.opencodeStatus.canRemove && } + +
+
+ +
+ +
{t("integrations.advanced")}{t("integrations.configPreview")}
+ +
+
+        {JSON.stringify(snapshot.opencodePreview.configPreview, null, 2)}
+      
+
+ ; +} + +function CursorIntegrationSections({ + snapshot, + isBusy, + onRunIntegrationAction, + onReloadIntegrations, +}: { + snapshot: AgentSetupSnapshot; + isBusy: boolean; + onRunIntegrationAction: (label: string, action: AgentSetupAction) => Promise; + onReloadIntegrations: () => Promise; +}) { + const { t } = useI18n(); + return <> +
+
{t("integrations.connection")}{t("integrations.globalMcp")}
+
+
+ {snapshot.cursorStatus.label} + {snapshot.cursorStatus.details} +
+ {snapshot.cursorStatus.state} +
+
+ +
+
{t("integrations.actions")}{t("integrations.management")}
+
+ {snapshot.cursorStatus.canInstall && } + {snapshot.cursorStatus.canReplace && } + {snapshot.cursorStatus.canRemove && } + +
+
+ +
+ +
{t("integrations.advanced")}{t("integrations.rulesPreview")}
+ +
+

{snapshot.cursorPreview.rulesPath}

+
+        {snapshot.cursorPreview.rulesContent}
+      
+
+ ; +} diff --git a/apps/desktop/src/renderer/src/control-center/integrations-view.tsx b/apps/desktop/src/renderer/src/control-center/integrations-view.tsx index 98c9c07b..45499f5b 100644 --- a/apps/desktop/src/renderer/src/control-center/integrations-view.tsx +++ b/apps/desktop/src/renderer/src/control-center/integrations-view.tsx @@ -1,31 +1,17 @@ import { useI18n } from "../i18n"; import { IntegrationIcon, - PathField, - claudeStatusTone, - cursorStatusTone, - opencodeStatusTone, } from "./integrations-view-shared"; -import { - FamiliarOSMcpServerSections, - McpToolServersSection, - McpToolkitSection, - PiIntegrationSection, -} from "./integrations-view-tooling-sections"; +import { IntegrationsDetailDialog } from "./integrations-view-detail-dialog"; import { useIntegrationsViewState } from "./integrations-view-state"; import * as Shared from "./shared"; const { Button, - CloseIcon, ConfigureIcon, GlassCard, - HookIcon, InstallIcon, - MemoryIcon, RefreshIcon, - RemoveIcon, - ReplaceIcon, Spinner, StatusPill, } = Shared; @@ -75,7 +61,6 @@ export function IntegrationsView() { ); } - const integrationDialogTitleId = selectedId ? `integration-detail-title-${selectedId}` : undefined; return (
@@ -129,202 +114,35 @@ export function IntegrationsView() {
{selectedId && ( -
- -
- -
- {(selectedId === "claude" || selectedId === "opencode" || selectedId === "cursor") && ( -
-
{t("integrations.commandSource")}{t("integrations.cliMode")}
-

{t("integrations.linkToCentralPanel")}

-

{t(Shared.commandModeLabelKeys[snapshot.commandMode])}

-
- )} - - {selectedId === "mcp-tool-servers" && ( - - )} - - {selectedId === "familiaros-mcp-server" && ( - updatePath("node", value)} - onSelectPet={(petId) => void loadIntegrations(petId)} - onTestServer={testFamiliarOSMcpServer} - onCopyMcpJson={copyFamiliarOSMcpJson} - /> - )} - - {selectedId === "claude" && ( - <> -
-
{t("integrations.connection")}{t("integrations.statusRouting")}
-
-
- {snapshot.status.label} - {snapshot.status.details} -
- {snapshot.status.state} -
-
- -
-
{t("integrations.configuration")}{t("integrations.commandPaths")}
-
- updatePath("claude", value)} disabled={isBusy} /> -
-
- -
-
-
{t("integrations.optional")}{t("integrations.claudeHooks")}
-
- {snapshot.hookStatus.status} -
-
- - -
-
-
-
{t("integrations.included")}{t("integrations.instructions")}
-
- {snapshot.memoryStatus.state} -
- -
-
- -
-
{t("integrations.actions")}{t("integrations.management")}
-
- {snapshot.status.canConfigure && } - {snapshot.status.canReplace && } - {snapshot.status.canRemove && } - -
-
- - )} - - {selectedId === "opencode" && ( - <> -
-
{t("integrations.connection")}{t("integrations.globalSetup")}
-
-
- {snapshot.opencodeStatus.label} - {snapshot.opencodeStatus.details} -
- {snapshot.opencodeStatus.state} -
-
- -
-
{t("integrations.configuration")}{t("integrations.commandPaths")}
-
- updatePath("opencode", value)} disabled={isBusy} /> -
-
- -
-
{t("integrations.actions")}{t("integrations.management")}
-
- {snapshot.opencodeStatus.canInstall && } - {snapshot.opencodeStatus.canRemove && } - -
-
- -
- -
{t("integrations.advanced")}{t("integrations.configPreview")}
- -
-
-                      {JSON.stringify(snapshot.opencodePreview.configPreview, null, 2)}
-                    
-
- - )} - - {selectedId === "cursor" && ( - <> -
-
{t("integrations.connection")}{t("integrations.globalMcp")}
-
-
- {snapshot.cursorStatus.label} - {snapshot.cursorStatus.details} -
- {snapshot.cursorStatus.state} -
-
- -
-
{t("integrations.actions")}{t("integrations.management")}
-
- {snapshot.cursorStatus.canInstall && } - {snapshot.cursorStatus.canReplace && } - {snapshot.cursorStatus.canRemove && } - -
-
- -
- -
{t("integrations.advanced")}{t("integrations.rulesPreview")}
- -
-

{snapshot.cursorPreview.rulesPath}

-
-                      {snapshot.cursorPreview.rulesContent}
-                    
-
- - )} - - {selectedId === "pi" && } - - {selectedId === "mcp-toolkit" && ( - - )} -
- - + )} ); diff --git a/apps/desktop/tests/integrations-view-detail-dialog.test.ts b/apps/desktop/tests/integrations-view-detail-dialog.test.ts new file mode 100644 index 00000000..4f6cb279 --- /dev/null +++ b/apps/desktop/tests/integrations-view-detail-dialog.test.ts @@ -0,0 +1,16 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const desktopRoot = process.env.FAMILIAROS_DESKTOP_ROOT ?? resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const integrationsViewSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view.tsx"), "utf8"); +const integrationsViewDetailDialogSource = readFileSync(resolve(desktopRoot, "src/renderer/src/control-center/integrations-view-detail-dialog.tsx"), "utf8"); + +assert.match(integrationsViewSource, /from "\.\/integrations-view-detail-dialog"/, "Integrations view must import the extracted detail dialog seam."); +assert.match(integrationsViewSource, /