Skip to content

Commit 7cb6fa2

Browse files
sheepbox8646claude
andcommitted
feat(client): open PR diffs in a commentable right-panel view
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 3469ff0 commit 7cb6fa2

6 files changed

Lines changed: 137 additions & 1 deletion

File tree

packages/client/src/renderer/components/file-tree/changed-files-tree.vue

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const props = defineProps<{
88
files: GitHubCommitFile[]
99
owner: string
1010
repo: string
11+
/** When set, file diffs open as commentable pull-request diffs. */
12+
pullRequestNumber?: number
1113
}>()
1214
1315
const { t } = useI18n()
@@ -81,6 +83,21 @@ function selectNode(node: GitHubRepositoryFileNode): void {
8183
return
8284
}
8385
86+
if (props.pullRequestNumber) {
87+
openRightPanel({
88+
type: 'pull-request-diff',
89+
owner: props.owner,
90+
repo: props.repo,
91+
number: props.pullRequestNumber,
92+
path: file.filename,
93+
patch: file.patch,
94+
additions: file.additions,
95+
deletions: file.deletions,
96+
title: file.filename,
97+
})
98+
return
99+
}
100+
84101
openRightPanel({
85102
type: 'diff',
86103
patch: file.patch,

packages/client/src/renderer/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export { default as MermaidRenderer } from "./mermaid/mermaid-renderer.vue";
1212
export { default as AppPagination } from "./navigation/app-pagination.vue";
1313
export { default as MultiSelectPicker } from "./navigation/multi-select-picker.vue";
1414
export { default as SectionSidebar } from "./navigation/section-sidebar.vue";
15+
export { default as PullRequestReviewDiffPanel } from "./review/pull-request-review-diff-panel.vue";
16+
export { default as ReviewDiff } from "./review/review-diff.vue";
1517
export { default as ShikiCode } from "./code/shiki-code.vue";
1618
export { default as ShikiDiff } from "./code/shiki-diff.vue";
1719
export { default as ConversationActorLine } from "./conversation/conversation-actor-line.vue";
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<script setup lang="ts">
2+
import { computed, onBeforeUnmount, watch } from 'vue'
3+
import { usePullRequestReviewThreadsQuery } from '@/composables/github/use-pull-requests'
4+
import { useReviewSelection } from '@/composables/use-review-selection'
5+
import ReviewDiff, { type ReviewDiffMarker } from './review-diff.vue'
6+
import type { ReviewDiffRange } from './review-diff-selection'
7+
8+
const props = defineProps<{
9+
owner: string
10+
repo: string
11+
number: number
12+
path: string
13+
patch: string
14+
}>()
15+
16+
const threadsQuery = usePullRequestReviewThreadsQuery(
17+
() => props.owner,
18+
() => props.repo,
19+
() => props.number,
20+
() => true,
21+
)
22+
23+
const { selection, setSelection, clearSelection, locateThread } = useReviewSelection()
24+
25+
// Outdated threads report line: null and cannot be anchored in the current diff.
26+
const markers = computed<ReviewDiffMarker[]>(() => {
27+
const threads = threadsQuery.data.value?.threads ?? []
28+
29+
return threads.flatMap((thread) => {
30+
if (thread.path !== props.path || thread.line === null) return []
31+
32+
return [{
33+
threadId: thread.id,
34+
side: thread.side,
35+
line: thread.line,
36+
count: thread.comments.length,
37+
isResolved: thread.isResolved,
38+
isPending: thread.isPending,
39+
}]
40+
})
41+
})
42+
43+
const activeSelection = computed<ReviewDiffRange | null>(() => {
44+
const value = selection.value
45+
if (!value) return null
46+
if (
47+
value.owner !== props.owner
48+
|| value.repo !== props.repo
49+
|| value.number !== props.number
50+
|| value.path !== props.path
51+
) return null
52+
53+
return { side: value.side, startLine: value.startLine, line: value.line }
54+
})
55+
56+
function onSelect(range: ReviewDiffRange): void {
57+
setSelection({
58+
owner: props.owner,
59+
repo: props.repo,
60+
number: props.number,
61+
path: props.path,
62+
side: range.side,
63+
startLine: range.startLine,
64+
line: range.line,
65+
})
66+
}
67+
68+
watch(
69+
() => [props.owner, props.repo, props.number, props.path] as const,
70+
() => {
71+
clearSelection()
72+
},
73+
)
74+
75+
onBeforeUnmount(() => {
76+
clearSelection()
77+
})
78+
</script>
79+
80+
<template>
81+
<div class="min-h-full overflow-x-auto py-2">
82+
<ReviewDiff
83+
class="w-max min-w-full"
84+
:filename="props.path"
85+
:markers="markers"
86+
:patch="props.patch"
87+
:selection="activeSelection"
88+
@locate-thread="locateThread"
89+
@select="onSelect"
90+
/>
91+
</div>
92+
</template>

packages/client/src/renderer/composables/use-right-panel.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ export type RightPanelContent =
1717
deletions?: number
1818
title?: string
1919
}
20+
| {
21+
type: 'pull-request-diff'
22+
owner: string
23+
repo: string
24+
number: number
25+
path: string
26+
patch: string
27+
additions?: number
28+
deletions?: number
29+
title?: string
30+
}
2031
| {
2132
type: 'markdown'
2233
content: string

packages/client/src/renderer/pages/pull-request/components/pull-request-review-tab.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ async function submitReview(event: GitHubPullRequestReviewEvent): Promise<void>
231231
<ChangedFilesTree
232232
:files="files"
233233
:owner="owner"
234+
:pull-request-number="number"
234235
:repo="repo"
235236
/>
236237
</div>

packages/client/src/renderer/pages/workspace/components/workspace-right-panel.vue

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n'
44
import { Download } from 'lucide-vue-next'
55
import { Button } from '@oh-my-github/ui'
66
import { useRightPanel } from '@/composables/use-right-panel'
7-
import { GitHubMarkdownRenderer, MarkdownRenderer, ShikiCode } from '@/components'
7+
import { GitHubMarkdownRenderer, MarkdownRenderer, PullRequestReviewDiffPanel, ShikiCode } from '@/components'
88
99
const props = defineProps<{
1010
expanded: boolean
@@ -101,6 +101,19 @@ function resizeWithKeyboard(event: KeyboardEvent): void {
101101
/>
102102
</div>
103103

104+
<div
105+
v-else-if="content.type === 'pull-request-diff'"
106+
class="min-h-full"
107+
>
108+
<PullRequestReviewDiffPanel
109+
:number="content.number"
110+
:owner="content.owner"
111+
:patch="content.patch"
112+
:path="content.path"
113+
:repo="content.repo"
114+
/>
115+
</div>
116+
104117
<div
105118
v-else-if="content.type === 'markdown'"
106119
class="p-4"

0 commit comments

Comments
 (0)