Skip to content

Sync: dev to main - #458

Open
github-actions[bot] wants to merge 26 commits into
mainfrom
dev
Open

github-actions[bot] wants to merge 26 commits into
mainfrom
dev

Conversation

@github-actions

@github-actions github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Automated PR tracking changes from dev into main.


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), maxParticipants is enforced in admin paths via transactional assertSeats, seated counts are only approved/checked_in, and accept waves are capped to remaining seats. Participants can withdrawRegistration before 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_FAILED otherwise); 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 includes closed editions 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.

aamoghS and others added 23 commits October 1, 2026 11:38
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.
@github-actions
github-actions Bot requested a review from aamoghS as a code owner October 2, 2026 19:48
@aamoghS
aamoghS added this pull request to stack #459 October 2, 2026 19:49
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor Author

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

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ 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),
),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

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'))`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f17759d. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant