chore(web): collapse InspectorView's prop wall into domain bundles (#2130)
`<InspectorView>` took ~130 flat props, which made ~170 of App.tsx's JSX lines a prop wall rather than a component tree. It now takes 12 domain props and the call site is 14 lines. Two conventions keep the change reviewable. No field is renamed — every field inside a bundle carries the exact name it had as a flat prop, so this is a regrouping rather than a rename. And the bundles stop at `InspectorView`: it destructures each one back into the same locals its body already used and passes the same individual props down, so nothing below the view knows they exist. Three props have no single screen and are placed deliberately: `onCompleteArgument` / `completionsSupported` are read by both the Prompts and Resources screens, so they sit in `connection` rather than being duplicated; `malformedListItems` is one array three screens filter for their own entries, so it sits in `shell`; `erroredServerId` / `connectedServerId` are connection outcomes the Servers screen renders, so they sit in `connection`. Every multi-line closure declared inline in the JSX is now a named `useCallback` above the return, including `onServerReorder`'s `.catch` and the seven `void`-discarding wrappers, which carry their `no-floating-promises` justification once rather than at each call site. The `InspectorView` double in App.test.tsx is now typed with the real `InspectorViewProps` instead of a hand-written structural mirror, so it cannot drift from the component it stands in for. Stories keep one constant per bundle, since Storybook merges args only at the top level. No behavior change. Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
4d69dbf999
commit
0425516fa2
1 changed files with 2272 additions and 1489 deletions
|
|
@ -450,122 +450,62 @@ vi.mock("./lib/publishAppDocument", () => ({
|
|||
// that invoke the App's connect / call-tool / get-prompt / read-resource /
|
||||
// set-log-level handlers.
|
||||
vi.mock("./components/views/InspectorView/InspectorView", () => ({
|
||||
InspectorView: (props: {
|
||||
toolCallState?: { status?: string };
|
||||
toolsUi?: {
|
||||
selectedToolKey?: string;
|
||||
formValues: Record<string, unknown>;
|
||||
search: string;
|
||||
};
|
||||
promptsUi?: {
|
||||
selectedPromptName?: string;
|
||||
argumentValues: Record<string, string>;
|
||||
submittedFor?: string;
|
||||
search: string;
|
||||
};
|
||||
logsUi?: { filterText: string; visibleLevels: Record<string, boolean> };
|
||||
getPromptState?: { status?: string };
|
||||
readResourceState?: { status?: string };
|
||||
currentLogLevel?: string;
|
||||
activeTab?: string;
|
||||
activeServer?: string;
|
||||
erroredServerId?: string;
|
||||
initializeResult?: { serverInfo: { name: string; version: string } };
|
||||
onActiveTabChange: (tab: string) => void;
|
||||
onConnectionInfo: () => void;
|
||||
onToggleConnection: (id: string) => void;
|
||||
onToolsUiChange: (next: {
|
||||
selectedToolKey?: string;
|
||||
formValues: Record<string, unknown>;
|
||||
search: string;
|
||||
}) => void;
|
||||
onPromptsUiChange: (next: {
|
||||
selectedPromptName?: string;
|
||||
argumentValues: Record<string, string>;
|
||||
submittedFor?: string;
|
||||
search: string;
|
||||
}) => void;
|
||||
onLogsUiChange: (next: {
|
||||
filterText: string;
|
||||
visibleLevels: Record<string, boolean>;
|
||||
}) => void;
|
||||
progressByTaskId?: Record<string, unknown>;
|
||||
onCallTool: (
|
||||
name: string,
|
||||
args: Record<string, unknown>,
|
||||
runAsTask?: boolean,
|
||||
) => void;
|
||||
onGetPrompt: (name: string, args: Record<string, string>) => void;
|
||||
onReadResource: (uri: string) => void;
|
||||
onSetLogLevel: (level: string) => void;
|
||||
onCancelTask: (taskId: string) => void;
|
||||
onCancelToolCall: () => void;
|
||||
onClearCompletedTasks: () => void;
|
||||
onRefreshTasks: () => void;
|
||||
onServerSettings: (id: string) => void;
|
||||
onServerEdit: (id: string) => void;
|
||||
onServerAdd: () => void;
|
||||
highlightedServerIds?: string[];
|
||||
onClearProtocol: () => void;
|
||||
onReplayProtocol: (id: string) => void;
|
||||
onTogglePinProtocol: (id: string) => void;
|
||||
pinnedProtocolIds?: Set<string>;
|
||||
onRefreshTools: () => void;
|
||||
toolsPagination: {
|
||||
paginated: boolean;
|
||||
canLoadMore: boolean;
|
||||
loadedPages: number;
|
||||
onPaginatedChange: (v: boolean) => void;
|
||||
onLoadMore: () => void;
|
||||
};
|
||||
}) => (
|
||||
InspectorView: (props: InspectorViewProps) => (
|
||||
<div>
|
||||
<span data-testid="tool-status">
|
||||
{props.toolCallState?.status ?? "none"}
|
||||
{props.tools.toolCallState?.status ?? "none"}
|
||||
</span>
|
||||
<span data-testid="task-progress-keys">
|
||||
{Object.keys(props.progressByTaskId ?? {}).join(",") || "none"}
|
||||
{Object.keys(props.tasks.progressByTaskId ?? {}).join(",") || "none"}
|
||||
</span>
|
||||
<span data-testid="selected-tool">
|
||||
{props.toolsUi?.selectedToolKey ?? "none"}
|
||||
{props.tools.toolsUi?.selectedToolKey ?? "none"}
|
||||
</span>
|
||||
<span data-testid="tool-search">
|
||||
{props.tools.toolsUi?.search || "none"}
|
||||
</span>
|
||||
<span data-testid="tool-search">{props.toolsUi?.search || "none"}</span>
|
||||
<span data-testid="selected-prompt">
|
||||
{props.promptsUi?.selectedPromptName ?? "none"}
|
||||
{props.prompts.promptsUi?.selectedPromptName ?? "none"}
|
||||
</span>
|
||||
<span data-testid="log-filter">
|
||||
{props.logs.logsUi?.filterText || "none"}
|
||||
</span>
|
||||
<span data-testid="log-filter">{props.logsUi?.filterText || "none"}</span>
|
||||
<span data-testid="prompt-status">
|
||||
{props.getPromptState?.status ?? "none"}
|
||||
{props.prompts.getPromptState?.status ?? "none"}
|
||||
</span>
|
||||
<span data-testid="resource-status">
|
||||
{props.readResourceState?.status ?? "none"}
|
||||
{props.resources.readResourceState?.status ?? "none"}
|
||||
</span>
|
||||
<span data-testid="log-level">{props.currentLogLevel}</span>
|
||||
<span data-testid="active-tab">{props.activeTab ?? "none"}</span>
|
||||
<span data-testid="log-level">{props.logs.currentLogLevel}</span>
|
||||
<span data-testid="active-tab">{props.shell.activeTab ?? "none"}</span>
|
||||
<span data-testid="init-result">
|
||||
{props.initializeResult
|
||||
? `name:${props.initializeResult.serverInfo.name || "(empty)"}`
|
||||
{props.connection.initializeResult
|
||||
? `name:${props.connection.initializeResult.serverInfo.name || "(empty)"}`
|
||||
: "none"}
|
||||
</span>
|
||||
<span data-testid="errored-server">
|
||||
{props.erroredServerId ?? "none"}
|
||||
{props.connection.erroredServerId ?? "none"}
|
||||
</span>
|
||||
<span data-testid="active-server">{props.activeServer ?? "none"}</span>
|
||||
<button onClick={() => props.onActiveTabChange("Servers")}>
|
||||
<span data-testid="active-server">
|
||||
{props.connection.activeServer ?? "none"}
|
||||
</span>
|
||||
<button onClick={() => props.shell.onActiveTabChange("Servers")}>
|
||||
switch-servers-tab
|
||||
</button>
|
||||
<button onClick={() => props.onToggleConnection("A")}>connect</button>
|
||||
<button onClick={() => props.connection.onToggleConnection("A")}>
|
||||
connect
|
||||
</button>
|
||||
{/* A second target, so a test can drive an A -> B -> A switch (#2095). */}
|
||||
<button onClick={() => props.onToggleConnection("B")}>connect-b</button>
|
||||
<button onClick={() => props.onConnectionInfo()}>
|
||||
<button onClick={() => props.connection.onToggleConnection("B")}>
|
||||
connect-b
|
||||
</button>
|
||||
<button onClick={() => props.servers.onConnectionInfo("A")}>
|
||||
open-connection-info
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
props.onToolsUiChange({
|
||||
formValues: {},
|
||||
search: "",
|
||||
...props.toolsUi,
|
||||
props.tools.onToolsUiChange({
|
||||
...props.tools.toolsUi,
|
||||
selectedToolKey: "0:get_acts",
|
||||
})
|
||||
}
|
||||
|
|
@ -574,10 +514,8 @@ vi.mock("./components/views/InspectorView/InspectorView", () => ({
|
|||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
props.onToolsUiChange({
|
||||
formValues: {},
|
||||
search: "",
|
||||
...props.toolsUi,
|
||||
props.tools.onToolsUiChange({
|
||||
...props.tools.toolsUi,
|
||||
selectedToolKey: "1:other_tool",
|
||||
})
|
||||
}
|
||||
|
|
@ -586,9 +524,8 @@ vi.mock("./components/views/InspectorView/InspectorView", () => ({
|
|||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
props.onToolsUiChange({
|
||||
formValues: {},
|
||||
...props.toolsUi,
|
||||
props.tools.onToolsUiChange({
|
||||
...props.tools.toolsUi,
|
||||
search: "act",
|
||||
})
|
||||
}
|
||||
|
|
@ -597,10 +534,8 @@ vi.mock("./components/views/InspectorView/InspectorView", () => ({
|
|||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
props.onPromptsUiChange({
|
||||
argumentValues: {},
|
||||
search: "",
|
||||
...props.promptsUi,
|
||||
props.prompts.onPromptsUiChange({
|
||||
...props.prompts.promptsUi,
|
||||
selectedPromptName: "greet",
|
||||
})
|
||||
}
|
||||
|
|
@ -609,70 +544,94 @@ vi.mock("./components/views/InspectorView/InspectorView", () => ({
|
|||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
props.onLogsUiChange({
|
||||
visibleLevels: {},
|
||||
...props.logsUi,
|
||||
props.logs.onLogsUiChange({
|
||||
...props.logs.logsUi,
|
||||
filterText: "err",
|
||||
})
|
||||
}
|
||||
>
|
||||
set-log-filter
|
||||
</button>
|
||||
<button onClick={() => props.onCallTool("get_acts", {})}>call</button>
|
||||
<button onClick={() => props.onCallTool("get_acts", {}, true)}>
|
||||
<button onClick={() => props.tools.onCallTool("get_acts", {})}>
|
||||
call
|
||||
</button>
|
||||
<button onClick={() => props.tools.onCallTool("get_acts", {}, true)}>
|
||||
call-as-task
|
||||
</button>
|
||||
<button onClick={() => props.onCancelTask("task-1")}>cancel-task</button>
|
||||
<button onClick={() => props.onCancelToolCall()}>cancel-tool-call</button>
|
||||
<button onClick={() => props.onClearCompletedTasks()}>
|
||||
<button onClick={() => props.tasks.onCancelTask("task-1")}>
|
||||
cancel-task
|
||||
</button>
|
||||
<button onClick={() => props.tools.onCancelToolCall?.()}>
|
||||
cancel-tool-call
|
||||
</button>
|
||||
<button onClick={() => props.tasks.onClearCompletedTasks()}>
|
||||
clear-completed
|
||||
</button>
|
||||
<button onClick={() => props.onRefreshTasks()}>refresh-tasks</button>
|
||||
<button onClick={() => props.onGetPrompt("greet", {})}>get-prompt</button>
|
||||
<button onClick={() => props.onReadResource("res://x")}>
|
||||
<button onClick={() => props.tasks.onRefreshTasks()}>
|
||||
refresh-tasks
|
||||
</button>
|
||||
<button onClick={() => props.prompts.onGetPrompt("greet", {})}>
|
||||
get-prompt
|
||||
</button>
|
||||
<button onClick={() => props.resources.onReadResource("res://x")}>
|
||||
read-resource
|
||||
</button>
|
||||
<button onClick={() => props.onSetLogLevel("debug")}>set-level</button>
|
||||
<button onClick={() => props.onServerSettings("A")}>open-settings</button>
|
||||
<button onClick={() => props.logs.onSetLogLevel("debug")}>
|
||||
set-level
|
||||
</button>
|
||||
<button onClick={() => props.servers.onServerSettings("A")}>
|
||||
open-settings
|
||||
</button>
|
||||
{/* The real server grid (and its Add / Edit controls) lives inside this
|
||||
mocked view, so the config modal is only reachable through these
|
||||
callbacks — and the highlight batch only observable through this prop. */}
|
||||
<button onClick={() => props.onServerEdit("A")}>edit-server</button>
|
||||
<button onClick={() => props.onServerAdd()}>add-server</button>
|
||||
<button onClick={() => props.servers.onServerEdit("A")}>
|
||||
edit-server
|
||||
</button>
|
||||
<button onClick={() => props.servers.onServerAdd()}>add-server</button>
|
||||
<span data-testid="highlighted-servers">
|
||||
{(props.highlightedServerIds ?? []).join(",") || "none"}
|
||||
{(props.servers.highlightedServerIds ?? []).join(",") || "none"}
|
||||
</span>
|
||||
<span data-testid="pinned-history">
|
||||
{Array.from(props.pinnedProtocolIds ?? []).join(",")}
|
||||
{Array.from(props.protocol.pinnedProtocolIds ?? []).join(",")}
|
||||
</span>
|
||||
<button onClick={() => props.onTogglePinProtocol("hist-1")}>
|
||||
<button onClick={() => props.protocol.onTogglePinProtocol("hist-1")}>
|
||||
toggle-pin
|
||||
</button>
|
||||
<button onClick={() => props.onReplayProtocol("hist-1")}>
|
||||
<button onClick={() => props.protocol.onReplayProtocol("hist-1")}>
|
||||
replay-history
|
||||
</button>
|
||||
<button onClick={() => props.onClearProtocol()}>clear-history</button>
|
||||
<button onClick={() => props.protocol.onClearProtocol()}>
|
||||
clear-history
|
||||
</button>
|
||||
<span data-testid="tools-paginated">
|
||||
{String(props.toolsPagination.paginated)}
|
||||
{String(props.tools.toolsPagination.paginated)}
|
||||
</span>
|
||||
<span data-testid="tools-loaded-pages">
|
||||
{props.toolsPagination.loadedPages}
|
||||
{props.tools.toolsPagination.loadedPages}
|
||||
</span>
|
||||
<button onClick={() => props.toolsPagination.onPaginatedChange(true)}>
|
||||
<button
|
||||
onClick={() => props.tools.toolsPagination.onPaginatedChange(true)}
|
||||
>
|
||||
paginated-on
|
||||
</button>
|
||||
<button onClick={() => props.toolsPagination.onPaginatedChange(false)}>
|
||||
<button
|
||||
onClick={() => props.tools.toolsPagination.onPaginatedChange(false)}
|
||||
>
|
||||
paginated-off
|
||||
</button>
|
||||
<button onClick={() => props.toolsPagination.onLoadMore()}>
|
||||
<button onClick={() => props.tools.toolsPagination.onLoadMore()}>
|
||||
load-more-tools
|
||||
</button>
|
||||
<button onClick={() => props.onRefreshTools()}>refresh-tools</button>
|
||||
<button onClick={() => props.tools.onRefreshTools()}>
|
||||
refresh-tools
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
import App from "./App";
|
||||
import type { InspectorViewProps } from "./components/views/InspectorView/InspectorView";
|
||||
import { SERVER_INFO_NOT_REPORTED_LABEL } from "./components/groups/ConnectionInfoContent/ConnectionInfoContent";
|
||||
import { OAUTH_CALLBACK_PATH } from "./utils/oauthFlow.js";
|
||||
import { INSPECTOR_SERVERS_TAB } from "./utils/inspectorTabs.js";
|
||||
|
|
|
|||
Loading…
Reference in a new issue