Let two signed-in users run a reciprocal mock interview with each other
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 15/100
- Loại issue
- Tính năng
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- javascript, rust, sqlite
- Lĩnh vực
- api, authentication, backend, frontend
Hướng nghiên cứu
This is a multi-phase epic, so pick a single sub-task such as P1 and leave the rest. For P1, start at the identity_verified field on SignedInUser in src/accounts.rs and the session query in src/accounts/sessions.rs, then read the session_handler in src/web/auth.rs. Done means /api/session reports identityVerified correctly for OAuth and self-declared sign-ins, with two new cases in tests/web/accounts.rs.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Need
CodeTrial only runs AI-led interviews today. Students who want to practice with a classmate or friend, taking turns as candidate and interviewer, have no way to do it inside CodeTrial: they fall back to a video call plus a shared editor, losing the problem bank, the test runner and any structured feedback. Practicing as the interviewer (asking, hinting, giving concrete feedback) is also a skill the AI mode cannot train.
Desired outcome
Two users who have already agreed on a partner and a time (outside CodeTrial) can run a complete two-person session in CodeTrial:
- A signs in with GitHub OAuth, creates a session (topic, difficulty, language, round length, who interviews first) and gets an invite link, which A shares by any means.
- B opens the link and signs in with GitHub OAuth if needed, landing back on the peer page, then accepts the invite and becomes the second fixed member.
- At the agreed time both click "Enter". Tokens are issued only after both have entered; the countdown starts only after both confirm the start. Both problems are locked at that point and each member can read the guide for the round they interview.
- Round 1: A interviews B. B owns the editor and the test runner; A watches read-only and sees the guide, the three hint rungs and private notes. The round is sealed, then feedback.
- Both confirm the swap. Round 2 runs with roles reversed on a different problem of the same difficulty, followed by feedback.
Default timing is 25 min per round with a 90 min hard cutoff counted from the actual start of round 1.
Non-goals for phase one
No matchmaking, waiting queue, slot recommendation, public booking, automatic re-pairing, or no-show ranking. No Gemini interviewer in a peer room. No recording, AI post-session review, co-editing, whiteboard sync, video, pause or extend. No peer LiveKit webhook: presence comes from heartbeats and state from polling. A 180-user class is a capacity target to validate, not a reason to add scheduling features. PKCE for the GitHub login is worth doing but is unrelated to this feature and gets its own issue.
Design decisions
- Creating, accepting, entering and starting are four distinct events. Accepting early creates no room, takes no reservation and starts no timer.
- Members are fixed; roles belong to a round. Two rounds, each member is candidate once and interviewer once.
- The candidate holds exclusive write access to the workspace for the round; the interviewer is read-only.
- HTTP + SQLite is the authority for membership, roles, rounds, deadlines, snapshots and feedback. LiveKit carries audio and disposable previews only; a receiver checks the sender by
participant.identityagainst the member list from the server, never by a field in the payload. - No CreateRoom call and no job outbox: LiveKit creates a room on first join, which the AI path already relies on, and each member keeps one opaque identity per session so a reconnect replaces the old connection instead of adding a third participant. Only DeleteRoom is called, for cleanup.
- Time-based transitions (round expiry, hard deadline, idle lobby, feedback reveal) are computed by one
settle(now)inside every peer transaction; one sweeper settles sessions nobody is polling and deletes rooms. - Peer mode gets its own API under
/api/peer/and its own pages. The editor and test panel are first extracted fromweb/interview.jsinto a module both pages import; no mode branch is added toweb/interview.js. - Only OAuth-verified accounts may use peer routes. Unsafe peer requests must carry an
Originmatching the host; the session cookie is alreadySameSite=Lax(src/web/auth.rs:548).
Current code, observed at 8394d54
Static reading only; nothing below has been prototyped.
| Location | Current behavior | Consequence |
|---|---|---|
src/accounts.rs:61, src/accounts/sessions.rs:118, src/web/auth.rs:357 |
OAuth users get a positive github_id, self-declared handles a negative one; SignedInUser does not say which |
Add a trusted identity_verified derived from github_id > 0, never from the request or from verified_email, and a VerifiedPeer extractor beside Owner |
src/web/auth.rs:267 |
The OAuth callback always redirects to / |
A signed-out invitee loses the invite; add a fixed-enum next carried in the signed state cookie |
src/accounts/schema.rs:14 |
ACCOUNT_SCHEMA_VERSION = 11, append-only migrations |
Peer tables arrive as new migrations; existing data untouched |
src/web/token.rs, src/web/pool.rs:333, src/dispatch.rs:36 |
/api/token picks a room name and calls ensure_agent() when a dispatcher exists; nothing calls CreateRoom |
The peer token route never reaches the dispatcher; RecordingDispatcher (tests/web.rs:321) proves it |
src/token.rs:39, src/token.rs:111 |
Join tokens live 2 h; the admin token carries only roomAdmin |
Add a 300 s peer token with agent: false, and a server-only roomCreate token for DeleteRoom; existing tokens unchanged |
src/livekit/rooms.rs:122 |
RoomService transport is private and tied to AgentConfig |
Move it into a credential-based module; agent duplicate handling stays where it is |
src/web/pool.rs, src/config.rs:214 |
Room names follow <prefix>[-<provider>]-<suffix>; ProviderPool::for_room falls back to the primary provider on names it cannot parse |
Bind provider and room once at Enter, keep the name format, never use fixed_room_name |
src/web/mod.rs:47, src/web/mod.rs:52 |
Each handler bounds its own body: MAX_BODY_BYTES 8 KiB, MAX_REPORT_BYTES 64 KiB |
Add MAX_PEER_SNAPSHOT_BYTES 64 KiB and MAX_PEER_FEEDBACK_BYTES 16 KiB; existing routes unchanged |
src/agent.rs:195, src/agent.rs:211, src/agent.rs:55 |
ProblemVariant already holds contract, constraints, clarifications, follow-ups and three hint rungs for all 151 problems; Problem holds optimal and pitfalls; variant_for is private |
Build the interviewer guide from these tables through a presenter; no hand-written guide set and no prompt change |
web/interview.js:716, :902, :990, :3433 |
Editor DOM wiring and the test panel live inside a 4,000-line page script; web/editor.js holds only pure helpers |
Extract web/workspace.js first, with no behavior change |
web/lib.js:275 |
isAgent() matches agent kind, the agent permission or the interviewer- prefix; topics is tied to TOPIC_ constants by tests/web/contract.rs |
Peer identities are opaque; peer topics go in a separate web/peer-lib.js |
src/web/policy.rs:85 |
CSP has style-src 'self' and img-src 'self' data: blob: |
Peer pages use no inline script or style and show the partner by login, not avatar |
docs/provider-cost-and-degradation.md "What may be cached" |
Never persist candidate code or personalized feedback as cache | Storing peer snapshots and feedback as the session record must be stated explicitly there (P9, P10) |
docs/provider-cost-and-degradation.md "What bounds concurrency" |
One place says what bounds the server | Peer rooms draw on the same LiveKit connection-minute quota; Enter goes through ProviderQuota::verdict_for (src/web/pool.rs:149) and the doc gains a peer paragraph (P6) |
Shared conventions for every sub-task
- All sub-tasks live in this issue; none gets an issue of its own. Each P item is one PR whose title follows the commit subject rules and whose body names the P number and ends with
Refs #<this issue>; only the PR that completes the last open item ends withCloses #<this issue>. Tick the item in the checklist below when its PR merges. - Migrations append to
ACCOUNT_MIGRATIONSand bumpACCOUNT_SCHEMA_VERSIONto whatever HEAD has at rebase time. - HTTP tests go in a new
tests/web/peer.rs, declared intests/web.rsbeside the#[path = "web/token.rs"]block (tests/web.rs:1224); unit test bodies go undertests/unit/declared via#[path]; browser tests undertests/browser/. Every PR passes./scripts/test.shand reports skipped lanes. spawn_github_stub(tests/web.rs:453) always returns id 303 (tests/web.rs:505); P5 makes the id a parameter so two OAuth accounts can be tested.- Error shape for all peer routes: 401 not signed in; 403
{"code":"verified_identity_required"}for a self-declared account; 403{"code":"origin_mismatch"}; 404 for unknown session or non-member; 409{"code":"version_conflict"}for a staleexpectedVersionorexpectedRevision; 410{"code":"invite_unavailable"}for any consumed, expired, revoked or unknown invite; 429 withRetry-Afterfrom the per-account peer bucket. - Order: P1, P2, P3, P4 and P12 can start in parallel. Then P5, P6, P7; then P8, P9 and P11 in parallel; then P10; then P13, P14, P15.
Progress
- P1. Tell OAuth accounts from self-declared ones
- P2. Peer configuration and startup gate
- P3. Return to the peer page after GitHub sign-in
- P4. Shared RoomService transport and DeleteRoom
- P5. Peer sessions, invites and membership
- P6. Enter, leave, provider binding and the peer token
- P7. Round state machine
- P8. Problem assignment and interviewer guide
- P9. Workspace snapshots, writer lease and final capture
- P10. Two-way feedback
- P11. Peer sweeper and room cleanup
- P12. Extract the workspace from web/interview.js
- P13. Peer lobby page
- P14. Peer interview page
- P15. Runbook and manual end-to-end check
Sub-tasks
P1. Tell OAuth accounts from self-declared ones
- Deliverable:
SignedInUser.identity_verified, reported asidentityVerifiedby/api/session. - Touch:
src/accounts.rs:61(field),src/accounts/sessions.rs:118(also selectusers.github_id),src/web/auth.rs:297session_handler. - Depends on: nothing.
- Acceptance:
tests/web/accounts.rsgains two cases. An OAuth sign-in throughspawn_github_stubwhose email list returns 403 getsidentityVerified: true; aPOST /api/loginsign-in getsfalse. - Out of scope: the extractor (P5), any change to
/api/tokenadmission or recording email rules.
P2. Peer configuration and startup gate
- Deliverable: optional peer config, off by default; startup refuses peer without a GitHub OAuth app.
- Touch:
src/web/mod.rs:59WebServerConfig(peer: Option<PeerConfig>),src/config.rs(PeerConfigand defaults),src/main.rs:257web_server_config,src/main.rs:972(addpeer_needs_oauthbesiderecording_needs_verified_identity),src/web/auth.rs:297(peerEnabled),config/codetrial.env.example. - Introduces:
CODETRIAL_PEER_ENABLED,CODETRIAL_PEER_MAX_SESSIONS(default 8),CODETRIAL_PEER_ROUND_MINUTES(25),CODETRIAL_PEER_HARD_MAX_MINUTES(90),CODETRIAL_PEER_INVITE_TTL_DAYS(7, capped at 14),CODETRIAL_PEER_RECONNECT_GRACE_SECONDS(120). - Depends on: nothing.
- Acceptance:
tests/unit/bin.rsassertspeer_needs_oauthreturnsErrnamingGITHUB_CLIENT_IDonly when peer is on and OAuth is off;tests/config.rsasserts the defaults and the 14-day cap;tests/web/accounts.rsassertspeerEnabledis false when unset. Peer works withoutGOOGLE_API_KEYalready (src/main.rs:300); P6's tests run with dispatcherNoneto keep it so. - Out of scope: any
/api/peerroute, recording settings.
P3. Return to the peer page after GitHub sign-in
- Deliverable:
GET /api/login?next=peer-joinreturns the user to/peer-lobbyafter the callback. - Touch:
src/web/auth.rs:92login_handler(acceptnextfrom a fixed enum and carry it inside the signed state cookie),src/web/auth.rs:200callback_handler(redirect to the mapped path, otherwise/). - Depends on: nothing.
- Acceptance:
tests/web/accounts.rs:next=peer-joingivesLocation: /peer-lobby;next=https://evil.exampleandnext=//evil.examplegive/; the existing callback test still gives/. - Out of scope: storing the invite server-side, the page itself (P13).
P4. Shared RoomService transport and DeleteRoom
- Deliverable: a RoomService client usable without
AgentConfig, plusdelete_room. - Touch: move
post_room_serviceandlivekit_room_service_request(src/livekit/rooms.rs:122,:138) into newsrc/livekit/room_service.rstaking(url, api_key, api_secret); agent duplicate handling stays inrooms.rsand calls it. Addlivekit_room_create_tokenbesidelivekit_room_admin_token(src/token.rs:111) withroomCreate: true.delete_roomtreats Twirpnot_foundas success. - Depends on: nothing.
- Acceptance: existing
tests/unit/livekit/rooms.rscases pass unedited. Newtests/unit/livekit/room_service.rsruns a local axum stub and asserts DeleteRoom posts to/twirp/livekit.RoomService/DeleteRoomwith a bearer token holdingroomCreateand notroomAdmin, and thatnot_foundreturnsOk.tests/token.rsasserts the new token's claims. - Out of scope: CreateRoom, any peer caller (P11).
P5. Peer sessions, invites and membership
- Deliverable: a verified user creates a session and invite; another verified user accepts; both read the session. Accepting creates no room, reservation or timer.
- Touch: new
src/accounts/peer.rs; newsrc/web/peer.rswith handlers;src/web/mod.rs:424router (register/api/peer/*only whenconfig.peeris set);VerifiedPeerbesideOwner(src/web/auth.rs:357); an Origin guard on unsafe peer methods;AppState(src/web/mod.rs:164) gains a per-accountTokenRateLimitbucket for peer routes. - Introduces: tables
peer_sessions(opaque id, creator_id, status, version, settings_json, created_at, expires_at),peer_members(session_id, slot 0 or 1, user_id, opaque participant_identity, unique per session and per user),peer_invites(SHA-256 ofrandom_token(32), session_id, expires_at, revoked_at, consumed_by, consumed_at). Routes:POST /api/peer/sessions,GET /api/peer/sessions,GET /api/peer/sessions/{id},POST /api/peer/invites/accept,POST /api/peer/sessions/{id}/invite(revoke and regenerate),DELETE /api/peer/sessions/{id}/invite,POST /api/peer/sessions/{id}/cancel. The PR description fixes theGETstate shape (sessionId,status,version,serverNow,members[{slot, login, identity}],you{slot},currentRound) that P13 and P14 build on. - Depends on: P1, P2.
- Acceptance:
tests/web/peer.rs, withspawn_github_stubtaking the GitHub id, asserts: a/api/loginaccount gets 403verified_identity_requiredon every route; an OAuth account without email can create; accept fills slot 1 and leaves no reservation; a third account, a reused token, a regenerated-away token and the creator's own accept all get 410invite_unavailable;Origin: http://evil.examplegets 403origin_mismatch; a non-member gets 404.tests/unit/accounts/schema.migration_tests.rscovers the migration from version 11. - Out of scope: Enter, tokens, rounds, pages.
P6. Enter, leave, provider binding and the peer token
- Deliverable: once both members have entered, each fetches a LiveKit token for one room on one provider; no agent is ever dispatched.
- Touch:
src/accounts/peer.rs,src/web/peer.rs;livekit_peer_tokeninsrc/token.rs(300 s,agent: false,canUpdateOwnMetadata: false, no room admin, create or record grants); provider viaroom_and_provider(src/web/pool.rs:333) andProviderQuota::verdict_for(src/web/pool.rs:149), neverfixed_room_name; a peer paragraph in "What bounds concurrency" ofdocs/provider-cost-and-degradation.md. - Introduces: table
peer_active_users(user_id primary key, session_id); columnsprovider_id,room_name,room_generationonpeer_sessions,entered_at,last_seen_atonpeer_members. Routes:POST .../enter,.../leave,.../token,.../heartbeat. Enter returns 409already_activewhen the user is in another active session and 503at_capacityatCODETRIAL_PEER_MAX_SESSIONS; token returns 409waiting_for_partneruntil both entered and 410session_closedafter cancel or completion. - Depends on: P5.
- Acceptance:
tests/web/peer.rswithweb_service_with_dispatcher(config, Some(RecordingDispatcher))asserts the dispatcher list stays empty after both fetch tokens; both tokens name the same room and provider URL;subequals the stored identity and is identical on a second call;exp - nbf == 300;provider_id_from_room(src/config.rs:232) resolves the room to the bound provider; leave releasespeer_active_users. The same flow passes with dispatcherNone. - Out of scope: room deletion (P11), rounds, UI.
P7. Round state machine
- Deliverable: start confirmation, two rounds with reversed roles, server deadlines, finish, swap and terminate, all version-checked, with time-based transitions applied by
settle(now). - Touch:
src/accounts/peer.rs, newsrc/web/peer/rounds.rs;GETstate gainscurrentRound {index, status, role, deadline}andhardDeadline. - Introduces: table
peer_rounds(session_id, idx 0 or 1, interviewer_slot, candidate_slot, problem_id, statusprepared|active|finishing|feedback|closed, version, started_at, deadline, sealed_at); session columns for the start proposal version, per-slot start and swap confirmations,hard_deadlineandoutcome(complete|partial|no_show|technical_failure). Routes:POST .../ready,.../start(propose or confirm withexpectedVersion),.../rounds/{n}/finish,.../advance(swap confirm),.../terminate. - Depends on: P6.
- Acceptance:
tests/unit/web/peer/rounds.rsdrivessettlewith an injected clock: a round past its deadline moves tofinishing; pasthard_deadlinethe session moves to finalizing; one confirmation does not start round 0; a confirmation with an old proposal version is refused; round 1 roles reverse round 0; no third round.tests/web/peer.rsasserts 409version_conflicton a stale version and that the deadline equalsstarted_atplus the round minutes. - Out of scope: problem choice (P8), snapshots (P9), pause and extend.
P8. Problem assignment and interviewer guide
- Deliverable: both problems locked at start; the current interviewer reads the guide, the candidate cannot.
- Touch:
src/agent.rs:55(re-exportvariant_foraspub(crate)); newsrc/web/peer/guide.rspresenting{contract, constraints, clarifications, followUps, hints[3], optimal, pitfalls}fromfind_problemandvariant_for; assignment insrc/web/peer/rounds.rspicks two distinct problems with the sameProblem.difficulty, filtered by topic viatopics_for, excluding problems either member has already seen as interviewer while alternatives remain. - Introduces: table
peer_problem_exposures(user_id, problem_id, kindinterviewer|candidate, at). Routes:GET .../rounds/{n}/problem(returns{page, title}; the statement is the publicweb/problems/<page>.json) andGET .../rounds/{n}/guide. - Depends on: P7.
- Acceptance:
tests/unit/web/peer/guide.rsasserts thetwo-sumpresenter output has three hints and the variant contract.tests/web/peer.rsasserts the two rounds get different problems; round 0's candidate gets 404 on its guide and the interviewer 200; nobody gets round 1's problem or guide before the swap; a second session between the same users avoids interviewer exposures../scripts/test.shregenerates no file undertests/golden/. - Out of scope: editing
problem-bank/or prompt text, server-only problems. The repository is public, so this only stops early disclosure through the peer UI.
P9. Workspace snapshots, writer lease and final capture
- Deliverable: only the current candidate holding a valid lease saves snapshots by revision CAS; finishing a round opens a 30 s final-capture window, then seals.
- Touch: new
src/web/peer/snapshots.rs,src/accounts/peer.rs,MAX_PEER_SNAPSHOT_BYTESbesideMAX_REPORT_BYTES(src/web/mod.rs:52), a snapshot bucket of 30 per minute per account; "What may be cached" indocs/provider-cost-and-degradation.mdgains a sentence that peer snapshots are the session record, visible only to the two members, never an input to any provider call. - Introduces: table
peer_snapshots(session_id, round_idx, revision, language, code, test_summary_json, updated_at);writer_lease,writer_lease_until,final_capture_incompleteonpeer_rounds. Routes:POST .../rounds/{n}/writer(take or take over the lease),PUT .../rounds/{n}/snapshotwith{lease, expectedRevision, language, code, tests, final},GET .../rounds/{n}/snapshot. - Depends on: P7.
- Acceptance:
tests/web/peer.rsasserts: the interviewer's PUT gets 403not_writer; an old revision gets 409; after a second tab takes the lease the first tab gets 409lease_lost; a 65 KiB body gets 413 and code over 48 KiB of UTF-8 (multi-byte fixture) is refused; duringfinishingonlyfinal: truefrom the lease holder is accepted; 30 s latersettleseals the last revision and setsfinal_capture_incompleteif no final arrived; after sealing every PUT gets 409round_sealed; a PUT to round 0 while round 1 is active is refused. - Out of scope: the preview channel (P14), whiteboard.
P10. Two-way feedback
- Deliverable: each member submits feedback on the round they interviewed and on the round they were interviewed in; drafts stay private; both submissions for a round are revealed when both exist or 24 h after the round closes.
- Touch: new
src/web/peer/feedback.rs,src/accounts/peer.rs,MAX_PEER_FEEDBACK_BYTES.docs/observable-delivery-policy.mdgains a "Peer feedback" section: the form repeats its "Not assessed" list (accent, filler words, eye contact, nervousness, confidence, personality), peer text is human-authored and unscanned, visible only to the two members, and never feeds a CodeTrial score. Peer feedback is not an agent report: no row inreports, nointerviewContract, no golden change. - Introduces: table
peer_feedback(session_id, round_idx, author_slot, kindcandidate|interviewer, rubric_versionpeer-rubric-v1, payload_json, submitted_at null for drafts). Dimensions: candidate (clarification, reasoning, correctness, testing, complexity, technical explanation) and interviewer (preparation, question quality, hint pacing, follow-up, feedback specificity), each 1 to 4 ornot_observedwith an example and one improvement. Routes:PUT .../rounds/{n}/feedback,GET .../rounds/{n}/feedback. - Depends on: P9.
- Acceptance:
tests/web/peer.rsasserts feedback before sealing gets 409; a draft is not returned to the other member; after one submission the other sees nothing; after both, both see both; with the clock 24 h past close a lone submission becomes visible; missing feedback does not block the swap; an unknown dimension or a score of 5 gets 400;reportsstays empty. - Out of scope: AI review, report history, teacher access.
P11. Peer sweeper and room cleanup
- Deliverable: one background task that settles sessions nobody is polling and deletes rooms of closed sessions, with retries.
- Touch: new
src/web/peer/sweeper.rs, registered throughBackgroundTasks::absorbbesidespawn_session_sweeper(src/web/mod.rs:347);delete_roomfrom P4; database work throughaccounts::blocking, never held across a network await. - Introduces:
room_deleted_at,delete_attempts,next_delete_atonpeer_sessions. A lobby idle for 10 minutes returns to pending and releasespeer_active_users; a pending session pastexpires_atis cancelled. - Depends on: P4, P6, P7.
- Acceptance:
tests/unit/web/peer/sweeper.rsruns one sweep against a stub RoomService and asserts a cancelled session gets exactly one DeleteRoom androom_deleted_at; a 500 leaves it null withdelete_attempts = 1and a laternext_delete_at; an idle lobby releases its reservation; a session pasthard_deadlineis finalized with no client request; dropping the app state stops the task. - Out of scope: a jobs table, a webhook, RemoveParticipant.
P12. Extract the workspace from web/interview.js
- Deliverable:
web/workspace.jsexportingmountWorkspace({nodes, language, readOnly, onEdit, onRunTests})for the editor (input, keydown, paint, font, theme) and the test panel;web/interview.jsuses it with no behavior change. - Touch:
web/interview.js(around:716,:902,:990,:3433and the test-run wiring), newweb/workspace.js. - Depends on: nothing.
- Acceptance:
tests/browser/editor.test.js,editor-pipeline.test.js,editor-options.test.js,editor-font-size.test.js,editor-theme.test.js,run-shortcut.test.js,test-drawer-resizer.test.jsanddom-contract.test.jspass unedited; newtests/browser/workspace.test.jsassertsreadOnlyignores keystrokes and still paints given text. - Out of scope: peer pages, whiteboard.
P13. Peer lobby page
- Deliverable:
web/peer-lobby.htmlandweb/peer-lobby.jsfor create, copy and regenerate invite, accept from#invite=<token>, sign-in through P3, Enter, Leave, Ready and Start confirmation, polling state every 5 s. - Touch: new files, plus one link in
web/index.htmlshown when/api/sessionreturnspeerEnabledandidentityVerified. No change tosrc/web/policy.rsorsrc/web/assets.rs: everything underweb/is embedded already. - Introduces: no route. The page clears the fragment after reading it, keeps the token in
sessionStorageonly until accept succeeds, and uses no inline script or style. - Depends on: P3, P5, P6.
- Acceptance:
tests/browser/peer-lobby.test.js, in the style oftests/browser/lobby.test.js, asserts a signed-out visit with#invite=xnavigates to/api/login?next=peer-joinand keepsxinsessionStorage; a signed-in visit posts accept once and clears the fragment; a 410 shows the unavailable message; Start stays disabled until both are ready.tests/web/assets.rsasserts/peer-lobby.htmlis served from disk and from the embedded copy with the same CSP as/. - Out of scope: the interview page, media.
P14. Peer interview page
- Deliverable:
web/peer-interview.htmlandweb/peer-interview.js. The candidate gets the P12 workspace writable; the interviewer gets it read-only plus the P8 guide and notes kept only in the page. Two-way audio with mute, device change, attach and detach, an explicit "enable sound" on autoplay block, and local tracks stopped on exit. Previews on a peer data topic at most every 300 ms (above 12 KiB of UTF-8 only a revision notice), snapshots every 5 s and on finish, timer from the server deadline, swap confirmation, the P10 feedback form. - Touch: new files and new
web/peer-lib.jsholding the topic name and pure helpers (role from state, preview payload, sender check). Nothing is added totopicsinweb/lib.js, andisAgent,roomInterviewerandacceptsReportare unchanged. - Introduces: no route. On reconnect: new token, then
GETstate, thenGETsnapshot, then resume previews; an unsaved local buffer is kept and offered for download when the lease is lost. - Depends on: P8, P9, P10, P12, P13.
- Acceptance:
tests/browser/peer-lib.test.jsasserts a preview from anyone but the current candidate is ignored whatever its payload claims; an oversized preview becomes a revision notice; a preview for an old round is dropped; role comes only from state.tests/browser/peer-interview.test.jswith a fake SDK asserts one attachment per track, detach on unsubscribe, no duplicate tracks after reconnect, and the editor read-only for the interviewer.tests/browser/lib.test.jsandobserver.test.jspass unedited. - Out of scope: recording, video, co-editing, whiteboard.
P15. Runbook and manual end-to-end check
- Deliverable:
docs/peer-interviews.mdwith the P2 settings, the shared quota note, and a manual check in the style ofdocs/meet-tab-audio-manual-check.md: two real GitHub accounts in two browser profiles run create, accept, enter, start, round 0, swap, round 1 and feedback; each side hears the other; the interviewer's editor refuses typing; closing one tab for 30 s and reopening restores role, timer and last snapshot; after the session the room is gone. It also lists load targets to validate (90 concurrent sessions: control p95 under 500 ms, snapshot p95 under 1 s, reconnect within 5 s) and rollout stages of 8 to 12, 30 to 40, then 180 users. - Touch:
docs/peer-interviews.md, a link fromREADME.md. - Depends on: P14.
- Acceptance: a reviewer runs the manual check on a staging deployment and records the result in the PR.
- Out of scope: an automated load harness (follow-up after the first stage), recording consent, AI review.
Done when
Every item in the Progress checklist is ticked, ./scripts/test.sh passes with no file under tests/golden/ regenerated, and the P15 manual check has been recorded on a staging deployment.
- Ngôn ngữ chính
- Rust
- Star
- 160
- Fork
- 64
- Merge trung bình
- 1 ngày 9 giờ
- Pull request đã merge (30 ngày)
- 114
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của sysprog21/codetrial
-
Highlight the active line in the code editorCó thể đã có người làm @ArthurArthurArthur0817 đã nhận 8 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
sysprog21/codetrial#247 · 3 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Include local time in downloaded report filenamesCó thể đã có người làm @106081011 đã nhận hôm nay. Đang mở
sysprog21/codetrial#309 · 1 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Avoid automatic bracket pairing inside comments and stringsCó thể đã có người làm @Jonathas2127 đã nhận hôm nay. Đang mở
sysprog21/codetrial#307 · 2 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Preserve test run historyCó thể đã có người làm @yunhsuan0510 đã nhận 1 ngày trước. Đang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 62/100
sysprog21/codetrial#301 · 2 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Standardize section header and toggle indicator styles for test panelCó thể đã có người làm @kumi222 đã nhận 1 ngày trước. Đang mở
sysprog21/codetrial#298 · 2 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của sysprog21/codetrial
Issue tương tự
-
Bump anybuf version to ^1.0.0Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
wormhole-foundation/wormhole#5062 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
RUSTSEC-2026-0348: Brotli certificate decompression can allocate up to 1 GiB before authenticationĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 60/100
-
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 62/100
-
core: Amount::from_str panics on values above 21M BTC instead of returning an errorCó thể đã có người làm @bhuvan-somisetty đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
crate: spate-s3
Độ khó 2/5 Nửa ngày Mức phù hợp với người mới 62/100
Maintainer thường phản hồi trong vòng 1 ngày