Skip to content

Commit 5b13dc3

Browse files
znorakaclaude
andcommitted
feat(pull-requests): add full PR workspace with overview, files, and 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>
1 parent aa92c00 commit 5b13dc3

23 files changed

Lines changed: 2772 additions & 159 deletions

FORK.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ This makes it trivial to grep for all upstream touchpoints (`grep -r '\[FORK\]'
4747

4848
### Composition patterns
4949

50-
| Situation | What to do |
51-
|---|---|
52-
| Upstream exports a function you want to extend | Create `_lempire/wrappedFn.ts` that re-exports with additions |
53-
| Upstream defines a type you want to augment | Intersection type in `_lempire/types.ts` |
50+
| Situation | What to do |
51+
| ------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
52+
| Upstream exports a function you want to extend | Create `_lempire/wrappedFn.ts` that re-exports with additions |
53+
| Upstream defines a type you want to augment | Intersection type in `_lempire/types.ts` |
5454
| Upstream has a router/server you want to add routes to | New route file in `_lempire/`, register with one `// [FORK]` line in the upstream router file |
55-
| Upstream has a React component you want to modify | Wrap it in `_lempire/WrappedComponent.tsx`, use the wrapper instead |
56-
| Upstream has a config you want to extend | Import upstream config in `_lempire/`, spread and override |
55+
| Upstream has a React component you want to modify | Wrap it in `_lempire/WrappedComponent.tsx`, use the wrapper instead |
56+
| Upstream has a config you want to extend | Import upstream config in `_lempire/`, spread and override |
5757

5858
---
5959

@@ -88,24 +88,24 @@ Run `grep -rn '\[FORK\]' . --include='*.ts' --include='*.tsx'` to get the curren
8888
> **Agents: update this table every time you add a `// [FORK]` marker to an upstream file.**
8989
> If you remove a fork change from an upstream file, remove it from this table too.
9090
91-
| File | Reason | PR / Feature |
92-
|---|---|---|
93-
| _(none yet)_ | | |
91+
| File | Reason | PR / Feature |
92+
| ------------ | ------ | ------------ |
93+
| _(none yet)_ | | |
9494

9595
---
9696

9797
## Fork-Only Features
9898

9999
> Track what this fork adds, so it's easy to audit what needs carrying forward after a large upstream rebase.
100100
101-
| Feature | Location | Notes |
102-
|---|---|---|
103-
| _(none yet)_ | | |
101+
| Feature | Location | Notes |
102+
| ------------ | -------- | ----- |
103+
| _(none yet)_ | | |
104104

105105
---
106106

107107
## Rebase Log
108108

109-
| Date | From commit | To commit | Conflicts | Notes |
110-
|---|---|---|---|---|
111-
| _(none yet)_ | | | | |
109+
| Date | From commit | To commit | Conflicts | Notes |
110+
| ------------ | ----------- | --------- | --------- | ----- |
111+
| _(none yet)_ | | | | |

apps/server/src/git/Layers/GitHubCliPR.ts

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,7 @@ export function makeGitHubCliPRMethods(execute: Execute) {
231231
Effect.catchTag("GitHubCliError", (error) =>
232232
Effect.succeed({
233233
ghAvailable: false as const,
234-
error:
235-
error.detail ?? "GitHub CLI is not authenticated. Run `gh auth login` and retry.",
234+
error: error.detail ?? "GitHub CLI is not authenticated. Run `gh auth login` and retry.",
236235
}),
237236
),
238237
);
@@ -624,16 +623,14 @@ export function makeGitHubCliPRMethods(execute: Execute) {
624623
}).pipe(
625624
Effect.map((result) => {
626625
const raw = JSON.parse(result.stdout) as Record<string, any>;
627-
const checks = (raw.statusCheckRollup ?? []).map(
628-
(check: Record<string, any>) => ({
629-
name: check.name ?? check.context ?? "unknown",
630-
status: classifyCheckStatus({
631-
status: check.status ?? null,
632-
conclusion: check.conclusion ?? null,
633-
state: check.state ?? null,
634-
}),
626+
const checks = (raw.statusCheckRollup ?? []).map((check: Record<string, any>) => ({
627+
name: check.name ?? check.context ?? "unknown",
628+
status: classifyCheckStatus({
629+
status: check.status ?? null,
630+
conclusion: check.conclusion ?? null,
631+
state: check.state ?? null,
635632
}),
636-
);
633+
}));
637634

638635
const reviewMap = new Map<string, string>();
639636
for (const review of raw.reviews ?? []) {
@@ -653,9 +650,7 @@ export function makeGitHubCliPRMethods(execute: Execute) {
653650
color: label.color ?? "",
654651
}));
655652

656-
const assignees = (raw.assignees ?? []).map(
657-
(a: Record<string, any>) => a.login ?? "",
658-
);
653+
const assignees = (raw.assignees ?? []).map((a: Record<string, any>) => a.login ?? "");
659654

660655
return {
661656
title: raw.title ?? "",
@@ -728,12 +723,7 @@ export function makeGitHubCliPRMethods(execute: Execute) {
728723
}
729724
return execute({
730725
cwd: input.cwd,
731-
args: [
732-
"api",
733-
`repos/${repo}/collaborators`,
734-
"--jq",
735-
".[].login",
736-
],
726+
args: ["api", `repos/${repo}/collaborators`, "--jq", ".[].login"],
737727
}).pipe(
738728
Effect.map((result) =>
739729
result.stdout

apps/server/src/git/Layers/GitManagerPR.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import {
66
type PullRequestCheck,
77
type PullRequestSummary,
88
} from "@t3tools/contracts";
9-
import type {
10-
GitHubPullRequestListEntry,
11-
} from "../Services/GitHubCli.ts";
9+
import type { GitHubPullRequestListEntry } from "../Services/GitHubCli.ts";
1210
import type { GitHubCliShape } from "../Services/GitHubCli.ts";
1311
import { GitHubCliError } from "../../sourceControl/GitHubCli.ts";
1412
import type { GitManagerShape } from "../GitManager.ts";
@@ -117,12 +115,10 @@ type PRMethods = Pick<
117115

118116
export function makeGitManagerPRMethods(gitHubCli: GitHubCliShape): PRMethods {
119117
const fetchCurrentUserLogin = (cwd: string) =>
120-
gitHubCli
121-
.execute({ cwd, args: ["api", "user", "--jq", ".login"] })
122-
.pipe(
123-
Effect.map((result) => result.stdout.trim()),
124-
Effect.catch(() => Effect.succeed("")),
125-
);
118+
gitHubCli.execute({ cwd, args: ["api", "user", "--jq", ".login"] }).pipe(
119+
Effect.map((result) => result.stdout.trim()),
120+
Effect.catch(() => Effect.succeed("")),
121+
);
126122

127123
const listPullRequests: PRMethods["listPullRequests"] = (input) =>
128124
wrapGhError(

apps/server/src/git/Services/GitHubCli.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@ import type { Effect } from "effect";
1010

1111
import type { ProcessRunResult } from "../../processRunner.ts";
1212
import type { GitHubCliError } from "../../sourceControl/GitHubCli.ts";
13-
import type {
14-
PullRequestCheck,
15-
PullRequestReviewer,
16-
PullRequestLabel,
17-
} from "@t3tools/contracts";
13+
import type { PullRequestCheck, PullRequestReviewer, PullRequestLabel } from "@t3tools/contracts";
1814

1915
export interface GitHubPullRequestSummary {
2016
readonly number: number;

apps/server/src/ws-pr.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -141,23 +141,17 @@ export function makePRHandlers(gitManager: GitManagerShape): PRHandlers {
141141
{ "rpc.aggregate": "git" },
142142
),
143143
[WS_METHODS.gitMergePullRequest]: (input) =>
144-
observeRpcEffect(
145-
WS_METHODS.gitMergePullRequest,
146-
gitManager.mergePullRequest(input),
147-
{ "rpc.aggregate": "git" },
148-
),
144+
observeRpcEffect(WS_METHODS.gitMergePullRequest, gitManager.mergePullRequest(input), {
145+
"rpc.aggregate": "git",
146+
}),
149147
[WS_METHODS.gitGetPullRequestDetail]: (input) =>
150-
observeRpcEffect(
151-
WS_METHODS.gitGetPullRequestDetail,
152-
gitManager.getPullRequestDetail(input),
153-
{ "rpc.aggregate": "git" },
154-
),
148+
observeRpcEffect(WS_METHODS.gitGetPullRequestDetail, gitManager.getPullRequestDetail(input), {
149+
"rpc.aggregate": "git",
150+
}),
155151
[WS_METHODS.gitEditPullRequest]: (input) =>
156-
observeRpcEffect(
157-
WS_METHODS.gitEditPullRequest,
158-
gitManager.editPullRequest(input),
159-
{ "rpc.aggregate": "git" },
160-
),
152+
observeRpcEffect(WS_METHODS.gitEditPullRequest, gitManager.editPullRequest(input), {
153+
"rpc.aggregate": "git",
154+
}),
161155
[WS_METHODS.gitGetRepositoryCollaborators]: (input) =>
162156
observeRpcEffect(
163157
WS_METHODS.gitGetRepositoryCollaborators,

apps/web/src/components/CommitModal.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ export function CommitModal({
5050
enabled: open && !!gitCwd && !!environmentId,
5151
});
5252

53-
const allFiles = useMemo((): readonly WorkingTreeFile[] => gitStatus?.workingTree.files ?? [], [gitStatus]);
53+
const allFiles = useMemo(
54+
(): readonly WorkingTreeFile[] => gitStatus?.workingTree.files ?? [],
55+
[gitStatus],
56+
);
5457
const [excluded, setExcluded] = useState<ReadonlySet<string>>(() => new Set());
5558
const [newBranch, setNewBranch] = useState(false);
5659
const [newBranchName, setNewBranchName] = useState("");

0 commit comments

Comments
 (0)