Refine preflight language and clean up ended starts
Follows up jserv's review comments on #502 after it merged, and his review of this PR. Rebased onto b326b6f. There are two commits.
Address post-merge review of preflight language
- Before a Strict assignment there is no judge to load, but the Before you join menu labeled C
C (unavailable: waiting for judge), and its title told the candidate to reload, which does nothing in Strict (comment). Whilestrict && !problem,applyLanguagesnow gives C a reason that names the assigned problem: C appears in the editor's language menu after joining if that problem can run it. After assignment the usual labels return, anddocs/interview-selection.mddescribes this. connectcommits the language before the Strict assignment, so the late-judge branch ininitializeLanguagesnever ran in the five Strict cases ofstrict-selection, and all five passed for the same reasonwaitdid (comment). They are now one Strict case, which checks that the token's language survives a late judge. The edit, choose, run and end guards are tested during the bounded C wait of a Practice start instead. There the workspace is open and a judge can still land before anything is committed, and awaitcase confirms the switch to C happens when nothing is.preflighttakes apauseoption that stops the page's clock once Start is ready and before it is pressed, so the 5 s wait cannot run out on a slow runner.
Leave nothing behind when a start is ended early
The workspace stays open from Start until the room is joined, so a candidate can end the interview while the start is still waiting for consent, a C judge, the token response, a Strict problem or the join, and an interviewer's report can arrive during the join. connect carried on anyway: the ended interview got a room and an interviewer, and the preflight's tracks were never released, because the report and leave paths stop state.localUserStream, which is only assigned when a join finishes. This window was already on main before #502, which did not introduce it.
- Before the room exists,
connectchecksstate.phasebefore the token request and after the token response. The second check comes after the Strict receipt is saved, so an assignment ended there is still excluded next time. An ended start releases the preflight's tracks throughabandonStart, which the failure path now uses too. - A join the interview moved on from, whether it finished or failed, is settled by
settleEndedJoinbeforestate.room = room, while the room is still the start's own. Any phase pastlivegives the room up, including thereportthe offline summary reaches 300 ms after an end with no interviewer. If an agent report is still using the room, the tracks stop and nothing starts behind it.report_recoveryis handed the room for its retry, and the offline summary's timer no longer draws over it. A report still being received keeps the room through its own handler, which leaves it once the receipt is out, so the join does not claim it. - A report that arrives once the page already shows one no longer claims the page, since
receiveReportignores it and nothing would release the claim. - Once the room is claimed, the join stops before restarting the integrity heartbeat and worker if the interview ended or a report landed during its integrity publishes, and
connectonly stops. The room and tracks belong to the report and leave paths from there. - A start that fails after the interview ended releases its tracks and stops, without offering the offline fallback or the preflight again under the ending.
- The interview fixture records the tracks it hands out and can hold a room join, a track publish or a data publish. New cases cover an end while the token response, the join or an integrity publish is held; the offline summary appearing before a held join finishes, with and without a late report; a recoverable or complete report during the join's track or integrity publishes, with its receipt leaving before or after the join finishes; a failed start after the interview ended; and an end before the join with a recoverable report arriving. They check that an unclaimed room is left, a room in use is kept and never taken back once left, recovery can still send its retry, nothing starts behind a report, no fallback appears, and every released track has ended. Cases that depend on a timer firing let it fire; the rest pause the clock before Start so the ending's own timers cannot act first. The
self-viewsource check now follows each path that stops the camera separately.
Not in this PR, as follow-ups: the server keeps a dispatch slot for up to 300 s when a start is abandoned after the token response; a start that fails after the integrity monitors started leaves their timers running; and connect does not check the phase after startRecording returns. The last two also exist on main.
Testing, on Windows, with each commit checked out on its own:
tests/browser/strict-selection.test.js: 57 passed after the first commit and 69 after the second, in repeated runs.- First commit: removing the commit in the editor input handler, in
chooseLanguageor inrunTests, or thestate.phasecheck ininitializeLanguages, fails its Practice case. Removing the Strict label replacement fails the preflight label cases. - Second commit: each check was removed or weakened in turn, and each change fails at least one case. That covers the phase checks before the room, in
settleEndedJoinand after the join; each exemption and the room handover insettleEndedJoin; the failed-join path; the offline summary guard; the report claim while a report is shown; the integrity monitor check; thecatchcheck; and the track release inabandonStart. Removing eachabandonStartcall inconnectfails theself-viewcheck. - The full browser suite at f8de280: 2030 passed, 3 failed, 14 skipped. The three failures, one in
face-detector.test.jsand twofloor-capture.test.jsinterruption tests, fail the same way onmainon this machine, and pass in CI. ESLint and Prettier are clean.