diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 00ddd9b73..b8d1c9aab 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -374,8 +374,7 @@ export default function Sidebar() { const isOnSubPage = pathname === "/settings" || pathname === "/pr-review" || - pathname === "/merge-conflicts" || - pathname === "/file-view"; + pathname === "/merge-conflicts"; const { settings: appSettings, updateSettings } = useAppSettings(); const { resolvedTheme } = useTheme(); const { handleNewThread } = useHandleNewThread(); diff --git a/apps/web/src/components/merge-conflicts/MergeConflictShell.tsx b/apps/web/src/components/merge-conflicts/MergeConflictShell.tsx index 68112ed8d..063681459 100644 --- a/apps/web/src/components/merge-conflicts/MergeConflictShell.tsx +++ b/apps/web/src/components/merge-conflicts/MergeConflictShell.tsx @@ -20,8 +20,9 @@ import { ShieldCheckIcon, WorkflowIcon, } from "lucide-react"; -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCodeViewerStore } from "~/codeViewerStore"; import { openInPreferredEditor } from "~/editorPreferences"; import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; import { useLocalStorage } from "~/hooks/useLocalStorage"; @@ -41,6 +42,7 @@ import { cn } from "~/lib/utils"; import { ensureNativeApi } from "~/nativeApi"; import { parsePullRequestReference } from "~/pullRequestReference"; import { findProjectMatchingPullRequestReference } from "~/pullRequestProjectMatch"; +import { useStore } from "~/store"; import type { Project } from "~/types"; import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert"; import { toastManager } from "~/components/ui/toast"; @@ -246,7 +248,26 @@ function MergeConflictGuidanceRail({ status: "done" | "active" | "todo" | "blocked"; }>; }) { - const navigateToFileView = useNavigate(); + const navigate = useNavigate(); + const threads = useStore((s) => s.threads); + const openCodeViewer = useCodeViewerStore((s) => s.open); + + const navigateToLatestThread = useCallback( + () => { + openCodeViewer(); + const sorted = [...threads].sort((a, b) => + (b.updatedAt ?? b.createdAt).localeCompare(a.updatedAt ?? a.createdAt), + ); + const latest = sorted[0]; + if (latest) { + void navigate({ to: "/$threadId", params: { threadId: latest.id } }); + } else { + void navigate({ to: "/" }); + } + }, + [navigate, openCodeViewer, threads], + ); + return (
@@ -355,18 +376,10 @@ function MergeConflictGuidanceRail({ className="cursor-pointer rounded-2xl border border-border/70 bg-muted/24 p-3 transition-colors hover:border-border hover:bg-muted/40" role="button" tabIndex={0} - onClick={() => - void navigateToFileView({ - to: "/file-view", - search: { cwd: project.cwd }, - }) - } + onClick={navigateToLatestThread} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { - void navigateToFileView({ - to: "/file-view", - search: { cwd: project.cwd }, - }); + navigateToLatestThread(); } }} > @@ -377,18 +390,10 @@ function MergeConflictGuidanceRail({ className="cursor-pointer rounded-2xl border border-border/70 bg-muted/24 p-3 transition-colors hover:border-border hover:bg-muted/40" role="button" tabIndex={0} - onClick={() => - void navigateToFileView({ - to: "/file-view", - search: { cwd: preparedWorkspace?.cwd ?? project.cwd }, - }) - } + onClick={navigateToLatestThread} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { - void navigateToFileView({ - to: "/file-view", - search: { cwd: preparedWorkspace?.cwd ?? project.cwd }, - }); + navigateToLatestThread(); } }} > diff --git a/apps/web/src/hooks/useFileViewNavigation.ts b/apps/web/src/hooks/useFileViewNavigation.ts index 2479c7abe..bcd636d4d 100644 --- a/apps/web/src/hooks/useFileViewNavigation.ts +++ b/apps/web/src/hooks/useFileViewNavigation.ts @@ -1,19 +1,38 @@ -import { useNavigate } from "@tanstack/react-router"; +import { useNavigate, useParams } from "@tanstack/react-router"; import { useCallback } from "react"; import { useCodeViewerStore } from "~/codeViewerStore"; +import { useStore } from "~/store"; +/** + * Opens a file in the code-viewer side panel of the active thread. + * If the caller is not on a thread page, navigates to the most recent thread first. + */ export function useFileViewNavigation() { const navigate = useNavigate(); const openFile = useCodeViewerStore((s) => s.openFile); + const threadId = useParams({ + strict: false, + select: (params) => (params as Record).threadId ?? null, + }); + const threads = useStore((s) => s.threads); return useCallback( (cwd: string, relativePath: string) => { openFile(cwd, relativePath); - void navigate({ - to: "/file-view", - search: { cwd, path: relativePath }, - }); + // If not already on a thread page, navigate to the most recent thread + // so the code-viewer inline sidebar is visible. + if (!threadId) { + const sorted = [...threads].sort((a, b) => + (b.updatedAt ?? b.createdAt).localeCompare(a.updatedAt ?? a.createdAt), + ); + const latest = sorted[0]; + if (latest) { + void navigate({ to: "/$threadId", params: { threadId: latest.id } }); + } else { + void navigate({ to: "/" }); + } + } }, - [navigate, openFile], + [navigate, openFile, threadId, threads], ); } diff --git a/apps/web/src/routes/_chat.file-view.tsx b/apps/web/src/routes/_chat.file-view.tsx index 44e7128f9..03acbfc98 100644 --- a/apps/web/src/routes/_chat.file-view.tsx +++ b/apps/web/src/routes/_chat.file-view.tsx @@ -1,28 +1,46 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { FileCodeIcon } from "lucide-react"; +import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { useEffect } from "react"; -import { FileViewShell } from "~/components/file-view/FileViewShell"; -import { ProjectSubpageShell } from "~/components/review/ProjectSubpageShell"; +import { useCodeViewerStore } from "~/codeViewerStore"; +import { useStore } from "~/store"; export interface FileViewSearch { cwd?: string; path?: string; } -function FileViewRouteView() { +/** + * Legacy route — the standalone file-view page has been removed. + * If a file was requested via search params, open it in the code-viewer + * side panel and redirect to the most recent thread. + */ +function FileViewRouteRedirect() { const { cwd, path } = Route.useSearch(); + const openFile = useCodeViewerStore((s) => s.openFile); + const navigate = useNavigate(); + const threads = useStore((s) => s.threads); - return ( - - {({ project }) => ( - - )} - - ); + useEffect(() => { + // Open the requested file in the side-panel store + if (cwd && path) { + openFile(cwd, path); + } + + // Navigate to the most recent thread (or home) + const sorted = [...threads].sort((a, b) => + (b.updatedAt ?? b.createdAt).localeCompare(a.updatedAt ?? a.createdAt), + ); + const latest = sorted[0]; + if (latest) { + void navigate({ to: "/$threadId", params: { threadId: latest.id }, replace: true }); + } else { + void navigate({ to: "/", replace: true }); + } + // Only run on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return null; } export const Route = createFileRoute("/_chat/file-view")({ @@ -38,5 +56,5 @@ export const Route = createFileRoute("/_chat/file-view")({ return validatedSearch; }, - component: FileViewRouteView, + component: FileViewRouteRedirect, });