The success branch had two adjacent calls:
if(typeof window._applyVoiceModePref==='function') window._applyVoiceModePref(); // pre-divider (the actual fix)
_applyComposerFooterVisibilitySettings();
if(typeof window._applyVoiceModePref==='function') window._applyVoiceModePref(); // post-divider (redundant)
The post-divider one ran after _applyComposerFooterVisibilitySettings,
so it couldn't affect the divider the PR was meant to fix. It looked
like a defensive double-tap, but the function is idempotent and the
pref source (localStorage) doesn't change between the two synchronous
calls — so it was just dead code that made the diff look bigger than
the actual semantic change.
Drop it. Net PR footprint is now strictly: 'use the window-qualified
name and apply once before divider recompute in both IIFE branches.'
Re-tested: 26/26 pass on test_issue4598_composer_control_visibility
+ test_issue1488_composer_voice_buttons.
Greptile P1: _applyVoiceModePref is declared with 'function' inside the
voice-mode IIFE (line 1205) and exported to window._applyVoiceModePref
at line 1212. The bare identifier _applyVoiceModePref is closure-local
and not visible from the settings-load IIFE branches at line 2997 and
line 3056 — so `typeof _applyVoiceModePref==='function'` was always
'undefined', the guard was permanently false, and the pre-divider call
silently never fired.
This undid the entire boot-order fix: divider was still computed
against the pre-sync #btnVoiceMode visibility, so the round-2 SILENT
race the PR was meant to close remained.
Fix: use window._applyVoiceModePref in both branches, matching the
already-correct call on line 2999 (which worked only by accident —
it ran AFTER _applyComposerFooterVisibilitySettings so it had no
effect on the bug, but at least it ran).
Tests: pytest tests/test_issue4598_composer_control_visibility.py
tests/test_issue1488_composer_voice_buttons.py — 26/26 pass.
Windows locks session JSON files briefly during antivirus scans or
browser polling, causing os.replace() to raise WinError 5
(ERROR_ACCESS_DENIED). Add _safe_replace() helper that retries with
exponential backoff (50ms to 800ms, max 5 attempts) on PermissionError.
No-op on non-Windows platforms.
Replaces all 5 os.replace() call sites in models.py:
- Session.save() (main file + backup)
- _write_session_index() (full rebuild + fast path)
- prune_session_from_index()