Skip to content

Release 0.11.0: Yuyu sync 3, hackathon suite, mobile API and /help - #48

Merged
Shaan-Shoukath merged 255 commits into
mainfrom
dev
Oct 28, 2026
Merged

Shaan-Shoukath merged 255 commits into
mainfrom
dev

Conversation

@Shaan-Shoukath

@Shaan-Shoukath Shaan-Shoukath commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Release 0.11.0: the third Yuyu sync at full parity, the Space Works hackathon suite (judging rounds, Mentor Desk, volunteer shifts, multi-stage admission, per-stage reimbursements), the mobile API for a future Space Works app, and the in-app guide at /help.

This PR also carries the 35 commits that were already on local dev before this work started: paid teams, event finance and the ticket designer, with their review fixes.

Every branch was built from dev, independently reviewed, fixed, and merged locally. A final seven-track audit followed, and its findings were fixed. Planning and decisions are in docs/plans/yuyu-sync-3.md.

Features

Yuyu parity (sync 3). All 44 Yuyu commits since sync 2 are ported or reviewed. The weekly check reports 0 pending, and a structural re-diff of the schema, routes, actions, mobile API, help, outbox, webhooks and environment found nothing missing.

  • Outbox drain and priority fixes.
  • Real 404s on dashboard pages.
  • no-store and no-referrer on signed-in and token pages.
  • A Redis command deadline.
  • DST-safe times.
  • Faster queries for large hackathons.
  • Streamed CSV exports.
  • Hackathon email switches.
  • Event crew.
  • Attendance confirmation.

Event crew.

  • Crew tickets are free and stay outside capacity, payment holds and teams.
  • Crew have a join link, a public section, check-in details, ID badges, designed tickets and crew expense claims.

Attendance confirmation.

  • Guests get an "Are you still coming?" email and the /attend page, plus reminders and share links.
  • A "No" on a paid place follows the refund policy, opens a refund request once the policy window has closed, or is held for the organiser.

Judging rounds (Space Works design).

  • Several rounds, each with its own rubric and judge panel.
  • Judges are assigned balanced or with seeded random assignment (preview, apply, re-roll), with optional fresh eyes.
  • A round can be finalised with an exception reason, which freezes its counted reviews.
  • Finalists advance and are published; awards come only from final-round finalists.
  • Judges can recuse themselves.
  • Single-round events behave as before: the planner output is byte-identical.

Mentor Desk (Space Works design).

  • MENTOR role with the staff authenticator check.
  • A help queue with leases and expiry.
  • Assignments per mentoring round: manual, round-robin, skill match or seeded random.
  • Conflicts carry over into judging.
  • Office hours, mentoring rounds and private nominations.

Volunteer shifts (Space Works design).

  • Shifts with a waitlist and approval.
  • Shift attendance kept separate from event check-in.
  • Hours verified by a second person.
  • Contributor certificates.

Multi-stage admission (any event, free or paid).

  • Stages, each with its own reviewer panel and seeded assignment.
  • Decision batches, then offers with a respond-by date.
  • "Accept & pay": a place is never confirmed unpaid, and on teams the leader pays.
  • FIFO waitlist promotion; applications are retained and private.

Per-stage reimbursements.

  • Policies for judging rounds, mentoring rounds, admission stages (accepted applicants only), crew (by kind) and confirmed participants.
  • Per-person caps, budgets and claim windows.
  • Terms are frozen at submission and eligibility is re-checked at approval.

Mobile API (/api/mobile/v1; the app itself is not built yet).

  • A Yuyu-compatible base with sign-in protections and staff MFA.
  • Paid tickets, orders, claims, entry fees, prizes, check-in lists, crew, attendance, rounds, the Mentor Desk, volunteers, admission and reimbursements.
  • Idempotent writes with path-bound keys, and bounded request bodies.
  • An OpenAPI contract with drift tests.

In-app guide /help.

  • About 130 articles, rewritten for Space Works.
  • Search, role-gated sections and screenshots.

Decisions (owner)

  • Port everything from Yuyu, plus the Space Works extras, following the Space Works design for mentors, judging and volunteers.
  • Admission works on any event, free or paid; paid teams are leader-pays only.
  • Reimbursements are per stage across all stage types; admission-stage policies pay accepted applicants only.
  • Every stage has organiser-chosen people, with reproducible random assignment.
  • docs/manual is kept unchanged.
  • The authenticator check is required for every staff grant.

Deferred (in docs/FUTURE_PHASES.md): per-member team payment, the award/reservation reimbursement flow, the native apps, and a Google mobile nonce.

Split-hosting fix found by CI (please review). In split hosting, a page render reads a dozen or more views from the API, and each read counted against the visitor's 300/min global API limit. A few quick dashboard changes then broke the page. The website's authenticated view reads (gateway key checked) now skip that bucket, as a single-server render does. Peer actions and every other API request keep their limits (proxy.ts, docs/DEPLOYMENT_SPLIT.md, unit-tested). The remaining CI fixes were test-only:

  • the trusted client-IP header and Origin on direct API posts in the new e2e specs;
  • stale headings and button names;
  • integration tests kept off CI's S3/SMTP;
  • Playwright traces uploaded on failure.

Before merging (action required)

  1. Reserved organisation slugs. Run this on production:
    SELECT slug FROM "Organisation" WHERE lower(slug) IN ('attend','crew','help','order','submit','team','verify-mfa');
    Rename any matches first. See docs/production-operations.md, "Reserved organisation addresses".
  2. Judging rounds migration. Close judging, or use the Recreate strategy, while upgrading an instance that is already judging.
  3. Hackathon emails default to off for existing events.
  4. MFA_ENCRYPTION_KEY must be identical on every replica. It now also keys mobile idempotency and payment return links.
  5. Merging bumps VERSION to 0.11.0, which publishes release images.

Checks

Run locally on dev at the head of this PR (Windows 11, Node 24):

Check Result
npm run lint, npx next typegen && npx tsc --noEmit 0 errors
npm run test:unit 2804 passed. The only failures are the known Windows-only vps-installer and docker-entrypoint tests (they pass on Linux CI), plus a load timeout in check-production-env that passes when run alone
npm run test:integration, on a database migrated from scratch 807 / 807 (142 files)
prisma migrate deploy → db:verify → reserved-slug check pass. Schema drift against the migrations: none
npm run build and npm run build:frontend (split frontend, no database/mail/Redis/storage code) pass
Frontend catalog and help index --check verified (120 views, 380 actions)
e2e (dev server): public smoke, dashboard 404s, help, help gating, series going count, crew, attendance 28/29 on a cold dev server. The one miss was a help article's first compile exceeding a 5 s expectation; it passed 4/4 on a warm re-run
Yuyu weekly-check logic 0 pending

The full e2e suite (including the payment specs and test:e2e:production) was not run locally because of memory limits on this machine. CI runs it.

Every branch was independently reviewed before merging. The final audit covered decisions, Yuyu parity, the end-to-end hackathon flow, Mentor Desk, judging, money, security/privacy/tenancy and deploy safety; all of its findings were fixed in fix/audit-* and chore/deploy-hardening.

🤖 Generated with Claude Code

twilighty-abhi and others added 30 commits October 25, 2026 23:50
Unit, PostgreSQL integration (local receiver) and production e2e coverage,
the quality gate flag for the e2e receiver, and docs/WEBHOOKS.md with
event types, payloads, signature verification, retries and SSRF rules.

(cherry picked from commit c113c53)

Ported to Space Works Events: Events' docs kept with the webhook text added (the beat delivers every 15 seconds; PENDING_PAYMENT status; renumbered migration); the scheduler test covers webhooks as a beat job; the integration suite drives Events' approve and cancel actions.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Events can register people in teams: capacity counts teams and solo
registrants as units, organisers approve, reject and promote whole teams,
leaders share a join link or add members, and an optional "looking for a
team" list matches solo registrants with teams. Includes the migration,
team pages, organiser Teams view, badges/CSV/export/report support, and
unit and PostgreSQL integration tests. Design contract: docs/TEAMS.md.

(cherry picked from commit 44ff7fb)

Ported to Space Works Events: migration renumbered; the team rules went into Events' split code (lib/rsvpCore/{reserve,submit,manual,team}.ts, validators/teams.ts, outbox/team.ts, email/team.ts in the Space Works email layout, the RSVP lifecycle/cancel/admin actions, the split check-in client); team names reach Events' server-rendered badges; every new page reads through a lib/views reader for the split frontend; teams and tickets are not combined yet (refused both ways); Yuyu's mobile API files dropped and the docs reworded; PENDING_PAYMENT added to team status types.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… pages

- Team settings in the Create Event dialog
- Solo registrants can join a team through a join link (event-locked move
  that frees their place and promotes the waitlist)
- Paged, searchable organiser Teams view; check-in lookup matches team names
- Team-specific register wording; seeker form resets after save
- Playwright teams journey; ticket, team and join pages send no-store in
  production (proxy.ts)

(cherry picked from commit 512934c)

Ported to Space Works Events: the join page's signed-in registration and the Guests panel's team page load through their lib/views readers; the ticket page's join form comes from lib/teamTicketSection; capability-page caching lives in lib/pageSecurity (both proxies); Events' Create Event and registration card keep their paid-ticket wording for individual events; the team name search went into Events' check-in lookup; mobile API docs dropped.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Whole-team approve, reject and promote, automatic team promotion, moves
into a team, leader removals, disbanding, organiser team deletion and its
undo now emit one registration.status_changed per member after commit,
with the same minimal payload as individual RSVPs; a leader adding a
member emits registration.created. setTeamStatusTx and
promoteOldestWaitlistedUnit return the emissions, and team cores collect
them in teamTransaction and emit once the transaction has committed.

Team undo snapshots now carry each member's custom check-in list records,
and restoring a team deletion (or a last-member deletion) recreates them
for lists that still belong to the event, like the single-RSVP undo.

(cherry picked from commit c535f46)

Ported to Space Works Events: promotion webhooks go into Events' cancel and organiser-remove actions (rsvp-admin helper returns them for emitting after commit); docs keep Events' wording without the mobile API.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entrants (teams or solo registrants) submit projects within a window;
a new JUDGE collaborator permission gives judges an isolated scoring
workspace with conflict-of-interest checks; organisers manage a weighted
rubric, judge assignments, disqualification, awards and a ranked results
table with CSV export, and publish results to a public results page and
optional project gallery with an entrant email. Design contract:
docs/HACKATHON.md.

(cherry picked from commit 6f0ac88)

Ported to Space Works Events: migrations renumbered; JUDGE flags live in Events' eventCapabilities; validators/outbox/email split into lib/validators/judging.ts, lib/outbox/judging.ts and lib/email/judging.ts (Space Works email layout); every judging page and panel reads through lib/views readers (submission editor view loaded by readers, so SubmissionSection is a plain component); the ticket page's submission card is computed in its loader; solo projects are retired in the shared move-into-team helper; submission snapshots go through Events' rsvp-admin; /submit links are capability pages (no-store, robots); mobile API parts dropped.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- proxy.ts: /submit/{token} and /account/submissions/{eventId} get the same
  private no-store Cache-Control as ticket and team links.
- Integration tests: join-by-link applies the solo project rules and still
  emits registration webhooks; one team undo snapshot restores the project,
  judging record and check-in list records, and older snapshot formats still
  restore; a JUDGE-only collaborator is refused by canManageRegistrationsFor,
  RSVP actions, check-in lists, email drafts, webhooks and the attendee CSV.
- Docs: judging rows and the JUDGE grant in PERMISSIONS.md; managing judging
  stays owner/admin (not MANAGE_REGISTRATIONS), recorded in HACKATHON.md;
  judging webhook events listed in FUTURE_PHASES.md.

(cherry picked from commit b4e80c1)

Ported to Space Works Events: the capability-page rule lives in lib/pageSecurity (/submit and /account/submissions added); Events' permission matrix keeps its own rows and gains the JUDGE column and judging rows; the integration tests drive Events' Server Actions.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The team management page (/team/:token) rendered with no site chrome: no
  header, no main landmark for the skip link, and no side gutter on phones.
  It now uses Yuyu's site chrome like /ticket/:token.
- Arrow keys in the registration form's "How are you taking part?" group
  dropped keyboard focus out of the dialog when moving to "Join with a link",
  because the form remounted. The team choice now stays mounted.
- Resetting the team management link emailed nothing, so a leader who lost the
  tab lost the team page. The new link is now queued to the leader
  (team-created, reason "reset").
- The solo-policy dropdown ("Can people sign up without a team?") had no
  accessible name in Create event and Publishing.
- /ticket/:token (now showing Join a team and the seeker list) sent the
  site-wide strict-origin-when-cross-origin Referrer-Policy header; it now
  sends no-referrer like the team pages.
- A confirmed incomplete team was told the organisers can't approve it, on
  events without approval; it is now told how many members to add.
- The join page promised "Register below" when the team was full or
  registration closed, and hid the "already registered" hint after closing
  although joining with an existing registration still works then.
- The check-in desk lookup on team events said "Name or email" though it also
  matches team names.

(cherry picked from commit e99815a)

Ported to Space Works Events: the reset email went into lib/email/team.ts and lib/outbox/team.ts; the team-aware search label into the split check-in client; Events' RsvpForm (with tickets) hides the fields while waiting for a join link.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Organiser Teams view: Approve on an incomplete team stays focusable
  (aria-disabled) and is described by visible helper text via
  aria-describedby instead of a title tooltip.
- Team page: the "Looking for a team" empty state says when everyone is
  already invited (or has a pending request) instead of "No one is looking".
- Register dialog: the "Join with a link" note has one icon and says the link
  opens the team's page to register straight into it.
- Create Event dialog: team sizes drop native min/max so only the inline
  message shows; it is a polite live region describing the invalid input,
  focus moves to that input, and the duplicate dialog alert is gone (server
  validation unchanged).
- Declined teams: Rename, join/management link resets, Make leader and
  "looking for members" are hidden on the team page and refused in the
  lib/core/teams.ts cores under the event lock; removing members, disbanding
  and leaving still work. docs/TEAMS.md updated.
- Public event page: the going/places count refreshes after a successful
  registration (router.refresh after /api/rsvp revalidates), for team and
  individual events alike.
- Tests: e2e assertions for each UI change in tests/e2e/teams.spec.ts and an
  integration test for declined-team refusals in teams.db.test.ts.

(cherry picked from commit a679581)

Ported to Space Works Events: kept Events' Create Event dialog and registration card (paid tickets), with the refresh after registering.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- /submit/{token} (and /team/{token}) rendered with no site chrome: no
  header, no main landmark for the skip link, no phone gutter. Both now use
  Yuyu's site chrome like /ticket/{token}.
- robots.txt did not disallow /submit/, unlike every other bearer-capability
  path; it does now (ROUTES.md updated).
- Judge workspace lost keyboard focus: opening a submission unmounted the
  focused list button (focus fell to <body>) and "All submissions" did the
  same. Focus now moves to the project heading, and back to the project's
  button on return.
- saveJudgeReviewAction wrote the scores before validating the comment, so an
  over-long comment returned an error yet left the scores changed. The review
  input is now validated before any write.
- The Settings tab's "Put it on your website" snippets read window.location
  during the hydrating render, a server/client text mismatch (intermittent
  React #418 on the Settings tab, where submissions are turned on). The
  origin is now read with useSyncExternalStore after hydration.
- Turning submissions on from Settings moved the card to the new Judging tab
  with only "Judging settings saved."; the toast now says where it went.
- New tests/e2e/judging.spec.ts covers the core journey: submissions on,
  rubric, team and solo submissions, a JUDGE-only judge scoring by keyboard,
  close, rank, award, publish/unpublish/republish without duplicate emails.

(cherry picked from commit d146a43)

Ported to Space Works Events: Events already had the hydration-safe origin and /submit/ in robots; /submit/ added to the site chrome prefixes; the hydration test uses Events' EventManageMore props.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit a25ffae)

Ported to Space Works Events: the unit counts went into Events' view loaders (event header, Analytics, super-admin activity as a plain record) and the split dashboard-home rows/cards; mobile API files dropped.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Submissions on a draft/offline/cancelled event: the judging settings card
  warns while the switch is on (with a link to Overview for drafts) and the
  save toast repeats it; entrants' tickets on a draft or offline event say
  when submissions open instead of showing nothing, and the editor's window
  line no longer says "Submissions aren't open right now".
- Results publish hint follows the judging status: open judging first (not
  started), close judging to finalise (open), pick winners and publish (closed).
- The organiser's Score tab uses organiser copy: an empty rubric links to the
  rubric editor on Setup, and "not started" says to open judging there.
- Judges tab separates "Invited judges" (JUDGE grants, with an empty state
  linking to Settings) from "Organisers who can also judge"; the sub-tab count
  is invited judges only.
- Tab strips scroll the active tab into view inside the row (never the page)
  on mount and on change, smooth unless reduced motion: shared underline tabs,
  link tabs and the dashboard sub-tabs (useActiveTabIntoView).
- Disqualify lists the awards the project holds and, after publication, warns
  that the live results page changes; reinstating now confirms and says lost
  awards are not given back.
- Judging CSV review times are ISO 8601 in the event timezone with an explicit
  offset, and the header names the zone ("Review submitted (Asia/Kolkata)").
- "The destination stream closed early." is React aborting a streamed render
  when the browser disconnects (Next.js logs it with a digest); onRequestError
  no longer turns it into an ops alert. Documented in production-operations.
- Tests: tests/unit/judging-panel-copy.test.ts,
  tests/unit/instrumentation-request-error.test.ts, CSV timezone in the
  judging integration suite, and new e2e steps in tests/e2e/judging.spec.ts.

(cherry picked from commit e24da7a)

Ported to Space Works Events: the not-live submission card comes from lib/teamTicketSection; the shared window phase is reused by lib/submissionEditorView.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A REJECTED team or registration got the generic "only confirmed
registrations" refusal and a notice saying the section would open once
confirmed. Refuse with a declined message instead and tell the entrant.

(cherry picked from commit ceb6c69)

Ported to Space Works Events: the declined flag goes through lib/submissionEditorView (Events loads the editor view in readers).

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After the team-registration merge the TEAM-event header shows a
"Places taken" detail line, pushing the judging sub-tab row a few pixels
below an 844px viewport. The test is about the active tabs being scrolled
into their rows, so assert horizontal containment within the row instead.

(cherry picked from commit 5bc6272)

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teams.spec.ts and judging.spec.ts both counted through 198.51.100.x in
parallel workers. With the team-registration e2e additions the ranges
overlap, so both specs share a per-IP action limit and judging publish
was refused with "Too many judging updates". Use 203.0.113.x instead.

(cherry picked from commit 35d9726)

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Server-side paging for every large list on team, guest, check-in and
judging screens. Each page goes through a read-only action that re-checks
its screen's permission, scopes by event and organisation, uses the search
rate-limit bucket, and orders with a unique id tiebreak.

- Guests · Teams: 50 at a time with Load more, server search and status filter.
- Guests · People: 50 at a time (createdAt, id keyset) via loadAttendeesPage; filters, search and chip counts on the server; 250 cap removed; series page shares the list.
- Export emails: new GET /api/exports/events/:eventId/attendee-emails for every match of the current filters (attendee-CSV access, 404 on denial, audited with filter names only); ExportEmailsButton removed.
- Move to team picker: searchMoveTargets, teams with room only, 20 at a time on a (nameKey, id) keyset; 200 cap removed.
- Invites: numbered pages of 50 with email search via loadInvitesPage (MANAGE_INVITATIONS).
- Check-in desk and venue station: Recent check-ins 25 at a time (checkedInAt, id) with Load more and refresh merging; CSV links to the full attendee export for exporters only.
- Custom check-in list roster: 100 at a time with server search and per-chip counts (CHECK_IN).
- Team page and ticket page: seekers, teams looking for members, invitations and requests 20 at a time with search.
- Account → Registrations: numbered pages of 50 per section.
- Judge Score list: 25 at a time with To score / In progress / Reviewed counts and search; visibility, assignments and conflicts still applied in SQL.
- Judging → Submissions: 50 at a time with server status filter and search.
- Judging → Results: numbered pages of 50 over the whole ranking (ranks and ties unchanged); Awards picker searches ranked SUBMITTED projects 20 at a time; Judges progress from aggregates.
- Public project gallery: 24 per page with ?page= and ?q=, page canonical URLs, searches not indexed; public results bounded by award limits.
- Shared PageNumberPager (44px targets) and useLoadMoreFocus: Load more / Show more move focus to the first new row.
- Docs (TEAMS, HACKATHON, DESIGN, FEATURES, PERMISSIONS, ROUTES, SECURITY, MOBILE_API, CHANGELOG) and unit plus PostgreSQL integration tests.

(cherry picked from commit 445d6f0)

Ported to Space Works Events: Events' view readers carry the paging (series, check-in desk and station, account registrations, projects); the account paging helpers live in the frontend-safe lib/accountLimits.ts; Recent check-ins paging is a hook in the split check-in desk with a thin loadRecentCheckIns action; docs condensed to Events' DESIGN/SECURITY layout.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add tests/e2e/pagination.spec.ts: on a seeded large event, Guests · People
pages 50 at a time with whole-event chip counts, Load more and the server
search (name and email) reach a guest past page one; Guests · Teams loads 50
then the rest; Judging → Results uses numbered pages of 50 with ranks
computed over the whole ranking; Guests · Invites uses numbered pages of 50
with an email search. List it in docs/TESTING.md.

(cherry picked from commit 40b972e)

Ported to Space Works Events: TESTING.md entry folded into Events' prose paragraph.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…core next)

- Automatic judge assignment: preview then apply an even, deterministic
  plan of N judges per submitted project from the picked pool (invited
  judges by default, organisers only when ticked), never a conflicted
  judge; keep or start over; apply re-plans under the event lock, refuses
  a changed plan, sets judgesSeeAll=false and reviewsPerProject, and is
  audited (JUDGES_AUTO_ASSIGNED). New Event.reviewsPerProject (1..20,
  CHECK) and ScoreNormalization enum (migration 20261025000000_judging_ops).
- Judging -> Progress (owner/admin): summary with an editable review
  target (audited JUDGING_REVIEW_TARGET_UPDATED), projects below target,
  per-judge completion, outlier scores and stale reviews, numbered pages
  of 25.
- Fairer scores: raw and z-score adjusted results with an owner/admin
  switch (audited JUDGING_SCORE_NORMALIZATION_CHANGED); Results, Awards
  and the CSV (new Raw score / Adjusted score columns) follow the choice.
- Score next project: server-picked next project for a judge within
  their visibility, auto-advance after submitting, "all caught up" state.
- Fix (review): automatic assignment now adopts work judges started
  without an assignment while judges saw every project (scores, drafts,
  submitted reviews) as assignments that count towards N, so applying
  (which switches to assigned-only) never locks a judge out of their own
  work or over-assigns already reviewed projects; without see-all,
  submitted unassigned reviews still count and their judge isn't re-picked.
  The preview, toast and audit report the adopted count.
- Fix (review): a "Start over" re-run on an unchanged event no longer
  removes and re-adds the same assignments; pairs in both lists stay put.

(cherry picked from commit 33c32e5)

Ported to Space Works Events: migration renumbered to 20261104030000_judging_ops; scoreNormalizationSchema in lib/validators/judging.ts; the Progress tab loads through Events' JudgingPanel view reader.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Saved views: SavedView model and migration (name/params CHECKs), address-bar
  filters with per-list allow-lists, a Views menu (presets, own and shared
  views, save, manage, copy link) on People, Teams, Submissions, Results and
  Progress; permission re-checked per list, scoped by event and organisation,
  audited SAVED_VIEW_* with the list name only.
- New server filters with counts: People "Not checked in", Teams "Looking for
  members", Submissions "Not submitted" (entrants without a submitted
  project), Results "Unscored" / "Fewer than 2 judges", Progress list chips.
- Filtered CSV exports: People, Teams (new route, TEAM_LIST_EXPORTED) and
  Judging Results/Submissions (JUDGING_SUBMISSIONS_EXPORTED) export exactly
  the filters showing, streamed server-side in bounded batches through a
  shared formula-safe helper; empty 404 on denial or bad parameters.
- Feedback responses load as numbered pages of 25 instead of stopping at 250.
- Remove next-auth SessionProvider (it forced a client re-render that left a
  hidden duplicate dashboard panel); header, footer and RSVP form read the
  session from a small store.
- Fix: TEAM_LIST_EXPORTED and JUDGING_SUBMISSIONS_EXPORTED are listed on the
  super-admin Security page (SECURITY_AUDIT_ACTIONS) and in docs/SECURITY.md;
  a unit test now requires every export route's audit action in that list.
- Fix: the Results "Export CSV" link carries the active score chip
  (scored=unscored/underTwo), so the file matches the table.
- Fix: the Submissions "Not submitted" view can be exported (entrants), with
  an entrant-specific reason when disabled.
- Fix: saved-view menus read the viewer's own views separately from shared
  ones, and the dashboard reads each list on its own, so many shared views
  can no longer hide a user's own views or another list's views.
- Docs: FEATURES, PERMISSIONS, TEAMS, HACKATHON, ROUTES, DESIGN, TESTING,
  SECURITY and CHANGELOG.

(cherry picked from commit 1009c92)

Ported to Space Works Events: migration renumbered to 20261104040000_saved_views; saved views and feedback paging load through Events' panel view readers; the attendee CSV keeps Events' canAccessEvent check and ticket/order columns; organiser team access widened to canManageRegistrationsFor (Yuyu 5ba1897) so registration grant holders who see Guests · Teams can act on it; single badge/ID-card downloads stay off the Security page list.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seed saved-views guests with emails slugged from the full name, so the two
team leaders no longer share an attendee key, and match the Views menu by its
own label so the organisation and account menus (seeded "Views ..." names)
no longer collide with it.

(cherry picked from commit d3fbff3)

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gallery: Newest (default), A-Z and Awards first orders, an Award winners
filter and award badges once results are published, all in SQL with id
tiebreaks and validated, shareable search params. GET form via next/form
and links (works without JS), count in a polite live region, canonical
without filter params. Results page gains the same search when it lists
more than 10 winners. Visibility rules and public DTOs unchanged.

(cherry picked from commit d201974)

Ported to Space Works Events: gallery and results query parsing runs in Events' projects/results view readers.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Gallery (/{org}/{event}/projects): ?q= search, ?sort=newest|az|awards
  (Newest default, id tiebreaks) and ?filter=winners with its count, all in
  SQL (lib/galleryQuery.ts); filter, awards order and badges only once
  results are published, so winners are never revealed early (builds on
  d201974).
- Results page (/{org}/{event}/results): the same ?q= search once more than
  10 winners are listed.
- Fix (review): gallery award badges put the place first and wrap instead of
  truncating (new Chip `wrap` option), so a long award name can no longer
  hide which place a project won.
- Fix (review): PublicAwardDto carries `isTop`, set before the results search
  filters awards, so a searched view never gives a lower-ranked award the
  headline accent style.
- Tests: unit render test for AwardBadges, integration check that `isTop`
  survives filtering, e2e badge text updated.

(cherry picked from commit 947e7ba)

Ported to Space Works Events: Chip wrap applied to Events' Space Works chip sizing.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wait for the search itself (q in the URL and the updated count) before
reading gallery cards, since the URL already carried the filter and sort,
and wait for the no-JS result cards before reading them.

(cherry picked from commit a009c86)

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Times just after midnight rendered as "0:05 AM" on the server under
Node 20 (its ICU resolves en-GB `hour12: true` to the h11 cycle) but as
"12:05 AM" in the browser, a hydration mismatch (React #418) on every
dashboard list showing such a time. CI runs Node 20 and ran the e2e
suite at 00:0x UTC, so the filtered-exports and judging specs failed.
Format with `hourCycle: "h12"` in formatTime and the sign-up closing
summary, and pin the 12 o'clock hour in unit tests.

(cherry picked from commit 2c22352)

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Webhook requests now carry x-spaceworks-event-id, x-spaceworks-event-type
and x-spaceworks-signature with the SpaceWorks-Webhooks/1 user agent, and
the signing-secret key label is spaceworks-org-webhook-signing-secret-v1
(nothing was encrypted under the old label). Settings copy, the webhook
guide and the verification example say Space Works.

The team join and ticket pages read saved RSVPs under Events' swe:rsvp:
prefix (RSVP_STORAGE_PREFIX, shared with the RSVP form); the ported
yuyu:rsvp: keys never matched, so a saved ticket was never offered.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The agent guide lists team registration, hackathon judging, webhooks,
custom check-in lists and saved views. Ported docs no longer refer to
Yuyu's mobile API, which Events doesn't have, and TEAMS.md says who
manages teams (owners, admins and MANAGE_REGISTRATIONS holders). The
judging-permissions unit test no longer leaves unused variables.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Splits the 32 files the Yuyu port left over the ESLint max-lines limit
into focused parts behind their original import paths: lib/core/teams,
lib/core/judging and lib/core/judgingOps become folders re-exporting the
same public names; "use server" action files keep every exported action
(helpers and the restoreRsvp body moved to sibling folders); large
components move state into hooks and sections into sibling folders; the
teams and judging integration suites split by topic with shared helpers
(402 integration and 86 e2e tests listed before and after). The event
broadcast delivery moved to lib/outbox/broadcast.ts. Unit tests that
load lib/core/teams now mock lib/eventAccess.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eck-in lists

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he ticket designer

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shaan-Shoukath and others added 29 commits October 28, 2026 18:55
Disqualifying can refund the entry fee in full; reinstating then left the project submitted unpaid, and a disqualified project can't pay again. Reinstatement is refused with a clear message while a fee order exists and no longer holds money; a waived or kept fee reinstates as before.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the buyer had already asked (ALREADY_REQUESTED), the check stored no refundRequestId, so a later No said the request was already handled and organisers got an extra notice. The open request's id is now stored on the check.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claims are read, capped and withdrawn by registration, so a transferred ticket handed the previous holder's claims to the new holder. A transfer is refused while the holder's claims are SUBMITTED, APPROVED or PAID, with OPEN_CLAIMS for the web action and the mobile API (new error code).

Adds tests/integration/money-audit-orders.db.test.ts covering this and the cancel, refund-request reopen and attendance fixes.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds secretEncrypted, manageTokenHash, keyHash, requestHash and emailHash
to the forbidden view and mobile DTO fields, and a unit test that reads
prisma/schema.prisma so a new such column must be listed.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ord write

Retried refunds, settles, payment links, orders, checkouts, cancels, transfers, refund requests, claims (both POSTs), claim decisions, check-ins and organiser decisions could act twice. authedMobileRoute, eventRoute and judgingRoute take an idempotent option (idempotentRequest: body read once, multipart matched by fields and files, ticket tokens never kept); POST /rsvp needs a key when signed in. Exemptions (sign-in routes, read-only POSTs, the attendance link, PATCH and DELETE) are documented and pinned by tests/unit/mobile-idempotency-coverage.test.ts; the OpenAPI marks the header required. Tests: a replayed partial refund refunds once, a replayed order registers once.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner decision: JUDGE, MENTOR and REVIEW_APPLICATIONS collaborators file and withdraw their own claims on that event only. Mobile fileClaims and the claim routes (anyGrant) now match the web action; the judge, mentor and reviewer workspaces show the Finance claim form. FINANCE.md, MOBILE_API.md and TESTING.md updated.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…awards, frozen counted reviews, recusal)

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drops the stale "Deferred" mobile lines from .github/yuyu-reviewed.txt
(every one is named on a cherry-pick line; the weekly check logic still
reports 0 pending), notes the deliberate Android fingerprint variable
name, says the native apps are not built yet and lists the sync 3
deferrals in FUTURE_PHASES.md.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ites, desk-owed cancels, waive refusal, transfers with claims, staff claims)

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he new fields

The ineligible check-in and list check-in read the registration's own and
team's application status in the same query instead of a second count;
the crew schema test covers withdrawApplication; the migration impact
table lists 20261119030000_admission_audit_fixes.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew, accepted-only stage claims, admission fresh eyes, data export, secret fields)

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/check-reserved-slugs.mjs runs read-only before the VPS updater's migrator and in the Helm migration Job, so an organisation on /attend, /crew, /help, /submit or /team stops the deploy with nothing migrated; Render gets a documented manual check. /order and /verify-mfa are reserved too, with a test that every top-level route is.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ile keys and audit-fix columns

Adds the WebhookEndpoint/WebhookDelivery tables, the payout ledger and ticket-design owner triggers, the four mobile and webhook unique keys, the remaining CHECK families, and the 20261119* columns and cascade.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Admission notices stay urgent: their offers carry a deadline to accept.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ayment return-link keys; ci: frontend catalog check

Explains what a mismatched key does to those HMACs across replicas.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Complete 0.11.0 changelog with upgrade notes and the migration list; sync 3 plan records the final audit.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rage, 0.11.0)

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s for the frontend

The judging panel imported FINALIZE_REASON_MAX_LENGTH from lib/core/judgingRounds,
which pulls the database client into the split frontend build. The limit now
lives in lib/validators/judgingRounds.ts. CI builds the split frontend too.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…I hosting runs

New specs hard-coded cf-connecting-ip, which CI's hosting runs ignore (they
trust x-real-ip), so the whole suite shared one rate-limit bucket. Direct
/api/rsvp posts now send Origin (the split proxy refuses unsafe requests
without it), the split spec passes the registrations view its searchParams,
and the request-actor test pins in-process check-in delivery.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/SMTP, upload e2e traces

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A split-hosted page render reads a dozen or more views from the API, each
counted against the visitor's 300/min global bucket, so a few quick dashboard
changes broke the page. Authenticated peer view reads now skip it, as a
single-server render does; peer actions keep their own limit. Also match the
Invite button's full accessible name in the team matching spec.

Co-Authored-By: Shaan-Shoukath <shaanshoukath4522@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Shaan-Shoukath
Shaan-Shoukath merged commit 40d1e27 into main Oct 28, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants