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:
@@ -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);
|
||||
}, []);
|
||||
|
||||
Reference in New Issue
Block a user