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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user