feat(github): pull request tool window, timeline and review flow (#477)

Replaces the Pull Requests tool window with a full review workflow on top of gh's authentication.

- List with search, quick filters and paging; a tab per pull request and a New Pull Request tab.
- Details: commit picker, changes tree with viewed state, CI and reviewer status, merge, close and reopen.
- Timeline editor: comments, review threads, reactions and suggested changes.
- Review in the diff viewer and, for the checked-out branch, in the editor (review mode).
- New generic vcs extension points: registerDiffViewerProvider, lineInlays, reviewGutter, branch widget extensions; new vcs_merge_base and gh_api commands.
- CSP: img-src allows https://avatars.githubusercontent.com for avatars.

Fixed while testing against a real repository: remotes behind an SSH host alias, mergeability stuck on UNKNOWN, Apply Locally staying disabled after a checkout, relative links navigating the app's page, and review inlays wider than the pane.

Co-authored-by: imacross <[email protected]>
This commit is contained in:
imacross authored and GitHub committed 2026-10-31 07:50:43 +08:00
1 parent 696377acc2
commit 059fd8a69b
192 files changed
+23668 -2076

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
/**
* Review-in-diff settings, persisted in local storage: "Show Review Threads"
* (`GithubPullRequestsProjectUISettings.editorReviewViewOption`), the new comment's preferred
* button (`reviewCommentsPreferred`) and "Automatically mark opened files as viewed"
* (`GithubSettings.isAutomaticallyMarkAsViewed`, on Settings › Version Control › GitHub).
*/
import { create } from "zustand";
import { THREADS_VIEW_OPTIONS, type ThreadsViewOption } from "./model";
export interface ReviewDiffSettings {
threadsView: ThreadsViewOption;
/** "Start Review" is the primary button of a new comment (else "Add Single Comment"). */
reviewCommentsPreferred: boolean;
autoMarkViewed: boolean;
}
const KEY = "openidea.github.reviewDiff.v1";
/** IntelliJ's defaults. */
const DEFAULTS: ReviewDiffSettings = {
threadsView: "unresolved",
reviewCommentsPreferred: true,
autoMarkViewed: false,
};
export function parseReviewDiffSettings(raw: unknown): ReviewDiffSettings {
const o = raw && typeof raw === "object" ? (raw as Record<string, unknown>) : {};
const bool = (v: unknown, d: boolean) => (typeof v === "boolean" ? v : d);
return {
threadsView: THREADS_VIEW_OPTIONS.some((x) => x.value === o.threadsView)
? (o.threadsView as ThreadsViewOption)
: DEFAULTS.threadsView,
reviewCommentsPreferred: bool(o.reviewCommentsPreferred, DEFAULTS.reviewCommentsPreferred),
autoMarkViewed: bool(o.autoMarkViewed, DEFAULTS.autoMarkViewed),
};
}
function load(): ReviewDiffSettings {
try {
const raw = globalThis.localStorage?.getItem(KEY);
return parseReviewDiffSettings(raw ? JSON.parse(raw) : null);
} catch {
return DEFAULTS;
}
}
export const useReviewDiffSettings = create<ReviewDiffSettings>(load);
export function setReviewDiffSettings(patch: Partial<ReviewDiffSettings>) {
useReviewDiffSettings.setState(patch);
try {
globalThis.localStorage?.setItem(KEY, JSON.stringify(useReviewDiffSettings.getState()));
} catch {
/* storage unavailable */
}
}