diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 9039149da193d6310f881ddb5aa50e62d7b31622..92a6a7392854bf12d7a1a033bb5dc2b57c3ccc11 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -28,6 +28,7 @@ import { } from "react"; import { useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query"; import { useDebouncedValue } from "@tanstack/react-pacer"; +import { useNavigate, useSearch } from "@tanstack/react-router"; import { type VirtualItem, useVirtualizer } from "@tanstack/react-virtual"; import { gitBranchesQueryOptions, gitCreateWorktreeMutationOptions } from "~/lib/gitReactQuery"; import { checkpointDiffQueryOptions } from "~/lib/providerReactQuery"; @@ -35,6 +36,7 @@ import { projectSearchEntriesQueryOptions } from "~/lib/projectReactQuery"; import { serverConfigQueryOptions, serverQueryKeys } from "~/lib/serverReactQuery"; import { isElectron } from "../env"; +import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch"; import { useAppSettings } from "../appSettings"; import { buildBootstrapInput } from "../historyBootstrap"; import { @@ -375,6 +377,8 @@ interface ChatViewProps { export default function ChatView({ threadId }: ChatViewProps) { const { state, dispatch } = useStore(); + const navigate = useNavigate(); + const rawSearch = useSearch({ strict: false }); const api = useNativeApi(); const { resolvedTheme } = useTheme(); const { settings: appSettings } = useAppSettings(); @@ -413,6 +417,11 @@ export default function ChatView({ threadId }: ChatViewProps) { const checkpointTurnCountSyncFingerprintRef = useRef(new Map()); const activeThread = state.threads.find((t) => t.id === threadId); + const diffSearch = useMemo( + () => parseDiffRouteSearch(rawSearch as Record), + [rawSearch], + ); + const diffOpen = diffSearch.diff === "1"; const activeThreadId = activeThread?.id ?? null; const activeSessionId = activeThread?.session?.sessionId ?? null; const activeThreadRuntimeId = @@ -1962,32 +1971,29 @@ export default function ChatView({ threadId }: ChatViewProps) { setExpandedImage(image); }, []); const onToggleDiff = useCallback(() => { - if (state.diffOpen) { - dispatch({ type: "CLOSE_DIFF" }); - return; - } - - if (!activeThread) { - dispatch({ type: "TOGGLE_DIFF" }); - return; - } - - dispatch({ - type: "OPEN_DIFF", - threadId: activeThread.id, + void navigate({ + to: "/$threadId", + params: { threadId }, + search: (previous) => { + const rest = stripDiffSearchParams(previous); + return diffOpen ? rest : { ...rest, diff: "1" }; + }, }); - }, [activeThread, dispatch, state.diffOpen]); + }, [diffOpen, navigate, threadId]); const onOpenTurnDiff = useCallback( (turnId: string, filePath?: string) => { - if (!activeThread) return; - dispatch({ - type: "OPEN_DIFF", - threadId: activeThread.id, - turnId, - ...(filePath ? { filePath } : {}), + void navigate({ + to: "/$threadId", + params: { threadId }, + search: (previous) => { + const rest = stripDiffSearchParams(previous); + return filePath + ? { ...rest, diff: "1", diffTurnId: turnId, diffFilePath: filePath } + : { ...rest, diff: "1", diffTurnId: turnId }; + }, }); }, - [activeThread, dispatch], + [navigate, threadId], ); const onRevertUserMessage = useCallback( (messageId: string) => { @@ -2035,7 +2041,7 @@ export default function ChatView({ threadId }: ChatViewProps) { keybindings={keybindings} api={api} gitCwd={gitCwd} - diffOpen={state.diffOpen} + diffOpen={diffOpen} onRunProjectScript={(script) => { void runProjectScript(script); }} @@ -2820,11 +2826,16 @@ const MessagesTimeline = memo(function MessagesTimeline({ row.message.id, ); if (!turnSummary) return null; - const checkpointFilesState = turnCheckpointFilesByTurnId.get(turnSummary.turnId); + const checkpointFilesState = turnCheckpointFilesByTurnId.get( + turnSummary.turnId, + ); const checkpointFiles = checkpointFilesState?.files ?? []; const summaryStat = checkpointFiles.reduce( (acc, file) => { - if (typeof file.additions !== "number" || typeof file.deletions !== "number") { + if ( + typeof file.additions !== "number" || + typeof file.deletions !== "number" + ) { return acc; } return { @@ -2835,7 +2846,9 @@ const MessagesTimeline = memo(function MessagesTimeline({ { additions: 0, deletions: 0 }, ); const changedFileCountLabel = - checkpointFilesState?.status === "ready" ? String(checkpointFiles.length) : "..."; + checkpointFilesState?.status === "ready" + ? String(checkpointFiles.length) + : "..."; return (
@@ -2846,7 +2859,9 @@ const MessagesTimeline = memo(function MessagesTimeline({ • +{summaryStat.additions} / - -{summaryStat.deletions} + + -{summaryStat.deletions} + )}

@@ -2854,12 +2869,15 @@ const MessagesTimeline = memo(function MessagesTimeline({ type="button" size="xs" variant="outline" - onClick={() => onOpenTurnDiff(turnSummary.turnId, checkpointFiles[0]?.path)} + onClick={() => + onOpenTurnDiff(turnSummary.turnId, checkpointFiles[0]?.path) + } > View diff
- {!checkpointFilesState || checkpointFilesState.status === "pending" ? ( + {!checkpointFilesState || + checkpointFilesState.status === "pending" ? (

Waiting for checkpoint metadata...

diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index a2678e03fcd39cd083a724f01645e5a492131f5e..33149c5088a7f5082a821512302c953f4295a4a1 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -1,16 +1,12 @@ import { parsePatchFiles } from "@pierre/diffs"; -import { - FileDiff, - type FileDiffMetadata, - Virtualizer, - WorkerPoolContextProvider, -} from "@pierre/diffs/react"; +import { FileDiff, type FileDiffMetadata, Virtualizer } from "@pierre/diffs/react"; import { useQuery } from "@tanstack/react-query"; -import { useParams } from "@tanstack/react-router"; +import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; import { Columns2Icon, Rows3Icon } from "lucide-react"; -import { type ReactNode, useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { checkpointDiffQueryOptions } from "~/lib/providerReactQuery"; import { cn } from "~/lib/utils"; +import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch"; import { isElectron } from "../env"; import { useNativeApi } from "../hooks/useNativeApi"; import { useTheme } from "../hooks/useTheme"; @@ -76,38 +72,23 @@ interface DiffPanelProps { mode?: "inline" | "sheet"; } -export function DiffWorkerPoolProvider({ children }: { children?: ReactNode }) { - const workerPoolSize = useMemo(() => { - const cores = typeof navigator === "undefined" ? 4 : Math.max(1, navigator.hardwareConcurrency || 4); - return Math.max(2, Math.min(6, Math.floor(cores / 2))); - }, []); - - return ( - - new Worker(new URL("../workers/diffs.worker.ts", import.meta.url), { type: "module" }), - poolSize: workerPoolSize, - totalASTLRUCacheSize: 240, - }} - highlighterOptions={{ - tokenizeMaxLineLength: 1_000, - }} - > - {children} - - ); -} +export { DiffWorkerPoolProvider } from "./DiffWorkerPoolProvider"; export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { const api = useNativeApi(); + const navigate = useNavigate(); const { resolvedTheme } = useTheme(); - const { state, dispatch } = useStore(); + const { state } = useStore(); const [diffRenderMode, setDiffRenderMode] = useState("stacked"); const patchViewportRef = useRef(null); const params = useParams({ strict: false }); + const rawSearch = useSearch({ strict: false }); const routeThreadId = typeof params.threadId === "string" ? params.threadId : null; - const activeThreadId = state.diffThreadId ?? routeThreadId; + const diffSearch = useMemo( + () => parseDiffRouteSearch(rawSearch as Record), + [rawSearch], + ); + const activeThreadId = routeThreadId; const activeThread = state.threads.find((thread) => thread.id === activeThreadId); const activeThreadRuntimeId = activeThread?.codexThreadId ?? activeThread?.session?.threadId ?? null; @@ -115,10 +96,8 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { const { turnDiffSummaries, inferredCheckpointTurnCountByTurnId } = useTurnDiffSummaries(activeThread); - const canApplyStoredTarget = Boolean(activeThread && state.diffThreadId === activeThread.id); - const selectedTurnId = canApplyStoredTarget ? state.diffTurnId : null; - const selectedFilePath = - canApplyStoredTarget && selectedTurnId !== null ? state.diffFilePath : null; + const selectedTurnId = diffSearch.diffTurnId ?? null; + const selectedFilePath = selectedTurnId !== null ? (diffSearch.diffFilePath ?? null) : null; const selectedTurn = selectedTurnId === null ? undefined @@ -218,17 +197,24 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { const selectTurn = (turnId: string) => { if (!activeThread) return; - dispatch({ - type: "SET_DIFF_TARGET", - threadId: activeThread.id, - turnId, + void navigate({ + to: "/$threadId", + params: { threadId: activeThread.id }, + search: (previous) => { + const rest = stripDiffSearchParams(previous); + return { ...rest, diff: "1", diffTurnId: turnId }; + }, }); }; const selectWholeConversation = () => { if (!activeThread) return; - dispatch({ - type: "SET_DIFF_TARGET", - threadId: activeThread.id, + void navigate({ + to: "/$threadId", + params: { threadId: activeThread.id }, + search: (previous) => { + const rest = stripDiffSearchParams(previous); + return { ...rest, diff: "1" }; + }, }); }; @@ -318,13 +304,6 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { ) : ( <>
- {!canApplyStoredTarget && state.diffThreadId && ( -
-

- Showing diffs for the active thread. -

-
- )} {checkpointDiffError && !renderablePatch && (

{checkpointDiffError}

diff --git a/apps/web/src/components/DiffWorkerPoolProvider.tsx b/apps/web/src/components/DiffWorkerPoolProvider.tsx new file mode 100644 index 0000000000000000000000000000000000000000..53939cf81cfefa1371776945a059c4e6df247ba8 --- /dev/null +++ b/apps/web/src/components/DiffWorkerPoolProvider.tsx @@ -0,0 +1,26 @@ +import { WorkerPoolContextProvider } from "@pierre/diffs/react"; +import { useMemo, type ReactNode } from "react"; + +export function DiffWorkerPoolProvider({ children }: { children?: ReactNode }) { + const workerPoolSize = useMemo(() => { + const cores = typeof navigator === "undefined" ? 4 : Math.max(1, navigator.hardwareConcurrency || 4); + return Math.max(2, Math.min(6, Math.floor(cores / 2))); + }, []); + + return ( + + new Worker(new URL("../workers/diffs.worker.ts", import.meta.url), { type: "module" }), + poolSize: workerPoolSize, + totalASTLRUCacheSize: 240, + }} + highlighterOptions={{ + tokenizeMaxLineLength: 1_000, + }} + > + {children} + + ); +} + diff --git a/apps/web/src/diffRouteSearch.test.ts b/apps/web/src/diffRouteSearch.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..becf9e335f0ff4111e8d5725f9cb0db56adf9f29 --- /dev/null +++ b/apps/web/src/diffRouteSearch.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from "vitest"; + +import { parseDiffRouteSearch } from "./diffRouteSearch"; + +describe("parseDiffRouteSearch", () => { + it("parses valid diff search values", () => { + const parsed = parseDiffRouteSearch({ + diff: "1", + diffTurnId: "turn-1", + diffFilePath: "src/app.ts", + }); + + expect(parsed).toEqual({ + diff: "1", + diffTurnId: "turn-1", + diffFilePath: "src/app.ts", + }); + }); + + it("drops turn and file values when diff is closed", () => { + const parsed = parseDiffRouteSearch({ + diff: "0", + diffTurnId: "turn-1", + diffFilePath: "src/app.ts", + }); + + expect(parsed).toEqual({}); + }); + + it("drops file value when turn is not selected", () => { + const parsed = parseDiffRouteSearch({ + diff: "1", + diffFilePath: "src/app.ts", + }); + + expect(parsed).toEqual({ + diff: "1", + }); + }); + + it("normalizes whitespace-only values", () => { + const parsed = parseDiffRouteSearch({ + diff: "1", + diffTurnId: " ", + diffFilePath: " ", + }); + + expect(parsed).toEqual({ + diff: "1", + }); + }); +}); + diff --git a/apps/web/src/diffRouteSearch.ts b/apps/web/src/diffRouteSearch.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd3d6cae7087bb84286b5dfa75009a330c4541cd --- /dev/null +++ b/apps/web/src/diffRouteSearch.ts @@ -0,0 +1,32 @@ +export interface DiffRouteSearch { + diff?: "1"; + diffTurnId?: string; + diffFilePath?: string; +} + +function normalizeSearchString(value: unknown): string | undefined { + if (typeof value !== "string") { + return undefined; + } + const normalized = value.trim(); + return normalized.length > 0 ? normalized : undefined; +} + +export function stripDiffSearchParams>( + params: T, +): Omit { + const { diff: _diff, diffTurnId: _diffTurnId, diffFilePath: _diffFilePath, ...rest } = params; + return rest as Omit; +} + +export function parseDiffRouteSearch(search: Record): DiffRouteSearch { + const diff = search.diff === "1" ? "1" : undefined; + const diffTurnId = diff ? normalizeSearchString(search.diffTurnId) : undefined; + const diffFilePath = diff && diffTurnId ? normalizeSearchString(search.diffFilePath) : undefined; + + return { + ...(diff ? { diff } : {}), + ...(diffTurnId ? { diffTurnId } : {}), + ...(diffFilePath ? { diffFilePath } : {}), + }; +} diff --git a/apps/web/src/routes/_chat.$threadId.tsx b/apps/web/src/routes/_chat.$threadId.tsx index 066f657ea332294601206c030cfa1f4cc06edccc..8bb8aa389cfdcc7483ce6286eb1c28a335a4cfd0 100644 --- a/apps/web/src/routes/_chat.$threadId.tsx +++ b/apps/web/src/routes/_chat.$threadId.tsx @@ -1,14 +1,67 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; -import { useEffect } from "react"; +import { Activity, Suspense, lazy, type ReactNode, useCallback, useEffect } from "react"; import ChatView from "../components/ChatView"; +import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch"; +import { useMediaQuery } from "../hooks/useMediaQuery"; import { useStore } from "../store"; +import { Sheet, SheetPopup } from "../components/ui/sheet"; + +const DiffPanel = lazy(() => import("../components/DiffPanel")); +const DIFF_INLINE_LAYOUT_MEDIA_QUERY = "(max-width: 1180px)"; + +const DiffPanelWrapper = (props: { + children: ReactNode; + sheet: boolean; + diffOpen: boolean; + onCloseDiff: () => void; +}) => { + if (props.sheet) { + return ( + { + if (!open) { + props.onCloseDiff(); + } + }} + > + + {props.children} + + + ); + } + + return ( + + ); +}; function ChatThreadRouteView() { const { state } = useStore(); const navigate = useNavigate(); const { threadId } = Route.useParams(); + const search = Route.useSearch(); const threadExists = threadId ? state.threads.some((thread) => thread.id === threadId) : false; + const diffOpen = search.diff === "1"; + const shouldUseDiffSheet = useMediaQuery(DIFF_INLINE_LAYOUT_MEDIA_QUERY); + const closeDiff = useCallback(() => { + void navigate({ + to: "/$threadId", + params: { threadId }, + search: (previous) => { + return stripDiffSearchParams(previous); + }, + }); + }, [navigate, threadId]); useEffect(() => { if (!threadId) { @@ -30,9 +83,32 @@ function ChatThreadRouteView() { return null; } - return ; + const diffLoadingFallback = + !diffOpen || shouldUseDiffSheet ? ( +
+ Loading diff viewer... +
+ ) : ( + + ); + + return ( + <> + + + + + + + + + + ); } export const Route = createFileRoute("/_chat/$threadId")({ + validateSearch: (search) => parseDiffRouteSearch(search), component: ChatThreadRouteView, }); diff --git a/apps/web/src/routes/_chat.tsx b/apps/web/src/routes/_chat.tsx index ed931773ee937844e77f9ac0ba289fbc7263b9c2..36c14455986a5adbbb5309100b92eeb1e85d03a2 100644 --- a/apps/web/src/routes/_chat.tsx +++ b/apps/web/src/routes/_chat.tsx @@ -1,53 +1,10 @@ import { Outlet, createFileRoute, useNavigate } from "@tanstack/react-router"; -import { Activity, Suspense, lazy, type ReactNode, useEffect } from "react"; +import { useEffect } from "react"; +import { DiffWorkerPoolProvider } from "../components/DiffWorkerPoolProvider"; import Sidebar from "../components/Sidebar"; -import { useMediaQuery } from "../hooks/useMediaQuery"; -import { useStore } from "../store"; -import { Sheet, SheetPopup } from "../components/ui/sheet"; - -const DiffPanel = lazy(() => import("../components/DiffPanel")); -const DiffWorkerPoolProvider = lazy(() => - import("../components/DiffPanel").then((module) => ({ - default: module.DiffWorkerPoolProvider, - })), -); -const DIFF_INLINE_LAYOUT_MEDIA_QUERY = "(max-width: 1180px)"; - -const DiffPanelWrapper = (props: { children: ReactNode; sheet: boolean }) => { - const { state, dispatch } = useStore(); - if (props.sheet) { - return ( - { - if (!open) { - dispatch({ type: "CLOSE_DIFF" }); - } - }} - > - - {props.children} - - - ); - } - - return ( - - ); -}; function ChatRouteLayout() { - const { state } = useStore(); - const shouldUseDiffSheet = useMediaQuery(DIFF_INLINE_LAYOUT_MEDIA_QUERY); const navigate = useNavigate(); useEffect(() => { @@ -66,30 +23,12 @@ function ChatRouteLayout() { }; }, [navigate]); - const diffLoadingFallback = - !state.diffOpen || shouldUseDiffSheet ? ( -
- Loading diff viewer... -
- ) : ( - - ); - return (
- - - - - - - - - - + + +
); } @@ -97,3 +36,4 @@ function ChatRouteLayout() { export const Route = createFileRoute("/_chat")({ component: ChatRouteLayout, }); + diff --git a/apps/web/src/store.test.ts b/apps/web/src/store.test.ts index 5b3c7a81117c465e86caa5a4ad7579b87c0886aa..6acd1e2859ec7cc23ea4849ae71cfe99fc167258 100644 --- a/apps/web/src/store.test.ts +++ b/apps/web/src/store.test.ts @@ -81,10 +81,6 @@ function makeState(thread: Thread): AppState { threads: [thread], threadsHydrated: true, runtimeMode: "full-access", - diffOpen: false, - diffThreadId: null, - diffTurnId: null, - diffFilePath: null, }; } @@ -149,21 +145,6 @@ describe("store reducer thread continuity", () => { expect(next.threads[0]?.terminalOpen).toBe(true); }); - it("opens diff panel with an explicit turn/file target", () => { - const state = makeState(makeThread()); - const next = reducer(state, { - type: "OPEN_DIFF", - threadId: "thread-local-1", - turnId: "turn-1", - filePath: "src/app.ts", - }); - - expect(next.diffOpen).toBe(true); - expect(next.diffThreadId).toBe("thread-local-1"); - expect(next.diffTurnId).toBe("turn-1"); - expect(next.diffFilePath).toBe("src/app.ts"); - }); - it("sets terminal open state per thread", () => { const state = makeState(makeThread({ terminalOpen: true })); const next = reducer(state, { @@ -618,10 +599,6 @@ describe("store reducer thread continuity", () => { ], threadsHydrated: true, runtimeMode: "full-access", - diffOpen: true, - diffThreadId: "thread-b", - diffTurnId: "turn-b", - diffFilePath: "src/removed.ts", }; const next = reducer(state, { @@ -645,10 +622,6 @@ describe("store reducer thread continuity", () => { expect(next.threads).toHaveLength(1); expect(next.threads[0]?.id).toBe("thread-a"); expect(next.threads[0]?.projectId).toBe("project-new-a"); - expect(next.diffOpen).toBe(false); - expect(next.diffThreadId).toBeNull(); - expect(next.diffTurnId).toBeNull(); - expect(next.diffFilePath).toBeNull(); }); it("treats empty scripts from sync as authoritative", () => { @@ -674,10 +647,6 @@ describe("store reducer thread continuity", () => { threads: [makeThread({ id: "thread-a", projectId: "project-old-a" })], threadsHydrated: true, runtimeMode: "full-access", - diffOpen: false, - diffThreadId: null, - diffTurnId: null, - diffFilePath: null, }; const next = reducer(state, { @@ -766,10 +735,6 @@ describe("store reducer thread continuity", () => { ], threadsHydrated: true, runtimeMode: "full-access", - diffOpen: true, - diffThreadId: "thread-a", - diffTurnId: "turn-a", - diffFilePath: "src/a.ts", }; const next = reducer(state, { @@ -781,10 +746,6 @@ describe("store reducer thread continuity", () => { expect(next.projects[0]?.id).toBe("project-2"); expect(next.threads).toHaveLength(1); expect(next.threads[0]?.id).toBe("thread-b"); - expect(next.diffOpen).toBe(false); - expect(next.diffThreadId).toBeNull(); - expect(next.diffTurnId).toBeNull(); - expect(next.diffFilePath).toBeNull(); }); it("marks completion as seen immediately for the active thread", () => { diff --git a/apps/web/src/store.ts b/apps/web/src/store.ts index f470a8c83bd5dba8c8eaa3a850e3e6e7c8bc60ab..cb45b5506cdcde65f101eb4f1ac0a5dcf331e3ae 100644 --- a/apps/web/src/store.ts +++ b/apps/web/src/store.ts @@ -48,20 +48,6 @@ type Action = | { type: "NEW_THREAD_TERMINAL"; threadId: string; terminalId: string } | { type: "SET_THREAD_ACTIVE_TERMINAL"; threadId: string; terminalId: string } | { type: "CLOSE_THREAD_TERMINAL"; threadId: string; terminalId: string } - | { type: "TOGGLE_DIFF" } - | { - type: "OPEN_DIFF"; - threadId: string; - turnId?: string; - filePath?: string; - } - | { - type: "SET_DIFF_TARGET"; - threadId: string; - turnId?: string; - filePath?: string; - } - | { type: "CLOSE_DIFF" } | { type: "APPLY_EVENT"; event: ProviderEvent; @@ -110,10 +96,6 @@ export interface AppState { threads: Thread[]; threadsHydrated: boolean; runtimeMode: RuntimeMode; - diffOpen: boolean; - diffThreadId: string | null; - diffTurnId: string | null; - diffFilePath: string | null; } const PERSISTED_STATE_KEY = "t3code:renderer-state:v7"; @@ -133,10 +115,6 @@ const initialState: AppState = { threads: [], threadsHydrated: false, runtimeMode: DEFAULT_RUNTIME_MODE, - diffOpen: false, - diffThreadId: null, - diffTurnId: null, - diffFilePath: null, }; // ── Helpers ────────────────────────────────────────────────────────── @@ -160,10 +138,6 @@ function readPersistedState(): AppState { ...hydrated, threads, threadsHydrated: threads.length > 0, - diffOpen: false, - diffThreadId: null, - diffTurnId: null, - diffFilePath: null, }; } catch { return initialState; @@ -191,32 +165,6 @@ function updateThread( return threads.map((t) => (t.id === threadId ? updater(t) : t)); } -function resetDiffTargetIfMissing(state: AppState, threads: Thread[]) { - if (!state.diffThreadId) { - return { - diffOpen: state.diffOpen, - diffThreadId: state.diffThreadId, - diffTurnId: state.diffTurnId, - diffFilePath: state.diffFilePath, - }; - } - const hasThread = threads.some((thread) => thread.id === state.diffThreadId); - if (hasThread) { - return { - diffOpen: state.diffOpen, - diffThreadId: state.diffThreadId, - diffTurnId: state.diffTurnId, - diffFilePath: state.diffFilePath, - }; - } - return { - diffOpen: false, - diffThreadId: null, - diffTurnId: null, - diffFilePath: null, - }; -} - function mergeTurnDiffSummaries( existing: Thread["turnDiffSummaries"], next: Thread["turnDiffSummaries"], @@ -615,13 +563,11 @@ export function reducer(state: AppState, action: Action): AppState { }); }) .filter((thread): thread is Thread => thread !== null); - const diffState = resetDiffTargetIfMissing(state, nextThreads); return { ...state, projects: nextProjects, threads: nextThreads, - ...diffState, }; } @@ -640,13 +586,11 @@ export function reducer(state: AppState, action: Action): AppState { } const threads = state.threads.filter((thread) => thread.projectId !== action.projectId); - const diffState = resetDiffTargetIfMissing(state, threads); return { ...state, projects, threads, - ...diffState, }; } @@ -830,29 +774,6 @@ export function reducer(state: AppState, action: Action): AppState { ), }; - case "TOGGLE_DIFF": - return { ...state, diffOpen: !state.diffOpen }; - - case "OPEN_DIFF": - return { - ...state, - diffOpen: true, - diffThreadId: action.threadId, - diffTurnId: action.turnId ?? null, - diffFilePath: action.filePath ?? null, - }; - - case "SET_DIFF_TARGET": - return { - ...state, - diffThreadId: action.threadId, - diffTurnId: action.turnId ?? null, - diffFilePath: action.filePath ?? null, - }; - - case "CLOSE_DIFF": - return { ...state, diffOpen: false }; - case "APPLY_TERMINAL_EVENT": if (!state.threads.some((thread) => thread.id === action.event.threadId)) { return state; @@ -1093,15 +1014,11 @@ export function reducer(state: AppState, action: Action): AppState { runtimeMode: action.mode, }; - case "DELETE_THREAD": { - const threads = state.threads.filter((t) => t.id !== action.threadId); - const diffState = resetDiffTargetIfMissing(state, threads); + case "DELETE_THREAD": return { ...state, - threads, - ...diffState, + threads: state.threads.filter((t) => t.id !== action.threadId), }; - } default: return state;