Sync: dev to main - #458
github-actions[bot] wants to merge 26 commits into
Conversation
The hackathon pages, submit, judging and their error and loading
screens had drifted into a fake-terminal style the rest of the portal
does not use: underscored titles ("Auth_Error", "Status_Code: 401"),
"System Failure" / "Reboot System" copy, neon glows, gradient text,
pulsing dots, blur blobs inside cards, hover lifts and a second green
accent. They now use the dashboard and settings conventions: Oswald
headings, the teal accent, one set of buttons and inputs, rounded-sm,
plain sentence-case copy, and one empty-state pattern.
- Registration statuses render through a shared StatusBadge
("Under review", "Accepted", "Checked in") instead of raw enums.
The detail page and registration card no longer say "Registered" or
"You're in" while an application is still pending.
- text-text-muted is not a utility, so "muted" text rendered at full
brightness in 89 places across the site. Now text-[var(--text-muted)].
- Hand-pasted SVG icons are lucide icons; icon-only project links have
visible labels; external links carry rel="noopener noreferrer".
- Empty states for a filter that matches nothing and for judges with no
open hackathons; the list page's back link no longer sends
hackathon-only users to the members-only club page.
- The login footer's "Internal Terminal // Auth Gateway v2.1" is gone.
maxParticipants used to be claimed at application time, so the form said "full" once N people had applied, even with most still pending, which defeats accepting in waves. Applying now never takes a seat. A seat is taken on acceptance: waves fill what is left, and Accept + email, single and bulk approve check the remaining seats under the hackathon row lock (assertSeats) and refuse rather than go over. currentParticipants counts approved and checked-in.
Team create/join opened only 12 hours into the event. The window now runs from acceptance up to +34h; checkAdmitted already limits who may form a team. The +12h mark stays as the submission opening only, and team.window reports opensAt: null.
- judge.setActive is isAdmin: every admin saw Approve, but only super admins could use it, so self-registered judges could stay inactive. Removing a judge stays super-admin. - judge.create takes an email and finds the account, so an organiser can add a sponsor or walk-in judge. - judge.register only accepts public editions that are announced or later. Any uuid used to pass, and a missing one hit the foreign key as a 500. Judge applications open while an event is announced.
- team.mySubmission returns the judging table number, and /submit shows it. Judges are sent to a table; the team was never told which. - The Judges tab's Assign Judge form could never render (this edition had no unassigned judges). It is now Add judge: by email via judge.create, assigned in the same step, plus a copyable apply link. - /submit defaults to a registration that is still active, matching the dropdown, and says waitlisted / not accepted in words.
- /hacklytics offered the interest form after registration opened, where registerInterest refuses it, and called a closed edition 'not announced'. It now shows the form only while announced, and otherwise a status line with a link to the event page. closed joins the public funnel statuses. - toggleJudging refuses to open judging while no judge has an unscored table queued; judges opened straight onto 'All done, 0 of 0'. A judge with an empty queue now reads 'No tables yet', and the Judges tab links to Prepare judging (/admin/judging?hackathonId=...).
- hackathon.withdrawRegistration removes your own registration and frees the seat. Refused once you are on a team, checked in, or have a project, since undoing those affects others. - The results email goes to checked-in participants (it reached every applicant, accepted or not) and links straight to the Results tab. - Graduation year is validated relative to the current year.
Scanlines, a cyan grid, rotating squares round the logo, floating glow
orbs and fake telemetry ("Core Operational", "REGION: ATL-08",
"Handshake Verified") are gone. The page is a short intro and a card
titled Sign in, in the portal's house style. Behaviour is unchanged.
The email error copy said link where the flow sends a code.
- A full event still takes applications (capacity caps acceptances), with a note that new applicants are waitlisted. - Participants can withdraw a pending, accepted or waitlisted registration, with an inline confirm; checked-in participants get a link to submit their project. - Team create/join only show for accepted participants, with a line saying why otherwise; the server already refused the rest. - Failed loads read as errors with Try again, not as empty lists, on projects, teams, schedule, admin hackathons and attendees. - Schedule times carry the day: 'Sat, Feb 27 · 2:00 PM'. - Approve without email says so; seat-limit refusals are shown. - The scanner explains that scans need a check-in event, and a pass from another hackathon says so instead of 'Event not found'.
At desktop width the intro and the card sat at opposite edges of a 6xl container. A 4xl container and a larger heading keep them together.
- updateParticipantStatus seat-checks checked_in as well as approved, and only promotes to checked_in from a seated status, so a check-in racing a waitlist change cannot seat somebody past capacity. - withdrawRegistration carries its checks into the DELETE (not checked in, not on a team) and reports CONFLICT if the row changed after it was read. Finished or cancelled events refuse withdrawals, and the same caches as an organiser status change are evicted. - toggleJudging blocks only when no queue was ever built, so judging can be reopened after every slot is scored.
- Opening the registration form fell through to 'Registration is closed.' right above the open form. - Team members are told to leave their team before withdrawing, instead of getting a refusal after two clicks. - Adding a judge refreshes the judge list even if the follow-up assign fails, so the new judge appears in the picker.
The admin judging page and the hackathon dashboard tabs kept the glow,
gradient-text and terminal copy the participant pages lost ("Data
Evaluation Layer // SYNC ACTIVE", "Awaiting Data Packet...", "Operations
Personnel"). They now use the portal's house style and plain words
(Results, Votes, Tied scores, Judges).
- Attendees: a failed filter or page refetch shows an inline error above
the rows it kept, instead of passing them off as the new results.
Status cells use StatusBadge.
- Scanner: a failed events load says so with a retry, rather than
telling the organiser to create an event.
- updateParticipantStatus: a check-in whose seated-only WHERE matches nothing (concurrent waitlist or reject) now throws CONFLICT instead of reporting success to the desk. - evictParticipantCaches drops the cached hackathon row, so the public seat count updates after accept and withdraw instead of at TTL. - Judging page shows the toggleJudging error; Start judging refused for missing queues was a silent no-op. - /submit hides Create team and Join team until the registration is approved or checked in, matching TeamsTab and the server check. - Judge page says All done only when progress shows assigned work; loading or failed progress no longer reads as finished.
The toggleJudging error stayed on screen after switching events, so a refusal for one hackathon read as a refusal for the next.
Every section below the hero was the same bare template: a grey label, a white headline, a hairline list and a lot of empty black. The pixel frames, neon type and sprites the hero uses were defined in globals.css and never reached the rest of the page. - Eyebrows use the pixel font, a section number and a per-section neon color. - About stats, tracks, prizes, schedule days, sponsors and FAQ are tinted pixel-frame cards; each track has its own color and sprite. - Schedule is grouped by day on a timeline with glowing milestones. - Headlines carry a neon accent word; body text moves from white/45 to white/65-70 for contrast. - Sponsors gets a real call to action instead of a plain text box. Content and copy are unchanged apart from the sponsor pitch line.
- Drop the two rotating petal rings behind the title: at that size and opacity they read as a pink smudge, not a lotus. - Say when and where in the hero (Feb 26-28, 2027, Klaus, Atlanta); it said neither. - The Data Science @ GT badge is a label, so it loses the pointer cursor and hover state, and one of its two blooms. - Countdown digits are one weight and color with seconds as the live accent, instead of fading to 40% across the row. - Subtitle, hint and nav text move up in contrast; nav and mobile Notify buttons use the pixel font like the hero buttons.
The page read as generated: a numbered label over every heading, a neon accent word in every headline, stat cards, the same tinted card grid in every section, slogans in place of copy, and CRT scanlines, an RGB-split glitch title, a synthwave grid and a vignette laid over everything. - Headings are plain words (Tracks, Prizes, Schedule, Sponsors, FAQ) with no numbered labels and no glowing accent words. - Tracks are the garden: one plant per track on a shared strip of the hero's soil, names beneath. Phones get a plain list. - About, prizes, sponsors and FAQ are text and rules, not cards. - Hero: no kicker badge, glitch, scanlines, grid or vignette. The tagline states what the event is, who runs it, and that it is free. - The vignette also darkened every heading near the screen edge to grey; text is now the white it was set to.
Table cards keep their QR code; an NFC tag is an optional second way in. The tag holds the judge page URL with ?table=<code>. Tapping it opens the page (iPhone background read or Android) and, once judge access and an open round are confirmed, calls startByQrCode with that code: the same start, clock and wrong-table check as scanning the card. The param is removed after use so a reload does not replay it, and sign-in keeps the full URL, so a signed-out judge comes back to the table. Each table card gets Copy link (for writing tags from an NFC app) and, on Chrome for Android, Write NFC tag via Web NFC. Neither prints. The in-page scanner also accepts the link form of the code.
Feature: feat/hacklytics-bloom to dev
Feature: feat/judge-nfc-tables to dev
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
|
Visit the preview URL for this PR (updated for commit f17759d): https://hacklytics2027--pr-458-0gg6iouu.web.app (expires Fri, 09 Oct 2026 19:52:21 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: c48ba34db61581e25fe2978355160b5eefe0e83f |
Feature: fix/hackathon-portal-finish to dev
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 f17759d. Configure here.
| ne(hackathonParticipants.registrationStatus, "checked_in"), | ||
| isNull(hackathonParticipants.teamId), | ||
| ), | ||
| ) |
There was a problem hiding this comment.
Waitlisted users re-enter accept wave
Medium Severity
withdrawRegistration deletes the participant row for any status except checked_in, including waitlisted and rejected. register then treats that user as new, so they come back as pending and acceptWave can pick them again.
Reviewed by Cursor Bugbot for commit f17759d. Configure here.
| .update(hackathons) | ||
| .set({ | ||
| currentParticipants: sql`(select count(*)::int from ${hackathonParticipants} where ${hackathonParticipants.hackathonId} = ${hackathonId} and ${hackathonParticipants.registrationStatus} in ('pending', 'approved', 'checked_in'))`, | ||
| currentParticipants: sql`(select count(*)::int from ${hackathonParticipants} where ${hackathonParticipants.hackathonId} = ${hackathonId} and ${hackathonParticipants.registrationStatus} in ('approved', 'checked_in'))`, |
There was a problem hiding this comment.
Stored seat count stays inflated
Medium Severity
currentParticipants now means accepted plus checked-in, but existing rows still hold the old apply-time increment. Nothing recounts them until a later status change or withdraw, so public capacity and the “all seats are taken” copy stay wrong after deploy.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit f17759d. Configure here.


Automated PR tracking changes from
devintomain.Note
High Risk
Changes core registration and seat accounting with transactional acceptance, waves, and withdrawals—organizer-facing behavior and capacity limits differ materially from before, though covered by extensive API tests.
Overview
Hackathon capacity and lifecycle move from “seat claimed on apply” to “seat claimed on accept”: registration always takes applications (even when full),
maxParticipantsis enforced in admin paths via transactionalassertSeats, seated counts are onlyapproved/checked_in, and accept waves are capped to remaining seats. Participants canwithdrawRegistrationbefore check-in/team/project attachment, with optimistic concurrency on delete; check-in and batch status updates get similar conflict handling.Event rules and judging: team formation is open from acceptance through +34h (no +12h delay); opening judging requires at least one queued assignment (
PRECONDITION_FAILEDotherwise); plain admins may activate judges; judge signup validates visible hackathon status; judges can be added by email. Portal surfaces withdrawal, judging prep errors,?hackathonId=on admin judging, and shared registration StatusBadge; Hacklytics funnel includesclosededitions and clearer post-registration CTAs.Hacklytics 2027 marketing site is a lighter hero/sections refresh (date/location up front, simplified backgrounds, track “garden” layout). Admin/portal pages drop heavy decorative chrome and sci-fi copy in favor of consistent tokens and calmer error/loading states.
Reviewed by Cursor Bugbot for commit f17759d. Bugbot is set up for automated code reviews on this repo. Configure here.