Add printable judge schedules - #655
Conversation
WalkthroughThe judge schedule flow now enforces organizer and competition-team access. It loads workout metadata, invitation-backed judges, and athlete rosters. The interface renders master schedules and selectable judge packets with responsive and printable layouts. ChangesJudge schedule printouts
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant CompetitionOrganizer
participant JudgesScheduleRoute
participant judgeSchedulingFns
participant JudgesScheduleContent
participant BrowserPrint
CompetitionOrganizer->>JudgesScheduleRoute: open judges schedule
JudgesScheduleRoute->>judgeSchedulingFns: request authorized schedule
judgeSchedulingFns-->>JudgesScheduleRoute: return enriched schedule data
JudgesScheduleRoute->>JudgesScheduleContent: render schedule
CompetitionOrganizer->>JudgesScheduleContent: select master or judge view
JudgesScheduleContent->>BrowserPrint: render current view for printing
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 87715c3b50
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
apps/wodsmith-start/src/routes/compete/$slug/-components/judges-schedule-content.tsx (1)
211-214: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueHoist
getMaxLanes(event)out of the heat loop.
getMaxLanesscans every heat of the event. Calling it per heat makes packet building O(heats²) per event. The value does not change inside the loop.♻️ Proposed refactor
for (const event of events) { + const maxLanes = getMaxLanes(event) for (const heat of event.heats) { - const maxLanes = getMaxLanes(event) const lanes = getLaneData(heat, maxLanes)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/wodsmith-start/src/routes/compete/`$slug/-components/judges-schedule-content.tsx around lines 211 - 214, Hoist the getMaxLanes(event) call in the event iteration so each event computes maxLanes once before entering its heat loop. Reuse that value when calling getLaneData for every heat, preserving the existing packet-building behavior.apps/wodsmith-start/src/server-fns/judge-scheduling-fns.ts (1)
1610-1626: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winThe registration owner is always added to the roster, not only as a fallback.
The comment at Line 1559 states that individual registrations fall back to the registration owner. The code adds
registration.userIdtorosterUserIdsfor every registration, including team registrations. If the owner is a coach or a non-competing manager, the owner name appears in printed team rosters. Consider using the owner only whenteamUserIdsis empty, or update the comment to match the behavior.♻️ Proposed change
- const rosterUserIds = [ - ...new Set([registration.userId, ...teamUserIds]), - ] + const rosterUserIds = + teamUserIds.length > 0 + ? [...new Set(teamUserIds)] + : [registration.userId]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/wodsmith-start/src/server-fns/judge-scheduling-fns.ts` around lines 1610 - 1626, Update the roster construction in the registration mapping to include registration.userId only when teamUserIds is empty, while retaining all team athlete IDs for team registrations. Keep the existing deduplication, name mapping, and sorting behavior unchanged, and ensure individual registrations still fall back to the registration owner.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/wodsmith-start/src/routes/compete/`$slug/-components/judges-schedule-content.tsx:
- Around line 166-172: Update getCompetitorLabel to use a falsy fallback for
teamName so empty strings fall through to the first athlete name. Also update
the teamName checks in MasterLaneCell and PrintMasterEvent to treat empty
strings as absent, preserving roster display for valid team names.
- Around line 265-268: Update the judge packet filtering around
visibleJudgePackets to resolve selectedJudgeId against the currently available
judge packets, falling back to all packets when no packet matches the selected
membershipId. Pass this resolved selection value to the judge select control so
it always corresponds to an available option and never renders blank after
events reload.
---
Nitpick comments:
In
`@apps/wodsmith-start/src/routes/compete/`$slug/-components/judges-schedule-content.tsx:
- Around line 211-214: Hoist the getMaxLanes(event) call in the event iteration
so each event computes maxLanes once before entering its heat loop. Reuse that
value when calling getLaneData for every heat, preserving the existing
packet-building behavior.
In `@apps/wodsmith-start/src/server-fns/judge-scheduling-fns.ts`:
- Around line 1610-1626: Update the roster construction in the registration
mapping to include registration.userId only when teamUserIds is empty, while
retaining all team athlete IDs for team registrations. Keep the existing
deduplication, name mapping, and sorting behavior unchanged, and ensure
individual registrations still fall back to the registration owner.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: cfeda5b6-adef-45c5-888c-0ea88e34ea32
📒 Files selected for processing (7)
apps/docs/docs/how-to/organizers/event-day.mdapps/docs/docs/how-to/organizers/schedule-heats.mdapps/wodsmith-start/src/routes/compete/$slug/-components/judges-schedule-content.tsxapps/wodsmith-start/src/routes/compete/$slug/judges-schedule.tsxapps/wodsmith-start/src/server-fns/judge-scheduling-fns.tsapps/wodsmith-start/test/components/judges-schedule-content.test.tsxlat.md/organizer-dashboard.md
There was a problem hiding this comment.
All reported issues were addressed across 7 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/wodsmith-start/src/server-fns/judge-scheduling-fns.ts`:
- Around line 1688-1705: Update the rosterUserIds construction near
confirmedAthleteNames so registration.userId is used only when
registration.athleteTeamId is null; when a team ID exists, preserve an empty
roster if it has no active memberships. Keep the existing teamUserIds handling
and downstream athlete-name processing unchanged.
- Around line 1411-1424: The forbidden branch guarded by canManageCompetition
and isCompetitionTeamMember must return an HTTP 403 response instead of throwing
a generic Error. Replace the throw in the judge-schedule access check with the
project’s established 403 error mechanism or a direct 403 response, preserving
the existing permission message.
In `@apps/wodsmith-start/test/components/judges-schedule-content.test.tsx`:
- Around line 52-66: Add exactly one nearby unique # `@lat`: reference to each of
the five tests in
apps/wodsmith-start/test/components/judges-schedule-content.test.tsx: lines
52-66 for the athlete-name fallback specification, 68-101 for selection reset,
103-147 for invitation-backed packet, 149-169 for blank division brief, and
171-213 for delimiter-safe brief grouping. Ensure every reference targets a
different test-specification leaf.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 8ac92c1f-62c8-4bd6-9180-c8bf21176828
📒 Files selected for processing (5)
apps/wodsmith-start/src/routes/compete/$slug/-components/judges-schedule-content.tsxapps/wodsmith-start/src/routes/compete/$slug/judges-schedule.tsxapps/wodsmith-start/src/server-fns/judge-scheduling-fns.tsapps/wodsmith-start/test/components/judges-schedule-content.test.tsxlat.md/organizer-dashboard.md
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/wodsmith-start/src/routes/compete/$slug/judges-schedule.tsx
- apps/wodsmith-start/src/routes/compete/$slug/-components/judges-schedule-content.tsx
- lat.md/organizer-dashboard.md
| const canManageCompetition = await hasTeamPermission( | ||
| competition.organizingTeamId, | ||
| TEAM_PERMISSIONS.MANAGE_COMPETITIONS, | ||
| ) | ||
| const isCompetitionTeamMember = competition.competitionTeamId | ||
| ? await isTeamMember(competition.competitionTeamId) | ||
| : false | ||
|
|
||
| if (!canManageCompetition && !isCompetitionTeamMember) { | ||
| throw new Error( | ||
| "FORBIDDEN: You don't have permission to view judge schedules", | ||
| ) | ||
| } | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline apps/wodsmith-start/src/server-fns/judge-scheduling-fns.ts --items all
rg -n -C 3 \
'FORBIDDEN|status:\s*403|throw new Response|throw new Error' \
apps/wodsmith-start/src/server-fnsRepository: wodsmith/thewodapp
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the server function and nearby imports/exports.
sed -n '1,40p' apps/wodsmith-start/src/server-fns/judge-scheduling-fns.ts
sed -n '1360,1475p' apps/wodsmith-start/src/server-fns/judge-scheduling-fns.ts
# Locate TanStack Start server function error/status handling and similar 403 patterns.
rg -n -C 4 \
'throw new (HttpFailError|AssertionError|Error)\(' \
'HttpFailError' \
'status:' \
'Response\.status' \
'FORBIDDEN|Forbidden|403' \
apps/wodsmith-start/src apps/wodsmith-start/package.json pnpm-lock.yaml 2>/dev/null | head -n 500Repository: wodsmith/thewodapp
Length of output: 39636
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Package manager files:"
fd -a -i '^(package.json|pnpm-lock.yaml|package-lock.json|bun.lockb|yarn.lock)$' . | sed 's#^\./##' | head -n 50
echo
echo "TanStack versions:"
rg -n '"`@tanstack/react-start`"|tanstack|`@tanstack/react-server`' apps/wodsmith-start/package.json pnpm-lock.yaml package.json 2>/dev/null | head -n 120 || true
echo
echo "HTTP handling symbols:"
rg -n --hidden --glob '!**/node_modules/**' \
'HttpFailError|Response\.status|response\.status|status:|Response\.json|throw new Response|catch \([^)]*\) =>|\.handler' \
apps/wodsmith-start/src app packages 2>/dev/null | head -n 400 || trueRepository: wodsmith/thewodapp
Length of output: 50374
🌐 Web query:
TanStack Start server functions throw HttpFailError status 403 createServerFn
💡 Result:
In TanStack Start, server functions (createServerFn) can sometimes default to an HTTP 500 status when an error is thrown, even if you intend to send a different status code like 403 [1][2]. This behavior often occurs because unhandled errors or specific error patterns cause the server runtime to override or ignore status codes set via setResponseStatus [3][2]. Key factors contributing to this behavior include: 1. Status Code Override: Historically, calling setResponseStatus(403) followed by throw new Error(...) would often result in a 500 response because the thrown error triggers the runtime to default to a 500 status [1][3][2]. Recent updates (such as those in pull request #6785) aim to preserve the status code when an error is thrown after setResponseStatus is called, provided the environment and router versions are up to date [3][2]. 2. Handling Errors as Responses: If you want to return a specific HTTP status code (like 403) from a server function, the recommended approach is to ensure the response is handled correctly by the framework. Using throw notFound() is supported for 404s [4][5], but for custom statuses like 403, you may need to ensure your error handling strategy aligns with the current version's expectations for status propagation [1][3]. 3. Development Environment Issues: Some users report that unexpected 500 errors occur during SSR or due to misconfigured error handling in development mode [6][7]. Ensuring you are using the latest version of @tanstack/start and @tanstack/router is critical, as several issues regarding error serialization and status code preservation have been actively addressed in recent releases [3][2]. To mitigate this, ensure that: - You are on the most recent version of TanStack Start and Router to benefit from fixes regarding status code preservation [3][2]. - You are using setResponseStatus(403) within the context of the server function or middleware before throwing your error [2][8]. - If you continue to see 500 errors, consider whether the error being thrown is an unhandled exception that the framework is forced to catch and wrap as a 500 [1][6]. For centralized error handling, TanStack Start does not currently have a fully automated global error handler equivalent to some other frameworks; developers often implement manual try-catch blocks or middleware to handle specific domain errors and translate them into appropriate HTTP responses [9][5].
Citations:
- 1: Start: Allow throwing custom errors with non-500 status from server functions TanStack/router#4698
- 2:
setResponseStatusdoes not work anymore withthrow new Error(...)TanStack/router#5107 - 3: fix(start-server-core): preserve status code when handler throws after setResponseStatus TanStack/router#6785
- 4: https://tanstack.com/start/latest/docs/framework/react/guide/server-functions.md
- 5: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/err-server-errors.md
- 6: https://www.answeroverflow.com/m/1454728752171978907
- 7: @tanstack/start issue - An error outside of the Outlet can't be caught and doesn't show error boundry TanStack/router#7133
- 8: https://github.com/TanStack/router/blob/main/packages/start-server-core/skills/start-server-core/SKILL.md
- 9: https://www.answeroverflow.com/m/1352064744810352711
🌐 Web query:
"TanStack Start" "createServerFn" "HttpFailError" "403"
💡 Result:
In TanStack Start, the HttpFailError (or similar generic HTTP error) often surfaces when a server function encounters an unhandled exception, resulting in a default 500 status code being returned to the client [1][2][3]. If you are specifically encountering a 403 Forbidden error, it is typically the result of authorization logic failing within your server function or its middleware [4][5]. TanStack Start server functions are same-origin RPC endpoints, and improper configuration or failed authentication/authorization checks will prevent execution [6][7]. Key points regarding 403 errors and error handling in TanStack Start: 1. Authorization Failures: If you use middleware to protect your server functions (e.g., checking session tokens or roles), you may be explicitly throwing a 403 status. If your implementation uses something like throw redirect({ statusCode: 403 }), note that this may result in unexpected behavior where the client receives a 200 OK with a JSON body indicating the redirect/status, rather than an actual HTTP 403 response [4]. 2. Error Handling Limitations: A common pain point in TanStack Start is that throwing standard Errors within a createServerFn handler generally defaults the HTTP response to 500, even if you attempt to call setResponseStatus(403) beforehand [3]. Because TanStack Start serializes errors, standard thrown errors are often captured as generic "500 Internal Server Error" responses by the framework [8][3]. 3. Recommended Patterns: - Use dedicated error types or helper functions if your library version supports them [9]. - For authorization, implement middleware that validates the request context (e.g., user session, roles) before the handler executes [7][5]. - If you need to return specific HTTP status codes (like 403) to the client, you may need to avoid throwing standard errors and instead return a Response object with the desired status if the framework's current architecture allows, or rely on custom middleware that sets the status appropriately before the request completes [9][3]. If you are debugging a specific HttpFailError, check the server-side logs for the actual stack trace, as the client-side error object is often sanitized and only contains minimal information [9][10].
Citations:
- 1: @tanstack/start issue - An error outside of the Outlet can't be caught and doesn't show error boundry TanStack/router#7133
- 2: https://www.answeroverflow.com/m/1454728752171978907
- 3: Start: Allow throwing custom errors with non-500 status from server functions TanStack/router#4698
- 4: start-basic-auth example is not secure TanStack/router#3348
- 5: https://makerkit.dev/docs/tanstack-prisma/admin/rbac-permissions
- 6: https://tanstack.com/start/latest/docs/framework/react/guide/server-functions
- 7: https://github.com/TanStack/router/blob/edf55759/docs/start/framework/react/guide/middleware.md
- 8: https://www.answeroverflow.com/m/1314891448952553594
- 9: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/err-server-errors.md
- 10: fix(start): Send error messages from server functions TanStack/router#2625
Return an HTTP 403 from forbidden schedule access.
throw new Error(...) is not tied to a response status for this server function, and this path currently does not show an explicit 403 mapping. Use an error mechanism that preserves a 403 response, or throw a 403 response directly.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/wodsmith-start/src/server-fns/judge-scheduling-fns.ts` around lines 1411
- 1424, The forbidden branch guarded by canManageCompetition and
isCompetitionTeamMember must return an HTTP 403 response instead of throwing a
generic Error. Replace the throw in the judge-schedule access check with the
project’s established 403 error mechanism or a direct 403 response, preserving
the existing permission message.
Source: Coding guidelines
| ...new Set( | ||
| teamUserIds.length > 0 ? teamUserIds : [registration.userId], | ||
| ), | ||
| ] | ||
| const confirmedAthleteNames = rosterUserIds | ||
| .map((userId) => { | ||
| const athlete = athleteUserMap.get(userId) | ||
| return [athlete?.firstName, athlete?.lastName] | ||
| .filter(Boolean) | ||
| .join(" ") | ||
| }) | ||
| .filter(Boolean) | ||
| const athleteNames = [ | ||
| ...new Set([ | ||
| ...confirmedAthleteNames, | ||
| ...parsePendingTeammateNames(registration.pendingTeammates), | ||
| ]), | ||
| ].sort((a, b) => a.localeCompare(b)) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Do not use the registration owner as a team-roster fallback.
When registration.athleteTeamId exists but has no active memberships, Line 1689 adds registration.userId to the roster. A team registration owner can be an organizer or payer rather than an athlete. Use the owner fallback only when athleteTeamId is null.
Proposed fix
- const rosterUserIds = [
- ...new Set(
- teamUserIds.length > 0 ? teamUserIds : [registration.userId],
- ),
- ]
+ const rosterUserIds = [
+ ...new Set(
+ registration.athleteTeamId ? teamUserIds : [registration.userId],
+ ),
+ ]📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ...new Set( | |
| teamUserIds.length > 0 ? teamUserIds : [registration.userId], | |
| ), | |
| ] | |
| const confirmedAthleteNames = rosterUserIds | |
| .map((userId) => { | |
| const athlete = athleteUserMap.get(userId) | |
| return [athlete?.firstName, athlete?.lastName] | |
| .filter(Boolean) | |
| .join(" ") | |
| }) | |
| .filter(Boolean) | |
| const athleteNames = [ | |
| ...new Set([ | |
| ...confirmedAthleteNames, | |
| ...parsePendingTeammateNames(registration.pendingTeammates), | |
| ]), | |
| ].sort((a, b) => a.localeCompare(b)) | |
| ...new Set( | |
| registration.athleteTeamId ? teamUserIds : [registration.userId], | |
| ), | |
| ] | |
| const confirmedAthleteNames = rosterUserIds | |
| .map((userId) => { | |
| const athlete = athleteUserMap.get(userId) | |
| return [athlete?.firstName, athlete?.lastName] | |
| .filter(Boolean) | |
| .join(" ") | |
| }) | |
| .filter(Boolean) | |
| const athleteNames = [ | |
| ...new Set([ | |
| ...confirmedAthleteNames, | |
| ...parsePendingTeammateNames(registration.pendingTeammates), | |
| ]), | |
| ].sort((a, b) => a.localeCompare(b)) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/wodsmith-start/src/server-fns/judge-scheduling-fns.ts` around lines 1688
- 1705, Update the rosterUserIds construction near confirmedAthleteNames so
registration.userId is used only when registration.athleteTeamId is null; when a
team ID exists, preserve an empty roster if it has no active memberships. Keep
the existing teamUserIds handling and downstream athlete-name processing
unchanged.
| it("falls back to an athlete name when a team name is empty", () => { | ||
| const events = scheduleEvents() | ||
| const lane = events[0]?.heats[0]?.laneAssignments[0] | ||
| if (lane?.registration) lane.registration.teamName = "" | ||
|
|
||
| render( | ||
| <JudgesScheduleContent | ||
| competitionName="Mountain Throwdown" | ||
| events={events} | ||
| timezone="America/Denver" | ||
| />, | ||
| ) | ||
|
|
||
| expect(screen.getAllByText("Avery Athlete").length).toBeGreaterThan(0) | ||
| }) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a unique @lat: reference to each new test.
Each new test needs exactly one nearby # @lat: comment. Each reference must point to a different test-specification leaf.
apps/wodsmith-start/test/components/judges-schedule-content.test.tsx#L52-L66: Add the athlete-name fallback specification reference.apps/wodsmith-start/test/components/judges-schedule-content.test.tsx#L68-L101: Add the selection-reset specification reference.apps/wodsmith-start/test/components/judges-schedule-content.test.tsx#L103-L147: Add the invitation-backed packet specification reference.apps/wodsmith-start/test/components/judges-schedule-content.test.tsx#L149-L169: Add the blank division-brief specification reference.apps/wodsmith-start/test/components/judges-schedule-content.test.tsx#L171-L213: Add the delimiter-safe brief grouping specification reference.
📍 Affects 1 file
apps/wodsmith-start/test/components/judges-schedule-content.test.tsx#L52-L66(this comment)apps/wodsmith-start/test/components/judges-schedule-content.test.tsx#L68-L101apps/wodsmith-start/test/components/judges-schedule-content.test.tsx#L103-L147apps/wodsmith-start/test/components/judges-schedule-content.test.tsx#L149-L169apps/wodsmith-start/test/components/judges-schedule-content.test.tsx#L171-L213
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/wodsmith-start/test/components/judges-schedule-content.test.tsx` around
lines 52 - 66, Add exactly one nearby unique # `@lat`: reference to each of the
five tests in
apps/wodsmith-start/test/components/judges-schedule-content.test.tsx: lines
52-66 for the athlete-name fallback specification, 68-101 for selection reset,
103-147 for invitation-backed packet, 149-169 for blank division brief, and
171-213 for delimiter-safe brief grouping. Ensure every reference targets a
different test-specification leaf.
Source: Coding guidelines
Summary
Why
Organizers were manually assembling judge assignments and team rosters in spreadsheets before an event. This gives floor staff printable schedules that work without requiring every judge to sign in.
Validation
pnpm test -- test/components/judges-schedule-content.test.tsxpnpm type-checkpnpm lintpnpm type-checklat checkSummary by cubic
Adds a print center with a master heat/lane grid and individual judge packets (run sheets + briefs) so organizers can print event‑day schedules that work offline. Access is restricted to organizers and competition‑team members.
description,scheme,timeCapSeconds, and division‑specific scaling descriptions; lanes includeregistrationwithteamNameand fullathleteNames;getJudgesScheduleDataFnrequiresMANAGE_COMPETITIONSor competition‑team membership.Written for commit d8c82cd. Summary will update on new commits.
Summary by CodeRabbit
New Features
Documentation
Bug Fixes