Commit Graph

472 Commits

Author SHA1 Message Date
Matt
d0e7bfd60a feat: show active ranking score on each list row
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m20s
Each ranking row now displays a small chip with the score that's
currently driving the rank — the juror-balanced average when the
round's useBalancedRanking toggle is on, the raw juror average when
it's off. The chip is labeled 'Bal' or 'Raw' so the source is
unambiguous. Per-juror score pills stay alongside; full Raw +
Balanced detail still lives in the side panel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:54:13 +02:00
Matt
9db8312b96 feat: render project averages to two decimals
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m15s
Project-level averages (Raw + Balanced in the side panel, observer
project detail score card, observer preview dialog Avg Score) now
show two decimals (e.g. 8.33 instead of 8.0/8.3) so admins can see
the actual computed value. Per-juror individual scores keep one
decimal — they're submissions, not aggregates. ScorePill gains an
optional precision prop so call sites can opt into 2-decimal display
where the value is an aggregate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:32:52 +02:00
Matt
3b12078e04 feat: mount score explainer dialog in admin and observer surfaces
Adds the 'How scores are calculated' affordance to:
- the admin ranking dashboard side panel (next to the Avg Score card)
- the observer full project detail page (in the score card)
- the observer reports preview dialog (next to Evaluation Summary)

so all three audiences can open the same explainer dialog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:31:29 +02:00
Matt
b4f5189a8e feat: shared 'How scores are calculated' explainer dialog
Reusable component used by admin and observer surfaces. Covers the
algorithm, a five-step plain-language walkthrough, a worked example
with three jurors of different grading styles, edge cases, and why
both Raw and Balanced are always shown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:25:20 +02:00
Matt
ee68f8af41 feat: side panel shows per-juror baseline and balanced contribution
Extends the ranking router's roundEvaluationScores response with
per-juror grading stats (mean, stddev, count) plus the round's overall
mean/stddev. The side-sheet juror rows render 'typical X.XX →
contributes Y.YY' next to each Score badge whenever balanced is on,
making the z-rescaling visible per individual rather than only as a
project-level number.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:24:34 +02:00
Matt
664a682585 feat: side panel shows raw + balanced averages, list drops delta
Removes the per-row '⇢ X.X' annotation from the ranking list — the
list view stays clean. The side panel's stats area gains a combined
Avg Score card that shows Raw and Balanced side-by-side, with the
active one (per the round's toggle) bolded and tagged 'used for
ranking'. Pass Rate and Evaluators move below into a 2-col grid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:23:29 +02:00
Matt
e12f26092a feat: list sort respects useBalancedRanking toggle
The two existing sort sites (initial init + threshold cutoff) now read
from the local toggle. A second effect re-sorts the list when the
toggle flips, but only when no manual reorder is pinned to the
snapshot — persisted manual reorders always win, matching prior
behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:22:11 +02:00
Matt
387f84c338 feat: per-round balanced-scoring toggle in side sheet
A Switch at the top of the project side panel writes
useBalancedRanking onto Round.configJson via the existing round.update
mutation. The flip is shared across all viewers because the value
lives in the round's persisted config; hydration runs on every
roundData refetch so the UI converges quickly when another admin
flips it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:20:21 +02:00
Matt
0680a5d601 feat: add useBalancedRanking flag to round config schema
Defaults to true so existing rounds preserve current behavior; toggled
per-round from the ranking dashboard side panel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:19:15 +02:00
Matt
6f3e8885e0 feat: resolve observer project page round default and add selector
The observer full project page used to call getProjectDetail without
a round, getting cross-round contaminated stats. It now resolves a
default — the currently OPEN round the project is in, falling back
to the most recently CLOSED one — and renders a selector chip in
the score card whenever the project participated in more than one
candidate round. Initial selection respects the ?round= query param.

A new observer procedure (getProjectRoundsForObserver) returns the
project's open or closed rounds for the picker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:18:36 +02:00
Matt
cfd9dc6afe fix: scope observer reports preview dialog to selected round
Threads the active roundId through ProjectPreviewDialog and its two
callers (filtering tabs, expandable juror table). When a round is in
scope, the preview's stats card now matches the per-juror list and
the page-level round selector. The roundId prop is optional so the
component still works in any future caller that lacks round context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:17:18 +02:00
Matt
9a2c10a6f8 fix: scope admin ranking dashboard side-sheet stats to current round
The admin dashboard fetches its side-sheet detail from project.getFullDetail
(not analytics.getProjectDetail as the audit assumed), and that procedure
had the same cross-round contamination bug. Add an optional roundId to
its input, filter the SUBMITTED-evaluations query when provided, and pass
roundId from the dashboard's useQuery so the Avg Score / Pass Rate /
Evaluators card now matches the per-juror list rendered below it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:15:47 +02:00
Matt
97d1f2a3af fix: compute z-context per-round in edition-mode rankings rollup
Previously the edition-level branch of analytics.getProjectRankings
(programId mode) pooled every juror's evaluations across every round
into a single z-normalization context. A juror's mean and stddev are
not stable across round types — quick intake screening produces a
very different grading profile than a deep evaluation round, and
mixing them yields a meaningless personal calibration.

The rollup now groups points by roundId, computes one balance context
per round, and aggregates per-project as the unweighted mean of the
per-round balanced averages. roundId mode is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:14:30 +02:00
Matt
7147115918 fix: scope analytics.getProjectDetail by optional roundId
The procedure pulled every SUBMITTED evaluation for a project across
every round it ever participated in, then computed Avg Score / Pass
Rate / Evaluators from that pool. Meanwhile the per-juror list rendered
in the admin sheet filters to the current round, producing a card that
disagreed with the visible list. With roundId in the input, callers
opt into round-scoped stats; omitting it preserves the old aggregate
behavior for any caller that hasn't been updated yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:12:27 +02:00
Matt
260baf3a41 docs: add implementation plan for juror-balance toggle and scoping fixes
15 TDD-style tasks covering the round-scoping bug fixes for
getProjectDetail and getProjectRankings, the per-round toggle, the
side-panel deeper display, the shared score explainer dialog, and the
decimal display audit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 12:59:36 +02:00
Matt
64e7be2418 docs: add design spec for juror-balance toggle and round-scoping fixes
Captures the per-round toggle, side-panel deeper display, "How scores
are calculated" explainer dialog, and the cross-round contamination
fixes for getProjectDetail and getProjectRankings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 12:50:32 +02:00
Matt
901d9ba982 feat: rank projects by balanced score in ranking dashboard
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m0s
Initial ranking order, advancement cutoff line, and per-row "advancing"
highlight now all use the juror-balanced (z-score corrected) average,
falling back to the raw avgGlobalScore when no balanced score exists.
The init effect waits for evalScores so the sort has the data it needs.

Admin drag-reorders still take precedence — saved reorders override the
default sort exactly as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 15:33:56 +02:00
Matt
2e080a5d09 feat: lift round selector to reports page top-level
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m42s
Top-level selector in the URL (?round=...) drives every single-round tab
(Overview, Analytics, Juror Consistency, Diversity) and narrows the Pipeline
tab to the selected program. Cross-Round keeps its own multi-select because
it compares multiple rounds by design.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 18:39:18 +02:00
Matt
982d5193c5 feat: surface juror-balanced scores and AI calibration advisory
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m27s
Adds a shared juror-balancing utility (z-score normalization per juror,
rescaled back onto the raw 1-10 scale) and wires it into:

- Admin reports page: Top-10 project table now shows "Raw Avg" and
  "Balanced" columns side by side, and the summary stats row shows a
  balanced-average tile. Sort defaults to balanced so harsh and lenient
  graders no longer skew the ranking.
- Ranking dashboard: each project row shows a green/amber balanced-score
  chip next to the raw average when the two differ by ≥0.05, making it
  obvious when juror calibration moved a project's effective ranking.

Also adds AI Juror Calibration Advisory — a mutation that takes
anonymized per-juror stats, calls OpenAI, and produces a plain-language
explanation of the cohort's grading patterns plus per-juror severity
(normal / notable / outlier) with a one-sentence narrative. The advisory
describes the statistical balance that already runs; it does not
introduce a new weighting layer. Rendered as a panel in the Juror
Consistency tab when a specific round is selected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:19:00 +02:00
Matt
07dd7a0692 fix: scope analytics assignments to selected round
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m38s
getProjectRankings and getAllProjects in the analytics router were
loading every assignment for each project regardless of the selected
round, so per-round rankings in the admin reports were counting
evaluations from prior rounds and mixing their scores into averages.
A Round 2 view with 3 reviews per project was showing 5 evaluations
and a skewed average that included Round 1 data.

Filter the nested assignments include by the round (or program) the
request is scoped to, matching the pattern already used by the ranking
service and by getStatusBreakdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:07:26 +02:00
Matt
f36f68bbf9 fix: don't mark next round as current before advancement is declared
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m48s
The applicant timeline sidebar was treating any previous round with
status ROUND_CLOSED as "advanced past", which caused the Grand Finale
to show "You are here" as soon as the semi-final round closed — before
admins declared which teams actually advanced.

Require explicit PASSED/COMPLETED project state on every prior round
before marking the next one as current. A closed-but-unresolved round
now correctly keeps the applicant anchored to it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 14:39:43 +02:00
Matt
be4449e4ef fix: surface advance-type criterion in ranking yes/no counts
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m7s
The ranking dashboard showed 0/X Yes for every project in rounds using
the 'advance' criterion type because the matcher only looked for
type === 'boolean' with a "move to the next stage" label.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:03:26 +02:00
Matt
f37a9b49b5 test: add integration coverage for admin proxy evaluation
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m0s
Seven scenarios covering the new admin procedures:
- non-admin users are rejected on adminStart/adminSubmitOnBehalf
- admin can list a juror's assignments with COI flag surfaced
- admin can complete the full draft→autosave→submit cycle with the
  voting window already closed, confirming bypass works
- COI-declared assignments still block admin submission
- feedback minimum length is enforced on admin submit
- adminAutosave refuses to overwrite a SUBMITTED evaluation
- audit log captures admin id, juror id, and bypassedWindow flag

Also swaps a lucide icon alias (FileEdit → Pencil) in the juror
assignments page to avoid the deprecated alias form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:00:03 +02:00
Matt
9cb3b9de13 feat: admin can fill in evaluations on behalf of jurors
All checks were successful
Build and Push Docker Image / build (push) Successful in 11m54s
When a juror cannot connect during an evaluation round, an admin
can now submit evaluations for them.

Router — new admin procedures:
- adminStart / adminAutosave: create and save drafts for any juror.
- adminSubmitOnBehalf: submit bypassing ROUND_ACTIVE and voting-window
  checks. COI block and feedback/criterion validation still enforced.
  Audit log records both admin and juror IDs plus bypassedWindow flag.
- getJurorAssignmentsForRound: list a juror's assignments + eval state.

UI — two new admin pages under /admin/rounds/[roundId]/jurors/[userId]/:
- evaluate: list of pending + completed assignments, COI flagged.
- evaluate/[projectId]: evaluation form reusing the juror's scoring UI,
  with an "acting on behalf" banner and confirmation dialog before
  submit. Back button returns to the assignments list.

Entry point: FilePen icon on each juror row in JuryProgressTable.

Refactor: extracted the scoring form JSX into shared
EvaluationFormFields component so the juror page and the admin proxy
page render identical inputs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 16:41:14 +02:00
Matt
fd4f6dde16 fix: humanize category label in unassigned queue row
Queue payload exposes the field as `category` (aliased from
`competitionCategory`), not `competitionCategory` itself, so the
previous lookup was always undefined and every project rendered as
"No category". Now shows "Startup" / "Business Concept" correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 16:40:54 +02:00
Matt
f1955b68f9 feat: show vote status on jury dashboard and add logos to award-master
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m47s
- Jury dashboard now shows "Submitted" badge (green) with "Edit
  Rankings" button when juror has already voted, instead of always
  showing "Vote Now" — prevents confusion about whether vote saved
- Award-master page now shows project logos next to project names
- Backend getMyAwardDetailEnhanced now returns logo URLs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:09:34 -04:00
Matt
3a6a9a2b45 feat: add inline file viewer and project logos to award voting
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m24s
- Replace custom download-only file list with full FileViewer component
  that supports inline preview (PDF, video, images, Office docs),
  open in new tab, and download
- Add project logos next to project names in award voting cards
- Backend now returns full file metadata (mimeType, size, pageCount,
  language detection) and project logo URLs
- Award jurors can access files for eligible projects (access control
  already added in prior commit)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 12:49:28 -04:00
Matt
0987d49817 feat: show awards on jury dashboard and add project details to award voting
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m27s
- Jury dashboard now shows active award voting banners with project
  count, deadline countdown, and direct link to vote
- Award voting page shows full project details: description, team
  members, tags, and downloadable files in expandable cards
- Award jurors can now download files for eligible projects (added
  awardJuror access check to file.getDownloadUrl)
- Backend query enhanced to include files and team members

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 12:28:53 -04:00
Matt
acd75427b3 feat: add jury group import to special awards and fix juror dropdown
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m43s
The juror dropdown was always empty because the page requested
perPage: 200 but the user.list API caps at 100 (Zod validation).
Fixed to perPage: 100 with role filter for JURY_MEMBER/AWARD_MASTER.

Added "Import from Jury Group" section to the awards juror tab:
select a jury group, see members with checkboxes (already-assigned
shown as disabled), bulk-add selected members via bulkAddJurors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 11:39:34 -04:00
Matt
eb1e8a7870 fix: case-insensitive email matching in auth and password reset
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m10s
Email lookups used findUnique (case-sensitive on PostgreSQL) but user
input was lowercased, causing login failures for users with mixed-case
emails stored in the DB (e.g. Laurent_Faure@dietsmann.com). Also
normalized 7 affected emails to lowercase on the production DB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 11:04:31 -04:00
Matt
ec69706bc7 fix: include direct round files in finalization doc count
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m36s
Files uploaded by admins with roundId but no requirementId were not
counted in the finalization page Docs column.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:37:52 -04:00
Matt
c7488b3e07 fix: save roundId on admin file upload and group assignments by round
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
The admin upload flow accepted roundId but never wrote it to the
ProjectFile record, causing all admin-uploaded files to appear under
"General". Fixed the create call, the listByProject filter, and the
listByProjectForStage grouping to also use the direct roundId field.

Jury assignments on the project detail page are now grouped by round
with per-round completion counts instead of a flat list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:20:48 -04:00
Matt
2344f2e4ff fix: drop stale unique index blocking per-category evaluation forms
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m35s
The category migration used DROP CONSTRAINT but the index was created
with CREATE UNIQUE INDEX, so it was never actually removed. This
prevented saving business concept criteria when startup criteria
already existed at the same version number.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 13:57:36 -04:00
Matt
498baa7e01 feat: add clickable projects and doc counts to finalization page
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m36s
Project names now link to their detail page on all finalization tabs.
Submission/intake rounds show a docs submitted/required column.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:38:05 -04:00
Matt
a51241f7ff feat: add multi-role editor to member detail page
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m42s
Adds an "Additional Roles" checkbox section below the primary Role
dropdown. Admins can now grant a user multiple dashboard views (e.g.,
Observer + Jury Member) without changing their primary role. The roles
array is saved alongside the primary role and used by the role switcher.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 21:08:32 -04:00
Matt
2d6cee394f feat: add bulk invite to jury group page + widen member search role filter
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m48s
Adds bulkInviteMembers procedure to juryGroup router and integrates
BulkInviteForm into the jury group members tab. Also removes the
JURY_MEMBER-only filter from the user search — any user can now be
added to a jury group.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:37:25 -04:00
Matt
5537946b5a feat: add bulk invite form to award juror tab, widen role filter to all users
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m57s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:15:55 -04:00
Matt
29502a2b88 feat: add Decision Mode dropdown to award edit page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:13:51 -04:00
Matt
b901047418 fix: show jury group selector at top of assignments tab when none assigned
Previously showed "Select a jury group below" but the selector was
buried at the bottom (or not rendered at all when no jury was assigned).
Now shows the actual dropdown + "New Jury" button right where the
empty state message was.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:12:25 -04:00
Matt
22a08ef957 feat: add reusable BulkInviteForm component for multi-row name+email invites
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:11:45 -04:00
Matt
158eba416d feat: add auto-retry (3 attempts) for file uploads on flaky connections
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:10:52 -04:00
Matt
97d7f9b625 docs: add implementation plan for award UX fixes and bulk invite
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 19:36:20 -04:00
Matt
3854b6ff0c fix: auto-resolve failed migrations in docker entrypoint
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m25s
On startup, check for any failed migration in _prisma_migrations and
automatically mark it as rolled-back before running migrate deploy.
This prevents a partially-applied migration from permanently blocking
all future deployments. Also reduce max retries from 30 to 6.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:50:20 -04:00
Matt
d9f3b3d2a4 fix: make evaluation form category migration fully idempotent
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m59s
The migration partially applied (column added, then failed on DROP
CONSTRAINT). Make every statement idempotent with IF EXISTS / IF NOT
EXISTS so it can safely re-run after resolve --rolled-back.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:34:47 -04:00
Matt
ef2186e89a fix: make EvaluationForm constraint drop idempotent (IF EXISTS)
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
The migration failed on deploy because the constraint
EvaluationForm_roundId_version_key did not exist in the target DB.
Using DROP CONSTRAINT IF EXISTS makes this safe for databases where
the constraint was already removed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:34:01 -04:00
Matt
2864579e92 fix: type-safe route for award master dashboard links
All checks were successful
Build and Push Docker Image / build (push) Successful in 12m23s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:50:38 -04:00
Matt
8800f2bcc7 feat: add award master voting page with project detail, documents, scores, justification, and chair confirmation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:48:46 -04:00
Matt
0be8c5ecc7 feat: add chair toggle to admin award juror management
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:45:25 -04:00
Matt
fbc8b5165a feat: add award master tRPC procedures — enhanced detail, vote with justification, confirm winner, set chair
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:42:21 -04:00
Matt
9368c1221f feat: add award master dashboard page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:36:47 -04:00