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

- the volume keys no longer defer to a scrollable card while in fullscreen
  (mirrored into a ref: the keydown handler is bound once and state would be stale)
- the debounced volume flush writes straight to storage; persisting inside a
  setState updater is not guaranteed to run for an unmounting component
- the HLS sweep also matches the original {key}_{start} directory naming, which
  is what the oldest leftovers on disk actually use
- run_rss_poll returns {new, failed} so an egress outage stops rendering as
  "ok — 0" in the scheduler card and the audit log; /api/sync/rss reports it too
- drop the unused poll_rss_channel (it silently gained a new exception)
- _Breaker.run is generic, so a call site keeps its item-id type
- test helper instead of the generator-throw idiom
This commit is contained in:
2026-07-23 23:59:58 +02:00
parent 2a7f49c981
commit 53ff61653d
7 changed files with 55 additions and 26 deletions
+17 -7
View File
@@ -35,7 +35,7 @@ import {
} from "../lib/format";
import { renderDescription } from "../lib/descriptionLinks";
import { useBackToClose } from "../lib/history";
import { LS, useAccountPersistedObject } from "../lib/storage";
import { LS, useAccountPersistedObject, writeAccount } from "../lib/storage";
import { useScrollFade } from "../lib/useScrollFade";
// Experiment (branch experiment/inline-player): play the video in-app via the
@@ -176,6 +176,9 @@ export default function PlayerModal({
// while the iframe itself sees mouse movement, so an armed overlay kept the bar — and with it
// the native fullscreen button — permanently hidden after pressing F.
const [isFullscreen, setIsFullscreen] = useState(false);
// Mirrored into a ref because the keydown handler is bound once (deps: [onClose]) and would
// otherwise read `false` forever — the house pattern here (see navRef/volumeRef).
const isFullscreenRef = useRef(false);
// Volume survives the modal, the next video and a reload: a YT.Player is created per video and
// 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
@@ -217,6 +220,11 @@ export default function PlayerModal({
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 });
};
const applyVolume = (level: number, flash = true) => {
@@ -468,9 +476,10 @@ export default function PlayerModal({
// fullscreen, where the wheel goes to YouTube's own iframe. But the modal card scrolls
// (a long description) and Up/Down is how you scroll it from the keyboard — so defer
// whenever the focused element can still scroll that way, exactly like Space defers to a
// focused button.
// focused button. NOT in fullscreen though: the card is off-screen there, so deferring
// would scroll something invisible and leave fullscreen with no volume control at all.
const up = e.key === "ArrowUp";
if (typing || canScrollBy(el, up ? -1 : 1)) return;
if (typing || (!isFullscreenRef.current && canScrollBy(el, up ? -1 : 1))) return;
e.preventDefault();
nudgeVolume(up ? 5 : -5);
} else if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
@@ -524,10 +533,11 @@ export default function PlayerModal({
// Track whether we're the fullscreen element (F, our button, or the browser's own Esc exit).
useEffect(() => {
const onChange = () =>
setIsFullscreen(
!!document.fullscreenElement && document.fullscreenElement === stageRef.current,
);
const onChange = () => {
const ours = !!document.fullscreenElement && document.fullscreenElement === stageRef.current;
isFullscreenRef.current = ours; // read by the once-bound keydown handler
setIsFullscreen(ours); // drives the overlay's pointer-events
};
document.addEventListener("fullscreenchange", onChange);
return () => document.removeEventListener("fullscreenchange", onChange);
}, []);