Two regressions in the existing localStorage-restore path that forced
the host to re-pick the monitor input after every hard refresh:
1. Pre-permission Firefox returns deviceId='' for every device in
enumerateDevices(). refreshMicList ran at page load BEFORE the
entry-click gesture granted gUM, every match against the saved
micDeviceId failed, and the `else: micDeviceId = sel.value` clobber
silently reset the saved selection to ''. After that, getMic()
picked the default mic instead of the monitor.
Fix: detect the all-empty case (allEmpty) and bail out — preserve
the saved selection until a real post-permission enumerate runs.
2. Chrome (and other browsers in some configs) rotates deviceIds
across browser sessions for privacy. Saved deviceId stops matching
anything. Old code fell through to the clobber.
Fix: also save the human-readable label (e.g. "Monitor of WH-
1000XM5") and fall back to label-match when deviceId doesn't
resolve. When the label matches, refresh micDeviceId to the
current session's value + persist the new deviceId.
Two new localStorage keys: MIC_LABEL_KEY, SPK_LABEL_KEY. Change
handlers strip the 'input '/'output ' prefix from the option's
textContent before saving. Same pattern applied to both refreshMic-
List and refreshSpeakerList.
Doesn't help when the saved label also doesn't match any current
device (e.g. headphones unplugged) — sel.value defaults to first
device, same as before. But the common case fox 2026-06-04 hit
("monitor selected, hard refresh, monitor not restored, manual re-
pick needed") is now zero-tap.