Repository navigation
Sync: dev to main - #487
github-actions[bot] wants to merge 55 commits into
Conversation
The analytics page and the metrics scrape share registrations, badge scans, and judging totals for the edition that is actually running.
Ranking and dispatch have to be extractable later, so they live under judging/ as pure functions with no host imports. Co-authored-by: Cursor <cursoragent@cursor.com>
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@v4...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](actions/download-artifact@v4...v7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 5.0.1 to 5.0.3. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.3/packages/vitest) --- updated-dependencies: - dependency-name: vitest dependency-version: 5.0.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Feature: feat/telemetry-prod-grading to dev
…ev/vitest-5.0.3 chore(deps-dev): bump vitest from 5.0.1 to 5.0.3
Dependency ReviewThe following issues were found:
License Issues.github/workflows/ci.yml
OpenSSF ScorecardScorecard details
Scanned Files
|
…1.0 (#485) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.70.1 to 8.71.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.71.0/packages/eslint-plugin) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-version: 8.71.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…/dev/actions/download-artifact-7 chore(deps): bump actions/download-artifact from 4 to 7
Bumps [dotenv](https://github.com/motdotla/dotenv) from 18.0.3 to 18.0.5. - [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md) - [Commits](motdotla/dotenv@v18.0.3...v18.0.5) --- updated-dependencies: - dependency-name: dotenv dependency-version: 18.0.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [turbo](https://github.com/vercel/turborepo) from 2.11.3 to 2.11.7. - [Release notes](https://github.com/vercel/turborepo/releases) - [Changelog](https://github.com/vercel/turborepo/blob/main/RELEASE.md) - [Commits](vercel/turborepo@v2.11.3...v2.11.7) --- updated-dependencies: - dependency-name: turbo dependency-version: 2.11.7 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…/dev/actions/upload-artifact-6 chore(deps): bump actions/upload-artifact from 4 to 6
One API and the club Postgres, with the judge desk, organizer console, and a handoff from the club site when an edition opts in. Co-authored-by: Cursor <cursoragent@cursor.com>
The root ignore rule was hiding judging/db/migrations, and a Playwright run result had been committed with the desk. Co-authored-by: Cursor <cursoragent@cursor.com>
Judging was a standalone product under judging/ with its own scope, licence, CI
workflow, compose file and an import boundary against @query/*. It now lives as
ordinary workspaces: packages/judging-{core,db,server,cli} and sites/judging,
scoped @query/judging-*. Lint, typecheck, test and build run in ci.yml; image
publishing on panel-v tags moved there too. The judging server joins the root
docker-compose under the "judging" profile on the shared Postgres.
packages/api now depends on @query/judging-core, so the ranking replay test
imports it by name and is back in the api tsconfig. Both pre-existing lint
failures (replay.test.ts and the web e2e spec outside tsconfig) are fixed.
Judging no longer has its own server or web app. mainweb mounts the judging API at /api/panel and resolves the caller from the portal session: staff organize, volunteers volunteer, and a judge is matched by the email on their portal judge row for a panel edition. The desk, organizer console, public board and feedback card are portal routes (/judge/panel, /admin/judging/panel, /judging), so the ticket handoff, magic-link sign-in and organizer token are gone. Club-side sync calls the judging procedures in process instead of over HTTP, and published results are read straight from the shared database. Live views poll, since the portal process has no WebSocket hub. Removed the standalone pieces this leaves unused: sites/judging, the server entrypoint and WebSocket hub, the two-server check, Dockerfiles, Terraform, and the PANEL_URL / PANEL_SERVICE_TOKEN / PANEL_WEB_URL settings.
Neon suspends after five idle minutes and the plan has 100 compute hours a month, so anything that polls keeps the database billing. The public board and the organizer floor now poll only during judging_live and only while the tab is visible; the board gets a refresh button otherwise. /api/panel/readyz (a Postgres round trip) and /metrics are no longer public. Judging now borrows the club's connection pool, which is tuned to let Neon scale to zero, instead of opening a second pool to the same database. On the 0.5 GB side, a drained outbox row is deleted rather than kept, since event_log already holds the history. The judge desk no longer holds a score the server refused (past the hard limit, a voided visit). It used to keep it on the phone and resend the same refusal every time the page opened; only outages are held now.
The desk lost these when the WebSocket hub went away. It now asks /v1/session/status every 15 seconds, but only while a visit is open and the screen is on. Judging is live whenever a visit is open, so Neon is awake anyway, and a phone in a pocket sends nothing. A recall does not void the visit, and dispatch can hand the same visit back, so the status carries the time of the latest recall and the desk acts once per recall. The lookup reads only log rows written since the hand-out, on the existing (event_id, created_at) index.
Judging is per hackathon. Staff switch an edition to panel on /admin/judging; that creates its judging event from the hackathon row (organization hacklytics, slug = hackathon id) with default timers and the club's five criteria, then copies in submitted projects and the portal's judges. The PANEL_EVENT_ID / PANEL_ORG_SLUG / PANEL_EVENT_SLUG settings are gone, and the portal routes are keyed by hackathon id. Synced judges were stored as invited, which the desk refuses, so no portal judge could ever score. Approval in the portal now approves them in judging and deactivating suspends them. Deploys run drizzle-kit push against the shared database. With the judging tables there, push stopped at a drop prompt with no TTY, exited 0, and skipped every club schema change; db:check would then fail this branch on the missing judging_backend column. The drizzle config now hides the judging tables (read from the judging migrations) and declares the judging enums. Publishing results drains the outbox, so it no longer needs a manual step, and panel doctor treats PANEL_JWT_SECRET as optional.
Rework judging around rooms and a saved schedule:
- judging_* tables: challenges and groups, buildings and rooms, rubric,
schedule and appointments, evaluations with drafts and revisions,
deliberation boards, room access links and guest judge sessions
- Devpost submissions CSV import alongside portal promotion
- schedule solver: aamoghCPSolver (counting bounds, room-class
symmetry, sweep, goal-ordered repair) plus a CP-SAT fallback script
(scripts/judging-schedule/cpsat.py) with problem export and checked
solution upload
- routers judging.{admin,schedule,desk,hacker,results}; judge.* trimmed
to the roster
- admin console, judge desk, hacker itinerary and results UI
- check-in by event code, staff lookup or static pass instead of live
QR codes
Add hackathon points: adjustments and a store with computed balances,
routers points.{admin,store,me}.
Delete the panel packages (judging-core, judging-db, judging-server,
judging-cli) and the pool dispatch judge routers. Panel enum types stay
declared in retired.ts and its tables are filtered in drizzle.config.ts
so drizzle-kit push does not stall on drop prompts.
Judges sit at numbered tables in a hall and teams rotate to them; sponsors keep their rooms. A table is a judging_room with kind "table" (new column, default "room"), so the scheduler treats it as any other station. - createTables adds a numbered run of tables to a hall, skipping numbers already in use and slotting each table in by number. - generateRoomLinks issues QR links for every station in a batch that has none, with one audit entry. - Rooms tab: "Add tables" form, tables shown as number chips per hall with QR and seating counts, bulk QR link button. - Judges, guests and hackers see "room" or "table" to match the station, including the desk, itinerary, QR sheets and judge emails. - QR sheet copy keeps table codes with the judges instead of on the table. - Reordering rooms writes only stations whose place changed.
…ign-in copy - Public site: a compact Notify me button in the phone header, and Hacker portal / Judge Hacklytics links in the footer. The CTA stays on the Typeform for now. - Sign-in: hacker framing and a school email placeholder when arriving from /hacklytics or /hackathons; in-app browsers (Instagram, TikTok, ...) get a notice and email-code as the primary option since Google refuses them. - Drop the fixed 1.5s wait before redirecting after sign-in. - Verify page: Resend code with a 60s cooldown, and "Use a different email" keeps the callbackUrl.
- Autosave never gives up on a flaky connection: capped back-off retries, answers mirrored on the device and restored when newer than the server copy (clock skew corrected), including answers typed offline before a deadline auto-submit. "Reload saved answers" asks first. - The screen stays awake while a presentation deadline is running. - "Next project" after submitting; Up next above the filters; an empty state that says everything here is judged. - The "join a room" notice opens the picker; the picker puts the judge's track first, groups by building and keeps Cancel within reach. - Hide judged hides only complete evaluations. - A named guest who scans another room's or table's code keeps their judge row (same browser and edition, within four hours of the session ending); the old session ends so room counts stay right. - Club judges are told to sign in before scanning, on the guest name gate and on every QR sheet.
- Judge sign-up prefills from the account, starts at the linked edition, shows an existing application, participant or organiser-added judge instead of the form, and drops the track-assignment promise. - Organisers adding a judge directly now send an "added as a judge" email. - Publishing the itinerary emails every scheduled team's members once (the flag flips with a conditional update, so a double click sends once). - First results publish emails accepted hackers and project members; a republish after a correction does not. - Approval emails link to this deploy's origin; the approval copy tells club judges to sign in before scanning. - Router tests stub every email send.
- Itinerary: "Walk to <building>, leave by <time>" between slots in different buildings and "Starts in N min" on the next one. - Judging tab links to the other tabs it mentions; a project already submitted on the portal says so instead of offering a claim. - Results poll while the event is in progress and tag the caller's team when its name is unambiguous.
…port Building the stub from the real module loaded the mail transport first, which could push the first emailing test past its timeout on a busy runner.
- Accepted hackers can tap "I'm coming" (attendanceConfirmedAt, a new
nullable column); organisers can filter "Approved, not confirmed" and
export it to chase no-shows.
- Pending applications can be edited while registration is open, held to
the same rules as the first submission; a decision landing mid-edit wins.
- The check-in desk can admit an approved hacker who has no phone ("Admit
(ID checked)"), sharing the pass-scan transaction and writing an audit
entry.
- Any full staff member can add and remove volunteers; every other role
stays super-admin only, checked on both the role set and the role held.
The staff page has a volunteer-only mode, and the scanner links to it.
- Hackers see the latest announcements sent to them on the event page (listForMe reads their frozen recipient rows only). - "In app only" publishes an announcement without emailing it. - "Send test to me" sends the draft to the organiser's own address, rate limited, writing nothing; a live preview mirrors the email. - CTA links must be http(s), so a link never vanishes in app while still going out by email.
- One pure helper decides a hacker's next step (register, wait, show your pass, find a team, submit by the deadline, next judging slot, results), shown on the dashboard, the event page header and /hacklytics. - The dashboard opens on the hackathon view while an accepted hacker's event is running. - "My <edition>" heads the nav for an accepted hacker from two days before the event to its end. - /hacklytics shows a registered hacker their status instead of Register, and still resolves after the event, pointing at the results.
- A complete, valid hint becomes the starting schedule, so running out of time can never discard it; every goal stage is bounded by the schedule in hand and skips presolve, so the result is never worse than the hint. - Without a hint, the first schedule may use the whole attempt before the goals share what is left. - The cross-building break is relaxed only when a strict schedule is proven impossible, not when the strict attempt merely timed out. - `-` reads the problem from stdin. - Python tests: brute-force agreement on small problems, the command line, exit codes, and hints that survive a timeout.
aamoghCPSolver runs 3.5 to 6.7x faster on the bench instances (forge-sized event 2.8s to 0.46s, 800-project event 8.9s to 2.6s) and finds full-break schedules on tight events where it used to fall back to relaxed breaks or fail: a seeded min-conflicts search runs only when the repair leaves presentations unplaced. Output stays deterministic and strict. Across 871 generated events it scores better on 368, the same on 454 and worse on 49.
JUDGING_CPSAT_PYTHON (and optionally JUDGING_CPSAT_SCRIPT) let the server run cpsat.py itself: judging.schedule.cpsatAvailability reports whether it can, and solveCpsat sends the problem with the greedy schedule as its hint, then reads the answer through the same validation as an upload. Nothing starts Python when the variables are unset, and the TypeScript solver stays the default. The process is killed well past its own time limit, and the problem file it reads is removed after every run.
Under the schedule preview, organisers can run CP-SAT on the server when it is configured, or download the problem, solve it on a laptop and upload the solution. Either way the result is shown against the in-app preview before it can be saved.
#66706E measured 4.46:1 on the paper ground, just under AA, and it is the colour of every 13px label and hint. #5F6967 clears 4.5:1 on every light ground and stays lighter than the muted text above it.
From a rendered-phone review against an anti-slop design rubric: the team list leads the Teams tab, cards are quieter (one tag for Full, overlapping initials, names under the pitch), the invite link uses the phone's share sheet where there is one, read-only teammates see a plain record instead of dead controls, and the submit notices sit next to the button that caused them. Tap targets are at least 44px.
Group heads and room lines no longer leave a separator dangling at a line break, table groups lead with what needs attention (no QR link, nobody seated), and counts read as one line per hall.
The email form is always open inside in-app browsers, errors are announced to screen readers, every action is at least 44px tall, and the verify page uses one-time-code autofill and keeps the masthead of the sign-in page.
Pink marks only the action and the blooms (the hero date line is ink grey now), the pixel display face is used at its real weight, section titles follow one type scale, the closed mobile menu is fully hidden, track titles and descriptions keep their spacing in the subgrid, and the moon and the healthcare flower sprites read as what they are.
…n Hacklytics On touch screens every button gets a 44px min-width, which replaced the flex default and squeezed a strip of tabs into each other; tabs no longer shrink. The Hacklytics page is always dark, so its success, warning and danger states take the night edition's colours (3:1 before, 7:1 or more now).
The next-step strip is one shared component with a readable title and detail, the status is said once, the registration form's checkboxes are 20px boxes inside 44px rows instead of 44px blocks, a failing field's error shows once beside it, past events are no longer faded below contrast, and a selected tab past the edge scrolls into view.
The pass leads the Schedule tab with the check-in line beside it, the day's schedule marks what is on now, and the check-in and points screens send a hacker who is not checked in straight to their pass.
One-line station title, larger rating targets with a weak-to-outstanding legend, 44px links and checkboxes that keep 20px boxes, the station picker marks where the judge is instead of offering it, long titles clamp to two lines, and urgent announcements stay visible while picking a station.
The scanner offers "Scan next badge" after a failure, the manual search keeps focus where the volunteer is typing, /scan hides the edition picker when only one is live, and the staff page's volunteer mode reads plainly.
The itinerary says how long the walk between buildings leaves, results read as a ledger with the caller's team marked, and /hacklytics shows a registered hacker's status and next step without the register pitch.
The 2027 artwork (the globe with the blossom ribbon) cropped square on its dark ground: favicon.ico at 16 to 64px and a 180px Apple touch icon, linked from the layout metadata.
- Closing or reloading the page now stops a server-side CP-SAT run: the request's abort signal reaches runCpsat, which kills the process and removes its problem file (about 20 ms instead of running to the limit). - The panel states the real cost: up to the time limit of solving per attempt, plus up to a minute of setup on large events. - Schedule router and planner copy say room or table, and previews count tables apart from rooms. - Runner tests that start a real process get a 30s limit; 5s was flaky on busy machines.
- Pressed bloom buttons keep white text: --ink on --bloom-deep was 2.0:1 in the light theme; white is 6.0:1 (dark) and 9.2:1 (light). - The browser bar follows a saved theme choice, not only the device: the pre-paint script and ThemeToggle set every theme-color meta to the page's ground, including metas re-rendered by hydration or a client navigation, and a cleared choice returns each meta to its own scheme. - A theme chosen in another tab applies to open tabs (storage event). - The service worker falls through to the network when navigation preload rejects or comes back empty, instead of serving a cached page while online.
| )} | ||
| {currentReg && (currentReg.team || admitted) && ( | ||
| <Link | ||
| href={`/hackathons/${selectedHackathonId}?tab=TEAMS`} |
| Your project is in judging. Your team presents to judges for | ||
| each challenge it entered, and the{" "} | ||
| <Link | ||
| href={`/hackathons/${selectedHackathonId}?tab=JUDGING`} |
| submit points there. */} | ||
| {projectSubmitted ? ( | ||
| <Link | ||
| href={`/hackathons/${selectedHackathonId}?tab=JUDGING`} |
| // draft kept for a first submission. | ||
| if (!showForm || editing) return; | ||
| try { | ||
| localStorage.setItem(draftKey, draft); |
|
Visit the preview URL for this PR (updated for commit e60be8d): https://hacklytics2027--pr-487-vpea6dyl.web.app (expires Thu, 22 Oct 2026 14:01:28 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: c48ba34db61581e25fe2978355160b5eefe0e83f |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e60be8d. Configure here.
| code: "TOO_MANY_REQUESTS", | ||
| message: `Too many codes tried. Wait ${limit.retryAfter ?? 60} seconds, then check the code on the screen.`, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Banned users can still self check-in
Medium Severity
points.me.checkIn is a new participation write and is not wrapped in notHackathonBanned. A banned hacker who already has a registration can still submit event codes, collect points, and keep taking part after staff blocked them.
Triggered by learned rule: Hackathon bans are email-keyed; notHackathonBanned on writes
Reviewed by Cursor Bugbot for commit e60be8d. Configure here.
Bumps [eslint-plugin-turbo](https://github.com/vercel/turborepo/tree/HEAD/packages/eslint-plugin-turbo) from 2.11.3 to 2.11.7. - [Release notes](https://github.com/vercel/turborepo/releases) - [Changelog](https://github.com/vercel/turborepo/blob/main/RELEASE.md) - [Commits](https://github.com/vercel/turborepo/commits/v2.11.7/packages/eslint-plugin-turbo) --- updated-dependencies: - dependency-name: eslint-plugin-turbo dependency-version: 2.11.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>


Automated PR tracking changes from
devintomain.Note
High Risk
Large behavioral change in hackathon judging, registration, and authorization (removed
isJudgemiddleware, new judging data model) plus production monitoring alerts—errors could block judging weekend or mis-route access.Overview
Replaces table-queue judging with room-based judging — new
judgingtRPC namespace (admin, schedule, desk, hacker, results),judging_*schema, Devpost import, and shared helpers underservices/judging/. Thejudgerouter is narrowed to roster only (apply, approve, add/remove); the globalisJudgeprocedure middleware is removed. Results merge legacyhackathon_resultsnapshots with newjudging_placementrows. A CP-SAT fallback (packages/api/scripts/judging-schedule/cpsat.py) can solve schedules offline when the in-app greedy solver fails.Adds hackathon points via
pointsrouter (admin adjustments, store,me) and ties badge scans to attendance metadata (method, points).Hackathon participant & staff flows: MLH consent fields on registration; confirm attendance and edit pending application; staff
admitfor manual desk check-in; ban gate on confirm; withdraw/resubmit blocked when a livejudging_entryexists. Announcements gain in-app-only delivery, per-hackerlistForMe,sendTestToMewith rate limits, separate waitlisted/rejected audiences, and stricter CTA URLs (http/https only). Captains canteam.setOpen.Admin & analytics: Non–super-admins may grant/revoke volunteers and use
findUserByEmail; overview splits badge scans vs club door check-ins and exposeshackathonLiveDB gauges. Hackathon update accepts null to clear deadline/cap; attendee lists support unconfirmed-only filter.Observability: DB-backed
dsgt_hackathon_*Prometheus metrics, Grafana hackathon row,HackathonJudgingIdlealert, ClickHouse hackathon history init SQL.Supporting: CI bumps GitHub artifact actions; root tests include
sites/mainweb/components; docs/glossary/architecture aligned with the new model; Hacklytics marketing doc notes Typeform interest CTA and footer portal links;__pycache__/in.gitignore.Reviewed by Cursor Bugbot for commit df9ec60. Bugbot is set up for automated code reviews on this repo. Configure here.