fix(r5): address the third /code-review high round — 7 findings

- the sweep guard anchors the rating_key to digits: the round-2 widening had
  degenerated to "anything ending in _<digits>" (backup_2024, release_10)
- RSS counts apply-side failures too, so a read-only database no longer reports
  a wholly failed poll as "ok — new=0, failed=0"
- the player reads/writes its volume directly (readAccount/writeAccount): the
  persisted-object hook rendered nothing and cost a second write per settle
- new tests: the sweep guard's accept/reject table, and run_ffmpeg's threading
  (drain, cancel while silent, stall watchdog, stderr tail on a nonzero exit)
- the breaker sentinel is checked one way (isinstance) at all four sites
- run_rss_poll is typed dict[str, int]

All three new test groups were mutation-checked: reverting each fix turns them
red (or, for the cancel/stall pair, hangs — which is the bug itself).
This commit is contained in:
2026-07-24 00:17:09 +02:00
parent 53ff61653d
commit c0db80d333
7 changed files with 271 additions and 35 deletions
+10 -17
View File
@@ -35,7 +35,7 @@ import {
} from "../lib/format";
import { renderDescription } from "../lib/descriptionLinks";
import { useBackToClose } from "../lib/history";
import { LS, useAccountPersistedObject, writeAccount } from "../lib/storage";
import { LS, readAccount, writeAccount } from "../lib/storage";
import { useScrollFade } from "../lib/useScrollFade";
// Experiment (branch experiment/inline-player): play the video in-app via the
@@ -183,15 +183,12 @@ export default function PlayerModal({
// always starts at full volume. Per-account, like the Plex player's prefs. Level 0 IS the muted
// state (that's how YouTube's own control behaves), so there's no separate `muted` flag to
// contradict it.
const [playerPrefs, patchPlayerPrefs] = useAccountPersistedObject(LS.ytPlayerPrefs, {
volume: 100,
});
// The live volume: updated on every notch, while the localStorage write behind it is debounced
// (a wheel spin fires ~16 events a second, and each patch is a synchronous setItem on the frame
// the player is decoding on). Everything that READS the level reads this ref, so it never sees
// the stale persisted value mid-spin. The window/wheel listeners are bound once, so a ref — not
// state — is what they can safely close over.
const volumeRef = useRef(playerPrefs.volume);
//
// Ref + explicit read/write rather than `useAccountPersistedObject`: nothing RENDERS the level
// (the on-player flash has its own `volumeUi` state), the once-bound wheel/key listeners need a
// ref anyway, and the hook's write lives inside a `setState` updater — which is both a second
// write on every settle and not something React must run while the component is unmounting.
const volumeRef = useRef(readAccount(LS.ytPlayerPrefs, { volume: 100 }).volume);
const volSaveTimerRef = useRef<number | undefined>(undefined);
// When the last volume gesture happened — see nudgeVolume (a spin must not re-read the player).
const lastNudgeAtRef = useRef(0);
@@ -215,18 +212,14 @@ export default function PlayerModal({
window.clearTimeout(volTimerRef.current);
volTimerRef.current = window.setTimeout(() => setVolumeUi(null), 1200);
};
/** Push a level (0–100) to the player and remember it; 0 also mutes (a player left at zero
* volume but unmuted shows YouTube's speaker icon as ON, which reads as broken). */
/** Write the current level out now, cancelling any pending debounce. */
const persistVolume = () => {
window.clearTimeout(volSaveTimerRef.current);
volSaveTimerRef.current = undefined;
// Write STRAIGHT to storage, not through patchPlayerPrefs: that helper persists inside a
// `setState` updater, and React is under no obligation to run an updater for a component that
// is unmounting — which is exactly when the flush below fires (close the player right after a
// wheel spin). The state copy is only needed while mounted, so patch that separately.
writeAccount(LS.ytPlayerPrefs, { volume: volumeRef.current });
patchPlayerPrefs({ volume: volumeRef.current });
};
/** Push a level (0–100) to the player and remember it; 0 also mutes (a player left at zero
* volume but unmuted shows YouTube's speaker icon as ON, which reads as broken). */
const applyVolume = (level: number, flash = true) => {
const next = Math.max(0, Math.min(100, Math.round(level)));
const p = playerRef.current;