Created
October 15, 2025 13:49
-
-
Save bregenspan/bf0e9706df5b675112a7e954aba6f9ae to your computer and use it in GitHub Desktop.
Script to make reviewing large PRs in GitHub more pleasant
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @ts-check | |
/** GitHub PR review mode UX improvements for | |
reviewing large PRs. Suitable for converting | |
to a bookmarklet or using as a user script | |
that is activated on the PR review page. | |
Automatically advances to next file after marking | |
a file as viewed, outlines the active file, and | |
allows for keyboard use to mark file as viewed. | |
*/ | |
(() => { | |
const CLASSNAME_ACTIVE = "br-reviewmode-active"; | |
const STYLE_ACTIVE = `outline: 3px blue solid;`; | |
/** @typedef {Object} Selectors | |
* @property {string} attribute - Name of attribute that changes when a file is marked as viewed | |
* @property {string} button - Selector for the "Not Viewed" button/checkbox within a diff | |
* @property {string} diff - Selector for the entire diff container for a single not-yet-reviewed file | |
* @property {string} filesChangedContainer - Selector for top-level container around all file diff views | |
*/ | |
/** New Github PR Files Changed review page (late 2025 preview) | |
* @type {Selectors} | |
*/ | |
const SELECTORS_NEW = { | |
attribute: "aria-pressed", | |
filesChangedContainer: 'div[data-hpc="true"]', // brittle, fix this | |
button: 'button[aria-label="Not Viewed"]', | |
diff: `div[role=region][class*="Diff-module"]:has(button[aria-label="Not Viewed"])`, | |
}; | |
/** Legacy review PR Files Changed review page (opted out of late 2025 preview) | |
* @type {Selectors} | |
*/ | |
const SELECTORS_LEGACY = { | |
attribute: "data-file-user-viewed", | |
filesChangedContainer: 'div[data-target="diff-layout.mainContainer"]', | |
button: 'input[type="checkbox"]:not(:checked)', | |
diff: `.js-details-container[data-details-container-group="file"]:has(input[type="checkbox"]:not(:checked))`, | |
}; | |
const selectors = document.querySelector( | |
SELECTORS_LEGACY.filesChangedContainer | |
) | |
? SELECTORS_LEGACY | |
: SELECTORS_NEW; | |
const filesChangedContainer = document.querySelector( | |
selectors.filesChangedContainer | |
); | |
if (!filesChangedContainer) { | |
console.warn("ReviewMode: could not find 'Files changed' container"); | |
return; | |
} | |
const style = document.createElement("style"); | |
style.innerHTML = `.${CLASSNAME_ACTIVE} { ${STYLE_ACTIVE} }`; | |
document.body.appendChild(style); | |
/** @type {HTMLElement | null} */ | |
let lastDiff; | |
function advanceDiff() { | |
lastDiff?.classList.remove(CLASSNAME_ACTIVE); | |
/** @type {HTMLElement | null} */ | |
const nextDiff = document.querySelector(selectors.diff); | |
if (!nextDiff) { | |
// No more to review | |
return; | |
} | |
nextDiff.scrollIntoView({ block: "center" }); | |
nextDiff.classList.add(CLASSNAME_ACTIVE); | |
/** @type {HTMLElement | null} */ | |
const nextButton = nextDiff.querySelector(selectors.button); | |
nextButton?.focus(); | |
lastDiff = nextDiff; | |
} | |
const observer = new MutationObserver((mutations) => { | |
mutations.forEach((mutation) => { | |
if ( | |
mutation.type === "attributes" && | |
mutation.attributeName === selectors.attribute | |
) { | |
advanceDiff(); | |
} | |
}); | |
}); | |
observer.observe(filesChangedContainer, { | |
attributes: true, | |
subtree: true, | |
}); | |
advanceDiff(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment