Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 22 additions & 25 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ import { projectScriptCwd, projectScriptRuntimeEnv } from "@t3tools/shared/proje
import { truncate } from "@t3tools/shared/String";
import { Debouncer } from "@tanstack/react-pacer";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useNavigate, useSearch } from "@tanstack/react-router";
import { useNavigate } from "@tanstack/react-router";
import { useShallow } from "zustand/react/shallow";
import { useGitStatus } from "~/lib/gitStatusState";
import { usePrimaryEnvironmentId } from "../environments/primary";
import { readEnvironmentApi } from "../environmentApi";
import { isElectron } from "../env";
import { readLocalApi } from "../localApi";
import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch";
import { stripDiffSearchParams } from "../diffRouteSearch";
import {
collapseExpandedComposerCursor,
parseStandaloneComposerSlashCommand,
Expand Down Expand Up @@ -620,10 +620,6 @@ export default function ChatView(props: ChatViewProps) {
const timestampFormat = settings.timestampFormat;
const autoOpenPlanSidebar = settings.autoOpenPlanSidebar;
const navigate = useNavigate();
const rawSearch = useSearch({
strict: false,
select: (params) => parseDiffRouteSearch(params),
});
const { resolvedTheme } = useTheme();
// Granular store selectors — avoid subscribing to prompt changes.
const composerRuntimeMode = useComposerDraftStore(
Expand Down Expand Up @@ -794,13 +790,16 @@ export default function ChatView(props: ChatViewProps) {
composerInteractionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE;
const isLocalDraftThread = !isServerThread && localDraftThread !== undefined;
const canCheckoutPullRequestIntoThread = isLocalDraftThread;
const diffOpen = rawSearch.diff === "1";
const activeThreadId = activeThread?.id ?? null;
const activeThreadRef = useMemo(
() => (activeThread ? scopeThreadRef(activeThread.environmentId, activeThread.id) : null),
[activeThread],
);
const activeThreadKey = activeThreadRef ? scopedThreadKey(activeThreadRef) : null;
const diffOpen = useUiStateStore((store) =>
activeThreadKey ? store.threadDiffOpenById[activeThreadKey] === true : false,
);
const setThreadDiffOpen = useUiStateStore((store) => store.setThreadDiffOpen);
const existingOpenTerminalThreadKeys = useMemo(() => {
const existingThreadKeys = new Set<string>([...serverThreadKeys, ...draftThreadKeys]);
return openTerminalThreadKeys.filter((nextThreadKey) => existingThreadKeys.has(nextThreadKey));
Expand Down Expand Up @@ -1498,25 +1497,14 @@ export default function ChatView(props: ChatViewProps) {
[keybindings, nonTerminalShortcutLabelOptions],
);
const onToggleDiff = useCallback(() => {
if (!isServerThread) {
if (!isServerThread || !activeThreadKey) {
return;
}
if (!diffOpen) {
onDiffPanelOpen?.();
}
void navigate({
to: "/$environmentId/$threadId",
params: {
environmentId,
threadId,
},
replace: true,
search: (previous) => {
const rest = stripDiffSearchParams(previous);
return diffOpen ? { ...rest, diff: undefined } : { ...rest, diff: "1" };
},
});
}, [diffOpen, environmentId, isServerThread, navigate, onDiffPanelOpen, threadId]);
setThreadDiffOpen(activeThreadKey, !diffOpen);
}, [activeThreadKey, diffOpen, isServerThread, onDiffPanelOpen, setThreadDiffOpen]);

const envLocked = Boolean(
activeThread &&
Expand Down Expand Up @@ -3237,10 +3225,11 @@ export default function ChatView(props: ChatViewProps) {
}, []);
const onOpenTurnDiff = useCallback(
(turnId: TurnId, filePath?: string) => {
if (!isServerThread) {
if (!isServerThread || !activeThreadKey) {
return;
}
onDiffPanelOpen?.();
setThreadDiffOpen(activeThreadKey, true);
void navigate({
to: "/$environmentId/$threadId",
params: {
Expand All @@ -3250,12 +3239,20 @@ export default function ChatView(props: ChatViewProps) {
search: (previous) => {
const rest = stripDiffSearchParams(previous);
return filePath
? { ...rest, diff: "1", diffTurnId: turnId, diffFilePath: filePath }
: { ...rest, diff: "1", diffTurnId: turnId };
? { ...rest, diffTurnId: turnId, diffFilePath: filePath }
: { ...rest, diffTurnId: turnId };
},
});
},
[environmentId, isServerThread, navigate, onDiffPanelOpen, threadId],
[
activeThreadKey,
environmentId,
isServerThread,
navigate,
onDiffPanelOpen,
setThreadDiffOpen,
threadId,
],
);
// Both the Map and the revert handler are read from refs at call-time so
// the callback reference is fully stable and never busts context identity.
Expand Down
Loading
Loading