fix: screen-reader fixes for day highlights button and chips toggle
The Highlights button's aria-label overrode its content, so the visible "· analysed" suffix was never announced — the analysed state is now mirrored into the label (at render and when a highlights run completes). The chips toggle's accessible text started with a stray space (left over once the aria-hidden arrow is dropped), which read as an indent; the separator space now lives inside the aria-hidden arrow span. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,7 +39,7 @@ Dependencies: `requests` (streams), `numpy` + `soundfile` (FLAC output and FLAC
|
||||
|
||||
`webui.html` (one `<script>` block):
|
||||
- Clip review: `clipQueue`/`clipCursor` globals, `playClip()`, `playFileSection()`, `hideClipBar()`; markup is the `#clip-bar` div. The clip label shows the wall-clock occurrence time + queue position (`03:46:20 to 03:46:22 (73 / 187)`): queue entries carry `absStart` (epoch s), derived from `fileStartEpoch(f.date)` — the filename clock — with in-file offsets as fallback for non-standard names; filename/score live in the label tooltip.
|
||||
- Day review: `dayHighlights()` builds `dayActiveSections` (chronological); `jumpToDaySection()` arms the queue. Section `absStart` comes from `fileStartEpoch(f.date)` (filename clock), mtime−duration only as fallback. **The user is blind and uses a screen reader — there is deliberately no day-timeline SVG** (one existed and was removed on request as useless); the highlights panel is linear text/buttons: summary line → key-hint note → chips toggle → chips. Do not add decorative visualizations; any future graphic must be aria-hidden and must not be the only carrier of information. Chip lists longer than 12 are collapsed behind an `aria-expanded` toggle button (the `.chips[hidden]{display:none}` rule is required — the author-level `display:flex` on `.chips` would otherwise override the UA `[hidden]` rule). Group `aria-label`s stay short ("Day loud sections") — the J/K/U/I key explanation lives only in the visible note, per user feedback against repeating info text in labels. The Highlights button is a collapse/expand toggle (`setHlExpanded()` keeps arrow + `aria-expanded` in sync, also from the day-collapse path): a built panel is kept and re-armed from `dayHlSections` instead of recomputing, keyed by `hlRow.dataset.loaded = hlParams()` (margin|gap|minDur string) so changed params force a re-run. The `#dayhls-<dayId>` "· analysed" suffix appears when every file's `cached_analysis` passes `cachedParamsMatch()`; `fetchAnalysis()` updates `f.cached_analysis` client-side so the marker survives re-renders without refetching `/api/files`.
|
||||
- Day review: `dayHighlights()` builds `dayActiveSections` (chronological); `jumpToDaySection()` arms the queue. Section `absStart` comes from `fileStartEpoch(f.date)` (filename clock), mtime−duration only as fallback. **The user is blind and uses a screen reader — there is deliberately no day-timeline SVG** (one existed and was removed on request as useless); the highlights panel is linear text/buttons: summary line → key-hint note → chips toggle → chips. Do not add decorative visualizations; any future graphic must be aria-hidden and must not be the only carrier of information. Chip lists longer than 12 are collapsed behind an `aria-expanded` toggle button (the `.chips[hidden]{display:none}` rule is required — the author-level `display:flex` on `.chips` would otherwise override the UA `[hidden]` rule). Group `aria-label`s stay short ("Day loud sections") — the J/K/U/I key explanation lives only in the visible note, per user feedback against repeating info text in labels. The Highlights button is a collapse/expand toggle (`setHlExpanded()` keeps arrow + `aria-expanded` in sync, also from the day-collapse path): a built panel is kept and re-armed from `dayHlSections` instead of recomputing, keyed by `hlRow.dataset.loaded = hlParams()` (margin|gap|minDur string) so changed params force a re-run. The `#dayhls-<dayId>` "· analysed" suffix appears when every file's `cached_analysis` passes `cachedParamsMatch()`; `fetchAnalysis()` updates `f.cached_analysis` client-side so the marker survives re-renders without refetching `/api/files`. **`aria-label` on a button replaces its visible content for screen readers** — any status text rendered inside a labelled button (like the analysed suffix) must be mirrored into the label (render path + the end of `dayHighlights()`), or the blind user never hears it. Likewise, the accessible text of a button built as `aria-hidden arrow span + text node` must not start the text node with a space (the hidden arrow drops out and the leading space reads as an indent) — keep separator spaces inside the aria-hidden span.
|
||||
- J/K/U/I/O: single document-level `keydown` listener — clip queue takes priority, in-player `currentTime` stepping is the fallback when no queue is armed; O calls `openClipInFile()` (shared with the "Open in file" button). J/K (and Prev/Next) always step in time order; U/I walk the loudest-first ranking from `scoreOrder()` — no top-N cutoff (the `#clip-top` input and `#clip-hl-only` checkbox were removed deliberately; J/K must never be affected by an auto-advance/highlights setting). Auto-advance is the `input[name="clip-adv"]` radio (off / next in time / next by loudness), read by `advanceMode()`; `stepClip(dir, byScore)` is the shared queue-stepping path. In-player U/I anchor the ranking on the section under the playhead, else start at the loudest.
|
||||
- Analysis: `fetchAnalysis()` (session `analysisCache`), `analyse()` (per-row render: meta line with section count + params, then chips — no waveform SVG, see day-review note on the blind user), `cachedParamsMatch()` (autoload guard).
|
||||
|
||||
|
||||
+9
-2
@@ -765,7 +765,7 @@ function renderFiles(files) {
|
||||
</h2>
|
||||
${canHl ? `<button class="day-hl" id="dayhln-${dayId}"
|
||||
aria-expanded="false" aria-controls="dayhl-${dayId}"
|
||||
aria-label="Day highlights for ${esc(day)}">
|
||||
aria-label="Day highlights for ${esc(day)}${analysed ? ', analysed' : ''}">
|
||||
<span class="day-arrow" aria-hidden="true">▸</span> Highlights<span
|
||||
class="day-hl-status" id="dayhls-${dayId}"${analysed ? '' : ' hidden'}> · analysed</span></button>` : ''}`;
|
||||
section.appendChild(headBar);
|
||||
@@ -1046,6 +1046,8 @@ async function dayHighlights(dayId, analyzableFiles) {
|
||||
const arrow = document.createElement('span');
|
||||
arrow.className = 'day-arrow';
|
||||
arrow.setAttribute('aria-hidden', 'true');
|
||||
// the separator space lives inside the aria-hidden arrow: a leading
|
||||
// space in the text node shows up as an indent in screen readers
|
||||
arrow.textContent = '▸ ';
|
||||
tog.appendChild(arrow);
|
||||
tog.appendChild(document.createTextNode(truncated
|
||||
@@ -1067,8 +1069,13 @@ async function dayHighlights(dayId, analyzableFiles) {
|
||||
hlRow.dataset.loaded = hlParams();
|
||||
dayHlSections.set(dayId, dayActiveSections);
|
||||
// Every file is now cached with the current params
|
||||
if (results.length === n)
|
||||
if (results.length === n) {
|
||||
document.getElementById('dayhls-' + dayId)?.removeAttribute('hidden');
|
||||
// aria-label overrides the button content, so the analysed marker has to
|
||||
// be mirrored into it or screen readers never hear it
|
||||
if (btn && !/, analysed$/.test(btn.getAttribute('aria-label') || ''))
|
||||
btn.setAttribute('aria-label', btn.getAttribute('aria-label') + ', analysed');
|
||||
}
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user