History

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

Julius Marminge committed 9 months ago

Commit e94a47a1 parent ea140aa2 files · +28 −26Browse files

Unified · Split

2 files
  1. apps/web/src/components/ChatView.tsx +8 −26
  2. apps/web/src/lib/serverReactQuery.ts +20 −0
apps/web/src/components/ChatView.tsx+8−26
@@ -7,6 +7,7 @@ import { · 6 unmodified lines
77 PROVIDER_SEND_TURN_MAX_IMAGE_BYTES,
88 type ProviderApprovalDecision,
99 type ProviderSendTurnAttachmentInput,
10+ type KeybindingsConfig,
1011 } from "@t3tools/contracts";
1112 import {
1213 type ClipboardEvent,
@@ -26,6 +27,7 @@ import { · 13 unmodified lines
2627 gitBranchesQueryOptions,
2728 gitCreateWorktreeMutationOptions,
2829 } from "~/lib/gitReactQuery";
30+import { serverConfigQueryOptions } from "~/lib/serverReactQuery";
2931
3032 import { isElectron } from "../env";
3133 import { buildBootstrapInput } from "../historyBootstrap";
@@ -54,7 +56,6 @@ import { · 22 unmodified lines
5456 isTerminalNewShortcut,
5557 isTerminalSplitShortcut,
5658 isTerminalToggleShortcut,
57− type ResolvedTerminalKeybindings,
5859 shortcutLabelForCommand,
5960 } from "../keybindings";
6061 import ChatMarkdown from "./ChatMarkdown";
@@ -89,6 +90,7 @@ const MAX_VISIBLE_WORK_LOG_ENTRIES = 6; · 28 unmodified lines
8990 const IMAGE_SIZE_LIMIT_LABEL = `${Math.round(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES / (1024 * 1024))}MB`;
9091 const IMAGE_ONLY_BOOTSTRAP_PROMPT =
9192 "[User attached one or more images without additional text. Respond using the conversation context and the attached image(s).]";
93+const EMPTY_KEYBINDINGS: KeybindingsConfig = [];
9294
9395 function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string {
9496 if (tone === "error") return "text-rose-300/50 dark:text-rose-300/50";
@@ -150,7 +152,6 @@ export default function ChatView() { · 55 unmodified lines
150152 const [expandedWorkGroups, setExpandedWorkGroups] = useState<Record<string, boolean>>({});
151153 const [nowTick, setNowTick] = useState(() => Date.now());
152154 const [terminalFocusRequestId, setTerminalFocusRequestId] = useState(0);
153− const [terminalKeybindings, setTerminalKeybindings] = useState<ResolvedTerminalKeybindings>([]);
154155 const messagesScrollRef = useRef<HTMLDivElement>(null);
155156 const messagesEndRef = useRef<HTMLDivElement>(null);
156157 const textareaRef = useRef<HTMLTextAreaElement>(null);
@@ -260,6 +261,11 @@ export default function ChatView() { · 103 unmodified lines
260261 } as const);
261262 const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null;
262263 const branchesQuery = useQuery(gitBranchesQueryOptions(api, gitCwd));
264+ const terminalKeybindingsQuery = useQuery({
265+ ...serverConfigQueryOptions(api),
266+ select: (config) => config.keybindings,
267+ });
268+ const terminalKeybindings = terminalKeybindingsQuery.data ?? EMPTY_KEYBINDINGS;
263269 // Default true while loading to avoid toolbar flicker.
264270 const isGitRepo = branchesQuery.data?.isRepo ?? true;
265271 const splitTerminalShortcutLabel = useMemo(
@@ -277,30 +283,6 @@ export default function ChatView() { · 11 unmodified lines
277283 (activeThread.session !== null && activeThread.session.status !== "closed")),
278284 );
279285
280− useEffect(() => {
281− if (!api || !api.server || typeof api.server.getConfig !== "function") {
282− setTerminalKeybindings([]);
283− return;
284− }
285−
286− let disposed = false;
287−
288− void api.server
289− .getConfig()
290− .then((config) => {
291− if (disposed) return;
292− setTerminalKeybindings(config.keybindings);
293− })
294− .catch(() => {
295− if (disposed) return;
296− setTerminalKeybindings([]);
297− });
298−
299− return () => {
300− disposed = true;
301− };
302− }, [api]);
303−
304286 const revokePreviewUrls = useCallback((images: Array<{ previewUrl?: string }>) => {
305287 for (const image of images) {
306288 if (!image.previewUrl) continue;
apps/web/src/lib/serverReactQuery.ts+20−0
@@ -0,0 +1,20 @@
1+import type { NativeApi } from "@t3tools/contracts";
2+import { queryOptions } from "@tanstack/react-query";
3+
4+export const serverQueryKeys = {
5+ all: ["server"] as const,
6+ config: () => ["server", "config"] as const,
7+};
8+
9+export function serverConfigQueryOptions(api: NativeApi | undefined) {
10+ return queryOptions({
11+ queryKey: serverQueryKeys.config(),
12+ queryFn: async () => {
13+ if (!api || !api.server || typeof api.server.getConfig !== "function") {
14+ throw new Error("Server config is unavailable.");
15+ }
16+ return api.server.getConfig();
17+ },
18+ enabled: !!api && !!api.server && typeof api.server.getConfig === "function",
19+ });
20+}