feat: carry mobile review feedback into Gitea (#129)
CI / lint (pull_request) Successful in 10s
CI / build-frontend (pull_request) Successful in 5s

This commit is contained in:
timmy committed 2026-08-06 18:56:06 +00:00
1 parent bd0ae619db
commit 331eb99c1e
3 files changed
+266

No files matched your search

+73
View File
@@ -77,6 +77,13 @@ textarea { resize: vertical; min-height: 120px; }
.review-file.reviewed .review-file-toggle { border-color:#22c55e; }
.review-mark { min-height:44px; width:100%; margin-top:8px; }
.review-mark[aria-pressed="true"] { border-color:#22c55e; background:#123c2a; }
.review-note { min-height:88px; margin-top:6px; }
.review-feedback { display:grid; gap:8px; }
.review-feedback label { display:grid; gap:6px; }
.review-feedback select { min-height:44px; padding:8px; border-radius:8px; border:1px solid #1f3a5f; background:#0b1526; color:#e5e7eb; }
.review-handoff { position:sticky; bottom:0; z-index:3; display:grid; gap:8px; padding:10px 4px; background:rgba(11,21,38,.98); border-top:1px solid #2a496e; }
.review-handoff button { min-height:44px; }
.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%; }
.review-diff { overflow-x:auto; max-width:100%; margin-top:8px; white-space:pre; }
@@ -232,6 +239,24 @@ textarea { resize: vertical; min-height: 120px; }
<span class="small" id="review-progress" aria-live="polite">0 of 0 files reviewed</span>
<button id="next-unreviewed-review" disabled>Next unreviewed</button>
</div>
<section class="review-feedback" aria-labelledby="review-feedback-title">
<h2 id="review-feedback-title">Draft feedback</h2>
<label>Intended decision
<select id="review-decision">
<option value="comment">Comment</option>
<option value="approve">Approve</option>
<option value="request_changes">Request changes</option>
</select>
</label>
<label>Overall summary
<textarea id="review-summary" placeholder="Summarize your review"></textarea>
</label>
<div class="review-handoff">
<button id="copy-review-feedback">Copy feedback &amp; continue in Gitea</button>
<span class="small" id="review-handoff-status" aria-live="polite"></span>
<textarea class="review-copy-fallback" id="review-copy-fallback" readonly hidden aria-label="Feedback to copy manually"></textarea>
</div>
</section>
<h2>Review history</h2>
<div id="review-history" class="muted"></div>
</section>
@@ -284,6 +309,8 @@ textarea { resize: vertical; min-height: 120px; }
let selectedReview = null;
let reviewTrigger = null;
let progress = null;
let draft = null;
let reviewFiles = [];
async function fetchReviewJson(url, options) {
const response = await fetch(url, options);
@@ -424,6 +451,13 @@ textarea { resize: vertical; min-height: 120px; }
qs('#next-unreviewed-review').disabled = true;
qs('#review-history').textContent = '';
qs('#open-review-gitea').href = item.url;
qs('#review-decision').value = 'comment';
qs('#review-summary').value = '';
qs('#review-copy-fallback').hidden = true;
qs('#review-copy-fallback').value = '';
qs('#review-handoff-status').textContent = '';
draft = null;
reviewFiles = [];
qs('#close-review-sheet').focus();
try {
const detail = await reviewController.load(selectedReview);
@@ -446,6 +480,21 @@ textarea { resize: vertical; min-height: 120px; }
headSha: detail.head_sha || 'unknown',
files: detail.files || [],
});
reviewFiles = detail.files || [];
draft = createReviewController.createDraft({
storage: localStorage,
repository: item.repository,
number: item.number,
headSha: detail.head_sha || 'unknown',
files: reviewFiles,
});
const draftSnapshot = draft.snapshot();
qs('#review-decision').value = draftSnapshot.decision;
qs('#review-summary').value = draftSnapshot.summary;
document.querySelectorAll('.review-note').forEach(note => {
note.value = draftSnapshot.notes[note.dataset.reviewFilename] || '';
note.addEventListener('input', () => draft.setNote(note.dataset.reviewFilename, note.value));
});
document.querySelectorAll('.review-mark').forEach(button => {
button.addEventListener('click', () => {
const snapshot = progress.markReviewed(button.dataset.reviewFilename);
@@ -473,6 +522,8 @@ textarea { resize: vertical; min-height: 120px; }
qs('#review-sheet').classList.remove('open');
selectedReview = null;
progress = null;
draft = null;
reviewFiles = [];
if (reviewTrigger?.isConnected) reviewTrigger.focus();
}
@@ -589,6 +640,28 @@ textarea { resize: vertical; min-height: 120px; }
qs('#next-unreviewed-review').addEventListener('click', () => {
if (progress) openNextUnreviewed(progress.snapshot());
});
qs('#review-summary').addEventListener('input', event => draft?.setSummary(event.target.value));
qs('#review-decision').addEventListener('change', event => draft?.setDecision(event.target.value));
qs('#copy-review-feedback').addEventListener('click', async () => {
if (!draft || !selectedReview) return;
const fallback = qs('#review-copy-fallback');
fallback.hidden = true;
const copied = await createReviewController.copyAndContinue({
text: createReviewController.formatFeedback(draft.snapshot(), reviewFiles),
url: selectedReview.url,
copy: text => navigator.clipboard.writeText(text),
open: url => window.open(url, '_blank', 'noopener,noreferrer'),
fallback: text => {
fallback.value = text;
fallback.hidden = false;
fallback.focus();
fallback.select();
},
});
qs('#review-handoff-status').textContent = copied ?
'Feedback copied. Opening Gitea…' :
'Clipboard unavailable. Copy the selected feedback, then use Open in Gitea.';
});
const contextPoller = createContextPoller({
fetchContext: fetchContextSnapshot,