From a686d85e96ce5ee18dd3b12b16775f8fe085ff41 Mon Sep 17 00:00:00 2001 From: talksik Date: Sat, 25 Apr 2026 13:06:26 -0700 Subject: [PATCH] fix: papercuts around last particle in stream Pause when holding space during close countdown. Prevent arrow keys starting exit countdown when on last particle --- js/src/features/particles/stream-view.tsx | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/js/src/features/particles/stream-view.tsx b/js/src/features/particles/stream-view.tsx index 3bfe371..d8a0247 100644 --- a/js/src/features/particles/stream-view.tsx +++ b/js/src/features/particles/stream-view.tsx @@ -197,6 +197,7 @@ function StreamViewInner({ path, streamParticle }: StreamViewProps) { const [composeStep, setComposeStep] = useState("idle"); const playbackSuspended = usePlaybackSuspenderStore((s) => s.suspendCount > 0); const playbackBlocked = composeActive || playbackSuspended; + const [spaceHeld, setSpaceHeld] = useState(false); const [progress, setProgress] = useState(0); const [fastPlayback, setFastPlayback] = useState(false); const [showKeybindings, setShowKeybindings] = useState(false); @@ -239,7 +240,7 @@ function StreamViewInner({ path, streamParticle }: StreamViewProps) { const exitRemainingMs = useExitCountdown( status, - playbackBlocked, + playbackBlocked || spaceHeld, handleExitNavigate, ); @@ -269,14 +270,18 @@ function StreamViewInner({ path, streamParticle }: StreamViewProps) { if (playbackBlocked) return; if (isInputTarget(e)) return; + const hasNext = currentIndex >= 0 && currentIndex < children.length - 1; + switch (e.key) { case "ArrowRight": e.preventDefault(); - if (!e.shiftKey || !mediaRef.current?.seek(SEEK_DELTA_SEC)) next(); + if (!e.shiftKey || !mediaRef.current?.seek(SEEK_DELTA_SEC)) { + if (hasNext) next(); + } break; case "ArrowDown": e.preventDefault(); - next(); + if (hasNext) next(); break; case "ArrowLeft": e.preventDefault(); @@ -288,7 +293,10 @@ function StreamViewInner({ path, streamParticle }: StreamViewProps) { break; case " ": e.preventDefault(); - if (!e.repeat) pause(); + if (!e.repeat) { + if (status !== "ended") pause(); + setSpaceHeld(true); + } break; case "Shift": if (!e.repeat) { @@ -329,7 +337,8 @@ function StreamViewInner({ path, streamParticle }: StreamViewProps) { if (e.key === " ") { e.preventDefault(); - resume(); + if (status !== "ended") resume(); + setSpaceHeld(false); } if (e.key === "Shift") { mediaRef.current?.setPlaybackRate(1); @@ -344,7 +353,7 @@ function StreamViewInner({ path, streamParticle }: StreamViewProps) { window.removeEventListener("keyup", handleKeyUp); }; }, - [playbackBlocked, next, prev, pause, resume, navigate, networkId, streamParticle.id, setShowKeybindings, handleToggleReaction, recordingMode, setRecordingMode], + [playbackBlocked, status, currentIndex, children.length, next, prev, pause, resume, navigate, networkId, streamParticle.id, setShowKeybindings, handleToggleReaction, recordingMode, setRecordingMode], ); if (children.length === 0) {