From 1cbb647ef2e483bbdcb5ada710c547ed7844dfc5 Mon Sep 17 00:00:00 2001 From: Jer Miller Date: Thu, 9 Apr 2026 11:45:03 -0600 Subject: [PATCH] Add error persistence, friendly error mapping, and aria-live regions to speakers workspace --- apps/speakers/workspace.html | 79 +++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/apps/speakers/workspace.html b/apps/speakers/workspace.html index 8406bdcd2..db6b1ba32 100644 --- a/apps/speakers/workspace.html +++ b/apps/speakers/workspace.html @@ -398,10 +398,29 @@ } .spk-status-error { + display: flex; + align-items: center; + justify-content: space-between; background: #fee2e2; color: #991b1b; } +.spk-status-dismiss { + background: none; + border: none; + color: inherit; + font-size: 16px; + line-height: 1; + cursor: pointer; + padding: 0 0 0 8px; + opacity: 0.7; +} + +.spk-status-dismiss:hover, +.spk-status-dismiss:focus { + opacity: 1; +} + .spk-status-info { background: #e8f4fd; border: 1px solid #b3d9f2; @@ -709,7 +728,7 @@
-
+
-
+

ready to review

@@ -740,6 +759,26 @@ audio: '' }; + const ERROR_MAP = [ + { match: 'Embedding too similar to owner voice', message: "this voice sounds like yours — it can't be saved as a different speaker" }, + { match: 'Cannot confirm unattributed sentence', message: 'this sentence needs to be assigned to a speaker first' }, + { match: 'Can only confirm medium-confidence', message: 'this attribution is already confirmed' }, + { match: 'Sentence embedding not found', message: 'something went wrong — try again' }, + { match: 'No speaker labels found', message: 'something went wrong — try again' }, + { match: 'not found in labels', message: "this sentence wasn't found — try refreshing the page" }, + { match: 'Sentence already has a speaker', message: 'this sentence already has a speaker — use correct to change it' }, + { match: 'is blocked', message: "that speaker is blocked and can't be used" }, + { match: 'not found', message: "that speaker wasn't found — they may have been removed. Try refreshing the page" }, + ]; + + function friendlyError(backendMessage) { + if (!backendMessage) return 'something went wrong — try again'; + for (const entry of ERROR_MAP) { + if (backendMessage.includes(entry.match)) return entry.message; + } + return 'something went wrong — try again'; + } + function emptyStateHTML(icon, heading, desc) { return '
' + '
' + icon + '
' + @@ -774,6 +813,7 @@ let reviewEntities = []; let hasLabels = false; let reviewSummary = null; + const statusTimers = {}; let currentFilter = 'all'; let playingSentenceId = null; let ownerDetectionInFlight = false; @@ -1353,7 +1393,7 @@
${escapeHtml(sentence.text)}
${speakerHtml}
-
+
@@ -1510,7 +1550,7 @@ .then(r => r.json()) .then(data => { if (data.error) { - showStatusBySentence(sentenceId, data.error, 'error'); + showStatusBySentence(sentenceId, friendlyError(data.error), 'error'); return; } @@ -1526,7 +1566,7 @@ loadSegments(); }) .catch(() => { - showStatusBySentence(sentenceId, 'Failed to confirm attribution', 'error'); + showStatusBySentence(sentenceId, 'Failed to confirm attribution — try again', 'error'); }); } @@ -1546,14 +1586,14 @@ .then(r => r.json()) .then(data => { if (data.error) { - showStatusBySentence(sentenceId, data.error, 'error'); + showStatusBySentence(sentenceId, friendlyError(data.error), 'error'); return; } loadReview(selectedSegment, selectedSource); loadSegments(); }) .catch(() => { - showStatusBySentence(sentenceId, 'Failed to correct attribution', 'error'); + showStatusBySentence(sentenceId, 'Failed to correct attribution — try again', 'error'); }); } @@ -1573,25 +1613,36 @@ .then(r => r.json()) .then(data => { if (data.error) { - showStatusBySentence(sentenceId, data.error, 'error'); + showStatusBySentence(sentenceId, friendlyError(data.error), 'error'); return; } loadReview(selectedSegment, selectedSource); loadSegments(); }) .catch(() => { - showStatusBySentence(sentenceId, 'Failed to assign attribution', 'error'); + showStatusBySentence(sentenceId, 'Failed to assign attribution — try again', 'error'); }); } function showStatusBySentence(sentenceId, message, type) { const container = document.getElementById(`status-${sentenceId}`); if (!container) return; - container.innerHTML = `
${escapeHtml(message)}
`; - setTimeout(() => { - const current = document.getElementById(`status-${sentenceId}`); - if (current) current.innerHTML = ''; - }, 3000); + if (statusTimers[sentenceId]) { + clearTimeout(statusTimers[sentenceId]); + delete statusTimers[sentenceId]; + } + if (type === 'error') { + container.setAttribute('aria-live', 'assertive'); + container.innerHTML = `
${escapeHtml(message)}
`; + } else { + container.setAttribute('aria-live', 'polite'); + container.innerHTML = `
${escapeHtml(message)}
`; + statusTimers[sentenceId] = setTimeout(() => { + const current = document.getElementById(`status-${sentenceId}`); + if (current) current.innerHTML = ''; + delete statusTimers[sentenceId]; + }, 3000); + } } function setupAudioListeners() { -- 2.51.2