# Files changed · PR \#7148 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/7148/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[feat\(web\): refresh pull request details](https://git.cafe/pingdotgg/t3code/pull/7148.md)

Version: 31

Head commit: eee6a0458dc34f61e7727095e6d4ba361eb2b776

Base commit: 8c85b4933fb7202c2d48e4c8f8ea0e1cfdbcdc29

Comparison base: 8c85b4933fb7202c2d48e4c8f8ea0e1cfdbcdc29

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/web/src/components/ChatView.tsx

Status: modified; +0; −1

```diff
@@ -6189,7 +6189,6 @@
             ? "thread"
             : "page"
         }
-        chromeVariant="collapse"
         composerDraftTarget={composerDraftTarget}
         onStateChange={handlePullRequestTabStatusChange}
       />
```

## apps/web/src/components/chat/PanelLayoutControls.test.tsx

Status: added; +28; −0

```diff
@@ -1,0 +1,28 @@
+import { renderToStaticMarkup } from "react-dom/server";
+import { describe, expect, it } from "vite-plus/test";
+
+import { PanelLayoutControls } from "./PanelLayoutControls";
+
+describe("PanelLayoutControls", () => {
+  it("keeps unavailable panel tooltip triggers interactive", () => {
+    const markup = renderToStaticMarkup(
+      <PanelLayoutControls
+        showTerminalControl
+        terminalAvailable={false}
+        terminalOpen={false}
+        terminalShortcutLabel={null}
+        rightPanelAvailable={false}
+        rightPanelOpen={false}
+        rightPanelShortcutLabel={null}
+        liveAgentCount={0}
+        onToggleTerminal={() => {}}
+        onToggleRightPanel={() => {}}
+      />,
+    );
+
+    expect(markup.match(/data-slot="tooltip-trigger"/g)).toHaveLength(2);
+    expect(markup.match(/data-slot="tooltip-trigger"[^>]*><button[^>]*disabled=""/g)).toHaveLength(
+      2,
+    );
+  });
+});
```

## apps/web/src/components/chat/PanelLayoutControls.tsx

Status: modified; +41; −43

```diff
@@ -12,6 +12,7 @@
   rightPanelAvailable: boolean;
   rightPanelOpen: boolean;
   rightPanelShortcutLabel: string | null;
+  rightPanelUnavailableLabel?: string;
   /** Running + waiting subagents in this thread; badges the right panel toggle. */
   liveAgentCount: number;
   onToggleTerminal: () => void;
@@ -26,6 +27,7 @@
   rightPanelAvailable,
   rightPanelOpen,
   rightPanelShortcutLabel,
+  rightPanelUnavailableLabel = "Right panel is unavailable",
   liveAgentCount,
   onToggleTerminal,
   onToggleRightPanel,
@@ -37,21 +39,19 @@
     >
       {showTerminalControl ? (
         <Tooltip>
+          <TooltipTrigger render={<span className="flex shrink-0" />}>
+            <Toggle
+              className="shrink-0 [-webkit-app-region:no-drag]"
+              pressed={terminalOpen}
-          <TooltipTrigger
-            render={
-              <Toggle
-                className="shrink-0 [-webkit-app-region:no-drag]"
-                pressed={terminalOpen}
-                onPressedChange={onToggleTerminal}
-                aria-label="Toggle terminal drawer"
-                variant="ghost"
-                size="sm"
-                disabled={!terminalAvailable}
-              >
-                <PanelBottomIcon className="size-4" />
-              </Toggle>
-            }
-          />
+              onPressedChange={onToggleTerminal}
+              aria-label="Toggle terminal drawer"
+              variant="ghost"
+              size="sm"
+              disabled={!terminalAvailable}
+            >
+              <PanelBottomIcon className="size-4" />
+            </Toggle>
+          </TooltipTrigger>
           <TooltipPopup side="bottom">
             {terminalAvailable
               ? `Toggle terminal drawer${terminalShortcutLabel ? ` (${terminalShortcutLabel})` : ""}`
@@ -60,33 +60,31 @@
         </Tooltip>
       ) : null}
       <Tooltip>
-        <TooltipTrigger
-          render={
-            <Toggle
-              className="shrink-0 [-webkit-app-region:no-drag]"
-              pressed={rightPanelOpen}
-              onPressedChange={onToggleRightPanel}
-              aria-label={
-                liveAgentCount > 0
-                  ? `Toggle right panel, ${liveAgentCount} ${liveAgentCount === 1 ? "agent" : "agents"} working`
-                  : "Toggle right panel"
-              }
-              variant="ghost"
+        <TooltipTrigger render={<span className="flex shrink-0" />}>
+          <Toggle
+            className="shrink-0 [-webkit-app-region:no-drag]"
+            pressed={rightPanelOpen}
+            onPressedChange={onToggleRightPanel}
+            aria-label={
-              size="sm"
-              disabled={!rightPanelAvailable}
-            >
-              <PanelRightIcon className="size-4" />
-              {liveAgentCount > 0 ? (
-                <span
-                  aria-hidden
-                  className="absolute -top-1 -right-1 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-info px-1 text-[9px] font-semibold tabular-nums text-white"
-                >
-                  {liveAgentCount}
-                </span>
-              ) : null}
-            </Toggle>
-          }
-        />
+              liveAgentCount > 0
+                ? `Toggle right panel, ${liveAgentCount} ${liveAgentCount === 1 ? "agent" : "agents"} working`
+                : "Toggle right panel"
+            }
+            variant="ghost"
+            size="sm"
+            disabled={!rightPanelAvailable}
+          >
+            <PanelRightIcon className="size-4" />
+            {liveAgentCount > 0 ? (
+              <span
+                aria-hidden
+                className="absolute -top-1 -right-1 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-info px-1 text-[9px] font-semibold tabular-nums text-white"
+              >
+                {liveAgentCount}
+              </span>
+            ) : null}
+          </Toggle>
+        </TooltipTrigger>
         <TooltipPopup side="bottom">
           {rightPanelAvailable
             ? `Toggle right panel${rightPanelShortcutLabel ? ` (${rightPanelShortcutLabel})` : ""}${
@@ -94,7 +92,7 @@
                   ? ` · ${liveAgentCount} ${liveAgentCount === 1 ? "agent" : "agents"} working`
                   : ""
               }`
-            : "Right panel is unavailable"}
+            : rightPanelUnavailableLabel}
         </TooltipPopup>
       </Tooltip>
     </div>
```

## apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx

Status: modified; +410; −431

```diff
@@ -25,6 +25,7 @@
   GitPullRequestDraftIcon,
   GitPullRequestIcon,
   HammerIcon,
+  LayersIcon,
   MessageCircleQuestionIcon,
   MessageSquareIcon,
   LinkIcon,
@@ -50,6 +51,7 @@
 import { type DraftId, useComposerDraftStore } from "~/composerDraftStore";
 import { useNewThreadHandler } from "~/hooks/useHandleNewThread";
 import { useCopyToClipboard, writeTextToClipboard } from "~/hooks/useCopyToClipboard";
+import { changeRequestRepositoryUrl } from "~/lib/openPullRequestLink";
 import { usePreparePullRequestThreadAction } from "~/lib/sourceControlActions";
 import { cn } from "~/lib/utils";
 import { readLocalApi } from "~/localApi";
@@ -60,6 +62,7 @@
 import { useLiveRefresh } from "~/hooks/useLiveRefresh";
 import { pullRequestEnvironment } from "~/state/pullRequests";
 import { useAtomCommand } from "~/state/use-atom-command";
+import { vcsEnvironment } from "~/state/vcs";
 import { formatRelativeTimeLabel } from "~/timestampFormat";
 
 import {
@@ -74,6 +77,7 @@
 import { Badge } from "../ui/badge";
 import { Button } from "../ui/button";
 import { Input } from "../ui/input";
+import { Toggle, ToggleGroup } from "../ui/toggle-group";
 import {
   Menu,
   MenuItem,
@@ -104,6 +108,7 @@
   handoffPrompt,
   handoffReviewComments,
   latestPullRequestReviewOutcomes,
+  isStackedPullRequestBase,
   pullRequestActionMenuHasGroup,
   pullRequestActionNeedsHostRefresh,
   pullRequestComposerTarget,
@@ -121,6 +126,7 @@
 } from "./pullRequestProjectAssignment.logic";
 import { PullRequestChecksPopover } from "./PullRequestChecksPopover";
 import {
+  PullRequestActorAvatar,
   PullRequestActorLabel,
   PullRequestDiffStat,
   PullRequestMetaLine,
@@ -362,7 +368,6 @@
   onClose,
   onStateChange,
   context = "page",
-  chromeVariant = "full",
   composerDraftTarget,
 }: {
   environmentId: EnvironmentId;
@@ -395,12 +400,6 @@
    */
   context?: "page" | "thread";
   /**
-   * How the metadata above the content behaves: `full` keeps every row pinned; `collapse`
-   * folds the whole of it into the top row once the active tab scrolls, and unfolds at the
-   * top — the chrome spends its height on what is being read.
-   */
-  chromeVariant?: "full" | "collapse";
-  /**
    * The open thread's composer. Beside the thread whose own pull request this is, hand-offs
    * land here instead of opening a new thread — the branch is already under the reader's feet.
    */
@@ -436,27 +435,16 @@
     );
   }, [tab]);
   const [chromeCondensed, setChromeCondensed] = useState(false);
+  // Each mounted tab remembers its own scroll chrome; short tabs cannot scroll to reopen it.
-  // Each tab remembers whether its chrome was condensed. Only the active tab can emit scroll
-  // events, so the capture handler always writes the active tab's entry — and a tab switch
-  // reads the destination's memory instead of inheriting the tab being left. A tab too short
-  // to scroll remembers "expanded", which is what keeps it from being stranded under a chrome
-  // it has no scrollbar to reopen.
   const chromeStateByTab = useRef<Partial<Record<DetailTab, boolean>>>({});
   useEffect(() => {
     setChromeCondensed(chromeStateByTab.current[tab] ?? false);
   }, [tab]);
+  const condensed = chromeCondensed;
-  const condensed = chromeVariant === "collapse" && chromeCondensed;
-  // Collapsing removes the fold's height from the chrome, which would otherwise hand that
-  // height to the scrollport and leap the content up by it mid-scroll. The cure is exact
-  // compensation: collapse only once the reader has scrolled at least the fold's height,
-  // then give that height back to `scrollTop` before the next paint — the content under
-  // their eyes does not move, and the collapse itself is the only thing that changes.
   const scrollerRef = useRef<HTMLElement | null>(null);
   const foldRef = useRef<HTMLDivElement | null>(null);
-  // The condensed chrome's second row opens as the fold closes, so the height the scrollport
-  // gains is the fold's minus this row's. Measured the same way the fold is: `scrollHeight`
-  // through a zero track reads its natural height in either state.
   const condensedRowRef = useRef<HTMLDivElement | null>(null);
+  // Refund after the fold commits so the content under the reader does not jump with its height.
   const compensationRef = useRef<number | null>(null);
   useLayoutEffect(() => {
     if (compensationRef.current === null) return;
@@ -478,7 +466,6 @@
     target: "branch name",
     timeout: 1600,
   });
-
   // The chunk is fetched as soon as the panel exists rather than waiting for the Code tab to be
   // clicked, so a reader who does click it lands on a chunk already in the module cache.
   useEffect(() => {
@@ -517,6 +504,23 @@
           },
     [activity, coreDetail],
   );
+  const repositoryUrl = detail === null ? null : changeRequestRepositoryUrl(detail.url);
+  const branchRefsQuery = useEnvironmentQuery(
+    detail === null
+      ? null
+      : vcsEnvironment.listRefs({
+          environmentId,
+          input: {
+            cwd: detail.workspaceRoot,
+            includeMatchingRemoteRefs: true,
+            // listRefs keeps the current ref first and a known default second.
+            limit: 2,
+          },
+        }),
+  );
+  const isStackedPullRequest =
+    detail !== null &&
+    isStackedPullRequestBase(detail.baseBranch, branchRefsQuery.data?.refs ?? []);
   const activityPending = activityQuery.isPending && activity === null;
   const activityError = activity === null ? activityQuery.error : null;
   const refreshDetail = useCallback(() => {
@@ -1046,17 +1050,17 @@
   const can = (action: PullRequestAction) =>
     detail?.capabilities.actions.includes(action) === true &&
     detail.viewerPermissions.actions.includes(action);
-  // One live action holds the slot. A conflicting change cannot be merged now, so the slot goes
-  // to the thing that would help instead of a Merge button that only ever says no.
+  // One live action holds the slot. Conflicts take priority because every other completion action
+  // depends on resolving them first, even for a reader who cannot merge on the host themselves.
   const primaryAction =
     detail === null || detail.state !== "open"
       ? null
-      : detail.isDraft && can("ready")
-        ? "ready"
-        : !can("merge")
+      : conflicting
+        ? "resolve"
-          ? null
-          : conflicting
-            ? "resolve"
+        : detail.isDraft && can("ready")
+          ? "ready"
+          : !can("merge")
+            ? null
             : allowedMergeMethods.length > 0
               ? "merge"
               : null;
@@ -1081,7 +1085,7 @@
     !conflicting &&
     allowedMergeMethods.length > 1;
   // The pull request number carries this state in the overview and the right-panel tab mirrors
-  // it. Conflicts keep their own row below: an open pull request remains green there.
+  // it. The conflict action is separate from this state: an open pull request remains green.
   const statePresentation = detail
     ? resolvePullRequestState({ state: detail.state, isDraft: detail.isDraft })
     : null;
@@ -1101,34 +1105,47 @@
         ).length
       : 0;
 
+  if (detailQuery.isPending && !detail) {
+    return <PullRequestDetailGhost />;
+  }
+
   return (
     <div className="flex h-full min-h-0 w-full flex-col bg-background">
-      {/* The top row's geometry never changes: both of its states occupy the same stacked
-          cell and crossfade, so the actions on the right have one home whatever the chrome
-          is doing below. The fold and this fade share one 200ms clock. */}
       <div className="grid shrink-0 grid-cols-[minmax(0,1fr)_auto] items-start gap-x-2 border-b border-border/60">
+        <div className="ml-4 grid h-7 min-w-0 items-center">
-        {/* The fixed height lives on the two top-row cells — not the grid, whose later rows
-            are the fold — so the actions have one immovable home in both states. */}
-        <div className="ml-4 grid h-11 min-w-0 items-center">
           <div
             aria-hidden={condensed}
             inert={condensed}
             className={cn(
-              "col-start-1 row-start-1 flex min-w-0 items-center gap-1 text-sm text-muted-foreground transition-opacity sm:text-xs motion-reduce:transition-none",
+              "col-start-1 row-start-1 flex min-w-0 items-center gap-1 text-sm text-muted-foreground transition-[opacity,transform] ease-out motion-reduce:transform-none motion-reduce:transition-none sm:text-xs",
-              // Sequenced, not simultaneous: the leaving layer clears quickly before the
-              // arriving one lands, so no frame shows both texts superimposed at half opacity.
               condensed
-                ? "pointer-events-none opacity-0 duration-100"
+                ? "pointer-events-none -translate-y-1 opacity-0 duration-100"
+                : "translate-y-0 opacity-100 delay-50 duration-150",
-                : "opacity-100 delay-75 duration-150",
             )}
           >
             {detail && statePresentation ? (
               <>
                 <Tooltip>
                   <TooltipTrigger
+                    render={
+                      repositoryUrl ? (
+                        <button
+                          type="button"
-                    render={<span className="min-w-0 truncate">{detail.repository}</span>}
+                          onClick={() => void readLocalApi()?.shell.openExternal(repositoryUrl)}
+                          className="min-w-0 cursor-pointer truncate text-left font-medium text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
+                        >
+                          {detail.repository}
+                        </button>
+                      ) : (
+                        <span className="min-w-0 truncate font-medium text-muted-foreground">
+                          {detail.repository}
+                        </span>
+                      )
+                    }
                   />
+                  <TooltipPopup side="top">
-                  <TooltipPopup side="top">{detail.repository}</TooltipPopup>
+                    {repositoryUrl ? `Open ${detail.repository} repository` : detail.repository}
+                  </TooltipPopup>
                 </Tooltip>
                 <Tooltip>
                   <TooltipTrigger
@@ -1156,10 +1173,10 @@
             aria-hidden={!condensed}
             inert={!condensed}
             className={cn(
+              "col-start-1 row-start-1 flex min-w-0 items-center gap-1 text-sm text-muted-foreground transition-[opacity,transform] ease-out motion-reduce:transform-none motion-reduce:transition-none sm:text-xs",
-              "col-start-1 row-start-1 flex min-w-0 items-center gap-1.5 text-sm transition-opacity sm:text-xs motion-reduce:transition-none",
               condensed
-                ? "opacity-100 delay-75 duration-150"
+                ? "translate-y-0 opacity-100 delay-50 duration-150"
+                : "pointer-events-none translate-y-1 opacity-0 duration-100",
-                : "pointer-events-none opacity-0 duration-100",
             )}
           >
             {detail && statePresentation ? (
@@ -1194,29 +1211,103 @@
                   />
                   <TooltipPopup side="top">{detail.title}</TooltipPopup>
                 </Tooltip>
-                {conflicting ? (
-                  <Badge
-                    variant="error"
-                    className="h-5 shrink-0 gap-1 rounded px-1.5 text-[10px] text-destructive"
-                  >
-                    <TriangleAlertIcon className="size-3" />
-                    Conflicts
-                  </Badge>
-                ) : checksSummary ? (
-                  <span className="flex shrink-0 items-center gap-1 text-[10px] text-muted-foreground">
-                    {detail && checksState !== null ? (
-                      <PullRequestChecksPopover checks={detail.checks} checksState={checksState} />
-                    ) : null}
-                    {checksSummary}
-                  </span>
-                ) : null}
               </>
             ) : null}
           </div>
         </div>
-        <div className="mr-4 flex h-11 min-w-0 flex-nowrap items-center justify-end gap-1">
+        <div className="mr-4 flex h-7 min-w-0 flex-nowrap items-center justify-end gap-1">
           {detail ? (
             <>
+              {/* Checking a pull request out is the reason to open one here at all, so it is a
+                  button of its own rather than a side effect of asking an agent for something.
+                  It asks where, because the two answers are not interchangeable: one leaves your
+                  work where it is, the other moves the repository you are standing in. Only on
+                  the page: beside a thread the branch is already checked out right there. */}
+              {context === "page" ? (
+                <Menu>
+                  <MenuTrigger
+                    disabled={handoff !== null}
+                    render={
+                      <Button size="xs" variant="outline">
+                        <GitBranchIcon aria-hidden className="size-3.5" />
+                        {handoff?.startsWith("checkout") ? "Checking out..." : "Check out"}
+                        <ChevronDownIcon aria-hidden className="size-3.5 text-muted-foreground" />
+                      </Button>
+                    }
+                  />
+                  <MenuPopup align="end" side="bottom" className="min-w-72">
+                    <MenuItem onClick={() => startCheckout("worktree")}>
+                      <GitBranchIcon className="mt-0.5 size-3.5 shrink-0 self-start" />
+                      <span className="flex min-w-0 flex-col">
+                        <span>In a separate worktree</span>
+                        <span className="text-xs text-muted-foreground">
+                          Its own folder and thread. Nothing you have open moves.
+                        </span>
+                      </span>
+                    </MenuItem>
+                    <MenuItem onClick={() => startCheckout("local")}>
+                      <FolderGit2Icon className="mt-0.5 size-3.5 shrink-0 self-start" />
+                      <span className="flex min-w-0 flex-col">
+                        <span>In this repository</span>
+                        <span className="text-xs text-muted-foreground">
+                          Switches the branch you are working in, like `gh pr checkout`.
+                        </span>
+                      </span>
+                    </MenuItem>
+                    {pickableEnvironments.length > 0 ? (
+                      <ActOnEnvironmentPicker
+                        environments={pickableEnvironments}
+                        value={actingEnvironmentId}
+                        onChange={(next) => setActingScope({ pullRequestKey, environmentId: next })}
+                        disabled={handoff !== null}
+                      />
+                    ) : null}
+                  </MenuPopup>
+                </Menu>
+              ) : null}
+              {/* Said where the Merge button is, because it is the answer to why nobody has
+                  pressed it: the merge is already asked for, and the host is holding it. */}
+              {autoMergeArmed ? (
+                <Tooltip>
+                  <TooltipTrigger
+                    render={
+                      <Badge
+                        variant="info"
+                        className="h-5 shrink-0 gap-1 rounded px-1.5 text-[10px]"
+                      >
+                        <GitMergeIcon aria-hidden className="size-3" />
+                        Auto-merge
+                      </Badge>
+                    }
+                  />
+                  <TooltipPopup side="top">
+                    The host will merge this on its own once its requirements are met
+                  </TooltipPopup>
+                </Tooltip>
+              ) : null}
+              {primaryAction === "resolve" ? (
+                <Button
+                  size="xs"
+                  variant="destructive-outline"
+                  disabled={handoff !== null}
+                  onClick={startResolveConflicts}
+                >
+                  <TriangleAlertIcon className="size-3.5" />
+                  {handoff === "conflicts" ? "Preparing..." : "Resolve conflicts"}
+                </Button>
+              ) : primaryAction === "ready" ? (
+                <Button size="xs" disabled={actionPending} onClick={() => void perform("ready")}>
+                  Ready for review
+                </Button>
+              ) : primaryAction === "merge" ? (
+                <Button
+                  size="xs"
+                  disabled={actionPending}
+                  onClick={() => setConfirmation({ open: true, action: "merge" })}
+                >
+                  {pendingAction === "merge" ? "Merging..." : selectedMergeMethodLabel}
+                </Button>
+              ) : null}
               <Menu>
                 <MenuTrigger
                   render={
@@ -1361,13 +1452,6 @@
                     <LinkIcon className="size-3.5" />
                     Copy link
                   </MenuItem>
-                  {/* Only where the button row could not take it, so it is never offered twice. */}
-                  {conflicting && primaryAction !== "resolve" ? (
-                    <MenuItem disabled={handoff !== null} onClick={startResolveConflicts}>
-                      <GitMergeIcon className="size-3.5" />
-                      {handoff === "conflicts" ? "Preparing..." : handoffLabels.resolveConflicts}
-                    </MenuItem>
-                  ) : null}
                   {detail.state === "open" && can("close") ? (
                     <>
                       <MenuSeparator />
@@ -1391,92 +1475,6 @@
                   ) : null}
                 </MenuPopup>
               </Menu>
-              {/* Checking a pull request out is the reason to open one here at all, so it is a
-                  button of its own rather than a side effect of asking an agent for something.
-                  It asks where, because the two answers are not interchangeable: one leaves your
-                  work where it is, the other moves the repository you are standing in. Only on
-                  the page: beside a thread the branch is already checked out right there. */}
-              {context === "page" ? (
-                <Menu>
-                  <MenuTrigger
-                    disabled={handoff !== null}
-                    render={
-                      <Button size="xs" variant="outline">
-                        {handoff?.startsWith("checkout") ? (
-                          "Checking out..."
-                        ) : (
-                          <>
-                            <GitBranchIcon className="size-3" />
-                            Check out
-                            <ChevronDownIcon className="size-3 text-muted-foreground" />
-                          </>
-                        )}
-                      </Button>
-                    }
-                  />
-                  <MenuPopup align="end" side="bottom" className="min-w-72">
-                    <MenuItem onClick={() => startCheckout("worktree")}>
-                      <GitBranchIcon className="mt-0.5 size-3.5 shrink-0 self-start" />
-                      <span className="flex min-w-0 flex-col">
-                        <span>In a separate worktree</span>
-                        <span className="text-xs text-muted-foreground">
-                          Its own folder and thread. Nothing you have open moves.
-                        </span>
-                      </span>
-                    </MenuItem>
-                    <MenuItem onClick={() => startCheckout("local")}>
-                      <FolderGit2Icon className="mt-0.5 size-3.5 shrink-0 self-start" />
-                      <span className="flex min-w-0 flex-col">
-                        <span>In this repository</span>
-                        <span className="text-xs text-muted-foreground">
-                          Switches the branch you are working in, like `gh pr checkout`.
-                        </span>
-                      </span>
-                    </MenuItem>
-                    {pickableEnvironments.length > 0 ? (
-                      <ActOnEnvironmentPicker
-                        environments={pickableEnvironments}
-                        value={actingEnvironmentId}
-                        onChange={(next) => setActingScope({ pullRequestKey, environmentId: next })}
-                        disabled={handoff !== null}
-                      />
-                    ) : null}
-                  </MenuPopup>
-                </Menu>
-              ) : null}
-              {/* Said where the Merge button is, because it is the answer to why nobody has
-                  pressed it: the merge is already asked for, and the host is holding it. */}
-              {autoMergeArmed ? (
-                <Tooltip>
-                  <TooltipTrigger
-                    render={
-                      <Badge
-                        variant="info"
-                        className="h-5 shrink-0 gap-1 rounded px-1.5 text-[10px]"
-                      >
-                        <GitMergeIcon aria-hidden className="size-3" />
-                        Auto-merge
-                      </Badge>
-                    }
-                  />
-                  <TooltipPopup side="top">
-                    The host will merge this on its own once its requirements are met
-                  </TooltipPopup>
-                </Tooltip>
-              ) : null}
-              {primaryAction === "ready" ? (
-                <Button size="xs" disabled={actionPending} onClick={() => void perform("ready")}>
-                  Ready for review
-                </Button>
-              ) : primaryAction === "merge" ? (
-                <Button
-                  size="xs"
-                  disabled={actionPending}
-                  onClick={() => setConfirmation({ open: true, action: "merge" })}
-                >
-                  {pendingAction === "merge" ? "Merging..." : selectedMergeMethodLabel}
-                </Button>
-              ) : null}
             </>
           ) : null}
           {onClose ? (
@@ -1491,14 +1489,9 @@
           ) : null}
         </div>
 
-        {/* The condensed chrome's second row: the tabs that the closing fold takes with it,
-            and compact copies of the branch pair and diff stat so they stay in sight while
-            the full rows are folded away. Same zero-track mechanism as the fold, inverted. */}
         <div
           className={cn(
             "col-span-2 grid",
-            // Inverse of the fold's track: closing eases while the fold above eases open,
-            // so the chrome trades rows in one motion instead of two jumps.
             condensed
               ? "grid-rows-[1fr]"
               : "grid-rows-[0fr] transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none",
@@ -1507,84 +1500,103 @@
           <div
             ref={condensedRowRef}
             className={cn(
-              "min-h-0 overflow-hidden",
+              "min-h-0 overflow-hidden transition-[opacity,transform] duration-150 ease-out motion-reduce:transform-none motion-reduce:transition-none",
               condensed
+                ? "translate-y-0 opacity-100 delay-50"
-                ? "opacity-100 transition-opacity duration-200 ease-out motion-reduce:transition-none"
+                : "translate-y-1 opacity-0 duration-100",
-                : "opacity-0",
             )}
             inert={!condensed}
           >
             {detail ? (
+              <div className="col-span-2 min-w-0 px-4 pb-2 pt-1">
+                <div className="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
+                  <span className="flex min-w-0 shrink items-center gap-1.5 overflow-hidden text-xs text-muted-foreground">
+                    <Tooltip>
+                      <TooltipTrigger
+                        render={
+                          <span
+                            className="shrink-0 rounded-full"
+                            aria-label={detail.author?.login ?? "ghost"}
+                          />
+                        }
+                      >
+                        <PullRequestActorAvatar actor={detail.author} />
+                      </TooltipTrigger>
+                      <TooltipPopup side="top">{detail.author?.login ?? "ghost"}</TooltipPopup>
+                    </Tooltip>
+                    <span className="shrink-0">{formatRelativeTimeLabel(detail.updatedAt)}</span>
+                  </span>
+                  <span aria-hidden className="h-3 w-px shrink-0 bg-border/70" />
+                  <span className="flex min-w-0 flex-1 items-center gap-1.5 font-mono text-[11px] text-muted-foreground/65">
+                    <Tooltip>
+                      <TooltipTrigger
+                        render={
+                          <span className="inline-flex min-w-0 max-w-[40%] shrink-0 items-center gap-1">
+                            {isStackedPullRequest ? (
+                              <LayersIcon
+                                aria-label="Stacked pull request"
+                                className="size-3 shrink-0"
+                              />
+                            ) : null}
+                            <code className="min-w-0 truncate">{detail.baseBranch}</code>
+                          </span>
+                        }
+                      />
+                      <TooltipPopup side="top">
+                        {isStackedPullRequest
+                          ? `Stacked on ${detail.baseBranch}`
+                          : detail.baseBranch}
+                      </TooltipPopup>
+                    </Tooltip>
+                    {freshness ? (
+                      <PullRequestBaseFreshnessWarning
+                        baseBranch={detail.baseBranch}
+                        freshness={freshness}
+                        pending={actionPending}
+                        onUpdate={(method) => void perform("update-branch", undefined, method)}
-              <div className="flex min-w-0 items-center gap-1 px-4 pb-2">
-                <nav aria-label="Pull request tabs" className="flex shrink-0 items-center gap-0.5">
-                  {visibleTabs.map((item) => (
-                    <button
-                      key={item.value}
-                      type="button"
-                      tabIndex={condensed ? 0 : -1}
-                      aria-pressed={tab === item.value}
-                      onClick={() => setTab(item.value)}
-                      className={cn(
-                        "rounded-md px-2 py-1 text-[11px] transition-colors",
-                        tab === item.value
-                          ? "bg-accent text-foreground"
+                        iconClassName="size-3"
+                      />
+                    ) : null}
+                    <ArrowLeftIcon
+                      aria-label="receives changes from"
+                      className="size-3 shrink-0 opacity-60"
+                    />
-                          : "text-muted-foreground hover:text-foreground",
+                    <Tooltip>
-                      )}
+                      <TooltipTrigger
+                        render={
+                          <code className="min-w-0 flex-1 truncate">{detail.headBranch}</code>
+                        }
+                      />
+                      <TooltipPopup side="top">{detail.headBranch}</TooltipPopup>
+                    </Tooltip>
+                  </span>
+                  <span className="ml-auto inline-flex shrink-0 items-center justify-end gap-2 text-[11px]">
+                    <span
+                      className="inline-flex items-center gap-1 tabular-nums"
+                      aria-label={`${detail.changedFiles.toLocaleString()} changed ${
+                        detail.changedFiles === 1 ? "file" : "files"
+                      }`}
                     >
-                      {item.label}
-                    </button>
+                      <FileDiffIcon aria-hidden className="size-3" />
+                      {detail.changedFiles.toLocaleString()}
+                    </span>
+                    <PullRequestDiffStat
+                      additions={detail.additions}
+                      deletions={detail.deletions}
+                      className="shrink-0 font-mono text-[11px]"
-                  ))}
-                </nav>
-                <span className="ml-auto inline-flex min-w-0 shrink items-center gap-1 font-mono text-[11px] text-muted-foreground">
-                  <Tooltip>
-                    <TooltipTrigger render={<span className="truncate" />}>
-                      {detail.baseBranch}
-                    </TooltipTrigger>
-                    <TooltipPopup side="top">{`${detail.baseBranch} ← ${detail.headBranch}`}</TooltipPopup>
-                  </Tooltip>
-                  {freshness ? (
-                    <PullRequestBaseFreshnessWarning
-                      baseBranch={detail.baseBranch}
-                      freshness={freshness}
-                      pending={actionPending}
-                      onUpdate={(method) => void perform("update-branch", undefined, method)}
-                      iconClassName="size-3"
                     />
-                  ) : null}
-                  <ArrowLeftIcon aria-label="receives changes from" className="size-3 shrink-0" />
-                  <Tooltip>
-                    <TooltipTrigger render={<span className="truncate" />}>
-                      {detail.headBranch}
-                    </TooltipTrigger>
-                    <TooltipPopup side="top">{`${detail.baseBranch} ← ${detail.headBranch}`}</TooltipPopup>
-                  </Tooltip>
-                </span>
-                <span className="ml-2 inline-flex shrink-0 items-center gap-2 text-[11px] text-muted-foreground">
-                  <span className="inline-flex items-center gap-1 tabular-nums">
-                    <FileDiffIcon className="size-3" />
-                    {detail.changedFiles.toLocaleString()}
                   </span>
-                  <PullRequestDiffStat
-                    additions={detail.additions}
+                </div>
-                    deletions={detail.deletions}
-                    className="shrink-0 font-mono text-[11px]"
-                  />
-                </span>
               </div>
             ) : null}
           </div>
         </div>
 
-        {/* Folding is a grid track going to zero: the rows below stay mounted, the track
-            animates closed over them, and `inert` takes the hidden controls out of the tab
-            order for as long as the chrome is condensed. */}
         <div
           className={cn(
             "col-span-2 grid",
+            // Collapse before the scroll refund paints; only reopening eases back in. Animating
-            // Collapse is instant: it happens mid-scroll, and the compensation that keeps the
-            // content pinned would fight an animated track frame by frame. Reopening happens
-            // at the top with no compensation, so the fold can ease back in instead of
-            // snapping the content down.
+            // both directions makes the shrinking track fight the scrollTop correction.
             condensed
               ? "grid-rows-[0fr]"
               : "grid-rows-[1fr] transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none",
@@ -1592,19 +1604,16 @@
         >
           <div
             ref={foldRef}
-            // One-way on purpose: appearing content eases in over ground the instant track
-            // already reserved; departing content cuts, because its ground is gone in the
-            // same frame and the scroll compensation reads it as scrolled past.
             className={cn(
-              "min-h-0 overflow-hidden",
+              "min-h-0 overflow-hidden transition-[opacity,transform] duration-150 ease-out motion-reduce:transform-none motion-reduce:transition-none",
               condensed
+                ? "-translate-y-1 opacity-0 duration-100"
+                : "translate-y-0 opacity-100 delay-50",
-                ? "opacity-0"
-                : "opacity-100 transition-opacity duration-200 ease-out motion-reduce:transition-none",
             )}
             inert={condensed}
           >
             {detail ? (
+              <div className="col-span-2 mt-1 min-w-0 px-4 pb-4">
-              <div className="col-span-2 mt-3 min-w-0 px-4 pb-4">
                 {titleDraft === null ? (
                   <div className="group flex min-w-0 items-start gap-1">
                     <h1 className="min-w-0 flex-1 text-base font-semibold leading-snug">
@@ -1671,60 +1680,75 @@
                 </PullRequestMetaLine>
 
                 <div className="mt-4 flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
+                  <span className="flex min-w-0 flex-1 items-center gap-1.5 font-mono text-xs text-muted-foreground/70">
+                    <Tooltip>
+                      <TooltipTrigger
-                  <Tooltip>
-                    <TooltipTrigger
-                      render={
-                        <code className="min-w-0 max-w-48 shrink truncate rounded-md bg-muted px-2 py-1 font-mono text-xs text-foreground">
-                          {detail.baseBranch}
-                        </code>
-                      }
-                    />
-                    <TooltipPopup side="top">{detail.baseBranch}</TooltipPopup>
-                  </Tooltip>
-                  {freshness ? (
-                    <PullRequestBaseFreshnessWarning
-                      baseBranch={detail.baseBranch}
+                        render={
+                          <span className="inline-flex min-w-0 max-w-[40%] shrink-0 items-center gap-1">
+                            {isStackedPullRequest ? (
+                              <LayersIcon
+                                aria-label="Stacked pull request"
+                                className="size-3 shrink-0"
+                              />
-                      freshness={freshness}
-                      pending={actionPending}
-                      onUpdate={(method) => void perform("update-branch", undefined, method)}
+                            ) : null}
+                            <code className="min-w-0 truncate">{detail.baseBranch}</code>
+                          </span>
+                        }
+                      />
+                      <TooltipPopup side="top">
+                        {isStackedPullRequest
+                          ? `Stacked on ${detail.baseBranch}`
+                          : detail.baseBranch}
+                      </TooltipPopup>
+                    </Tooltip>
+                    {freshness ? (
+                      <PullRequestBaseFreshnessWarning
+                        baseBranch={detail.baseBranch}
+                        freshness={freshness}
+                        pending={actionPending}
+                        onUpdate={(method) => void perform("update-branch", undefined, method)}
+                      />
+                    ) : null}
+                    <ArrowLeftIcon
+                      aria-label="receives changes from"
+                      className="size-3.5 shrink-0 opacity-60"
                     />
-                  ) : null}
-                  <ArrowLeftIcon aria-label="receives changes from" className="size-4 shrink-0" />
-                  <Tooltip>
-                    <TooltipTrigger
-                      render={
-                        <button
-                          type="button"
+                    <Tooltip>
+                      <TooltipTrigger
+                        render={
+                          <button
-                          className="grid min-w-0 max-w-64 shrink cursor-pointer rounded-md bg-muted px-2 py-1 font-mono text-xs text-foreground outline-none transition-colors hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"
+                            type="button"
-                          aria-label={
+                            className="grid w-fit min-w-0 max-w-full shrink cursor-pointer rounded px-1 py-0.5 text-left outline-none transition-colors hover:bg-accent/45 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"
+                            aria-label={
+                              isBranchCopied ? "Branch name copied" : "Copy pull request branch"
+                            }
+                            onClick={() => copyBranchToClipboard(detail.headBranch)}
+                          />
+                        }
-                            isBranchCopied ? "Branch name copied" : "Copy pull request branch"
-                          }
-                          onClick={() => copyBranchToClipboard(detail.headBranch)}
-                        />
-                      }
-                    >
-                      <code
-                        className={cn(
-                          "col-start-1 row-start-1 min-w-0 truncate transition-opacity duration-150 motion-reduce:transition-none",
-                          isBranchCopied ? "opacity-0" : "opacity-100",
-                        )}
                       >
+                        <code
+                          className={cn(
+                            "col-start-1 row-start-1 min-w-0 truncate transition-opacity duration-150 motion-reduce:transition-none",
+                            isBranchCopied ? "opacity-0" : "opacity-100",
+                          )}
+                        >
+                          {detail.headBranch}
+                        </code>
+                        <span
-                        {detail.headBranch}
-                      </code>
-                      <span
-                        aria-hidden="true"
+                          aria-hidden="true"
-                        className={cn(
+                          className={cn(
+                            "col-start-1 row-start-1 truncate text-center transition-opacity duration-150 motion-reduce:transition-none",
+                            isBranchCopied ? "opacity-100" : "opacity-0",
+                          )}
+                        >
+                          Copied
+                        </span>
+                      </TooltipTrigger>
+                      <TooltipPopup side="top">
+                        {`${isBranchCopied ? "Copied" : "Copy pull request branch"}: ${detail.headBranch}`}
+                      </TooltipPopup>
+                    </Tooltip>
+                  </span>
-                          "col-start-1 row-start-1 truncate text-center transition-opacity duration-150 motion-reduce:transition-none",
-                          isBranchCopied ? "opacity-100" : "opacity-0",
-                        )}
-                      >
-                        Copied
-                      </span>
-                    </TooltipTrigger>
-                    <TooltipPopup side="top">
-                      {`${isBranchCopied ? "Copied" : "Copy pull request branch"}: ${detail.headBranch}`}
-                    </TooltipPopup>
-                  </Tooltip>
                   <span className="ml-auto inline-flex shrink-0 items-center justify-end gap-2">
                     <span className="inline-flex items-center gap-1.5 tabular-nums">
                       <FileDiffIcon className="size-3.5" />
@@ -1740,165 +1764,130 @@
                 </div>
               </div>
             ) : null}
-
-            {detail && conflicting ? (
-              <div className="col-span-2 flex items-center gap-1 px-4 pb-3">
-                <Badge
-                  variant="error"
-                  className="h-auto gap-1.5 rounded-md px-3 py-1.5 text-xs text-destructive"
-                >
-                  <TriangleAlertIcon className="size-3.5" />
-                  Merge conflicts
-                </Badge>
-                <Button
-                  size="xs"
-                  variant="ghost"
-                  className="ml-auto text-destructive hover:bg-destructive/8 hover:text-destructive"
-                  disabled={handoff !== null}
-                  onClick={startResolveConflicts}
-                >
+          </div>
+        </div>
-                  {handoff === "conflicts" ? "Preparing..." : handoffLabels.resolve}
-                  <ArrowUpRightIcon className="size-3.5 text-destructive" />
-                </Button>
-              </div>
-            ) : null}
 
-            {detail ? (
-              <nav
-                className="col-span-2 flex min-w-0 items-center gap-1 overflow-x-auto border-t border-border/60 px-4 py-2 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
-                aria-label="Pull request tabs"
+        {detail ? (
+          <nav
+            className="col-span-2 flex min-w-0 items-center gap-1 overflow-x-auto border-t border-border/60 px-4 py-2 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
+            aria-label="Pull request tabs"
+          >
+            <ToggleGroup
+              size="segmented"
+              variant="segmented"
+              value={[tab]}
+              onValueChange={(next) => {
+                const nextTab = visibleTabs.find((item) => item.value === next[0])?.value;
+                if (nextTab) setTab(nextTab);
+              }}
+            >
+              {visibleTabs.map((item) => (
+                <Toggle key={item.value} value={item.value}>
+                  {item.label}
+                </Toggle>
+              ))}
+            </ToggleGroup>
+            {tab === "summary" ? (
+              <span
+                className="ml-auto inline-flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground"
+                aria-label={checksSummary ? `Checks: ${checksSummary}` : "Checks"}
               >
+                {checksState !== null ? (
+                  <PullRequestChecksPopover checks={detail.checks} checksState={checksState} />
+                ) : (
+                  <CircleDotIcon aria-hidden className="size-3.5" />
+                )}
+                {checksSummary}
+              </span>
+            ) : tab === "timeline" ? (
+              <div className="ml-auto flex shrink-0 items-center gap-2 text-xs text-muted-foreground">
+                <PullRequestMetaLine
+                  className={cn(
+                    "whitespace-nowrap text-[11px] transition-opacity",
+                    (activityPending || activityError) && "opacity-35",
+                  )}
+                >
+                  <span
-                {visibleTabs.map((item) => (
-                  <button
-                    key={item.value}
+                    className="inline-flex items-center gap-1"
-                    type="button"
-                    aria-pressed={tab === item.value}
-                    onClick={() => setTab(item.value)}
-                    className={cn(
-                      "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs transition-colors",
-                      tab === item.value
-                        ? "bg-accent text-foreground"
-                        : "text-muted-foreground hover:text-foreground",
-                    )}
+                    aria-label={
+                      activityError
+                        ? "Comments unavailable"
+                        : `${detail.commentCount.toLocaleString()} ${
+                            detail.commentCount === 1 ? "comment" : "comments"
+                          }`
+                    }
                   >
+                    <MessageSquareIcon aria-hidden className="size-3" />
+                    {activityError
+                      ? "—"
+                      : activityPending
+                        ? "…"
+                        : detail.commentCount.toLocaleString()}
-                    {item.label}
-                  </button>
-                ))}
+                  </span>
-                {tab === "summary" ? (
                   <span
+                    className="inline-flex items-center gap-1"
+                    aria-label={
-                    className="ml-auto inline-flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground"
+                      activityError
-                    aria-label={checksSummary ? `Checks: ${checksSummary}` : "Checks"}
+                        ? "Commits unavailable"
+                        : `${detail.commits.length.toLocaleString()} ${
+                            detail.commits.length === 1 ? "commit" : "commits"
+                          }`
+                    }
                   >
+                    <GitCommitHorizontalIcon aria-hidden className="size-3" />
+                    {activityError
-                    {/* The rollup icon opens the checks behind the summary; with none reported
+                      ? "—"
-                        there is nothing to open, so the plain glyph stays. */}
-                    {detail && checksState !== null ? (
-                      <PullRequestChecksPopover checks={detail.checks} checksState={checksState} />
-                    ) : (
-                      <CircleDotIcon aria-hidden className="size-3.5" />
-                    )}
-                    {checksSummary}
+                      : activityPending
+                        ? "…"
+                        : detail.commits.length.toLocaleString()}
                   </span>
+                  {approvalCount > 0 ? (
-                ) : tab === "timeline" ? (
+                    <span
-                  <div className="ml-auto flex shrink-0 items-center gap-2 text-xs text-muted-foreground">
-                    <PullRequestMetaLine
                       className={cn(
-                        "whitespace-nowrap text-[11px] transition-opacity",
-                        (activityPending || activityError) && "opacity-35",
+                        "inline-flex items-center gap-1",
+                        pullRequestReviewOutcomeToneClassName("approved"),
                       )}
                     >
-                      <span
-                        className="inline-flex items-center gap-1"
-                        aria-label={
-                          activityError
-                            ? "Comments unavailable"
-                            : `${detail.commentCount.toLocaleString()} ${
-                                detail.commentCount === 1 ? "comment" : "comments"
-                              }`
-                        }
-                      >
-                        <MessageSquareIcon aria-hidden className="size-3" />
-                        {activityError
-                          ? "—"
-                          : activityPending
+                      <PullRequestReviewOutcomeIcon outcome="approved" className="size-3" />
+                      {approvalCount.toLocaleString()}
+                      <span className="sr-only">
+                        {approvalCount === 1 ? "approval" : "approvals"}
-                            ? "…"
-                            : detail.commentCount.toLocaleString()}
                       </span>
+                    </span>
+                  ) : null}
+                </PullRequestMetaLine>
+                <Button
+                  size="xs"
+                  variant="ghost"
+                  className="h-7 px-2 text-[10px] text-muted-foreground"
+                  aria-label={
+                    timelineOrder === "newest"
+                      ? "Show oldest activity first"
+                      : "Show newest activity first"
+                  }
+                  onClick={() =>
+                    setTimelineOrder((value) => (value === "newest" ? "oldest" : "newest"))
+                  }
+                >
+                  <ArrowDownUpIcon aria-hidden className="size-3" />
-                      <span
+                  {timelineOrder === "newest" ? "Newest first" : "Oldest first"}
+                </Button>
+              </div>
-                        className="inline-flex items-center gap-1"
-                        aria-label={
-                          activityError
-                            ? "Commits unavailable"
-                            : `${detail.commits.length.toLocaleString()} ${
-                                detail.commits.length === 1 ? "commit" : "commits"
-                              }`
-                        }
-                      >
-                        <GitCommitHorizontalIcon aria-hidden className="size-3" />
-                        {activityError
-                          ? "—"
-                          : activityPending
-                            ? "…"
-                            : detail.commits.length.toLocaleString()}
-                      </span>
-                      {/* Only once somebody has approved: a nought beside a tick would read as a
-                          verdict of its own on a change nobody has looked at yet. */}
-                      {approvalCount > 0 ? (
-                        <span
-                          className={cn(
-                            "inline-flex items-center gap-1",
-                            pullRequestReviewOutcomeToneClassName("approved"),
-                          )}
-                        >
-                          <PullRequestReviewOutcomeIcon outcome="approved" className="size-3" />
-                          {approvalCount.toLocaleString()}
-                          {/* The icon is decorative and a name written onto a generic span is
-                              not announced, so the bare number says what it counts in words. */}
-                          <span className="sr-only">
-                            {approvalCount === 1 ? "approval" : "approvals"}
-                          </span>
-                        </span>
-                      ) : null}
-                    </PullRequestMetaLine>
-                    <Button
-                      size="xs"
-                      variant="ghost"
-                      className="h-7 px-2 text-[10px] text-muted-foreground"
-                      aria-label={
-                        timelineOrder === "newest"
-                          ? "Show oldest activity first"
-                          : "Show newest activity first"
-                      }
-                      onClick={() =>
-                        setTimelineOrder((value) => (value === "newest" ? "oldest" : "newest"))
-                      }
-                    >
-                      <ArrowDownUpIcon aria-hidden className="size-3" />
-                      {timelineOrder === "newest" ? "Newest first" : "Oldest first"}
-                    </Button>
-                  </div>
-                ) : null}
-              </nav>
             ) : null}
+          </nav>
+        ) : null}
-          </div>
-        </div>
       </div>
 
       <div
         className="relative min-h-0 flex-1 overflow-hidden"
-        // Scroll does not bubble, but it captures: one listener hears every tab's own scroll
-        // container. Collapse past two line-heights, expand only back at the very top, so the
-        // boundary row cannot flap the chrome open and shut.
         onScrollCapture={(event) => {
-          if (chromeVariant !== "collapse") return;
           const scroller = event.target as HTMLElement;
           scrollerRef.current = scroller;
           const top = scroller.scrollTop;
           setChromeCondensed((previous) => {
             let next = previous;
-            // `scrollHeight` reads the fold's natural height whichever state the track is in.
             const foldHeight = foldRef.current?.scrollHeight ?? 0;
-            // The chrome trades the fold for the condensed second row, so the height the
-            // scrollport actually gains is the difference between the two.
+            // The condensed row remains mounted, so refund only the height that actually leaves.
             const chromeDelta = foldHeight - (condensedRowRef.current?.scrollHeight ?? 0);
             if (previous) {
               // The hard top reopens the chrome with no refund: the reader asked for the top,
@@ -1916,17 +1905,7 @@
           });
         }}
       >
-        {detailQuery.isPending && !detail ? (
-          // The ghost wears the shape of the tab being waited on, so switching tabs mid-load
+        {detailQuery.error && !detail ? (
-          // does not flash a summary outline under a timeline heading.
-          tab === "timeline" ? (
-            <PullRequestTimelineGhost />
-          ) : tab === "code" ? (
-            <DiffPanelLoadingState label="Loading pull request diff..." />
-          ) : (
-            <PullRequestDetailGhost />
-          )
-        ) : detailQuery.error && !detail ? (
           <PullRequestsUnavailableState error={detailQuery.error} onRetry={refreshDetail} />
         ) : detail ? (
           <>
```

## apps/web/src/components/pullRequest/PullRequestGhosts.tsx

Status: modified; +91; −24

```diff
@@ -45,13 +45,11 @@
           <GhostBar className="size-4 rounded-full" />
           <div className="min-w-0 space-y-1.5">
             <GhostBar className={cn("h-3.5", TITLE_WIDTHS[index % TITLE_WIDTHS.length])} />
-            <GhostBar
-              className={cn("bg-muted-foreground/10", META_WIDTHS[index % META_WIDTHS.length])}
-            />
+            <GhostBar className={META_WIDTHS[index % META_WIDTHS.length]} />
           </div>
           <div className="flex flex-col items-end gap-1.5">
             <GhostBar className="w-12" />
-            <GhostBar className="w-16 bg-muted-foreground/10" />
+            <GhostBar className="w-16" />
           </div>
         </div>
       ))}
@@ -59,32 +57,101 @@
   );
 }
 
+/**
+ * The detail panel's current expanded shape. Keeping the chrome, summary facts, and description
-/** The summary's own shape: a title, a byline, the facts rows, the description. */
+ * boundaries in the ghost prevents the loaded pull request from replacing one layout with
+ * another a moment later.
+ */
 export function PullRequestDetailGhost() {
   return (
     <div
       role="status"
       aria-label="Loading pull request"
-      className="animate-ghost-pulse space-y-6 px-4 py-5"
+      className="animate-ghost-pulse flex h-full min-h-0 flex-col overflow-hidden bg-background"
     >
+      <div className="shrink-0 border-b border-border/60">
+        <div className="flex h-7 items-center justify-between gap-3 px-4">
+          <div className="flex min-w-0 flex-1 items-center gap-1.5">
+            <GhostBar className="w-24" />
+            <GhostBar className="w-9" />
-      <div className="space-y-2">
-        <GhostBar className="h-5 w-4/5" />
-        <GhostBar className="w-2/5 bg-muted-foreground/10" />
+          </div>
+          <div className="flex shrink-0 items-center gap-1">
+            <GhostBar className="h-5 w-16 rounded-md" />
+            <GhostBar className="size-5 rounded-md" />
+          </div>
+        </div>
+
+        <div className="px-4 pb-4 pt-1">
+          <GhostBar className="h-5 w-4/5 max-w-md" />
+          <div className="mt-2 flex items-center gap-1.5">
+            <GhostBar className="size-4 rounded-full" />
+            <GhostBar className="w-24" />
+          </div>
+          <div className="mt-4 flex min-w-0 items-center gap-2">
+            <GhostBar className="h-6 w-24 rounded-md" />
+            <GhostBar className="size-3 rounded-full" />
+            <GhostBar className="h-6 w-32 rounded-md" />
+            <div className="ml-auto flex shrink-0 items-center gap-2">
+              <GhostBar className="w-10" />
+              <GhostBar className="w-20" />
+            </div>
+          </div>
+        </div>
+
+        <div className="flex min-h-10 items-center justify-between gap-3 border-t border-border/60 px-4 py-2">
+          <div className="flex items-center gap-1 p-0.5">
+            <GhostBar className="h-6 w-16 rounded-md" />
+            <GhostBar className="h-6 w-16 rounded-md" />
+            <GhostBar className="h-6 w-12 rounded-md" />
+          </div>
+          <GhostBar className="w-20" />
+        </div>
       </div>
+
+      <div className="min-h-0 flex-1 overflow-hidden">
+        <section className="px-4 py-3">
+          <div className="grid min-h-8 grid-cols-[6rem_minmax(0,1fr)] items-center gap-2">
+            <div className="flex items-center gap-1.5">
+              <GhostBar className="size-3.5 rounded-full" />
+              <GhostBar className="w-14" />
+            </div>
+            <div className="flex items-center gap-1">
+              <GhostBar className="size-4 rounded-full" />
+              <GhostBar className="size-4 rounded-full" />
+              <GhostBar className="ml-1 size-5 rounded-md" />
+            </div>
+          </div>
+          <div className="grid min-h-8 grid-cols-[6rem_minmax(0,1fr)] items-center gap-2">
+            <div className="flex items-center gap-1.5">
+              <GhostBar className="size-3.5 rounded-full" />
+              <GhostBar className="w-10" />
+            </div>
+            <div className="flex items-center gap-1">
+              <GhostBar className="h-5 w-24 rounded-full" />
+              <GhostBar className="h-5 w-20 rounded-full" />
+            </div>
+          </div>
+          <div className="grid min-h-8 grid-cols-[6rem_minmax(0,1fr)] items-center gap-2">
+            <div className="flex items-center gap-1.5">
+              <GhostBar className="size-3.5 rounded-full" />
+              <GhostBar className="w-14" />
+            </div>
+            <GhostBar className="w-20" />
+          </div>
+        </section>
+
-      <div className="space-y-3">
+        <section className="border-t border-border/60">
+          <div className="flex min-h-11 items-center gap-1.5 px-4 py-3">
+            <GhostBar className="h-4 w-24" />
-        {Array.from({ length: 4 }, (_, index) => (
-          <div key={index} className="flex items-center gap-3">
             <GhostBar className="size-3.5 rounded-full" />
-            <GhostBar className="w-20 bg-muted-foreground/10" />
-            <GhostBar className={TITLE_WIDTHS[(index + 1) % TITLE_WIDTHS.length]} />
           </div>
-        ))}
-      </div>
-      <div className="space-y-2 pt-1">
-        <GhostBar className="w-full bg-muted-foreground/10" />
-        <GhostBar className="w-11/12 bg-muted-foreground/10" />
-        <GhostBar className="w-4/5 bg-muted-foreground/10" />
+          <div className="space-y-2 px-4 pb-4">
+            <GhostBar className="w-full" />
+            <GhostBar className="w-11/12" />
+            <GhostBar className="w-4/5" />
+            <GhostBar className="w-2/3" />
+          </div>
-        <GhostBar className="w-2/3 bg-muted-foreground/10" />
+        </section>
       </div>
     </div>
   );
@@ -113,7 +180,7 @@
           <div key={index} className="relative pb-5">
             <GhostBar className="absolute -left-[1.55rem] top-1 size-2 rounded-full" />
             <GhostBar className={cn("h-3.5", TITLE_WIDTHS[index % TITLE_WIDTHS.length])} />
-            <GhostBar className="mt-1.5 w-16 bg-muted-foreground/10" />
+            <GhostBar className="mt-1.5 w-16" />
           </div>
         ))}
       </div>
@@ -134,8 +201,8 @@
           <GhostBar className="size-5 shrink-0 rounded-full" />
           <div className="flex-1 space-y-1.5">
             <GhostBar className={META_WIDTHS[index % META_WIDTHS.length]} />
+            <GhostBar className="w-full" />
-            <GhostBar className="w-full bg-muted-foreground/10" />
+            <GhostBar className="w-3/4" />
-            <GhostBar className="w-3/4 bg-muted-foreground/10" />
           </div>
         </div>
       ))}
```

## apps/web/src/components/pullRequest/PullRequestListFilters.tsx

Status: modified; +9; −6

```diff
@@ -25,6 +25,7 @@
 import { getSourceControlPresentationForKind } from "~/sourceControlPresentation";
 import { ProjectFavicon } from "../ProjectFavicon";
 import { InputGroup, InputGroupAddon, InputGroupInput } from "../ui/input-group";
+import { Button } from "../ui/button";
 
 import {
   Menu,
@@ -273,12 +274,14 @@
   return (
     <Menu>
       <MenuTrigger
+        render={
+          <Button
+            className={cn("relative", filtered && "[--control-icon-color:currentColor]")}
+            size="icon"
+            variant="outline"
+            aria-label="Filter pull requests"
+          />
-        className={cn(
-          // The icon-button size that pairs with a full-height input, so the two read as one strip.
-          "relative inline-flex size-9 shrink-0 items-center justify-center rounded-lg border border-input text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground sm:size-8",
+        }
-          filtered && "text-foreground",
-        )}
-        aria-label="Filter pull requests"
       >
         <ListFilterIcon className="size-4" />
         {filtered ? (
```

## apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx

Status: modified; +3; −3

```diff
@@ -220,12 +220,12 @@
   children: ReactNode;
 }) {
   return (
-    <div className="flex items-center gap-2 py-1.5 text-xs">
+    <div className="grid min-h-8 grid-cols-[6rem_minmax(0,1fr)] items-center gap-2 py-1.5 text-xs">
-      <span className="flex w-24 shrink-0 items-center gap-1.5 text-muted-foreground">
+      <span className="flex min-w-0 items-center gap-1.5 text-muted-foreground">
         {icon}
         {label}
       </span>
+      <span className="min-w-0 text-foreground">{children}</span>
-      <span className="min-w-0 flex-1 text-foreground">{children}</span>
     </div>
   );
 }
```

## apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts

Status: modified; +42; −4

```diff
@@ -17,6 +17,7 @@
   handoffPrompt,
   handoffReviewComments,
   isPullRequestVerdictStale,
+  isStackedPullRequestBase,
   isThreadOwnPullRequest,
   latestPullRequestReviewOutcomes,
   newestPullRequestCommitAt,
@@ -142,8 +143,6 @@
       fixFinding: "Fix in this thread",
       fixCheck: "Fix in this thread",
       fixFindings: "Fix findings in this thread",
-      resolve: "Resolve in this thread",
-      resolveConflicts: "Resolve conflicts in this thread",
     });
   });
 
@@ -152,8 +151,6 @@
       fixFinding: "Fix in a thread",
       fixCheck: "Fix",
       fixFindings: "Fix findings in a thread",
-      resolve: "Resolve in a new thread",
-      resolveConflicts: "Resolve conflicts in a thread",
     });
   });
 });
@@ -167,6 +164,47 @@
   });
 });
 
+describe("stacked pull request classification", () => {
+  it("requires a known default branch", () => {
+    expect(isStackedPullRequestBase("main", [{ name: "main", isDefault: false }])).toBe(false);
+  });
+
+  it("recognizes local and remote forms of the default branch", () => {
+    expect(
+      isStackedPullRequestBase("main", [{ name: "main", isDefault: true, isRemote: false }]),
+    ).toBe(false);
+    expect(
+      isStackedPullRequestBase("main", [
+        { name: "origin/main", isDefault: true, isRemote: true, remoteName: "origin" },
+      ]),
+    ).toBe(false);
+  });
+
+  it("classifies a non-default base as stacked once the default is known", () => {
+    expect(
+      isStackedPullRequestBase("feature-base", [
+        { name: "origin/main", isDefault: true, isRemote: true, remoteName: "origin" },
+      ]),
+    ).toBe(true);
+  });
+
+  it("does not mistake a nested branch suffix for the default branch", () => {
+    expect(
+      isStackedPullRequestBase("main", [
+        {
+          name: "origin/feature/main",
+          isDefault: true,
+          isRemote: true,
+          remoteName: "origin",
+        },
+      ]),
+    ).toBe(true);
+    expect(
+      isStackedPullRequestBase("1.0", [{ name: "release/1.0", isDefault: true, isRemote: false }]),
+    ).toBe(true);
+  });
+});
+
 describe("ordering comments", () => {
   it("reverses the chronological list for newest first, and leaves oldest first alone", () => {
     const comments = [{ createdAt: "a" }, { createdAt: "b" }, { createdAt: "c" }];
```

## apps/web/src/components/pullRequest/pullRequestDetail.logic.ts

Status: modified; +15; −4

```diff
@@ -11,6 +11,7 @@
   PullRequestReviewThread,
   PullRequestState,
   PullRequestUpdateMethod,
+  VcsRef,
 } from "@t3tools/contracts";
 
 import { inferReviewCommentFenceLanguage, type ReviewCommentContext } from "~/reviewCommentContext";
@@ -75,15 +76,11 @@
         fixFinding: "Fix in this thread",
         fixCheck: "Fix in this thread",
         fixFindings: "Fix findings in this thread",
-        resolve: "Resolve in this thread",
-        resolveConflicts: "Resolve conflicts in this thread",
       }
     : {
         fixFinding: "Fix in a thread",
         fixCheck: "Fix",
         fixFindings: "Fix findings in a thread",
-        resolve: "Resolve in a new thread",
-        resolveConflicts: "Resolve conflicts in a thread",
       };
 }
 
@@ -103,6 +100,20 @@
   return showsDraftToggle || showsAutoMerge || showsMergeMethods;
 }
 
+export function isStackedPullRequestBase(
+  baseBranch: string,
+  refs: ReadonlyArray<Pick<VcsRef, "name" | "isDefault" | "isRemote" | "remoteName">>,
+): boolean {
+  const defaultRef = refs.find((refName) => refName.isDefault);
+  if (!defaultRef) return false;
+  if (defaultRef.isRemote !== true) return defaultRef.name !== baseBranch;
+  const remotePrefix = `${defaultRef.remoteName ?? defaultRef.name.split("/")[0]}/`;
+  const defaultBranch = defaultRef.name.startsWith(remotePrefix)
+    ? defaultRef.name.slice(remotePrefix.length)
+    : defaultRef.name;
+  return defaultBranch !== baseBranch;
+}
+
 /** Plain-language state, shown beside the author. Conflicts are a merge signal, not a state. */
 export function describePullRequestState(state: PullRequestState, isDraft: boolean): string {
   if (state === "merged") return "Merged";
```

## apps/web/src/lib/openPullRequestLink.test.ts

Status: modified; +19; −0

```diff
@@ -1,6 +1,7 @@
 import { describe, expect, it, vi } from "vite-plus/test";
 
 import {
+  changeRequestRepositoryUrl,
   findProjectForChangeRequest,
   openPullRequestLink,
   parseChangeRequestUrl,
@@ -8,6 +9,24 @@
   shouldOpenPullRequestExternally,
 } from "./openPullRequestLink";
 
+describe("changeRequestRepositoryUrl", () => {
+  it("preserves repository path casing", () => {
+    expect(
+      changeRequestRepositoryUrl(
+        "https://gitlab.example.test/Team/Platform/Repo/-/merge_requests/42/diffs#note_1",
+      ),
+    ).toBe("https://gitlab.example.test/Team/Platform/Repo");
+  });
+
+  it("keeps pull-like segments inside nested GitLab repository paths", () => {
+    expect(
+      changeRequestRepositoryUrl(
+        "https://gitlab.example.test/group/pull/123/repo/-/merge_requests/42",
+      ),
+    ).toBe("https://gitlab.example.test/group/pull/123/repo");
+  });
+});
+
 describe("openPullRequestLink", () => {
   it("opens the requested pull request URL", async () => {
     const openExternal = vi.fn(async () => undefined);
```

## apps/web/src/lib/openPullRequestLink.ts

Status: modified; +17; −0

```diff
@@ -118,6 +118,23 @@
   return null;
 }
 
+/** The repository root behind a recognised change-request URL, without PR-specific state. */
+export function changeRequestRepositoryUrl(targetUrl: string): string | null {
+  const changeRequest = parseChangeRequestUrl(targetUrl);
+  if (changeRequest === null) return null;
+  const url = new URL(targetUrl);
+  const repositoryPath =
+    /^(.*?)\/-\/merge_requests\/\d+(?:\/|$)/iu.exec(url.pathname)?.[1] ??
+    /^(.*?)(?:\/pull\/\d+|\/-\/merge_requests\/\d+|\/pull-requests\/\d+|\/pullrequest\/\d+)(?:\/|$)/iu.exec(
+      url.pathname,
+    )?.[1];
+  if (!repositoryPath) return null;
+  url.pathname = repositoryPath;
+  url.search = "";
+  url.hash = "";
+  return url.toString();
+}
+
 function claim(host: string, match: RegExpExecArray | null): ChangeRequestLink | null {
   const repository = match?.[1];
   const number = Number(match?.[2]);
```

## apps/web/src/routes/\_chat.pull-requests.tsx

Status: modified; +59; −48

```diff
@@ -74,6 +74,9 @@
   WorkspaceBreadcrumbItem,
   WorkspaceBreadcrumbSeparator,
 } from "../components/WorkspaceBreadcrumb";
+import { WorkspacePageContainer } from "../components/WorkspacePageContainer";
+import { WorkspacePageHeader } from "../components/WorkspacePageHeader";
+import { isElectron } from "../env";
 import { PanelLayoutControls } from "../components/chat/PanelLayoutControls";
 import { Button } from "../components/ui/button";
 import { Menu, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuTrigger } from "../components/ui/menu";
@@ -100,7 +103,6 @@
 import { useAtomCommand } from "../state/use-atom-command";
 import { cn } from "~/lib/utils";
 import { getSourceControlPresentationForKind } from "~/sourceControlPresentation";
-import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar";
 
 export interface PullRequestsSearch {
   readonly involvement: PullRequestInvolvement;
@@ -1181,6 +1183,7 @@
         : null,
     [search.number, search.repository, selectedProject],
   );
+  const rightPanelAvailable = selectedPullRequestSurface !== null;
   useEffect(() => {
     if (!pullRequestsSupported || rightPanelRef === null || linkedSelection === null) return;
     useRightPanelStore.getState().openPullRequest(rightPanelRef, linkedSelection);
@@ -1294,9 +1297,10 @@
       terminalAvailable={false}
       terminalOpen={false}
       terminalShortcutLabel={null}
+      rightPanelAvailable={rightPanelAvailable}
-      rightPanelAvailable={rightPanelState.surfaces.length > 0}
       rightPanelOpen={rightPanelState.isOpen}
       rightPanelShortcutLabel={null}
+      rightPanelUnavailableLabel="Select a pull request first"
       liveAgentCount={0}
       onToggleTerminal={() => undefined}
       onToggleRightPanel={toggleRightPanel}
@@ -1603,7 +1607,6 @@
                 reviewingQuery.refresh();
               }}
               onStateChange={handlePullRequestTabStatusChange}
-              chromeVariant="collapse"
             />
           </RightPanelTabs>
         ) : null}
@@ -1612,10 +1615,7 @@
   );
 }
 
-/**
+/** A compact stand-in for one pill group when the header is narrow. */
- * A compact stand-in for one pill group: the trigger wears the current choice, the choices
- * live in a menu. Same options, same handler — only the footprint changes.
- */
 function CompactFilterMenu<Value extends string>({
   label,
   value,
@@ -1627,7 +1627,8 @@
   options: ReadonlyArray<PullRequestFilterOption<Value>>;
   onChange: (value: Value) => void;
 }) {
-  const current = options.find((option) => option.value === value) ?? options[0]!;
+  const current = options.find((option) => option.value === value) ?? options[0];
+  if (!current) return null;
   return (
     <Menu>
       <MenuTrigger
@@ -1640,15 +1641,12 @@
       <MenuPopup align="start" side="bottom" className="min-w-40">
         <MenuRadioGroup value={value} onValueChange={(next) => onChange(next as Value)}>
           {options.map((option) => {
-            // A host the server has already said it cannot read is not a choice here either.
-            // The pills disable it; a menu that offers it would answer the press by replacing
-            // a working list with the same failure the pill row exists to explain.
             const item = (
               <MenuRadioItem
                 key={option.value}
                 value={option.value}
-                className={option.unavailable ? "data-disabled:pointer-events-auto" : undefined}
                 disabled={option.unavailable !== undefined}
+                className="data-disabled:pointer-events-auto"
               >
                 <span className="flex min-w-0 items-center gap-2">
                   <option.Icon aria-hidden className="size-3.5" />
@@ -1656,11 +1654,12 @@
                 </span>
               </MenuRadioItem>
             );
-            if (!option.unavailable) return item;
-            return (
+            return option.unavailable === undefined ? (
+              item
+            ) : (
               <Tooltip key={option.value}>
                 <TooltipTrigger render={item} />
-                <TooltipPopup side="top" className="max-w-80">
+                <TooltipPopup side="right" className="max-w-64 break-words">
                   {option.unavailable}
                 </TooltipPopup>
               </Tooltip>
@@ -1838,18 +1837,10 @@
     // Painted flat like the chat column: the inset underneath carries the chrome grain, and a
     // content surface that lets it show reads as a different background than every thread.
     <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-background">
-      <header
-        className={cn(
-          "drag-region flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center gap-1.5 px-3 sm:px-5",
-          // A closed right panel leaves this column full-width, so its header runs
-          // underneath the native window controls on Windows; reserve the inset the
+      {/* A closed right panel leaves this column full-width, so the shared header
+          reserves native window controls. While the panel is open, the column ends
+          at the panel and the absolute controls strip owns the top-right corner. */}
+      <WorkspacePageHeader electron={isElectron} reserveNativeControls={!rightPanelOpen}>
-          // way Settings and the chat view do. While the panel is open the column
-          // ends at the panel's left edge and the absolute controls strip (already
-          // WCO-aware) owns the top-right corner.
-          !rightPanelOpen && "wco:pr-[var(--workspace-native-controls-inset)]",
-          COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
-        )}
-      >
         {condensed ? (
           <WorkspaceBreadcrumb ariaLabel="Pull request scope">
             {/* The page name remains the foreground anchor in both states; the live filters are
@@ -1891,27 +1882,22 @@
         )}
         <div className="min-w-0 flex-1" />
         {condensed ? (
+          <div className="flex shrink-0 items-center gap-1.5">
+            <ExpandableSearch
+              searchInput={searchInput}
+              searchValue={searchValue}
+              open={searchOpen}
+              onOpenChange={setSearchOpen}
+              focusToken={searchFocusToken}
+              onFocusWithin={(focused) => {
+                topbarSearchFocusedRef.current = focused;
-          <ExpandableSearch
-            searchInput={searchInput}
-            searchValue={searchValue}
-            open={searchOpen}
-            onOpenChange={setSearchOpen}
-            focusToken={searchFocusToken}
-            onFocusWithin={(focused) => {
+              }}
+            />
+            <PullRequestRefreshControl compact refreshing={refreshing} onRefresh={onRefresh} />
+          </div>
-              topbarSearchFocusedRef.current = focused;
-            }}
-          />
         ) : null}
-        <Button
-          size="icon-sm"
-          variant="ghost"
-          aria-label="Refresh pull requests"
-          onClick={onRefresh}
-        >
-          <RefreshCwIcon className={cn("size-4", refreshing && "animate-spin")} />
-        </Button>
         {rightPanelControl}
+      </WorkspacePageHeader>
-      </header>
 
       <div
         ref={scrollRef}
@@ -1920,19 +1906,44 @@
         {/* The top padding is the fade band's own height (1.5rem here), the same pairing the
             settings page makes: at rest the controls sit fully below the mask, and only
             content actually passing under the chrome fades. */}
-        <div className="mx-auto flex w-full max-w-4xl flex-col gap-4 px-5 pt-6 pb-12">
+        <WorkspacePageContainer className="gap-4">
           <div className="flex flex-col gap-3">
             <div ref={inFlowSearchRef} className="flex items-center gap-2">
               {searchInput}
               {filtersMenu}
+              {!condensed ? (
+                <PullRequestRefreshControl refreshing={refreshing} onRefresh={onRefresh} />
+              ) : null}
             </div>
             {/* Scrolled past this marker, the controls are gone and the title takes over. */}
             <div ref={markerRef} aria-hidden className="-mt-3 h-px w-full" />
           </div>
 
           {listBody}
-        </div>
+        </WorkspacePageContainer>
       </div>
     </div>
   );
 }
+
+function PullRequestRefreshControl({
+  compact = false,
+  refreshing,
+  onRefresh,
+}: {
+  compact?: boolean;
+  refreshing: boolean;
+  onRefresh: () => void;
+}) {
+  return (
+    <Button
+      size={compact ? "icon-sm" : "icon"}
+      variant={compact ? "ghost" : "outline"}
+      aria-label="Refresh pull requests"
+      onClick={onRefresh}
+      disabled={refreshing}
+    >
+      <RefreshCwIcon className={cn("size-4", refreshing && "animate-spin")} />
+    </Button>
+  );
+}
```
