feat: split the web UI into modules and rework it for screen readers
webui.html becomes webui/ (index.html, style.css, ES modules in js/, no
build step); web.py serves it under / and /ui/<path> with the same
containment check as recordings.
How it feels with a screen reader:
- Days read "Today, Saturday 17 October"; recordings are named by their
wall-clock span ("22:00 to 23:00"), which is the row header and what
every button says. The filename moves to the last column.
- Focus is never dropped: busy buttons stay focused (aria-disabled),
finished analyses focus their first section, delete moves to the next
row, closing the clip bar (x or Escape) returns to where you were.
- Reloads rebuild only the days whose content changed; DOM ids are stable
per filename, so a rollover no longer resets the reading position.
- Only user-initiated actions are announced; highlights completion is
spoken (the progress area was stuck aria-busy before).
- Media Session previous/next track step sections like J/K (AirPods,
lock screen, media keys).
- Row layout: Time | Length | Loudness | Player | Options | File. The
player opens inside its cell; Download/Cut/Delete are always there,
and Cut toggles its start/end fields in place.
- Settings tucked behind a <details>; pre-roll and auto-advance are
remembered per browser; no CSS uppercase on headers or badges.
Fixes the REC badge showing on every row: its inline-flex display beat
the UA [hidden] rule. One global [hidden]{display:none !important} now
covers every case.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
// Entry point: wire the page up and start.
|
||||
//
|
||||
// Modules import only from modules earlier in this list:
|
||||
// util, api, store, settings, player, clips, analysis, day, list, nav, main
|
||||
// Keep it that way. A module that needs something from a later one reaches it
|
||||
// through the DOM (openClipInFile clicks the day toggle) rather than an import.
|
||||
|
||||
import {$} from './util.js';
|
||||
import {getConfig} from './api.js';
|
||||
import {initSettings} from './settings.js';
|
||||
import {initClipBar} from './clips.js';
|
||||
import {initNav} from './nav.js';
|
||||
import {load, applyFilters, pollStatus} from './list.js';
|
||||
|
||||
initClipBar();
|
||||
initNav();
|
||||
|
||||
$('refresh-btn').addEventListener('click', () => load({speak: true}));
|
||||
|
||||
let filterDebounce;
|
||||
$('filter-name').addEventListener('input', () => {
|
||||
clearTimeout(filterDebounce);
|
||||
filterDebounce = setTimeout(() => applyFilters({speak: true}), 400);
|
||||
});
|
||||
$('filter-from').addEventListener('change', () => applyFilters({speak: true}));
|
||||
$('filter-to').addEventListener('change', () => applyFilters({speak: true}));
|
||||
$('filter-clear').addEventListener('click', () => {
|
||||
$('filter-name').value = '';
|
||||
$('filter-from').value = '';
|
||||
$('filter-to').value = '';
|
||||
applyFilters({speak: true});
|
||||
});
|
||||
|
||||
getConfig().catch(() => ({})).then(initSettings)
|
||||
.finally(() => load().then(() => setInterval(pollStatus, 5000)));
|
||||
Reference in New Issue
Block a user