Files
paseo/packages/app/src/panels/terminal-panel.tsx
T
Mohamed Boudra 30dd54a318 refactor: extract panel registry and pane context from workspace screen
Replace hardcoded renderContent() switch and per-kind descriptor logic
with a registry-based panel interface. Each panel type (agent, draft,
terminal, file) now self-registers with its component, useDescriptor
hook, and optional confirmClose. Panels access workspace context via
usePaneContext() instead of prop drilling. This prepares the workspace
screen for split pane support.
2026-03-17 21:58:11 +07:00

81 lines
2.6 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import { Terminal } from "lucide-react-native";
import { View } from "react-native";
import { useIsFocused } from "@react-navigation/native";
import invariant from "tiny-invariant";
import type { ListTerminalsResponse } from "@server/shared/messages";
import { TerminalPane } from "@/components/terminal-pane";
import { usePaneContext } from "@/panels/pane-context";
import type { PanelDescriptor, PanelRegistration } from "@/panels/panel-registry";
import { useSessionStore } from "@/stores/session-store";
type ListTerminalsPayload = ListTerminalsResponse["payload"];
function trimNonEmpty(value: string | null | undefined): string | null {
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : null;
}
function useTerminalPanelDescriptor(
target: { kind: "terminal"; terminalId: string },
context: { serverId: string; workspaceId: string }
): PanelDescriptor {
const client = useSessionStore((state) => state.sessions[context.serverId]?.client ?? null);
const terminalsQuery = useQuery({
queryKey: ["terminals", context.serverId, context.workspaceId] as const,
enabled: Boolean(client && context.workspaceId),
queryFn: async (): Promise<ListTerminalsPayload> => {
if (!client) {
return { cwd: context.workspaceId, terminals: [], requestId: "missing-client" };
}
return client.listTerminals(context.workspaceId);
},
staleTime: 5_000,
});
const terminal =
terminalsQuery.data?.terminals.find((entry) => entry.id === target.terminalId) ?? null;
return {
label: trimNonEmpty(terminal?.name ?? null) ?? "Terminal",
subtitle: "Terminal",
titleState: "ready",
icon: Terminal,
statusBucket: null,
};
}
function TerminalPanel() {
const isFocused = useIsFocused();
const { serverId, workspaceId, target, openTab } = usePaneContext();
invariant(target.kind === "terminal", "TerminalPanel requires terminal target");
if (!isFocused) {
return <View style={{ flex: 1 }} />;
}
return (
<TerminalPane
serverId={serverId}
cwd={workspaceId}
selectedTerminalId={target.terminalId}
onSelectedTerminalIdChange={(terminalId) => {
if (!terminalId) {
return;
}
openTab({ kind: "terminal", terminalId });
}}
hideHeader
manageTerminalDirectorySubscription={false}
/>
);
}
export const terminalPanelRegistration: PanelRegistration<"terminal"> = {
kind: "terminal",
component: TerminalPanel,
useDescriptor: useTerminalPanelDescriptor,
};