diff --git a/src-tauri/src/git.rs b/src-tauri/src/git.rs index 192429e9e..dc5f8896b 100644 --- a/src-tauri/src/git.rs +++ b/src-tauri/src/git.rs @@ -679,7 +679,7 @@ pub(crate) async fn get_github_pull_requests( "--limit", "50", "--json", - "number,title,url,updatedAt,headRefName,baseRefName,isDraft,author", + "number,title,url,updatedAt,body,headRefName,baseRefName,isDraft,author", ]) .current_dir(&repo_root) .output() diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index 5f21b9d83..eea4714a2 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -65,6 +65,7 @@ pub(crate) struct GitHubPullRequest { pub(crate) url: String, #[serde(rename = "updatedAt")] pub(crate) updated_at: String, + pub(crate) body: String, #[serde(rename = "headRefName")] pub(crate) head_ref_name: String, #[serde(rename = "baseRefName")] diff --git a/src/App.tsx b/src/App.tsx index 1c43f145e..22b2092bf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1060,6 +1060,7 @@ function MainApp() { gitPullRequestsLoading, gitPullRequestsError, selectedPullRequestNumber: selectedPullRequest?.number ?? null, + selectedPullRequest: diffSource === "pr" ? selectedPullRequest : null, onSelectPullRequest: handleSelectPullRequest, gitRemoteUrl, gitRoot: activeGitRoot, diff --git a/src/features/git/components/GitDiffViewer.tsx b/src/features/git/components/GitDiffViewer.tsx index c991bd96b..0696810c4 100644 --- a/src/features/git/components/GitDiffViewer.tsx +++ b/src/features/git/components/GitDiffViewer.tsx @@ -4,6 +4,9 @@ import { FileDiff, WorkerPoolContextProvider } from "@pierre/diffs/react"; import type { FileDiffMetadata } from "@pierre/diffs"; import { parsePatchFiles } from "@pierre/diffs"; import { workerFactory } from "../../../utils/diffsWorker"; +import type { GitHubPullRequest } from "../../../types"; +import { formatRelativeTime } from "../../../utils/time"; +import { Markdown } from "../../messages/components/Markdown"; type GitDiffViewerItem = { path: string; @@ -16,6 +19,7 @@ type GitDiffViewerProps = { selectedPath: string | null; isLoading: boolean; error: string | null; + pullRequest?: GitHubPullRequest | null; onActivePathChange?: (path: string) => void; }; @@ -110,6 +114,7 @@ export function GitDiffViewer({ selectedPath, isLoading, error, + pullRequest, onActivePathChange, }: GitDiffViewerProps) { const containerRef = useRef(null); @@ -215,12 +220,60 @@ export function GitDiffViewer({ }; }, [diffs, onActivePathChange, rowVirtualizer]); + const prUpdatedLabel = pullRequest?.updatedAt + ? formatRelativeTime(new Date(pullRequest.updatedAt).getTime()) + : null; + const prAuthor = pullRequest?.author?.login ?? "unknown"; + const prBody = pullRequest?.body?.trim() ?? ""; + return (
+ {pullRequest && ( +
+
+
+ + #{pullRequest.number} + + + {pullRequest.title} + +
+
+ @{prAuthor} + {prUpdatedLabel && ( + <> + · + {prUpdatedLabel} + + )} + · + + {pullRequest.baseRefName} ← {pullRequest.headRefName} + + {pullRequest.isDraft && ( + Draft + )} +
+
+
+ {prBody ? ( + + ) : ( +
+ No description provided. +
+ )} +
+
+ )} {!error && stickyEntry && (
diff --git a/src/features/layout/hooks/useLayoutNodes.tsx b/src/features/layout/hooks/useLayoutNodes.tsx index ca162a275..92264bccb 100644 --- a/src/features/layout/hooks/useLayoutNodes.tsx +++ b/src/features/layout/hooks/useLayoutNodes.tsx @@ -164,6 +164,7 @@ type LayoutNodesOptions = { gitPullRequestsLoading: boolean; gitPullRequestsError: string | null; selectedPullRequestNumber: number | null; + selectedPullRequest: GitHubPullRequest | null; onSelectPullRequest: (pullRequest: GitHubPullRequest) => void; gitRemoteUrl: string | null; gitRoot: string | null; @@ -513,6 +514,7 @@ export function useLayoutNodes(options: LayoutNodesOptions): LayoutNodesResult { selectedPath={options.selectedDiffPath} isLoading={options.gitDiffLoading} error={options.gitDiffError} + pullRequest={options.selectedPullRequest} onActivePathChange={options.onDiffActivePathChange} /> ); diff --git a/src/styles/diff-viewer.css b/src/styles/diff-viewer.css index e566524b1..87f55f5fc 100644 --- a/src/styles/diff-viewer.css +++ b/src/styles/diff-viewer.css @@ -16,6 +16,102 @@ padding-top: 0; } +.diff-viewer-pr { + margin: 12px 16px 16px; + padding: 16px; + border-radius: 14px; + border: 1px solid var(--border-subtle); + background: color-mix(in srgb, var(--surface-strong) 84%, transparent); + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.22); +} + +.diff-viewer-pr-header { + display: flex; + flex-direction: column; + gap: 10px; +} + +.diff-viewer-pr-title { + display: flex; + flex-wrap: wrap; + gap: 8px; + font-size: 18px; + font-weight: 600; + color: var(--text-strong); +} + +.diff-viewer-pr-number { + color: var(--text-faint); + font-variant-numeric: tabular-nums; +} + +.diff-viewer-pr-title-text { + color: var(--text-stronger); +} + +.diff-viewer-pr-meta { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + font-size: 12px; + color: var(--text-muted); +} + +.diff-viewer-pr-author { + color: var(--text-strong); + font-weight: 600; +} + +.diff-viewer-pr-sep { + color: var(--text-faint); +} + +.diff-viewer-pr-branch { + font-family: "SFMono-Regular", "Menlo", "Monaco", monospace; + font-size: 11px; + padding: 2px 6px; + border-radius: 999px; + border: 1px solid var(--border-subtle); + background: var(--surface-control); + color: var(--text-quiet); +} + +.diff-viewer-pr-pill { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + padding: 2px 6px; + border-radius: 999px; + background: rgba(242, 153, 74, 0.16); + color: #f2994a; + border: 1px solid rgba(242, 153, 74, 0.4); +} + +.diff-viewer-pr-body { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--border-subtle); +} + +.diff-viewer-pr-markdown { + font-size: 13px; + color: var(--text-stronger); +} + +.diff-viewer-pr-markdown :where(h1, h2, h3, h4, h5, h6) { + margin-top: 16px; +} + +.diff-viewer-pr-markdown :where(p, ul, ol, pre) { + margin-top: 10px; +} + +.diff-viewer-pr-empty { + font-size: 13px; + color: var(--text-muted); +} + .diff-viewer-list { position: relative; width: 100%; diff --git a/src/types.ts b/src/types.ts index 15ee3a8e5..88591adfa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -158,6 +158,7 @@ export type GitHubPullRequest = { title: string; url: string; updatedAt: string; + body: string; headRefName: string; baseRefName: string; isDraft: boolean;