diff --git a/src/components/OrbitSessionBar.tsx b/src/components/OrbitSessionBar.tsx index 5ff60311..c6fd671f 100644 --- a/src/components/OrbitSessionBar.tsx +++ b/src/components/OrbitSessionBar.tsx @@ -59,11 +59,11 @@ export default function OrbitSessionBar() { return () => window.clearInterval(id); }, [state, phase]); - // Bar is visible while active, ended (pre-ack), or explicitly kicked. + // Bar is visible while active, ended (pre-ack), or explicitly kicked / soft-removed. const shouldShowBar = !!state && ( phase === 'active' || phase === 'ended' - || (phase === 'error' && errorMessage === 'kicked') + || (phase === 'error' && (errorMessage === 'kicked' || errorMessage === 'removed')) ); if (!shouldShowBar || !state) return ( diff --git a/src/hooks/useOrbitGuest.ts b/src/hooks/useOrbitGuest.ts index 82af6da3..573b3ba3 100644 --- a/src/hooks/useOrbitGuest.ts +++ b/src/hooks/useOrbitGuest.ts @@ -6,7 +6,6 @@ import { getSong } from '../api/subsonic'; import { readOrbitState, writeOrbitHeartbeat, - leaveOrbitSession, } from '../utils/orbit'; import { orbitOutboxPlaylistName, estimateLivePosition, type OrbitState } from '../api/orbit'; @@ -93,9 +92,13 @@ export function useOrbitGuest(): void { if (cancelled) return; if (!state) { - // Session playlist is gone — host must have nuked it. Tear down - // silently; the exit-modal is the "ended" path below, not this. - void leaveOrbitSession(); + // Session playlist is gone — almost always means the host ended the + // session and the `ended:true` write was missed because we polled + // after the subsequent playlist delete. Surface the same modal the + // explicit `state.ended` branch does; the store still holds the last + // known state so the modal can render the host + session name copy. + // Outbox cleanup runs from the modal's OK handler via leaveOrbitSession. + useOrbitStore.getState().setPhase('ended'); return; }