Skip to content

Warn before leaving until report is saved - #250

Open
davidtsai15 wants to merge 2 commits into
sysprog21:mainfrom
davidtsai15:fix/interview-lifecycle-warning
Open

davidtsai15 wants to merge 2 commits into
sysprog21:mainfrom
davidtsai15:fix/interview-lifecycle-warning

Conversation

@davidtsai15

@davidtsai15 davidtsai15 commented Oct 7, 2026 •

Copy link
Copy Markdown

Timed interviews currently allow accidental refreshes or navigation before the report is safely persisted.

This change keeps the native beforeunload guard active from the timed interview start until either the local or account report copy has been saved. LiveKit's automatic page-leave disconnect is disabled because it runs before the candidate chooses whether to stay, so room cleanup now waits for pagehide.

Tests cover the interview lifecycle, report persistence results, listener cleanup, and LiveKit page-leave behavior. The credential-free test gate passes locally, including all 936 browser tests with no failures or skips.

Closes #243


Summary by cubic

Warns before leaving a timed interview until a report copy is saved, so candidates can't lose an unsaved report by refreshing or navigating away.

  • LiveKit's automatic page-leave disconnect is disabled; room cleanup now waits for pagehide so it doesn't run before the candidate chooses to stay.
  • The warning turns on when the timed interview starts and turns off only after a local or account report copy is persisted.
  • An explicit Leave button disables the warning before tearing down the session.
  • When overlapping report saves finish out of order, only the newest save can release the warning.

Closes #243.

Written for commit aef6158. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files

Reply to a comment to ask cubic a question or push back. It learns from your replies.

Turn on auto-fix | Re-trigger cubic

Comment thread web/interview.js
Comment thread web/interview.js

function finalizeRecoveryOnPageHide() {
if (state.phase === "report_recovery") reportRecovery.finish();
void state.room?.disconnect?.().catch?.(() => {});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since pagehide now owns the room disconnect, leaveRoom tears down too early. It still calls state.room?.disconnect(), stopAvatar() and stopLocalMedia() before setting window.location.href = "/". In phase ending with no saved report, the guard is still armed, so that assignment raises the native prompt. A candidate who picks Stay is left on the ending screen with the room gone, so the report they are waiting for can never arrive. Either release the guard in leaveRoom before navigating (the button is already an explicit choice to leave), or drop the disconnect and media teardown there and let this handler do it. A test that cancels navigation through the real Leave button would catch this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Released the unload guard before an explicit Leave action, as suggested, so the existing teardown can run without triggering the accidental-navigation prompt. Added a regression test covering the Leave-button path.

Comment thread web/interview.js Outdated
state.reportPersisted = false;
updateBeforeUnloadGuard();
const result = await saveReportHistory(entry);
if (reportIsPersisted(result)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An older save that finishes late can release the guard while the newer copy is still unsaved. In report recovery, keep() saves the provisional report, then finalize saves the outcome under the same id. If the provisional account request is still in flight when the final save sets state.reportPersisted = false, its later success sets it back to true. If both writes of the final copy then fail, the page is unguarded and the final evaluation is lost without a warning. That contradicts the comment above it, which says only this save can release the warning. Tag each call with a generation number and let only the latest one write the flag:

Suggested change
if (reportIsPersisted(result)) {
if (generation === saveGeneration && reportIsPersisted(result)) {

with const generation = ++saveGeneration; next to the reset, plus a test with overlapping provisional and final saves.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a save generation counter so only the newest report save can release the unload guard. Also added an overlapping provisional/final save regression test where the older save finishes after the newer save has started.

@jserv
jserv requested review from ColtenOuO and alanhc October 7, 2026 04:10
A timed interview can be lost to refresh or navigation before its report
reaches Past attempts. LiveKit's page-leave hook also disconnects before
the candidate can choose Stay, so wait for pagehide and keep the warning
until a saved report can be reopened.
Let an explicit Leave bypass the accidental-navigation warning.

Ignore stale save completions after a newer report save starts.
@davidtsai15
davidtsai15 force-pushed the fix/interview-lifecycle-warning branch from a14db9c to aef6158 Compare October 7, 2026 07:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Warn before leaving an interview session before its report is safely saved

2 participants