feat: visually review mobile issue evidence (Closes #835)
CI / lint (pull_request) Successful in 1m44s
CI / build-release (pull_request) Successful in 6s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-14 15:56:08 +00:00
1 parent a500915223
commit d797179844
5 files changed
+221 -2

No files matched your search

+8 -2
View File
@@ -654,8 +654,14 @@ textarea { resize: vertical; min-height: 120px; }
.issue-filing-review-summary > div { display:grid; gap:4px; padding:10px; border:1px solid #2a496e; border-radius:10px; min-width:0; }
.issue-filing-review-summary dt { color:#94a3b8; font-size:12px; font-weight:700; text-transform:uppercase; }
.issue-filing-review-summary dd { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; }
.issue-filing-review ol { display:grid; gap:8px; margin:0; padding-left:24px; overflow-wrap:anywhere; }
.issue-filing-review ol:empty::after { content:'No screenshots attached.'; display:list-item; color:#94a3b8; }
.issue-filing-review ol { display:grid; grid-template-columns:repeat(auto-fit,minmax(72px,1fr)); gap:8px; margin:0; padding:0; list-style:none; overflow-wrap:anywhere; }
.issue-filing-evidence-selector { min-height:44px; width:100%; padding:4px; border:2px solid #31577f; border-radius:10px; background:#08111f; }
.issue-filing-evidence-selector[aria-pressed="true"] { border-color:#93c5fd; box-shadow:0 0 0 2px rgba(96,165,250,.35); }
.issue-filing-evidence-selector img { display:block; width:100%; height:64px; object-fit:cover; border-radius:6px; }
.issue-filing-evidence-preview { display:grid; gap:8px; margin:10px 0 0; padding:10px; border:1px solid #31577f; border-radius:12px; background:#020617; }
.issue-filing-evidence-preview[hidden] { display:none; }
.issue-filing-evidence-preview > img { display:block; width:100%; max-height:42dvh; object-fit:contain; border-radius:8px; background:#000; }
.issue-filing-evidence-preview figcaption { display:grid; gap:3px; min-width:0; overflow-wrap:anywhere; }
.issue-filing-review-actions { position:sticky; bottom:0; display:grid; grid-template-columns:1fr 1fr; gap:8px; padding:10px 0 calc(10px + env(safe-area-inset-bottom)); background:#0b1526; }
.issue-filing-review-actions button { min-height:44px; width:100%; }
@media(max-width:320px) { .issue-filing-review-panel { padding:12px; padding-bottom:calc(12px + env(safe-area-inset-bottom)); } }
+8
View File
@@ -557,6 +557,12 @@
confirmButton: qs('#confirm-issue-filing'),
backButton: qs('#back-to-issue-edit'),
evidenceList: qs('#issue-filing-review-evidence'),
evidenceEmpty: qs('#issue-filing-review-evidence-empty'),
evidencePreview: qs('#issue-filing-review-evidence-preview'),
evidenceImage: qs('#issue-filing-review-evidence-image'),
evidencePosition: qs('#issue-filing-review-evidence-position'),
evidenceFilename: qs('#issue-filing-review-evidence-filename'),
evidenceNote: qs('#issue-filing-review-evidence-note'),
repository: qs('#issue-filing-review-repository'),
intent: qs('#issue-filing-review-intent'),
title: qs('#issue-filing-review-title'),
@@ -564,6 +570,8 @@
metadata: qs('#issue-filing-review-metadata'),
status: qs('#issue-filing-review-status'),
document,
createObjectURL: blob => URL.createObjectURL(blob),
revokeObjectURL: url => URL.revokeObjectURL(url),
onConfirm: admitReviewedIssue,
});
const authoredOutbox = createAuthoredOutbox({
+9
View File
@@ -866,7 +866,16 @@
</dl>
<section aria-labelledby="issue-filing-review-evidence-heading">
<h3 id="issue-filing-review-evidence-heading">Evidence in filing order</h3>
<p class="small muted" id="issue-filing-review-evidence-empty" hidden>No screenshots will be filed.</p>
<ol id="issue-filing-review-evidence"></ol>
<figure class="issue-filing-evidence-preview" id="issue-filing-review-evidence-preview" hidden>
<img id="issue-filing-review-evidence-image" alt="" />
<figcaption>
<span class="small" id="issue-filing-review-evidence-position"></span>
<strong id="issue-filing-review-evidence-filename"></strong>
<span class="small" id="issue-filing-review-evidence-note"></span>
</figcaption>
</figure>
</section>
<div class="issue-filing-review-actions">
<button id="back-to-issue-edit" type="button">Back to edit</button>
+67
View File
@@ -20,6 +20,68 @@
let reviewed = null;
let trigger = null;
let busy = false;
let evidenceItems = [];
const generatedUrls = new Set();
function releaseEvidenceUrls() {
generatedUrls.forEach(url => options.revokeObjectURL?.(url));
generatedUrls.clear();
evidenceItems = [];
}
function evidenceUrl(attachment) {
if (attachment?.blob) {
const url = options.createObjectURL(attachment.blob);
generatedUrls.add(url);
return url;
}
if (attachment?.data) {
return 'data:' + String(attachment.contentType || 'application/octet-stream') +
';base64,' + String(attachment.data);
}
return '';
}
function selectEvidence(index) {
const selected = evidenceItems[index];
if (!selected) return;
evidenceItems.forEach((item, position) =>
item.button.setAttribute('aria-pressed', position === index ? 'true' : 'false'));
const filename = selected.attachment.filename || 'Screenshot';
options.evidencePreview.hidden = false;
options.evidenceImage.src = selected.url;
options.evidenceImage.alt = 'Evidence ' + (index + 1) + ' of ' + evidenceItems.length + ': ' + filename;
options.evidencePosition.textContent = 'Evidence ' + (index + 1) + ' of ' + evidenceItems.length;
options.evidenceFilename.textContent = filename;
options.evidenceNote.textContent = selected.attachment.note || 'No evidence note.';
}
function renderVisualEvidence(attachments) {
releaseEvidenceUrls();
options.evidenceEmpty.hidden = attachments.length > 0;
options.evidencePreview.hidden = true;
options.evidenceImage.src = '';
evidenceItems = attachments.map((attachment, index) => {
const url = evidenceUrl(attachment);
const item = options.document.createElement('li');
const button = options.document.createElement('button');
const image = options.document.createElement('img');
const filename = attachment.filename || 'Screenshot';
button.setAttribute('type', 'button');
button.className = 'issue-filing-evidence-selector';
button.setAttribute('aria-label', 'Preview evidence ' + (index + 1) + ' of ' + attachments.length + ': ' + filename);
button.setAttribute('aria-pressed', 'false');
image.src = url;
image.alt = '';
button.appendChild(image);
item.appendChild(button);
return { attachment, button, item, url };
});
evidenceItems.forEach((entry, index) =>
entry.button.addEventListener('click', () => selectEvidence(index)));
options.evidenceList.replaceChildren(...evidenceItems.map(entry => entry.item));
if (evidenceItems.length) selectEvidence(0);
}
function close() {
options.sheet.hidden = true;
@@ -27,6 +89,7 @@
trigger = null;
reviewed = null;
busy = false;
releaseEvidenceUrls();
options.confirmButton.disabled = false;
previousTrigger?.focus();
}
@@ -47,6 +110,10 @@
'Assigned to you',
].join(' · ');
const attachments = (draft.attachments || (draft.attachment ? [draft.attachment] : [])).filter(Boolean);
if (options.evidencePreview) {
renderVisualEvidence(attachments);
return;
}
options.evidenceList.replaceChildren(...attachments.map((attachment, index) => {
const item = options.document.createElement('li');
item.textContent = (index + 1) + '. ' + (attachment.filename || 'Screenshot') +