Repository navigation
feat(judging): refined judge pages, inline score adjustment, round locks, admin progress + project-submission tie-break - #407
Merged
Conversation
…cks, admin progress + submission tie-break
Judge side
- judgeApi now throws JudgeApiError (it faked {success:true} on failed score
submits/drafts/team loads — scores were silently lost).
- /judge, /judge/[event], /judge/[event]/team/[id] rebuilt on the refined design.
- Dashboard: explicit 'Adjust scores' inline editor per scored team — segmented
1–5 controls, nothing persists until 'Save changes', diff summary, Cancel/Esc
discards; locked rounds hide scoring and explain.
- Scoring page: small uncolored total (shoulder-surfing), dirty-gated draft
autosave only before first submit, explicit 'Update score' after, read-only
when locked, failed submits stay on the page with the error.
- Local MUI snackbars: the app has no notistack provider, so the old toasts
never rendered.
Admin side
- judging/JudgeProgressPanel on Round 1 + Round 2: who submitted / drafts /
missing per judge and per team, CSV, lock toggle with confirm; polls 30s.
- Round 1/2 saves use POST /api/judge/admin/assignments/bulk (one request per
panel instead of one per judge×team), falling back on 404.
- JudgingResults rewritten on one bulk-scores call; ties broken by earliest
project_submitted_at with a visible column + Tie chip (resultsRanking.js).
- Admin tabs mirror toasts through the host page's onSnack.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…y-precision ties, Round 2 wipe guard, lock state, unsaved-changes guard Judge side - Inline adjuster: Close and every other card's Adjust are disabled while a save is in flight; a result always lands on its own card (no silent 500). - "Adjust scores" is offered only when a complete stored score exists; Save requires every criterion set (backend would 400 otherwise). - Rows wrap on phones; Esc inside the feedback box no longer discards; focus returns to the Adjust button on close; ARIA radio arrow directions. - Toast no longer prints the score; "Not scored yet" uses the quiet tag. - Scoring page: total pinned at the top of a scrollable sticky aside; dirty- gated beforeunload + routeChangeStart guard; judge_score_submitted GA event; W3C reference link restored on the accessibility prize. Admin side - Results: ties compared at the displayed two decimals (never an epsilon — not transitive, reordered 14- vs 15-judge averages); only currently assigned judges' scores count (parity with the old page), unassigned shown as "not counted"; WinnerPodium at module scope; assignments no longer cached so Refresh refetches. - Round 2 save refuses an empty judge list and records a new panel before assigning (no duplicate panels on a failed call); Round 1 save skips panels with no judges or no teams instead of sending an empty replace. - JudgeProgressPanel: lock state independent of the progress load (public locks fallback, toggle disabled while unknown), array guards, refreshSignal prop instead of a key remount, orphan copy corrected. Tests: scoring-page autosave contract (never on load / submitted / locked, once after a change, draft restore), progress panel (render, lock flow, 404, unknown-lock fallback), dashboard save race + incomplete-score gate, display-precision ranking. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Judging experience for Fall 2026 (~15 judges on
/judgeat once) plus the admin judging tabs. Pairs with backend PR opportunity-hack/backend-ohack.dev#307 — deploy the backend first; every new call here treats a 404 as "feature off" (no progress panel / no locks), so the order is safe but the new features only appear once the backend is live. The rubric, rounds and results math are unchanged apart from the tie-break described below.Bugs this fixes (worth knowing even if you skip the rest)
src/lib/judgeApi.jsswallowed every error and returned a fake{success: true}fromsubmitScore/saveDraft/getJudgeTeams. A 403/409/500 showed "Score submitted successfully!" while the score was lost, and a backend outage read as "You have no teams assigned". It now throws a typed error and every page renders the failure.enqueueSnackbarcalls never rendered anything. Judge pages use local MUI snackbars; the admin tabs mirror toasts through the host page'sonSnack.Judge side (refined civic-editorial design)
/judge— assignments as calm cards with navy progress rails, tutorial band, quick reference./judge/[event]— the dashboard. Each already-scored team card has an explicit "Adjust scores" button that opens an inline editor: segmented 1–5 controls (no sliders), the diff is spelled out ("1 change · total 24 → 26"), nothing is sent until "Save changes", Cancel/Esc discards, Save is disabled until there's a real change with valid feedback. Unscored teams go to the full page (first scoring needs the video and criteria). Locked rounds hide scoring and show a closed notice. Scores on cards are a small muted line, no colored boxes. Demo videos open in one page-level dialog./judge/[event]/team/[id]— same design; the total is a small, uncolored line pinned at the top of the side column (people look over judges' shoulders); draft autosave only after a real change and only before first submit; an already-submitted score shows "Submitted " and an explicit Update score; locked ⇒ read-only; failed submits stay on the page with the error; an unsaved-changes guard on Back/unload.Admin side
?section=judging&subtab=round1|round2— new "scoring progress & lock" panel: judges complete / scores in / drafts in progress, by-judge table (progress bar, missing teams, drafts, last submitted) or by-team view, CSV, and a Lock scoring toggle with a confirm dialog. Polls every 30s while open.replaceso the backend set matches the screen); Round 2 refuses to save with an empty judge list; Round 1 skips panels with no judges or no teams.subtab=results— rewritten on ONE bulk-scores call. Ties on average score are broken by the earliestproject_submitted_at(the first time the team pressed "Submit your project" on its dashboard; stamped once). A new "Project submitted" column shows that time in the event timezone with seconds, tied rows get a Tie chip with the reason, and the CSV exports both. Averages compare at the displayed two decimals (never an epsilon). Only currently assigned judges' scores count — same rule as before; a judge later removed from the panel shows in the dialog as "not counted".ga-events-reference.md(judge_inline_adjust_open/_saved,judge_score_submitted,admin_judging_lock/_unlock).Verification
npx jest --ci: 84 suites, 604 passed (new:Judge/__tests__/*,admin/judging/__tests__/resultsRanking.test.js+JudgeProgressPanel.test.js,lib/__tests__/judgeApi.test.js,src/__tests__/pages/judge-event.test.js+judge-team.test.js— inline adjust end to end, save race, incomplete-score gate, locked round, failed submit stays put, autosave never-on-load / never-when-submitted / never-when-locked, lock confirm flow, 404 → unavailable).npx eslinton every touched file: clean.npm run buildagainst the local backend: exit 0 (twice — before and after the review fixes).Test plan (please click through)
Needs backend #307 deployed. Use a judge account that is assigned in Round 1 (and Round 2 once set up) for
fall-2026, and an admin account.Judge
/judge— your events render as cards with "Round 1 · find the top teams" progress; "Open judging" →/judge/fall-2026./judge/fall-2026?tab=round1— event title in the masthead; unscored teams show Score this team; scored teams show a small "Your score · N of 40" line plus Adjust scores. Open the demo video (dialog, not a new tab)./judge/fall-2026/team/<id>?round=round1(an unscored team): the total line is at the top of the side column and stays visible; change a score → "Unsaved changes…" then "Draft saved h:mm"; reload → the draft is restored (info toast). Submit with < 20 characters of feedback → button disabled with the hint; with feedback → confirm dialog → Submit score → back on the dashboard, team now "Scored".Admin —
/admin/hackathons/fall-2026?section=judging9.
subtab=round1→ "Round 1 scoring progress & lock" panel: judges with scores show green progress, "Still missing" lists team names, drafts show as "(draft)"; By team view lists who each team is waiting on; CSV downloads.10. Click Lock Round 1 scoring → confirm → chip reads "Round 1 scoring locked · Locked by at ". As a judge, reload
/judge/fall-2026: the round shows "Scoring closed", no Score/Adjust buttons, and the full page is read-only. Try to submit from a stale tab opened before the lock → warning toast "Round 1 scoring was just closed…", nothing saved. Unlock → judges can score again.11.
subtab=round2→ same panel for Round 2 above the Round 1 results table. Select finalists → Save Round 2 Setup → one success toast naming the pair count; saving again reports0 new; remove a finalist and save → the removed pairs disappear from the judge's Round 2 tab. With judges not loaded (simulate by blocking the judges request) the save is refused with a message instead of wiping assignments.12. Round 1 tab: Save Round 1 Panel with a panel that has judges but no teams → saved with a "Skipped " warning, nothing deleted.
13.
subtab=results→ ranking table has a "Project submitted" column (event timezone, seconds). Make two teams tie on average (e.g. one judge each, same total): the later-submitted team ranks below with a Tie chip whose tooltip names the rule; the CSV has "Tie" and "Tie broken by" columns; the podium shows the chip. A team that never pressed Submit shows "Not submitted" and loses the tie. Refresh reflects a judge just removed from Round 2 (their score shows "not counted" in the dialog and the average moves).14. Toasts: every admin action above shows a snackbar (they used to be silent).
🤖 Generated with Claude Code