Skip to content

feat(pull-requests): add full PR workspace with overview, files, and conversation views#2810

Closed
znoraka wants to merge 6 commits into
pingdotgg:mainfrom
znoraka:feat/pr-workspace
Closed

feat(pull-requests): add full PR workspace with overview, files, and conversation views#2810
znoraka wants to merge 6 commits into
pingdotgg:mainfrom
znoraka:feat/pr-workspace

Conversation

@znoraka

@znoraka znoraka commented May 26, 2026

Copy link
Copy Markdown

Summary

  • Replace the single-pane PR review view with a multi-view PR workspace supporting overview, files, and conversation tabs
  • Add new components: PullRequestWorkspace, OverviewPanel, FilesPane, ConversationPane, FileTree, InlineComment, MergeButton, ReviewBar, and ReviewSidebar
  • Add PR detail query, merge mutation, review submission, and checkout/worktree support from the PR view
  • Wire up URL-driven view navigation with persistent state

Test plan

  • Open a PR from the pull requests list — verify overview panel loads with checks, reviewers, labels
  • Switch between overview / files / conversation tabs
  • Review files with inline comments and submit a review
  • Merge a PR using the merge button
  • Checkout a PR branch locally and via worktree
  • Verify URL state persists across navigation

🤖 Generated with Claude Code


Note

High Risk
Exposes merge, review, and comment mutations via gh on the server RPC path with a large new UI surface; mistakes or auth issues could affect real GitHub PRs.

Overview
Adds fork maintenance docs in FORK.md (isolation under _lempire/, rebase workflow) while this change set still extends core apps/server and apps/web paths rather than _lempire/ wrappers.

On the server, GitManager gains a full GitHub PR surface (list, diffs, comments, viewed files, reviews, merge, detail/edit, collaborators) implemented via new GitHubCliPR / GitManagerPR layers on gh, wired through ws-pr.ts into the WebSocket RPC layer and GitHubCli.layer in server.ts; tests now provide GitHubCli in the manager test harness.

On the client, introduces a pull-request workspace: list panel, tabbed overview / files / conversation (PullRequestWorkspace), rich file tree and Pierre-based diffs with GitHub “viewed” sync, conversation and inline/pending review UI, merge and review submission, review sidebar (agent review + checkout/worktree hooks), plus Commit header flow (CommitControl / CommitModal) that sends a structured agent prompt for commit/push/PR.

Risk: Operations run through authenticated gh against the user’s machine (merge, comments, GraphQL viewed state)—high impact if mis-invoked, but no new hosted credential store in this diff.

Reviewed by Cursor Bugbot for commit 5b13dc3. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add full pull request workspace with overview, files, conversation, and review views

  • Adds a PullRequestWorkspace component with tabbed overview, files, and conversation panes, plus a review sidebar for submitting COMMENT, REQUEST_CHANGES, and APPROVE events.
  • PullRequestFilesPane renders a filterable file tree with per-file diffs, viewed-file tracking (persisted to localStorage via usePullRequestViewedFiles), and unified/split diff toggle.
  • PullRequestOverviewPanel shows PR metadata, labels, checks, reviewer states, and mergeability; PullRequestConversationPane lists and posts issue/review comments.
  • Extends GitManagerShape and wires new WebSocket RPC handlers in ws-pr.ts for the full set of PR operations (list, diff, comments, review, merge, etc.), backed by the GitHub CLI via makeGitManagerPRMethods.
  • Adds a PullRequestListPanel and a /pull-requests route with search-param persistence, and adds a PR navigation button to SidebarChromeFooter.
  • Adds useNotificationSounds hook that plays audible chimes when chat threads finish or need attention, bootstrapped at the root route.
  • UserMessageBody now renders via ChatMarkdown instead of plain text, enabling markdown in user messages.
📊 Macroscope summarized 5b13dc3. 35 files reviewed, 8 issues evaluated, 0 issues filtered, 2 comments posted

🗂️ Filtered Issues

znoraka and others added 6 commits May 11, 2026 09:00
Introduce a full PR review workflow: list PRs, view diffs per file,
read/post review and issue comments, mark files as viewed, submit
reviews, merge PRs, and edit PR metadata — all powered by the `gh` CLI.

Server: new GitHubCli service, GitHubCliPR/GitManagerPR layers, and
dedicated WebSocket RPC handlers (ws-pr.ts).
Web: PullRequestListPanel, PullRequestReviewView, CommitControl/Modal,
SidebarPRButton, WorkspacePickerModal, notification sounds, and route.
Contracts: PR-specific schemas, types, and RPC definitions.
Also adds FORK.md documenting the fork isolation strategy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire up useNotificationSounds hook in the root route so that
notification sounds play when the user is authenticated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	apps/web/src/components/chat/MessagesTimeline.tsx
Replace plain text rendering in UserMessageBody with ChatMarkdown
component, enabling markdown formatting in user messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…conversation views

Introduce a multi-pane PR workspace replacing the single review view.
New components: PullRequestWorkspace, OverviewPanel, FilesPane,
ConversationPane, FileTree, InlineComment, MergeButton, ReviewBar,
and ReviewSidebar. Add PR detail query, merge mutation, and review
submission to React Query hooks. Support checkout and worktree
creation from the PR view. Wire up URL-driven view navigation
(overview/files/conversation).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b467aaad-7269-4c9b-b0de-1ce136f7f60b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels May 26, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 4 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5b13dc3. Configure here.

collected.push(line);
}
}
return collected.length > 0 ? `${collected.join("\n")}\n` : "";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed PR files show empty diff

Medium Severity

extractFileDiff locates a file section only when the unified diff header is diff --git a/{path} b/{path}. Git rename patches use different a/ and b/ paths, so per-file diff extraction returns empty for renamed files while the file list still shows the new path.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5b13dc3. Configure here.

>
<GitMergeIcon className="size-3.5" aria-hidden="true" />
Merge
</Button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge button never mounted in UI

Medium Severity

PullRequestMergeButton is added with merge mutation wiring, but nothing in the pull-request workspace or route imports or renders it, so users cannot merge from the new PR UI despite backend and query support.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5b13dc3. Configure here.

),
Effect.map((entries) => entries.map(normalizePrListEntry)),
Effect.catch(() => Effect.succeed([] as ReadonlyArray<GitHubPullRequestListEntry>)),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR list buckets hide CLI failures

Medium Severity

In listWorkspacePullRequests, each fetchBucket pipeline ends with Effect.catch(() => Effect.succeed([])), so auth can succeed while a failed gh pr list call is shown as an empty bucket instead of surfacing an error.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5b13dc3. Configure here.

const nodes = data?.data?.repository?.pullRequest?.files?.nodes ?? [];
return nodes
.filter((node) => node.viewerViewedState === "VIEWED")
.map((node) => node.path);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Viewed files capped at one hundred

Low Severity

getPullRequestViewedFiles GraphQL query uses files(first: 100) with no pagination, so viewed state from GitHub is incomplete when a pull request changes more than one hundred files.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5b13dc3. Configure here.

@znoraka znoraka closed this May 26, 2026
Comment on lines +30 to +47
function extractFileDiff(fullDiff: string, filePath: string): string {
const marker = `diff --git a/${filePath} b/${filePath}`;
const lines = fullDiff.split("\n");
let inSection = false;
const collected: string[] = [];
for (const line of lines) {
if (line.startsWith("diff --git ")) {
if (inSection) break;
if (line === marker) {
inSection = true;
}
}
if (inSection) {
collected.push(line);
}
}
return collected.length > 0 ? `${collected.join("\n")}\n` : "";
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Layers/GitManagerPR.ts:30

extractFileDiff constructs the diff header marker as diff --git a/${filePath} b/${filePath}, but git diff headers for renamed files use different paths (diff --git a/oldname b/newname). Requesting the diff for a renamed file by either its old or new name returns an empty string even when the file exists in the diff. Consider matching the header with a regex that handles both a/${oldPath} b/${newPath} patterns, or document that this function does not support renamed files.

-function extractFileDiff(fullDiff: string, filePath: string): string {
-  const marker = `diff --git a/${filePath} b/${filePath}`;
-  const lines = fullDiff.split("\n");
-  let inSection = false;
-  const collected: string[] = [];
-  for (const line of lines) {
-    if (line.startsWith("diff --git ")) {
-      if (inSection) break;
-      if (line === marker) {
-        inSection = true;
-      }
-    }
-    if (inSection) {
-      collected.push(line);
-    }
-  }
-  return collected.length > 0 ? `${collected.join("\n")}\n` : "";
-}
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/git/Layers/GitManagerPR.ts around lines 30-47:

`extractFileDiff` constructs the diff header marker as `diff --git a/${filePath} b/${filePath}`, but git diff headers for renamed files use different paths (`diff --git a/oldname b/newname`). Requesting the diff for a renamed file by either its old or new name returns an empty string even when the file exists in the diff. Consider matching the header with a regex that handles both `a/${oldPath} b/${newPath}` patterns, or document that this function does not support renamed files.

Evidence trail:
apps/server/src/git/Layers/GitManagerPR.ts lines 30-47 (extractFileDiff implementation, marker construction at line 31), line 172 (caller passing input.filePath). Git diff format for renames: `diff --git a/oldpath b/newpath` is standard git behavior (https://git-scm.com/docs/diff-format).

[viewedMap, currentHashes],
);

const setViewed = useCallback(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low lib/pullRequestViewedFiles.ts:145

onSetViewed fires even when setViewedMap returns prev unchanged — for example, when toggling a file off that isn't in the map, or when the hash computation fails. This triggers unnecessary GitHub API mutations via setFileViewedMutation.mutate in the calling component. Consider moving the callback inside the functional updater so it only fires when next !== prev.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/lib/pullRequestViewedFiles.ts around line 145:

`onSetViewed` fires even when `setViewedMap` returns `prev` unchanged — for example, when toggling a file off that isn't in the map, or when the hash computation fails. This triggers unnecessary GitHub API mutations via `setFileViewedMutation.mutate` in the calling component. Consider moving the callback inside the functional updater so it only fires when `next !== prev`.

Evidence trail:
apps/web/src/lib/pullRequestViewedFiles.ts lines 145-165 (setViewed function): line 152 returns `prev` unchanged when hash is falsy; lines 156-157 return `prev` when file not in map; line 162 calls `onSetViewed` unconditionally after `setViewedMap`. apps/web/src/components/PullRequestFilesPane.tsx lines 310-315: onSetViewed calls setFileViewedMutation.mutate. apps/web/src/components/PullRequestReviewView.tsx lines 258-263: same pattern, onSetViewed calls setFileViewedMutation.mutate.

@macroscopeapp

macroscopeapp Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

1 blocking correctness issue found. Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

You can customize Macroscope's approvability policy. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant