feat: show requested-change feedback (Closes #1336)
CI / lint (pull_request) Successful in 3m30s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 5m24s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-24 05:38:30 +00:00
1 parent d2a815c4bc
commit c4bb2f5eba
8 files changed
+340 -12

No files matched your search

+12 -2
View File
@@ -1261,9 +1261,19 @@ textarea { resize: vertical; min-height: 120px; }
.pull-review-tools button { min-height:44px; }
.pull-reviewer-summary { margin:10px 0; padding:10px; border:1px solid #203a5c; border-radius:10px; }
.pull-reviewer-summary h3 { margin:0 0 6px; font-size:14px; }
.pull-reviewer-status { display:flex; justify-content:space-between; gap:10px; padding:8px 0; border-top:1px solid #203a5c; overflow-wrap:anywhere; }
.pull-reviewer-status { padding:8px 0; border-top:1px solid #203a5c; overflow-wrap:anywhere; }
.pull-reviewer-status:first-child { border-top:0; }
.pull-reviewer-status span { text-align:right; }
.pull-reviewer-status-heading { display:flex; justify-content:space-between; gap:10px; }
.pull-reviewer-status-heading span { text-align:right; }
.pull-review-feedback { margin-top:8px; border:1px solid #2a496e; border-radius:8px; padding:0 8px 8px; }
.pull-review-feedback > summary { min-height:44px; display:flex; align-items:center; cursor:pointer; }
.pull-review-feedback-summary { margin:4px 0 10px; white-space:pre-wrap; }
.pull-review-feedback-file { margin-top:8px; padding-top:8px; border-top:1px solid #203a5c; }
.pull-review-feedback-file h4 { margin:0 0 4px; overflow-wrap:anywhere; }
.pull-review-feedback-file ul { margin:0; padding-left:20px; }
.pull-review-feedback-file li { margin:6px 0; }
.pull-review-feedback-file small { display:block; color:#93c5fd; }
.pull-review-feedback-file button { min-height:44px; width:100%; margin-top:4px; }
#request-updated-pull-review { min-height:44px; width:100%; margin-top:8px; }
.pull-review { margin-top:14px; overflow:hidden; border:1px solid #2a496e; border-radius:10px; padding:0 10px 10px; }
.pull-review summary { min-height:44px; display:flex; align-items:center; cursor:pointer; }
+38 -7
View File
@@ -28,6 +28,29 @@ function mergeEligibility(detail, reviewState) {
return { allowed: true, reason: 'Ready to merge' };
}
function renderReviewerFeedback(reviewer, escapeHtml) {
const groups = new Map();
(Array.isArray(reviewer?.comments) ? reviewer.comments : []).forEach(comment => {
if (!comment?.path || !comment?.body) return;
if (!groups.has(comment.path)) groups.set(comment.path, []);
groups.get(comment.path).push(comment);
});
const summary = reviewer?.summary ? '<p class="pull-review-feedback-summary">' +
escapeHtml(reviewer.summary) + '</p>' : '';
const files = Array.from(groups.entries()).map(([path, comments]) =>
'<section class="pull-review-feedback-file"><h4>' + escapeHtml(path) + '</h4><ul>' +
comments.map(comment => '<li>' + escapeHtml(comment.body) +
(comment.line ? '<small>Line ' + Number(comment.line) + '</small>' : '') + '</li>').join('') +
'</ul><button type="button" data-review-feedback-file="' + escapeHtml(path) +
'">View in changes</button></section>'
).join('');
if (!summary && !files) return '';
const reviewedHead = reviewer?.status === 'outdated' && reviewer?.head_sha ?
'<p class="small">Reviewed head ' + escapeHtml(reviewer.head_sha.slice(0, 8)) + '</p>' : '';
return '<details class="pull-review-feedback"><summary>Review feedback</summary>' +
reviewedHead + summary + files + '</details>';
}
function renderReviewerStatuses(reviewers, escapeHtml) {
const labels = {
waiting: 'Waiting for review',
@@ -38,9 +61,10 @@ function renderReviewerStatuses(reviewers, escapeHtml) {
};
return (Array.isArray(reviewers) ? reviewers : []).map(reviewer => {
const status = labels[reviewer?.status] || 'Status unavailable';
return '<div class="pull-reviewer-status" data-reviewer-status="' +
escapeHtml(reviewer?.status || 'unknown') + '"><strong>@' +
escapeHtml(reviewer?.login || 'unknown') + '</strong><span>' + escapeHtml(status) + '</span></div>';
return '<article class="pull-reviewer-status" data-reviewer-status="' +
escapeHtml(reviewer?.status || 'unknown') + '"><div class="pull-reviewer-status-heading"><strong>@' +
escapeHtml(reviewer?.login || 'unknown') + '</strong><span>' + escapeHtml(status) + '</span></div>' +
renderReviewerFeedback(reviewer, escapeHtml) + '</article>';
}).join('');
}
@@ -48,6 +72,9 @@ function renderReviewerPanel(doc, detail, escapeHtml = value => String(value)
.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;')) {
const reviewers = Array.isArray(detail?.reviewers) ? detail.reviewers : [];
doc.querySelector('#pull-reviewer-statuses').innerHTML = renderReviewerStatuses(reviewers, escapeHtml);
doc.querySelectorAll('[data-review-feedback-file]').forEach(button => {
button.addEventListener('click', () => focusPullFile(doc, button.dataset.reviewFeedbackFile));
});
doc.querySelector('#pull-reviewer-status').textContent = reviewers.length ?
reviewers.length + ' reviewer status' + (reviewers.length === 1 ? '' : 'es') + ' for this pull request.' :
'No review has been requested; existing merge policy is unchanged.';
@@ -83,18 +110,22 @@ function renderFile(file, index, reviewed, escapeHtml) {
'" aria-pressed="' + String(reviewed) + '">' + (reviewed ? 'Reviewed' : 'Mark reviewed') + '</button></article>';
}
function focusNextUnreviewed(doc, detail, state, controller) {
if (!detail || !state) return;
const filename = controller.nextUnreviewed(detail, state);
function focusPullFile(doc, filename) {
const article = Array.from(doc.querySelectorAll('#pull-files .pull-file'))
.find(file => file.dataset.pullFilename === filename);
const toggle = article?.querySelector('.pull-file-toggle');
const panel = toggle && doc.getElementById(toggle.getAttribute('aria-controls'));
if (!toggle || !panel) return;
if (!toggle || !panel) return false;
toggle.setAttribute('aria-expanded', 'true');
panel.hidden = false;
toggle.scrollIntoView({ block: 'center', behavior: 'smooth' });
toggle.focus();
return true;
}
function focusNextUnreviewed(doc, detail, state, controller) {
if (!detail || !state) return;
focusPullFile(doc, controller.nextUnreviewed(detail, state));
}
function removeFromSnapshot(data, item) {