feat: continue assigned approvals into merge (#240)
CI / lint (pull_request) Successful in 19s
CI / build-frontend (pull_request) Successful in 4s

This commit is contained in:
timmy committed 2026-08-07 23:12:55 +00:00
1 parent 9289434ad2
commit 2bd8426db8
5 files changed
+102 -6

No files matched your search

+29 -3
View File
@@ -113,6 +113,9 @@ textarea { resize: vertical; min-height: 120px; }
.review-handoff button { min-height:44px; }
.review-handoff-link { min-height:44px; display:flex; align-items:center; justify-content:center; border:1px solid #60a5fa; border-radius:8px; color:#bfdbfe; font-weight:700; text-decoration:none; }
.review-handoff-link[hidden] { display:none; }
.review-merge-continuation { position:sticky; bottom:0; z-index:5; padding:10px 4px; padding-bottom:calc(10px + env(safe-area-inset-bottom)); background:rgba(11,21,38,.98); }
#continue-review-to-merge { min-height:44px; width:100%; }
#continue-review-to-merge[hidden] { display:none; }
.review-copy-fallback { min-height:140px; }
.review-progress-actions { position:sticky; bottom:0; z-index:2; display:flex; align-items:center; justify-content:space-between; gap:10px; margin:8px -4px 0; padding:10px 4px; background:rgba(11,21,38,.96); border-top:1px solid #2a496e; }
.review-progress-actions button { min-height:44px; max-width:100%; }
@@ -707,6 +710,9 @@ textarea { resize: vertical; min-height: 120px; }
<textarea class="review-copy-fallback" id="review-copy-fallback" readonly hidden aria-label="Feedback to copy manually"></textarea>
<a class="review-handoff-link" id="review-handoff-link" target="_blank" rel="noopener noreferrer" hidden>Continue to Gitea</a>
</div>
<div class="review-merge-continuation">
<button id="continue-review-to-merge" type="button" hidden>Continue to merge</button>
</div>
</section>
<h2>Review history</h2>
<div id="review-history" class="muted"></div>
@@ -2119,6 +2125,7 @@ textarea { resize: vertical; min-height: 120px; }
qs('#review-handoff-link').href = item.url;
qs('#review-handoff-status').textContent = '';
qs('#review-submit-status').textContent = '';
qs('#continue-review-to-merge').hidden = true;
qs('#submit-review').disabled = true;
closeInlineComposer();
draft = null;
@@ -3133,12 +3140,21 @@ textarea { resize: vertical; min-height: 120px; }
button.disabled = true;
qs('#review-submit-status').textContent = 'Submitting review…';
try {
const item = selectedReview;
const progressSnapshot = progress?.snapshot() || { reviewed: [] };
const result = await reviewController.submit(selectedReview, {
decision: snapshot.decision,
body: createReviewController.formatFeedback(snapshot, reviewFiles),
expected_head_sha: selectedReviewHead,
comments: snapshot.comments,
});
const canContinueToMerge = createReviewController.prepareMergeContinuation({
storage: localStorage,
item,
headSha: selectedReviewHead,
reviewed: progressSnapshot.reviewed,
decision: snapshot.decision,
});
draft.clear();
progress?.clear();
qs('#review-decision').value = 'comment';
@@ -3146,15 +3162,25 @@ textarea { resize: vertical; min-height: 120px; }
document.querySelectorAll('.review-note').forEach(note => { note.value = ''; });
if (progress) showReviewProgress(progress.snapshot());
qs('#review-submit-status').textContent = 'Review submitted · ' + (result.state || 'complete') + '.';
await load();
if (workSession.active()) workSession.complete();
else button.focus();
if (canContinueToMerge) {
qs('#continue-review-to-merge').hidden = false;
qs('#continue-review-to-merge').focus();
} else {
await load();
if (workSession.active()) workSession.complete();
else button.focus();
}
} catch (error) {
qs('#review-submit-status').textContent = error.message + ' Your draft is safe; retry or open in Gitea.';
button.disabled = false;
button.focus();
}
});
qs('#continue-review-to-merge').addEventListener('click', () => {
const item = selectedReview;
if (!item) return;
workRoute.open({ ...item, kind:'pull', is_review:false }, { replace:true });
});
qs('#copy-review-feedback').addEventListener('click', async () => {
if (!draft || !selectedReview || reviewHandoffPending) return;
reviewHandoffPending = true;
+13
View File
@@ -309,6 +309,18 @@ async function copyAndContinue({ text, url, copy, open, fallback }) {
return { copied: true, opened: true };
}
function prepareMergeContinuation({ storage, item, headSha, reviewed, decision }) {
const reasons = Array.isArray(item?.work_reasons) ? item.work_reasons : [];
if (decision !== 'approve' || !reasons.includes('assigned_to_me') || !headSha) return false;
const filenames = Array.isArray(reviewed)
? reviewed.filter(filename => typeof filename === 'string' && filename)
: [];
const key = 'stackchain.pull-review.v1:' + item.repository + '#' + item.number + ':' + headSha;
try { storage?.setItem(key, JSON.stringify(filenames)); }
catch (_error) { return false; }
return true;
}
createReviewController.renderDiffFile = renderDiffFile;
createReviewController.parseDiffLines = parseDiffLines;
createReviewController.toggleDiff = toggleDiff;
@@ -316,6 +328,7 @@ createReviewController.createProgress = createProgress;
createReviewController.createDraft = createDraft;
createReviewController.formatFeedback = formatFeedback;
createReviewController.copyAndContinue = copyAndContinue;
createReviewController.prepareMergeContinuation = prepareMergeContinuation;
if (typeof module !== 'undefined' && module.exports) {
module.exports = createReviewController;
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'stackchain-dashboard-shell-v8';
const CACHE = 'stackchain-dashboard-shell-v9';
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
const BASE = new URL('./', self.location.href).pathname;
const SHELL = [