From 61e9ba4b19fc4ed58bb59e23a2aaa995ee3197bd Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sun, 15 Feb 2026 11:40:39 -0800 Subject: [PATCH] Load terminal keybindings via React Query server config helper - add `serverConfigQueryOptions` in new `serverReactQuery` utility - switch `ChatView` from manual `getConfig` effect/state to `useQuery` - default to empty keybindings while config is unavailable --- apps/web/src/components/ChatView.tsx | 34 +++++++--------------------- apps/web/src/lib/serverReactQuery.ts | 20 ++++++++++++++++ 2 files changed, 28 insertions(+), 26 deletions(-) create mode 100644 apps/web/src/lib/serverReactQuery.ts diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 30a039048733d95fd49f0991b5808c2636d22778..a11d28930946f491b34e6e6ab404189a5460bcaa 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -7,6 +7,7 @@ import { PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, type ProviderApprovalDecision, type ProviderSendTurnAttachmentInput, + type KeybindingsConfig, } from "@t3tools/contracts"; import { type ClipboardEvent, @@ -26,6 +27,7 @@ import { gitBranchesQueryOptions, gitCreateWorktreeMutationOptions, } from "~/lib/gitReactQuery"; +import { serverConfigQueryOptions } from "~/lib/serverReactQuery"; import { isElectron } from "../env"; import { buildBootstrapInput } from "../historyBootstrap"; @@ -55,7 +57,6 @@ import { isTerminalNewShortcut, isTerminalSplitShortcut, isTerminalToggleShortcut, - type ResolvedTerminalKeybindings, shortcutLabelForCommand, } from "../keybindings"; import ChatMarkdown from "./ChatMarkdown"; @@ -90,6 +91,7 @@ const MAX_VISIBLE_WORK_LOG_ENTRIES = 6; const IMAGE_SIZE_LIMIT_LABEL = `${Math.round(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES / (1024 * 1024))}MB`; const IMAGE_ONLY_BOOTSTRAP_PROMPT = "[User attached one or more images without additional text. Respond using the conversation context and the attached image(s).]"; +const EMPTY_KEYBINDINGS: KeybindingsConfig = []; function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string { if (tone === "error") return "text-rose-300/50 dark:text-rose-300/50"; @@ -151,7 +153,6 @@ export default function ChatView() { const [expandedWorkGroups, setExpandedWorkGroups] = useState>({}); const [nowTick, setNowTick] = useState(() => Date.now()); const [terminalFocusRequestId, setTerminalFocusRequestId] = useState(0); - const [terminalKeybindings, setTerminalKeybindings] = useState([]); const messagesScrollRef = useRef(null); const messagesEndRef = useRef(null); const shouldAutoScrollRef = useRef(true); @@ -262,6 +263,11 @@ export default function ChatView() { } as const); const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null; const branchesQuery = useQuery(gitBranchesQueryOptions(api, gitCwd)); + const terminalKeybindingsQuery = useQuery({ + ...serverConfigQueryOptions(api), + select: (config) => config.keybindings, + }); + const terminalKeybindings = terminalKeybindingsQuery.data ?? EMPTY_KEYBINDINGS; // Default true while loading to avoid toolbar flicker. const isGitRepo = branchesQuery.data?.isRepo ?? true; const splitTerminalShortcutLabel = useMemo( @@ -279,30 +285,6 @@ export default function ChatView() { (activeThread.session !== null && activeThread.session.status !== "closed")), ); - useEffect(() => { - if (!api || !api.server || typeof api.server.getConfig !== "function") { - setTerminalKeybindings([]); - return; - } - - let disposed = false; - - void api.server - .getConfig() - .then((config) => { - if (disposed) return; - setTerminalKeybindings(config.keybindings); - }) - .catch(() => { - if (disposed) return; - setTerminalKeybindings([]); - }); - - return () => { - disposed = true; - }; - }, [api]); - const revokePreviewUrls = useCallback((images: Array<{ previewUrl?: string }>) => { for (const image of images) { if (!image.previewUrl) continue; diff --git a/apps/web/src/lib/serverReactQuery.ts b/apps/web/src/lib/serverReactQuery.ts new file mode 100644 index 0000000000000000000000000000000000000000..2da9e3b0cd35eb6a1e193efd42aaa592445551ac --- /dev/null +++ b/apps/web/src/lib/serverReactQuery.ts @@ -0,0 +1,20 @@ +import type { NativeApi } from "@t3tools/contracts"; +import { queryOptions } from "@tanstack/react-query"; + +export const serverQueryKeys = { + all: ["server"] as const, + config: () => ["server", "config"] as const, +}; + +export function serverConfigQueryOptions(api: NativeApi | undefined) { + return queryOptions({ + queryKey: serverQueryKeys.config(), + queryFn: async () => { + if (!api || !api.server || typeof api.server.getConfig !== "function") { + throw new Error("Server config is unavailable."); + } + return api.server.getConfig(); + }, + enabled: !!api && !!api.server && typeof api.server.getConfig === "function", + }); +} -- 2.54.0