From 778e1a02f5c7b5fc8193cc7fc562b3a406196214 Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 23:23:20 +0000 Subject: [PATCH 1/3] fix(replay): give the recording placeholder buttons visible feedback Remove Reload from the terminal no-full-snapshot screen, because a reload cannot bring that data back. On the still-ingesting screen, the button is now "Check for new data". It shows a loading state, starts a source listing even while a background poll is in flight, and then tells the viewer that the recording is not ready yet. Generated-By: PostHog Desktop Task-Id: a7a258da-cc35-45c3-be8b-8e3070fed5a8 --- .../session-recordings/player/PurePlayer.tsx | 22 ++++----- ...ssionRecordingDataCoordinatorLogic.test.ts | 17 +++++++ .../sessionRecordingDataCoordinatorLogic.ts | 48 ++++++++++++++++++- 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/frontend/src/scenes/session-recordings/player/PurePlayer.tsx b/frontend/src/scenes/session-recordings/player/PurePlayer.tsx index a8663d43e20e..f4b0e7d8d9ec 100644 --- a/frontend/src/scenes/session-recordings/player/PurePlayer.tsx +++ b/frontend/src/scenes/session-recordings/player/PurePlayer.tsx @@ -109,8 +109,9 @@ export function PurePlayer({ noMeta = false, noBorder = false }: PurePlayerProps isRecordingDeleted, recordingDeletedAt, recordingDeletedBy, + snapshotCheckState, } = useValues(sessionRecordingDataCoordinatorLogic(logicProps)) - const { loadSnapshots } = useActions(sessionRecordingDataCoordinatorLogic(logicProps)) + const { checkForNewSnapshots } = useActions(sessionRecordingDataCoordinatorLogic(logicProps)) const { isPlaylistCollapsed, showMetadataFooter } = useValues(playerSettingsLogic) const { setPlaylistCollapsed } = useActions(playerSettingsLogic) @@ -368,18 +369,11 @@ export function PurePlayer({ noMeta = false, noBorder = false }: PurePlayerProps The snapshot of the screen taken when this recording started never reached PostHog, so there is nothing to play back. This usually happens when the browser is closed or goes offline before the recording finishes - uploading.{' '} + uploading. Reloading won't bring this data back.{' '} Learn more

- - Reload - ) : ( <> @@ -392,10 +386,16 @@ export function PurePlayer({ noMeta = false, noBorder = false }: PurePlayerProps - Reload + Check for new data + {snapshotCheckState === 'checked' ? ( +

+ It's not ready to play yet. Try again in a minute. +

+ ) : null} )} diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.test.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.test.ts index aad51e2b3d55..82daac8e2c67 100644 --- a/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.test.ts +++ b/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.test.ts @@ -422,6 +422,23 @@ describe('sessionRecordingDataCoordinatorLogic', () => { isOldAndInvalid: logic.values.isOldAndInvalid, }).toEqual(expected) }) + it('settles a viewer-requested check for new data while a background poll is in flight', async () => { + mountWithSnapshots(incrementalOnlySnapshotsAsJSONLines(dayjs().subtract(1, 'minute').valueOf())) + await loadFully() + expect(logic.values.snapshotCheckState).toBe('idle') + + jest.useFakeTimers() + try { + logic.actions.loadSnapshotSources(60000) + logic.actions.checkForNewSnapshots() + expect(logic.values.snapshotCheckState).toBe('checking') + + await jest.advanceTimersByTimeAsync(1000) + expect(logic.values.snapshotCheckState).toBe('checked') + } finally { + jest.useRealTimers() + } + }) }) // TODO need deduplication tests for blob_v2 sources before we deprecate blob_v1 diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.ts index b7f05288e87d..4c08e1bb316d 100644 --- a/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.ts +++ b/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.ts @@ -238,6 +238,11 @@ export function isWithinIngestionGracePeriod(start: Dayjs | null): boolean { return start != null && now().diff(start, 'minute') <= INGESTION_GRACE_PERIOD_MINUTES } +export type SnapshotCheckState = 'idle' | 'checking' | 'checked' + +const settleSnapshotCheck = (state: SnapshotCheckState): SnapshotCheckState => + state === 'checking' ? 'checked' : state + // Generated by kea-typegen. Update if you're an agent, ignore if you're human. export interface sessionRecordingDataCoordinatorLogicValues { sessionComments: CommentType[] // commentsLogic @@ -283,6 +288,7 @@ export interface sessionRecordingDataCoordinatorLogicValues { reportedLoaded: boolean segments: RecordingSegment[] sessionPlayerData: SessionPlayerData + snapshotCheckState: SnapshotCheckState snapshots: RecordingSnapshot[] snapshotsByWindowId: Record snapshotsInvalid: boolean @@ -450,9 +456,30 @@ export interface sessionRecordingDataCoordinatorLogicActions { metadata: SessionRecordingType | null playerData: SessionPlayerData } // sessionRecordingEventUsageLogic + loadSnapshotSourcesFailure: ( + error: string, + errorObject?: any + ) => { + error: string + errorObject?: any + } // snapLogic + loadSnapshotSourcesSuccess: ( + snapshotSources: SessionRecordingSnapshotSource[], + payload?: { + breakpointLength: number | undefined + } + ) => { + payload?: { + breakpointLength: number | undefined + } + snapshotSources: SessionRecordingSnapshotSource[] + } // snapLogic storeUpdated: () => { value: true } // snapLogic + checkForNewSnapshots: () => { + value: true + } loadRecordingData: () => { value: true } @@ -614,7 +641,7 @@ export const sessionRecordingDataCoordinatorLogic = kea ({ reportedLoaded: [ @@ -681,12 +711,28 @@ export const sessionRecordingDataCoordinatorLogic = kea snapshots, }, ], + // Only a viewer-requested check reaches 'checked', so a background poll cannot report a check + // that the viewer did not ask for. + snapshotCheckState: [ + 'idle' as SnapshotCheckState, + { + checkForNewSnapshots: () => 'checking', + loadSnapshotSourcesSuccess: settleSnapshotCheck, + loadSnapshotSourcesFailure: settleSnapshotCheck, + }, + ], })), listeners(({ values, actions, props, cache }) => ({ loadRecordingData: () => { actions.loadRecordingMeta() }, + checkForNewSnapshots: () => { + // Call the loader directly: loadSnapshots skips while a background poll is in flight, + // and then the check never settles. A new loader call cancels the poll at its breakpoint. + actions.loadSnapshotSources() + }, + loadRecordingMetaSuccess: () => { if (props.sessionRecordingId && !values.recordingTooLargeToPlay) { actions.loadSnapshotSources() From 2b61739bd3494b4598e8a9ae60c387602dba088c Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 23:24:24 +0000 Subject: [PATCH 2/3] chore(replay): make the check-for-new-data comment match the behavior Generated-By: PostHog Desktop Task-Id: a7a258da-cc35-45c3-be8b-8e3070fed5a8 --- .../player/sessionRecordingDataCoordinatorLogic.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.ts index 4c08e1bb316d..fb287d9daa3d 100644 --- a/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.ts +++ b/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.ts @@ -728,8 +728,8 @@ export const sessionRecordingDataCoordinatorLogic = kea { - // Call the loader directly: loadSnapshots skips while a background poll is in flight, - // and then the check never settles. A new loader call cancels the poll at its breakpoint. + // Call the loader directly: loadSnapshots skips while a background poll waits at its + // breakpoint, so the click would wait out the poll interval. A new loader call cancels that wait. actions.loadSnapshotSources() }, From b725c9efca59d8f33d03d5b61597fa87e0dacca3 Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 23:40:55 +0000 Subject: [PATCH 3/3] chore(replay): match kea-typegen output for connected snapshot actions Generated-By: PostHog Desktop Task-Id: a7a258da-cc35-45c3-be8b-8e3070fed5a8 --- .../player/sessionRecordingDataCoordinatorLogic.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.ts index fb287d9daa3d..74fe1d0622b4 100644 --- a/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.ts +++ b/frontend/src/scenes/session-recordings/player/sessionRecordingDataCoordinatorLogic.ts @@ -464,10 +464,12 @@ export interface sessionRecordingDataCoordinatorLogicActions { errorObject?: any } // snapLogic loadSnapshotSourcesSuccess: ( - snapshotSources: SessionRecordingSnapshotSource[], - payload?: { - breakpointLength: number | undefined - } + snapshotSources: import('@posthog/replay-shared').SessionRecordingSnapshotSource[], + payload?: + | { + breakpointLength: number | undefined + } + | undefined ) => { payload?: { breakpointLength: number | undefined