Webv3 contest page and registration
Read this when you change a Webv3 contest page, its activity or participant profiles, log moderation or contest registration.
/contests/$id/leaderboard is a contest's main page, with an optional trailing
page number such as /contests/<id>/leaderboard/2, read like the
leaderboards' page segments. /contests/$id/registration
joins the contest or changes a registration's languages. Both keep Webv2's paths.
An unknown or malformed contest ID answers 404 with a not-found page.
frontend/apps/webv3/src/lib/leaderboard.tsowns the contest board query, read as the visitor because administrators can also open deleted contests, and the contest's phase on the UTC day the board was read.frontend/apps/webv3/src/lib/registration.tsowns the viewer's registration, the language options anduseSaveRegistration(), which refreshes every read a save changes, by query-key prefix, including the Joined list and the account's contest history.frontend/apps/webv3/src/lib/registration.server.tscalls the API with the visitor's session cookies.frontend/apps/webv3/src/components/ContestPage.tsxcomposes the page header and actions around the board thatfrontend/apps/webv3/src/components/ContestLeaderboard.tsxshares with the latest official leaderboard: notices, standings and conditions; the Activity tab lists the contest's logs. The selected Leaderboard tab names the standings, so the contest page shows no separate standings heading.frontend/apps/webv3/src/components/ContestRegistration.tsxholds the registration states and form.
Rules
The round's status (upcoming, live or ended) and registration are separate: a live contest can be closed to new participants. Contest dates are UTC calendar days. A contest accepts new participants through its registration end day; registered participants can change their languages until the contest's last day, as the API enforces. An unlisted contest is left out of contest lists but anyone with its link can view and join it.
The page shows only the actions Webv2 allows the viewer: Join contest while registration is open and the viewer hasn't joined, Update registration until the contest ends for participants, and Scoring rules for administrators. Join contest is the page's only button. The other two are items in a Contest options menu (an icon-only "⋯" trigger), which is left out when it would be empty. On phones the trigger sits at the end of the back link's row, so the title keeps the full width. Logging uses the header's Log activity action. Anonymous visitors see a login prompt; joining sends them to log in and back to the registration. Suspended accounts get no join action. Scoring rules opens the native scoring page, participant rows open their contest profile and the organizer opens their Webv3 profile.
The registration form accepts one to three distinct languages: the contest's allowed languages, or every configured language when it has no restriction. Removing a registered language warns that the contest logs in that language will be removed. A save is refused before the API call when the session ended or another account signed in since the form opened. After the deadline the page explains that registration has closed instead of offering the form; an API refusal after the deadline shows Webv2's message and keeps the form. A saved registration returns to the contest page.
Activity and participant profiles
The Leaderboard and Activity tabs share the page header and actions.
/contests/$id/updates lists every participant's contest logs newest first, as
Webv2's updates page does, 25 to a page, without deleted logs, each naming its
learner beside their avatar and opening its
native detail page. A missing or
malformed page segment reads as the first page; a page past the end links back
to it.
/contests/$id/profile/$userId shows one participant in one contest: their
overall score and the score for each registered language, each active UTC day's
score per language with the running total, and their contest logs, paged the
same way. Webv2 draws the daily scores as a chart; Paper has no bar chart, so
Webv3 lists them in a table. The API answers 404 for an unknown contest or a
learner who has not joined it, and the page is not found. The daily scores fail
on their own while the scores and logs still show. View profile opens the
learner's Webv3 profile.
The contest's organizer and administrators see Remove from contest on each log
in both lists. It asks for a reason of up to 1,000 characters before it sends
ImmersionContestModerationDetachLog, which enforces the same permission. A
removal is refused before the API call when the session ended or another account
signed in since the page opened. The log stays in the learner's personal record;
the contest's logs, participant profiles and places, the standings and the
log's submissions are read again.
frontend/apps/webv3/src/lib/contest-activity.tsowns the contest log and participant queries anduseDetachLog();frontend/apps/webv3/src/lib/contest-activity.server.tsreads them anonymously and removes a log with the visitor's session.frontend/apps/webv3/src/components/ContestUpdates.tsxrenders a contest's logs and the moderation dialog,frontend/apps/webv3/src/components/ContestProfile.tsxthe participant profile, andfrontend/apps/webv3/src/components/LogEntries.tsxthe log list it shares with profile activity.
Checks
frontend/apps/webv3/e2e/rank.spec.ts checks that a participant below the first
standings page sees their rank on the home page and their participant page.
frontend/apps/webv3/e2e/viewer-change.spec.ts signs in from another tab and
switches accounts, then checks that the contest, registration and log pages show
their loading state while the new viewer's data loads, never their error state.
frontend/apps/webv3/e2e/contest.spec.ts compares the server-rendered page with
the API, follows page segments, checks unknown contests, the anonymous login
redirect and containment at 390px and 1280px. With the Dev Reader and Dev Admin
credentials it creates community contests in the selected tenant, registers Dev
Reader, changes its languages and checks a refused registration after the
deadline.
frontend/apps/webv3/e2e/contest-activity.spec.ts compares the server-rendered
activity and a standings participant's contest profile with the API, pages through
them, opens a log, checks not-found addresses and a 390px layout. With both
fixture credentials it submits a Dev Reader log to a contest Dev Admin creates,
checks that Dev Reader sees no moderation action, and removes the log as Dev
Admin with a reason; a contest Dev Reader organizes checks the organizer's
removal. The container smoke's synthetic API serves 51 contest logs and the
standings learner's contest profile.