Skip to content

[#574] Push Blade judging updates with SSE - #575

Open
DGoel1602 wants to merge 4 commits into
mainfrom
blade/instant-judging-updates
Open

DGoel1602 wants to merge 4 commits into
mainfrom
blade/instant-judging-updates

Conversation

@DGoel1602

@DGoel1602 DGoel1602 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Why

Judges could wait up to the next 30-second poll to see an organizer's announcement.
Blade now pushes a change notification when a judging transaction commits, then
refreshes the existing authorized views. The recorded local announcement appeared
in 0.973 seconds, compared with 29.987 seconds on main.

What

Add a tRPC SSE subscription to the judge workspace and command center. PostgreSQL
LISTEN/NOTIFY carries invalidations between app processes. Announcements, room and
access changes, saved schedule changes, resets, and finalized scores publish
within their transactions.

The stream rechecks access, sends keepalives, and refetches on reconnect. Offline
page refreshes pause until networking returns. Existing queries retain their
room/guest filtering; polling remains for deadlines and recovery. Scope is Blade
and its API implementation.

Closes #574.

Before and after

Organizer on the left; judge on the right. These are inline animations of the
reviewed browser recordings, at normal speed. The full polling wait and the
simulated network outage are shown. Startup footage was trimmed; the final
three-second result screenshot in each recording is explicitly labeled as a still.

Before — main at 361d10a5: the announcement waits for the next poll.

Before: organizer publishes an announcement; the judge waits almost 30 seconds

After — SSE: announcement, reassignment, and recovery from a network outage.

After: instant judging updates and automatic recovery after the judge goes offline

Action Before After
Publish announcement → visible to judge 29.987 s 0.973 s
Save reassignment → new room visible 0.939 s 0.864 s
Restore network → missed announcement visible Not recorded 0.214 s

These are single local observations. The baseline reassignment happened just
before a poll, so it was already quick. Exact timings were measured from the
button click to visible text, independently of video clocks. The actual SSE
notification arrived 223 ms after Publish and 151 ms after Save; the visible
result includes the subsequent refetch and rendering.

Full-size result screenshots

Announcement visible in the judge's view:

Judge announcement

Aurora reassigned to ENG 102:

Judge sees the new room

Missed announcement received after restoring networking, without a page reload:

Judge catches up after reconnecting

Recording setup and reproduction

Recorded September 16, 2026 in two independent Chromium sessions against a local
Next.js development server and disposable PostgreSQL database. People, projects,
and assignments are synthetic. Both versions use the same fixture and actions.
Discord was deliberately disconnected.

  1. Create an active hackathon with judging open, a rubric, two staffed rooms, and
    a future Aurora appointment in ENG 101. Sign an officer and a judge into
    separate browser contexts.
  2. Open the officer's Rooms tab and the judge's Projects page. Browse all rooms
    so Aurora stays visible after reassignment. Let the initial queries finish.
  3. Publish “Judges: please check the updated room assignment for Aurora.” Measure
    from Publish to that text appearing in the judge's view.
  4. Dismiss the banner, then reassign Aurora to ENG 102 from Schedule. Measure
    from Save to ENG 102 appearing in the judge's Aurora row.
  5. Put the judge browser offline, replace the announcement in the officer
    browser, wait at least 20 seconds, and restore networking. The judge should
    receive the missed announcement without reloading.

The baseline opened no SSE connection. The after recording used native
EventSource with no substituted transport. The endpoint returned HTTP 200,
text/event-stream, X-Accel-Buffering: no, and an initial invalidation.
Original WebMs, MP4s, screenshots, and timestamps are retained in this PR's
feature bundle for provenance; the comparison above is self-contained.

Test Plan

CI on a9ee0e7e: passed, including all 2,481 tests, database tests, migrations, lint, formatting, typechecking, and the full production build.

  • Repository pnpm format, pnpm lint, and pnpm typecheck: passed.
  • Initial focused tests: 25 API tests and 11 Blade tests passed.
  • CI exposed two existing page tests that assumed the workspace was the outer
    React element. Reproduced both failures locally and updated them to render the
    page and inspect workspace props. The original hackathon-selection/read-only
    assertions remain; both tests also check the listener's hackathon scope.
  • Full CI test command, pnpm exec turbo run test --filter='!@forge/db',
    against a fresh PostgreSQL 16 container: 2,481 tests passed across 351 files,
    including all 988 API tests and all 881 Blade tests. The database package has
    its own CI job, which passed on the initial PR run.
  • Full monorepo production build: 21 tasks passed, with no cached tasks.
    Ran pnpm build --env-mode=loose with temporary process values from
    .env.example, allowing those CI example values through Turbo. No environment
    files or build configuration were changed.
  • Four changed/new React components/pages passed strict analysis.
  • pnpm analyze:react:changed hits an existing parser error in the tRPC provider:
    Cannot read properties of undefined (reading 'type'). Reproduced on main at
    361d10a5; no check bypass was added.

Local workstation roots in the analyzer transcript are redacted. CodeRabbit's
advisory docstring-coverage suggestion was not applied: the transport helpers
already document transaction delivery, listener ownership, and reconnect behavior;
extra docstrings repeating handler names would add little.

Production proxy streaming and database connection mode still need verification;
the recordings use loopback HTTP. No deployment was run. Schedule solver progress
and private draft autosaves retain their existing refresh behavior.

Checklist

  • Database: no schema changes.
  • Environment Variables: no environment variables changed.

Co-authored-by: Codex <codex@openai.com>
@DGoel1602 DGoel1602 added Feature New Feature or Request Major Big change - 2+ reviewers required Blade Change modifies code in Blade app API Change modifies code in the global API/tRPC package labels Sep 16, 2026
@DGoel1602 DGoel1602 self-assigned this Sep 16, 2026
Co-authored-by: Codex <codex@openai.com>
@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

📝 Walkthrough

Walkthrough

Adds PostgreSQL LISTEN/NOTIFY judging notifications and a tRPC SSE subscription. Judging mutations publish hackathon-scoped changes inside transactions. Blade judge and admin pages subscribe, invalidate queries, refresh Server Components, and reconnect when online. Access is rechecked during streams. Existing polling remains for offline recovery and deadlines. Tests cover transport lifecycle, transaction behavior, permissions, guest access, reconnects, and rendering. Evidence records before/after timings and SSE responses.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🔵 Low · up to 6b706

Redacting the workstation paths avoids unnecessarily publishing local identity and directory details; this is localized and does not otherwise block the feature.

🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 17 files. (10 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (7 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #574 requires SSE updates for announcements, room and access changes, saved schedules, resets, and finalized scores. The PR adds judging.onChange, PostgreSQL LISTEN/NOTIFY, and notification …
Out of Scope Changes check ✅ Passed The changes stay within Issue #574 scope. They modify Blade judging pages, the API endpoint and judging write paths, client subscription handling, focused regression tests, and feature evidence. The d…
No Hardcoded Secrets ✅ Passed No changed file contains a recognizable hardcoded API key, password, token, private key, JWT, or connection secret. JUDGING_ACCESS_SECRET is read from authEnv, and tests generate it with `randomBy…
Validated Env Access ✅ Passed No pull-request changed file contains a direct process.env access. The added and modified Blade/API source files also contain no bracketed process["env"] access. Repository-wide raw accesses found…
No Typescript Escape Hatches ✅ Passed No changed TypeScript code uses the any type, @ts-ignore, @ts-expect-error, or non-null assertions. The only any match is the test matcher expect.any(Object), and all added ! usages are lo…
Title check ✅ Passed The title starts with the required issue number format, concisely describes the SSE judging update, and is 42 characters long.
Description check ✅ Passed The description clearly explains the SSE implementation, PostgreSQL notifications, behavior, evidence, testing, and remaining deployment validation.
Full details: Docstring Coverage

Explanation

Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 17 files. (10 skipped: 10 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch blade/instant-judging-updates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Co-authored-by: Codex <codex@openai.com>

@coderabbitai coderabbitai 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.

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
.forge/features/instant-judging-updates/evidence/react-analyzer-baseline.txt-2-3 (1)

2-3: 🔒 Security & Privacy | 🛡️ Analyzed with Security Review | 🟡 Minor | ⚡ Quick win

Information Disclosure

Reachability: Internal
Exploitability: Trivial
CWE: CWE-200 — Exposure of Sensitive Information to an Unauthorized Actor

Redact local paths from the committed analyzer evidence. Forge is a public monorepo, and this generated output exposes the local username rhygon and workstation directory layout. Replace absolute paths with repository-relative or redacted paths.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: QUIET

Plan: Advanced

Run ID: 56af8028-258a-44ab-ab2a-a33cd4482ceb

📥 Commits

Reviewing files that changed from the base of the PR and between 361d10a and 6b70662.

⛔ Files ignored due to path filters (16)
  • .forge/features/instant-judging-updates/evidence/after.gif is excluded by !**/*.gif
  • .forge/features/instant-judging-updates/evidence/after.mp4 is excluded by !**/*.mp4
  • .forge/features/instant-judging-updates/evidence/after/announcement.png is excluded by !**/*.png
  • .forge/features/instant-judging-updates/evidence/after/judge.webm is excluded by !**/*.webm
  • .forge/features/instant-judging-updates/evidence/after/organizer-schedule.png is excluded by !**/*.png
  • .forge/features/instant-judging-updates/evidence/after/organizer.webm is excluded by !**/*.webm
  • .forge/features/instant-judging-updates/evidence/after/reassignment.png is excluded by !**/*.png
  • .forge/features/instant-judging-updates/evidence/after/reconnected.png is excluded by !**/*.png
  • .forge/features/instant-judging-updates/evidence/before.gif is excluded by !**/*.gif
  • .forge/features/instant-judging-updates/evidence/before.mp4 is excluded by !**/*.mp4
  • .forge/features/instant-judging-updates/evidence/before/announcement.png is excluded by !**/*.png
  • .forge/features/instant-judging-updates/evidence/before/judge.webm is excluded by !**/*.webm
  • .forge/features/instant-judging-updates/evidence/before/organizer-schedule.png is excluded by !**/*.png
  • .forge/features/instant-judging-updates/evidence/before/organizer.webm is excluded by !**/*.webm
  • .forge/features/instant-judging-updates/evidence/before/reassignment.png is excluded by !**/*.png
  • packages/api/src/tests/root/__snapshots__/api-surface.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (27)
  • .forge/features/instant-judging-updates/evidence/README.md
  • .forge/features/instant-judging-updates/evidence/after/timings.json
  • .forge/features/instant-judging-updates/evidence/before/timings.json
  • .forge/features/instant-judging-updates/evidence/react-analyzer-baseline.txt
  • .forge/features/instant-judging-updates/evidence/sse-response.json
  • .forge/features/instant-judging-updates/pr-description.md
  • .forge/features/instant-judging-updates/spec.md
  • .forge/features/instant-judging-updates/srd.md
  • .forge/features/instant-judging-updates/status.md
  • .forge/features/instant-judging-updates/test-cases.md
  • apps/blade/src/app/_components/judging/judging-live-updates.tsx
  • apps/blade/src/app/_components/projects/judge-project-workspace.tsx
  • apps/blade/src/app/admin/judging/page.tsx
  • apps/blade/src/app/api/trpc/[trpc]/route.ts
  • apps/blade/src/app/judge/projects/page.tsx
  • apps/blade/src/tests/judging/prod-flow-regressions.test.tsx
  • apps/blade/src/tests/projects/judging-live-updates.test.tsx
  • apps/blade/src/trpc/react.tsx
  • packages/api/src/judging-access.server.ts
  • packages/api/src/routers/judging-schedule.ts
  • packages/api/src/routers/judging-scores.ts
  • packages/api/src/routers/judging.ts
  • packages/api/src/tests/integration/judging-access.test.ts
  • packages/api/src/tests/judging/realtime.test.ts
  • packages/api/src/trpc.ts
  • packages/api/src/utils/judging/evaluation-write.ts
  • packages/api/src/utils/judging/realtime.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Co-authored-by: Codex <codex@openai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API Change modifies code in the global API/tRPC package Blade Change modifies code in Blade app Feature New Feature or Request Major Big change - 2+ reviewers required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Push judging updates to connected Blade clients

1 participant