Skip to content

Preselect the configured interview length - #584

Open
RWong04 wants to merge 1 commit into
sysprog21:mainfrom
RWong04:preselect-configured-duration
Open

RWong04 wants to merge 1 commit into
sysprog21:mainfrom
RWong04:preselect-configured-duration

Conversation

@RWong04

@RWong04 RWong04 commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Problem and behavior

CODETRIAL_DURATION_MIN is documented as the length the lobby preselects, but only check-gemini, the setup probe and the recording cap check read it, so an operator who set 30 still saw 45 for Medium and Hard, and a token request that named no length got the constant 45.
With this change /api/session reports the default as defaultDurationMin, the lobby preselects it for Medium and Hard and at most 30 for Easy, and a token request that names no length is granted it; with the variable unset nothing changes.
For example, with CODETRIAL_DURATION_MIN=60 a Medium lobby preselects 60 and an Easy lobby preselects 30; a value that is not one of the three buttons selects the nearest one in the lobby (20 gives 30, and 75 gives 60 only because there is no 90 button).

Approach

One function, default_duration in src/web/token.rs, clamps the configured value into 10 to 90 and to the recording ceiling, and both /api/session and /api/token call it, so the length that is offered and the length that is granted cannot drift apart.
WebServerConfig and TokenConfig carry the configured value, web/app.js takes the operator value in suggestedDuration, and it re-suggests after /api/session only when that value differs from the markup's 45, so a length or a problem the candidate picked before the session answered is kept.
Snapping to the nearest button happens in the lobby only, and /api/token grants the clamped value as is; the new tests for the token default live in tests/web/token_default.rs because tests/web/token.rs is at its size ceiling.

Validation

cargo test --locked and cargo clippy --locked --workspace --all-targets pass, and node --test --test-name-pattern="operator default" tests/browser/lobby.test.js passes eight tests; I broke the new session, wiring, snapping, latch and manual-problem paths one at a time and each broke a test.
./scripts/test.sh still fails task-authoring-tests, run-budget-tests, interactive-review-hook-tests, git-hooks and shell-syntax on this machine, and the same gates fail on a clean upstream/main at 8f5ad5c, before #581 merged; the javac and ffmpeg tests were skipped.
I did not check the lobby by hand in a browser: the first paint shows 45 until /api/session answers, and a non-button value snaps without a note, so those two are unverified as user experience.

Interactive review

This is one commit of 382 added plus deleted lines, over the 200-line threshold, so I went through the local interactive review on the committed head 8a905a0 and explained the key change in my own words there.
Suggested reading order: src/web/token.rs (default_duration), src/web/auth.rs (/api/session), web/app.js (suggestedDuration and loadAccount), then the tests; the rest is the new field threaded through fixtures.
I am happy to explain the change to a maintainer for the explained label.

Closes #582


Summary by cubic

CODETRIAL_DURATION_MIN now actually preselects the interview length in the lobby and becomes the default granted to a token request that names none; previously it was documented as doing this, but the lobby always started on 45 for Medium and Hard, and a nameless request got the constant 45.

  • CODETRIAL_DURATION_MIN is clamped to the allowed 10–90 range and held to the recording cap; a value that cannot be read prints a warning and falls back to 45, and where recording is on, startup refuses a recording cap below the clamped value.
  • /api/session reports the default as defaultDurationMin; the lobby preselects it for Medium and Hard, caps Easy at 30, and snaps non-button values to the nearest enabled button. A length or problem the candidate already chose is never replaced.
  • A token request naming no length gets the configured default, granted unsnapped (75 stays 75) and clamped only to range and cap; the interview page omits durationMin when the URL names none (the round plan waits for the server default), and a Start pressed before /api/session answers leaves the length out too unless the lobby already changed it.

With the variable unset, behavior is unchanged.

Closes #582

Written for commit 254616c. Summary will update on new commits.

View guided diff

jserv

This comment was marked as outdated.

cubic-dev-ai[bot]

This comment was marked as resolved.

@jserv jserv added the explained label Nov 4, 2026
@RWong04
RWong04 force-pushed the preselect-configured-duration branch from 72f6667 to e64598e Compare November 4, 2026 11:39
Comment thread web/interview.js Resolved

@Alan52254 Alan52254 left a comment

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.

The server side is well factored: default_duration is the single source for both /api/session and /api/token, which is exactly what docs/interview-length.md asks for. My comments are about where an invalid or missing input silently changes the result, and about two test-scope issues. The one change I would make before merging is the defaultDuration fallback in web/app.js; the others are smaller. I did not repeat the open threads on web/interview.js:355.

Comment thread web/app.js Resolved
Comment thread src/main.rs Resolved
Comment thread src/web/token.rs Resolved
Comment thread tests/unit/bin.rs Resolved
Comment thread tests/web/token_default.rs Resolved
Comment thread src/web/auth.rs Resolved
Comment thread docs/interview-length.md Resolved
cubic-dev-ai[bot]

This comment was marked as resolved.

@RWong04
RWong04 force-pushed the preselect-configured-duration branch from 0609ef1 to ceaea79 Compare November 5, 2026 06:21
cubic-dev-ai[bot]

This comment was marked as resolved.

@RWong04
RWong04 force-pushed the preselect-configured-duration branch from ceaea79 to 46a811e Compare November 5, 2026 06:25
Comment thread src/main.rs Resolved
Comment thread web/app.js Resolved
Comment thread web/interview.js Resolved
jserv

This comment was marked as outdated.

@RWong04
RWong04 force-pushed the preselect-configured-duration branch from 46a811e to 32f8528 Compare November 5, 2026 07:51
cubic-dev-ai[bot]

This comment was marked as resolved.

Comment thread src/main.rs Resolved
Comment thread src/config.rs Resolved
Comment thread src/web/token.rs Resolved
Comment thread web/app.js Resolved
Comment thread web/interview.js Resolved
jserv

This comment was marked as outdated.

@RWong04
RWong04 force-pushed the preselect-configured-duration branch from 32f8528 to 4d13dd5 Compare November 5, 2026 08:35
jserv

This comment was marked as outdated.

Comment thread src/main.rs Resolved
jserv

This comment was marked as outdated.

@jserv jserv left a comment

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.

Rebase the current branch onto the upstream default branch and resolve any conflicts. Run /codetrial-review in AI agents (use $codetrial-review in Codex) and resolve all identified issues. Rework the series into minimal, logically coherent commits, squash closely related commits, and ensure all commit messages follow the project’s rules.

@jserv

jserv commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

The interactive-review check changed in #610: the explained label no longer passes it, and this pull request is over the large-change thresholds, so its next run needs a signed record of the interactive review.

  1. Once: set up a signing key, as "Before your first large change" in CONTRIBUTING.md lists.
  2. Rebase this branch onto main; the pre-push hook that signs and sends the record is there.
  3. Run /codetrial-review on the branch and push. If the branch did not change, send the record with sh scripts/git-pre-push.sh --publish-review <fork-remote> [<branch>], then close and reopen this pull request.

Each refusal and its fix: https://github.com/sysprog21/codetrial/blob/main/docs/large-change-explanation.md#troubleshooting

@jserv jserv removed the explained label Nov 7, 2026
@RWong04
RWong04 force-pushed the preselect-configured-duration branch from 16dba04 to 7728eb5 Compare November 7, 2026 11:09
Comment thread web/app.js Resolved
Comment thread web/interview.js Resolved
@RWong04
RWong04 force-pushed the preselect-configured-duration branch 2 times, most recently from 2e11c3f to e280bfd Compare November 7, 2026 13:03
Comment thread tests/browser/interviewer-voice.test.js Resolved
@RWong04
RWong04 force-pushed the preselect-configured-duration branch from e280bfd to 45a824f Compare November 7, 2026 14:20
Comment thread README.md Resolved
Comment thread src/config.rs Resolved
Comment thread src/launch.rs Resolved
Comment thread tests/web/accounts.rs Resolved
Comment thread web/interview.js Resolved
@jserv

jserv commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Some review comments on this pull request are not settled yet. Please refine the change per the review comments listed below, reply on each thread with what you changed or why you did not, and mark each thread resolved yourself once it is settled (Resolve conversation, at the bottom of the thread). The pull request stays blocked while review threads are open.

Not addressed at the current head:

Checked at 45a824f.

The README and docs/interview-length.md call CODETRIAL_DURATION_MIN the
length the lobby preselects, but only check-gemini, the setup probe and
the recording cap check read it. The lobby always started on 45 for
Medium and Hard, and a token request that named no length got the
constant, so an operator who set 30 saw no change.

/api/session now reports the default as defaultDurationMin, clamped into
the range and to the recording ceiling by the same function that
/api/token uses for a request that names no length. The lobby
preselects it for Medium and Hard, and at most 30 for Easy. A value that
is not one of the buttons selects the nearest one, and a length the
candidate chose is left alone. With the variable unset, nothing changes.

Closes sysprog21#582

Change-Id: Ib0f5f1712933e916c31e93f79179aecd2532979e
@RWong04
RWong04 force-pushed the preselect-configured-duration branch from 45a824f to 254616c Compare November 7, 2026 17:24
Comment thread web/app.js
destination.searchParams.append("difficulty", difficulty);
} else destination.searchParams.set("problem", problem.id);
destination.searchParams.set("duration", String(duration));
if (manualDuration || durationSettled || duration !== markupDuration)

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.

No test reaches the manualDuration part of this condition. A candidate who clicks the 45 button before /api/session answers leaves duration equal to markupDuration with durationSettled still false, so only manualDuration keeps their explicit 45 in the URL; drop it and the server grants the operator's default (say 60) with every test still green. Add a case beside "a start pressed before the session answers sends a length the lobby changed" that holds the session, clicks [data-duration="45"], presses Start and asserts duration=45 in the URL.

Comment thread web/app.js
destination.searchParams.append("difficulty", difficulty);
} else destination.searchParams.set("problem", problem.id);
destination.searchParams.set("duration", String(duration));
if (manualDuration || durationSettled || duration !== markupDuration)

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.

Start is enabled before /api/session answers, and this branch then leaves duration out, so the candidate who sees the markup's 45 gets the operator's default from /api/token (60, or 75 with CODETRIAL_DURATION_MIN=75). That is a longer or shorter interview than the one on screen, which the description lists as unverified. Either keep Start disabled until durationSettled, or follow up by showing a "loading" state on the duration row so the 45 is not read as the answer.

Comment thread web/interview.js
// A delayed judge may make the requested C tab available after the first
// paint. Do not switch to it after the candidate has chosen a tab or edited.
let languageChoiceCommitted = false;
const named = Number(params.get("duration"));

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.

Number(...) here is looser than the parseInt it replaces. ?duration=42.8 now goes to /api/token as 42.8, but the agent reads the signed metadata through duration_from_metadata, which truncates to 42, so the page's budget and the interviewer's disagree. ?duration=Infinity also counts as named and clamps to 90, although the docs say a non-number names no length. Truncate and require a finite value:

Suggested change
const named = Number(params.get("duration"));
const named = Math.trunc(Number(params.get("duration")));

and use const namedDuration = Number.isFinite(named) && named > 0; below. The 42.8 row in interviewer-voice.test.js would then expect 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.

CODETRIAL_DURATION_MIN does not preselect the interview length in the lobby

4 participants