Skip to content
Merged
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
3 changes: 1 addition & 2 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
49 changes: 27 additions & 22 deletions apps/web/src/components/merge-conflicts/MergeConflictShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -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 (
<div className="flex min-h-0 min-w-0 flex-col bg-background/96">
<div className="border-b border-border/70 px-4 py-4">
Expand Down Expand Up @@ -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();
}
}}
>
Expand All @@ -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();
}
}}
>
Expand Down
31 changes: 25 additions & 6 deletions apps/web/src/hooks/useFileViewNavigation.ts
Original file line number Diff line number Diff line change
@@ -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<string, string | undefined>).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],
);
}
52 changes: 35 additions & 17 deletions apps/web/src/routes/_chat.file-view.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<ProjectSubpageShell
emptyMessage="Open a file to view it here."
icon={FileCodeIcon}
title="File View"
>
{({ project }) => (
<FileViewShell initialCwd={cwd ?? project.cwd} initialPath={path ?? null} />
)}
</ProjectSubpageShell>
);
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")({
Expand All @@ -38,5 +56,5 @@ export const Route = createFileRoute("/_chat/file-view")({

return validatedSearch;
},
component: FileViewRouteView,
component: FileViewRouteRedirect,
});
Loading