diff --git a/README.md b/README.md index f9051f3..7767130 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,12 @@ Approve, and Request changes reviews, and assigned-PR merge require repository write permission. Native Comment, Approve, and Request changes reviews support head-scoped draft comments anchored to changed lines; the dashboard validates each comment path and submits the summary, decision, and inline comments in one review -request. The dashboard rechecks the current pull-request head, CI success, draft +request. On phone-width review sheets, changed lines wrap inside a stable old/new +line-number gutter by default so long source lines remain readable and commentable +without horizontal panning. **Lines wrapped** toggles back to whitespace-preserving +horizontal inspection, and an explicit choice persists on the device across files, +review-sheet reopen, and reload. Desktop review diffs remain horizontally scrollable +until wrapping is explicitly enabled. The dashboard rechecks the current pull-request head, CI success, draft state, and mergeability immediately before every merge. Serve the dashboard only to trusted users on its own origin; cross-origin API access is intentionally disabled. Authentication defaults to fail-closed diff --git a/frontend/dashboard.css b/frontend/dashboard.css index a307500..f72881b 100644 --- a/frontend/dashboard.css +++ b/frontend/dashboard.css @@ -187,9 +187,18 @@ textarea { resize: vertical; min-height: 120px; } .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-display-tools { display:flex; align-items:center; gap:10px; margin-bottom:8px; } +.review-display-tools button { min-height:44px; flex:0 0 auto; } +.review-display-tools button[aria-pressed="true"] { color:#bfdbfe; background:#17365a; outline:1px solid #60a5fa; } .review-diff { overflow-x:auto; max-width:100%; margin-top:8px; white-space:pre; } -.review-diff-line { display:block; min-width:max-content; } -.review-inline-target { min-height:44px; width:100%; padding:8px; border:0; border-radius:0; text-align:left; font:inherit; white-space:pre; } +.review-diff-line { display:grid; grid-template-columns:max-content minmax(max-content,1fr); align-items:stretch; min-width:max-content; } +.review-line-numbers { position:sticky; left:0; z-index:1; display:grid; grid-template-columns:4ch 4ch; align-self:stretch; background:#0b1526; color:#7694b6; user-select:none; } +.review-line-number { padding:8px 4px; text-align:right; border-right:1px solid #244363; } +.review-line-code { min-width:0; padding:8px; white-space:pre; overflow-wrap:anywhere; } +.review-inline-target { min-height:44px; width:100%; padding:0; border:0; border-radius:0; text-align:left; font:inherit; white-space:normal; } +.wrap-lines .review-diff { overflow-x:hidden; white-space:normal; } +.wrap-lines .review-diff-line { grid-template-columns:max-content minmax(0,1fr); min-width:0; } +.wrap-lines .review-line-code { white-space:pre-wrap; overflow-wrap:anywhere; } .review-inline-target.has-draft { box-shadow:inset 4px 0 #fbbf24; } .review-inline-composer { position:sticky; bottom:0; z-index:4; display:grid; gap:8px; padding:10px; padding-bottom:calc(10px + env(safe-area-inset-bottom)); border:1px solid #60a5fa; border-radius:10px; background:#0b1526; } .review-inline-composer[hidden] { display:none; } diff --git a/frontend/dashboard.js b/frontend/dashboard.js index d6906db..3523262 100644 --- a/frontend/dashboard.js +++ b/frontend/dashboard.js @@ -237,6 +237,10 @@ return payload; } const reviewController = createReviewController({ fetchJson: fetchReviewJson, storage: localStorage }); + const wrapPreference = createReviewController.createWrapPreference({ + storage: localStorage, + mobile: window.matchMedia('(max-width: 600px)').matches, + }); const issueController = createIssueSheet({ fetchJson: fetchReviewJson, storage: localStorage }); const planningLoader = createIssueSheet.createPlanningLoader({ loadLabels: item => issueController.loadLabels(item), @@ -1765,6 +1769,14 @@ ); } + function applyReviewWrap(snapshot = wrapPreference.snapshot()) { + const reviewFilesElement = qs('#review-files'); + const button = qs('#review-wrap-lines'); + reviewFilesElement.classList.toggle('wrap-lines', snapshot.wrapped); + button.setAttribute('aria-pressed', String(snapshot.wrapped)); + button.textContent = snapshot.wrapped ? 'Lines wrapped' : 'Wrap lines'; + } + function showReviewProgress(snapshot) { const complete = snapshot.total > 0 && snapshot.reviewedCount === snapshot.total; qs('#review-progress').textContent = complete ? @@ -2582,6 +2594,7 @@ qs('#retry-review-load').hidden = true; qs('#review-sheet-body').textContent = ''; qs('#review-files').textContent = ''; + applyReviewWrap(); qs('#review-progress').textContent = '0 of 0 files reviewed'; qs('#next-unreviewed-review').disabled = true; qs('#review-history').textContent = ''; @@ -3910,6 +3923,10 @@ qs('#next-unreviewed-review').addEventListener('click', () => { if (progress) openNextUnreviewed(progress.snapshot()); }); + qs('#review-wrap-lines').addEventListener('click', () => { + const next = !wrapPreference.snapshot().wrapped; + applyReviewWrap(wrapPreference.setWrapped(next)); + }); qs('#review-summary').addEventListener('input', event => draft?.setSummary(event.target.value)); qs('#review-decision').addEventListener('change', event => draft?.setDecision(event.target.value)); qs('#save-inline-comment').addEventListener('click', () => { diff --git a/frontend/index.html b/frontend/index.html index 813ac2c..156ce30 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -539,6 +539,10 @@
CI unknownOpen in Gitea

Changed files

+
+ + Keep long diffs inside the review sheet. +