From dc87c639856ae8415222cf5224f37c0dae1421d3 Mon Sep 17 00:00:00 2001
From: oshrizak <63424207+oshrizak@users.noreply.github.com>
Date: Tue, 26 May 2026 10:54:13 -0700
Subject: [PATCH 1/2] fix(demo): stop repetitive screen-reader status
announcements during conversion
---
public/demo.html | 31 ++++++++++++++++++++++++++++---
1 file changed, 28 insertions(+), 3 deletions(-)
diff --git a/public/demo.html b/public/demo.html
index c5271ea..796b4d5 100644
--- a/public/demo.html
+++ b/public/demo.html
@@ -145,7 +145,10 @@
Upload your pages
Converting…
- Starting…
+
+ Starting…
This can take a minute or two.
@@ -188,11 +191,14 @@ About
let sourceBase = 'document';
let currentUserCode = null;
let currentBlobUrl = null;
+ // Tracks the last polled phase so step changes are announced to screen readers
+ // once, instead of re-announcing the same status on every poll (issue #18).
+ let lastPhase = null;
const $ = (id) => document.getElementById(id);
const show = (id) => { $(id).hidden = false; };
const hide = (id) => { $(id).hidden = true; };
- const announce = (msg) => { $('status-live').textContent = msg; };
+ const announce = (msg) => { $('status-text').textContent = msg; };
const live = (msg) => { $('live').textContent = msg; };
const setError = (msg) => { $('error').textContent = msg || ''; };
const focusHeading = (id) => { $(id).focus(); };
@@ -333,6 +339,7 @@ About
sessionId = d.session_id;
hide('upload-section'); show('status-section'); focusHeading('step3-h');
announce('Starting… ' + d.image_count + ' page(s) uploaded.');
+ beginConverting('Converting your document.');
pollStatus();
} catch (e) {
setError('Upload failed: ' + e.message);
@@ -348,6 +355,15 @@ About
reconciliation: 'stitching pages together', assembly: 'assembling the document',
review: 'reviewing for accessibility', done: 'finishing up',
};
+ // One-time announcement when conversion starts: tells screen-reader users it
+ // runs in the background so they can leave and come back, without the repeated
+ // status chatter (issue #18).
+ function beginConverting(lead) {
+ lastPhase = null;
+ $('status-live').setAttribute('aria-busy', 'true');
+ live((lead || 'Converting your document.') +
+ ' This can take a few minutes — you can leave this page and come back; the conversion keeps running in the background.');
+ }
async function pollStatus() {
let d = null;
try {
@@ -358,7 +374,9 @@ About
}
if (d && d.status === 'ready_for_review') {
+ $('status-live').setAttribute('aria-busy', 'false');
announce('Done! Your accessible HTML is ready.');
+ live('Conversion complete — your accessible HTML is ready.');
try {
await showResult(d);
} catch (e) {
@@ -375,7 +393,13 @@ About
// queued / running / a transient error / an unexpected shape: keep going
// so a single hiccup never strands the user on the spinner.
if (d && (d.phase || d.status)) {
- announce('Working — ' + (PHASE_LABEL[d.phase] || d.phase || d.status) + '…');
+ const phase = d.phase || d.status;
+ const label = PHASE_LABEL[phase] || phase;
+ announce('Working — ' + label + '…'); // visible only — not a live region, so it never re-announces
+ if (phase !== lastPhase) { // announce a step change to screen readers exactly once
+ lastPhase = phase;
+ live(label.charAt(0).toUpperCase() + label.slice(1) + '.');
+ }
}
setTimeout(pollStatus, 2500);
}
@@ -437,6 +461,7 @@ About
$('feedback-text').value = '';
hide('result-section'); show('status-section'); focusHeading('step3-h');
announce('Re-running with your feedback…');
+ beginConverting('Re-running the conversion with your feedback.');
pollStatus();
} catch (e) {
setError('Could not submit feedback: ' + e.message);
From 66b0502abec4cdc7abfa337b292fe18a7414380f Mon Sep 17 00:00:00 2001
From: oshrizak <63424207+oshrizak@users.noreply.github.com>
Date: Tue, 26 May 2026 11:46:43 -0700
Subject: [PATCH 2/2] fix(demo): non-repetitive SR status + best-effort screen
wake lock during conversion
---
public/demo.html | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/public/demo.html b/public/demo.html
index 796b4d5..a315c44 100644
--- a/public/demo.html
+++ b/public/demo.html
@@ -194,6 +194,9 @@ About
// Tracks the last polled phase so step changes are announced to screen readers
// once, instead of re-announcing the same status on every poll (issue #18).
let lastPhase = null;
+ // Screen Wake Lock sentinel + whether a conversion is currently active (#16).
+ let wakeLock = null;
+ let converting = false;
const $ = (id) => document.getElementById(id);
const show = (id) => { $(id).hidden = false; };
@@ -202,6 +205,23 @@ About
const live = (msg) => { $('live').textContent = msg; };
const setError = (msg) => { $('error').textContent = msg || ''; };
const focusHeading = (id) => { $(id).focus(); };
+ // Best-effort Screen Wake Lock: keep the screen awake while a conversion is in
+ // progress (issue #16). The browser auto-releases it when the tab is hidden, so
+ // we re-acquire on visibilitychange. No-op where unsupported or not permitted.
+ async function keepAwake() {
+ if (!('wakeLock' in navigator) || wakeLock) return;
+ try {
+ wakeLock = await navigator.wakeLock.request('screen');
+ wakeLock.addEventListener('release', () => { wakeLock = null; });
+ } catch { wakeLock = null; }
+ }
+ function releaseAwake() {
+ try { if (wakeLock) wakeLock.release(); } catch {}
+ wakeLock = null;
+ }
+ document.addEventListener('visibilitychange', () => {
+ if (converting && document.visibilityState === 'visible') keepAwake();
+ });
// Spell a code out so screen readers announce each character clearly,
// e.g. "WXYZ-1234" -> "W X Y Z dash 1 2 3 4".
const spellCode = (code) => code.split('').map((c) => (c === '-' ? 'dash' : c)).join(' ');
@@ -342,6 +362,7 @@ About
beginConverting('Converting your document.');
pollStatus();
} catch (e) {
+ converting = false; releaseAwake();
setError('Upload failed: ' + e.message);
} finally {
btn.removeAttribute('aria-busy');
@@ -360,6 +381,8 @@ About
// status chatter (issue #18).
function beginConverting(lead) {
lastPhase = null;
+ converting = true;
+ keepAwake();
$('status-live').setAttribute('aria-busy', 'true');
live((lead || 'Converting your document.') +
' This can take a few minutes — you can leave this page and come back; the conversion keeps running in the background.');
@@ -374,6 +397,7 @@ About
}
if (d && d.status === 'ready_for_review') {
+ converting = false; releaseAwake();
$('status-live').setAttribute('aria-busy', 'false');
announce('Done! Your accessible HTML is ready.');
live('Conversion complete — your accessible HTML is ready.');
@@ -385,6 +409,7 @@ About
return; // terminal — stop polling
}
if (d && d.status === 'failed') {
+ converting = false; releaseAwake();
setError('Conversion failed: ' + (d.error || 'unknown error') + '. You can try again.');
hide('status-section'); show('upload-section'); focusHeading('step2-h');
return; // terminal — stop polling
@@ -464,6 +489,7 @@ About
beginConverting('Re-running the conversion with your feedback.');
pollStatus();
} catch (e) {
+ converting = false; releaseAwake();
setError('Could not submit feedback: ' + e.message);
}
});