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
4 changes: 3 additions & 1 deletion apps/web/src/components/BranchToolbarBranchSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,9 @@ export function BranchToolbarBranchSelector({
number={prNumber}
url={prUrl}
status={displayedPrStatus}
onOpenStack={() => useRightPanelStore.getState().open(threadRef, "pull-requests")}
onOpenStack={() =>
useRightPanelStore.getState().openRepository(threadRef, "pull-requests")
}
onOpenPullRequest={(event) => {
if (prUrl) openPrLink(event, prUrl);
}}
Expand Down
51 changes: 27 additions & 24 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,6 @@ import { PullRequestsUnavailableState } from "./pullRequest/PullRequestsUnavaila
import { RightPanelTabs, type IssueTabStatus } from "./RightPanelTabs";
import { AgentsPanel } from "./AgentsPanel";
import { LinkPullRequestDialogHost } from "./pullRequest/LinkPullRequestDialog";
import { ThreadPullRequestsPanel } from "./pullRequest/ThreadPullRequestsPanel";
import { useDeviceState } from "~/state/device";
import { DeviceSetup } from "./device/DeviceSetup";
import { Dialog } from "./ui/dialog";
Expand Down Expand Up @@ -634,7 +633,7 @@ const selectAutoShowFloatingPreview = (settings: { browserAutoShowFloatingPrevie
const DevicePanel = lazy(() =>
import("./device/DevicePanel").then((module) => ({ default: module.DevicePanel })),
);
const GitHistoryPanel = lazy(() => import("./GitHistoryPanel"));
const RepositoryPanel = lazy(() => import("./RepositoryPanel"));
const FilePreviewPanel = lazy(() => import("./files/FilePreviewPanel"));
const EMPTY_PENDING_FILE_SURFACE_IDS: ReadonlySet<string> = new Set();
const TYPE_TO_FOCUS_EDITABLE_SELECTOR = [
Expand Down Expand Up @@ -4591,10 +4590,6 @@ export default function ChatView(props: ChatViewProps) {
useRightPanelStore.getState().open(activeThreadRef, "diff");
onDiffPanelOpen?.();
}, [activeThreadRef, isGitRepo, isServerThread, onDiffPanelOpen]);
const addGitHistorySurface = useCallback(() => {
if (!activeThreadRef || !isGitRepo || !supportsGitHistory) return;
useRightPanelStore.getState().open(activeThreadRef, "git-history");
}, [activeThreadRef, isGitRepo, supportsGitHistory]);
const addFilesSurface = useCallback(() => {
if (!activeThreadRef || !activeProject) return;
useRightPanelStore.getState().open(activeThreadRef, "files");
Expand All @@ -4613,8 +4608,18 @@ export default function ChatView(props: ChatViewProps) {
isServerThread && supportsThreadPullRequests && visiblePullRequestCount > 0;
const addPullRequestsSurface = useCallback(() => {
if (!activeThreadRef || !pullRequestsSurfaceAvailable) return;
useRightPanelStore.getState().open(activeThreadRef, "pull-requests");
useRightPanelStore.getState().openRepository(activeThreadRef, "pull-requests");
}, [activeThreadRef, pullRequestsSurfaceAvailable]);
const addRepositorySurface = useCallback(() => {
if (!activeThreadRef || (!(isGitRepo && supportsGitHistory) && !pullRequestsSurfaceAvailable))
return;
useRightPanelStore
.getState()
.openRepository(
activeThreadRef,
isGitRepo && supportsGitHistory ? "history" : "pull-requests",
);
}, [activeThreadRef, isGitRepo, pullRequestsSurfaceAvailable, supportsGitHistory]);
const { state: deviceState, loaded: deviceStateLoaded } = useDeviceState(
activeThreadRef?.environmentId ?? null,
);
Expand Down Expand Up @@ -4864,7 +4869,7 @@ export default function ChatView(props: ChatViewProps) {
) {
panels.openProactive(
activeThreadRef,
{ id: "pull-requests", kind: "pull-requests" },
{ id: "repository", kind: "repository", view: "pull-requests" },
userActionRevision,
);
} else if (
Expand Down Expand Up @@ -9750,17 +9755,19 @@ export default function ChatView(props: ChatViewProps) {
workspaceMutationId={workspaceMutationId}
/>
</Suspense>
) : renderedRightPanelSurface?.kind === "git-history" &&
supportsGitHistory &&
isGitRepo &&
gitCwd !== null ? (
) : renderedRightPanelSurface?.kind === "repository" ? (
<Suspense fallback={null}>
<GitHistoryPanel
<RepositoryPanel
environmentId={environmentId}
cwd={gitCwd}
cwd={isGitRepo ? gitCwd : null}
threadRef={activeThreadRef}
view={renderedRightPanelSurface.view}
active={rightPanelOpen}
stateStore={gitHistoryPanelStore}
stateScopeKey={`${activeThreadRef.environmentId}:${activeThreadRef.threadId}`}
gitHistoryAvailable={supportsGitHistory && isGitRepo}
gitHistoryPanelStore={gitHistoryPanelStore}
onViewChange={(view) =>
useRightPanelStore.getState().selectRepositoryView(activeThreadRef, view)
}
/>
</Suspense>
) : renderedRightPanelSurface?.kind === "pull-request" && !pullRequestsCapabilityKnown ? (
Expand Down Expand Up @@ -9865,8 +9872,6 @@ export default function ChatView(props: ChatViewProps) {
}}
onStateChange={handleIssueTabStatusChange}
/>
) : renderedRightPanelSurface?.kind === "pull-requests" && activeThreadRef ? (
<ThreadPullRequestsPanel threadRef={activeThreadRef} />
) : renderedRightPanelSurface?.kind === "agents" ? (
<AgentsPanel
model={agentPanelModel}
Expand Down Expand Up @@ -10528,17 +10533,16 @@ export default function ChatView(props: ChatViewProps) {
onAddBrowserInProfile={createBrowserSurface}
onAddTerminal={addTerminalSurface}
onAddDiff={addDiffSurface}
onAddGitHistory={addGitHistorySurface}
onAddRepository={addRepositorySurface}
onAddFiles={addFilesSurface}
onAddPullRequest={addPullRequestSurface}
onAddIssue={addIssueSurface}
onAddPullRequests={addPullRequestsSurface}
onAddAgents={addAgentsSurface}
onAddDevice={addDeviceSurface}
browserAvailable={isPreviewSupportedInRuntime()}
terminalAvailable={activeProject !== null}
diffAvailable={isServerThread && isGitRepo}
gitHistoryAvailable={isGitRepo && supportsGitHistory}
repositoryAvailable={(isGitRepo && supportsGitHistory) || pullRequestsSurfaceAvailable}
filesAvailable={activeProject !== null}
pullRequestAvailable={pullRequestSurfaceAvailable}
issueAvailable={issueSurfaceAvailable}
Expand Down Expand Up @@ -10591,17 +10595,16 @@ export default function ChatView(props: ChatViewProps) {
onAddBrowserInProfile={createBrowserSurface}
onAddTerminal={addTerminalSurface}
onAddDiff={addDiffSurface}
onAddGitHistory={addGitHistorySurface}
onAddRepository={addRepositorySurface}
onAddFiles={addFilesSurface}
onAddPullRequest={addPullRequestSurface}
onAddIssue={addIssueSurface}
onAddPullRequests={addPullRequestsSurface}
onAddAgents={addAgentsSurface}
onAddDevice={addDeviceSurface}
browserAvailable={isPreviewSupportedInRuntime()}
terminalAvailable={activeProject !== null}
diffAvailable={isServerThread && isGitRepo}
gitHistoryAvailable={isGitRepo && supportsGitHistory}
repositoryAvailable={(isGitRepo && supportsGitHistory) || pullRequestsSurfaceAvailable}
filesAvailable={activeProject !== null}
pullRequestAvailable={pullRequestSurfaceAvailable}
issueAvailable={issueSurfaceAvailable}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1809,12 +1809,12 @@ function OpenCommandPaletteDialog(props: {
actionItems.push({
kind: "action",
value: "action:open-thread-pull-requests",
searchTerms: ["pull requests", "linked", "stack", "prs"],
searchTerms: ["repository", "pull requests", "linked", "stack", "prs"],
title: "Show linked pull requests",
disabled: visibleThreadPullRequests(activeThread.pullRequests).length === 0,
icon: <PullRequestGlyph.link className={ITEM_ICON_CLASS} />,
run: async () => {
useRightPanelStore.getState().open(threadRef, "pull-requests");
useRightPanelStore.getState().openRepository(threadRef, "pull-requests");
},
});
}
Expand Down
16 changes: 16 additions & 0 deletions apps/web/src/components/RepositoryPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { describe, expect, it } from "vite-plus/test";

import { repositoryViewFromKey } from "./RepositoryPanel";

describe("RepositoryPanel", () => {
it("moves through repository views with the tablist keys", () => {
expect(repositoryViewFromKey("history", "ArrowRight")).toBe("pull-requests");
expect(repositoryViewFromKey("pull-requests", "ArrowLeft")).toBe("history");
expect(repositoryViewFromKey("pull-requests", "Home")).toBe("history");
expect(repositoryViewFromKey("history", "End")).toBe("pull-requests");
});

it("leaves unrelated keys to the browser", () => {
expect(repositoryViewFromKey("history", "Enter")).toBeNull();
});
});
113 changes: 113 additions & 0 deletions apps/web/src/components/RepositoryPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import type { EnvironmentId, ScopedThreadRef } from "@t3tools/contracts";
import { Activity, lazy, Suspense, useRef, useState, type KeyboardEvent } from "react";

import type { RepositoryView } from "~/rightPanelStore";
import type { GitHistoryPanelStore } from "./git-history/GitHistoryPanelState";

import { ThreadPullRequestsPanel } from "./pullRequest/ThreadPullRequestsPanel";
import { Button } from "./ui/button";

const GitHistoryPanel = lazy(() => import("./GitHistoryPanel"));

const views = ["history", "pull-requests"] as const;

export function repositoryViewFromKey(view: RepositoryView, key: string): RepositoryView | null {
const index = views.indexOf(view);
if (key === "ArrowRight") return views[(index + 1) % views.length] ?? null;
if (key === "ArrowLeft") return views[(index + views.length - 1) % views.length] ?? null;
if (key === "Home") return views[0] ?? null;
if (key === "End") return views.at(-1) ?? null;
return null;
}

export default function RepositoryPanel(props: {
readonly environmentId: EnvironmentId;
readonly cwd: string | null;
readonly threadRef: ScopedThreadRef;
readonly view: RepositoryView;
readonly active: boolean;
readonly gitHistoryAvailable: boolean;
readonly gitHistoryPanelStore: GitHistoryPanelStore;
readonly onViewChange: (view: RepositoryView) => void;
}) {
const tabs = useRef<Array<HTMLButtonElement | null>>([]);
const [historyActivated, setHistoryActivated] = useState(props.view === "history");
if (props.view === "history" && !historyActivated) setHistoryActivated(true);
const select = (view: RepositoryView) => props.onViewChange(view);
const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>, view: RepositoryView) => {
const next = repositoryViewFromKey(view, event.key);
if (next === null) return;
event.preventDefault();
select(next);
tabs.current[views.indexOf(next)]?.focus();
};

return (
<section
className="flex size-full min-h-0 min-w-0 flex-col bg-background"
aria-label="Repository"
>
<div
className="flex shrink-0 gap-1 border-b border-border/70 px-3 py-1.5"
role="tablist"
aria-label="Repository views"
>
{views.map((view, index) => (
<Button
key={view}
ref={(node) => {
tabs.current[index] = node;
}}
size="xs"
variant={props.view === view ? "secondary" : "ghost"}
role="tab"
id={`repository-tab-${view}`}
aria-selected={props.view === view}
aria-controls={`repository-panel-${view}`}
tabIndex={props.view === view ? 0 : -1}
onClick={() => select(view)}
onKeyDown={(event) => onKeyDown(event, view)}
>
{view === "history" ? "History" : "Pull Requests"}
</Button>
))}
</div>
{historyActivated ? (
<Activity mode={props.view === "history" ? "visible" : "hidden"}>
<div
id="repository-panel-history"
role="tabpanel"
aria-labelledby="repository-tab-history"
className="min-h-0 flex-1"
>
{props.gitHistoryAvailable && props.cwd !== null ? (
<Suspense fallback={null}>
<GitHistoryPanel
environmentId={props.environmentId}
cwd={props.cwd}
active={props.active && props.view === "history"}
stateStore={props.gitHistoryPanelStore}
stateScopeKey={`${props.threadRef.environmentId}:${props.threadRef.threadId}`}
/>
</Suspense>
) : (
<div className="flex size-full items-center justify-center p-6 text-center text-sm text-muted-foreground">
Update the environment server to browse Git History.
</div>
)}
</div>
</Activity>
) : null}
<Activity mode={props.view === "pull-requests" ? "visible" : "hidden"}>
<div
id="repository-panel-pull-requests"
role="tabpanel"
aria-labelledby="repository-tab-pull-requests"
className="min-h-0 flex-1"
>
<ThreadPullRequestsPanel threadRef={props.threadRef} />
</div>
</Activity>
</section>
);
}
3 changes: 2 additions & 1 deletion apps/web/src/components/RightPanelTabs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,9 @@ function renderTabs(
onAddBrowserInProfile={() => undefined}
onAddTerminal={() => undefined}
onAddPullRequest={() => undefined}
onAddRepository={() => undefined}
repositoryAvailable={false}
onAddIssue={() => undefined}
onAddPullRequests={() => undefined}
onAddDiff={() => undefined}
onAddFiles={() => undefined}
onAddAgents={() => undefined}
Expand Down
Loading
Loading