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>
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>
Add ability to define completely different evaluation criteria for each
competition category. Admins toggle "Separate Criteria per Category" in
round config, then configure criteria independently via tabbed editor.
- Schema: add nullable `category` to EvaluationForm with updated constraints
- Config: add `perCategoryCriteria` boolean to EvaluationConfigSchema
- Helper: new `findActiveForm()` with category-aware resolution + fallback
- Backend: getForm, upsertForm, getStageForm, startStage all category-aware
- AI services: use project category for form lookup in summaries + ranking
- Export/ranking: merge criteria from all active forms for cross-category reports
- Admin UI: toggle switch + tabbed criteria editor with per-category builders
- Jury UI: auto-selects correct form based on project category (invisible to juror)
- Fully backwards compatible: toggle defaults OFF, existing forms unchanged
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Schema:
- Drop 4 dead models: OverrideAction, NotificationPolicy, AssignmentException, AdvancementRule
- Drop 2 dead enums: OverrideReasonCode, AdvancementRuleType
- Drop 3 stale columns: Project.roundId, ConflictOfInterest.roundId, Evaluation.version
- Remove 3 back-relation fields from User, Assignment, Round
Code:
- Fix 6 COI queries in assignment.ts + 1 in juror-reassignment.ts
(roundId filter → assignment.roundId after column drop)
- Remove orphaned Project.roundId write in project.ts createProject
- Remove advancementRules include from round.ts getById
- Remove AdvancementRule from RoundWithRelations type
- Clean up seed.ts (remove advancement rule seeding)
- Clean up tests/helpers.ts (remove dead model cleanup)
- Add TODO comments on user delete mutations (FK violation risk)
Migration: 20260308000000_drop_dead_models_and_stale_columns
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- z.any() replaced with z.record(z.string()) on webhook headers
- availabilityJson typed with z.array(z.object({ start, end }))
- Frontend webhook headers converted from array to Record before API call
- Docker HEALTHCHECK added to Dockerfile (health endpoint already existed)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Prisma: connection_limit=10, pool_timeout=30 on DATABASE_URL in both compose files
- Graceful shutdown: SIGTERM/SIGINT forwarded to Node process in docker-entrypoint.sh
- testEmailConnection: replaced real email send with transporter.verify(), simplified UI to single button
- NotificationLog.userId index: confirmed already present, no change needed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Webhook HMAC: added consumer verification JSDoc with Node.js example using crypto.timingSafeEqual
- CSRF rate limiting: 20 requests/15min per IP on NextAuth /csrf endpoint
- Renamed withRateLimit to withPostRateLimit/withGetRateLimit for clarity
- 429 responses include Retry-After header
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add escapeHtml() helper and apply to all user-supplied variables in 20+ HTML email templates
- Auto-escape in sectionTitle() and statCard() helpers for defense-in-depth
- Replace 5 instances of incomplete manual escaping with escapeHtml()
- Refactor bulkInviteTeamMembers: batch all DB writes in $transaction, then send emails via Promise.allSettled with concurrency pool of 10
- Fix inner catch block in award-eligibility-job.ts to capture its own error variable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Files on the observer project detail page are now grouped by round
(e.g., "Application Intake", "Semi-Finals Document Submission") instead
of shown in a flat list. Uses FileViewer's existing groupedFiles prop.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix 0% completion on SUBMISSION pipeline cards — now based on teams
with uploads / total projects instead of evaluation completions
- Add page count, detected language, and non-English warning indicator
to Recent Documents list items
- Add project avatar (logo) to document and project list rows
- Make document rows clickable into project detail page
- Remove team name from project list (none have names), show country only
- Rename "Teams Submitted" to "Teams with Uploads" for clarity
- Add "See all" link to Projects section → /observer/projects
- Rename section from "Project Teams" to "Projects"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extends CountryDisplay component usage to all remaining pages that showed
raw country codes: mentor dashboard/projects, jury competitions/awards,
admin awards/project detail, applicant team, and project-list-compact.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix project list returning empty for users with both SUPER_ADMIN and
JURY_MEMBER roles (jury filter now skips admins) in project, assignment,
and evaluation routers
- Add CountryDisplay component showing flag emoji + name everywhere
country is displayed (admin, observer, jury, mentor views — 17 files)
- Add countdown deadline banner on applicant dashboard for INTAKE,
SUBMISSION, and MENTORING rounds with live timer
- Remove quick action buttons from applicant dashboard
- Fix competition timeline sidebar: green dots/connectors only up to
current round, yellow dot for current round, red connector into
rejected round, grey after
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Convert all Back buttons platform-wide (38 files) to use router.back()
for natural browser-back behavior regardless of entry point
- Add read-only view for submitted evaluations in closed rounds with
blue banner, disabled inputs, and contextual back navigation
- Add auth audit logs: MAGIC_LINK_SENT, PASSWORD_RESET_LINK_CLICKED,
PASSWORD_RESET_LINK_EXPIRED, PASSWORD_RESET_LINK_INVALID
- Learning Hub links navigate in same window for all roles
- Update settings descriptions to reflect all-user scope
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Observer projects: default sort by status (rejected last), sortable status column
- Observer projects: search by country, institution, geographic zone
- Observer project detail: vertical timeline connectors between rounds
- Fix React key warning in ExpandableJurorTable and FilteringReportTabs
- Fix ScoreBadge text always white for better contrast on all backgrounds
- Remove misleading /30 denominator from heatmap juror reviewed count
- INTAKE stats: show Start-ups, Business Concepts, Countries (not States/Categories)
- DiversityMetrics: extractCountry() for country-only display in charts
- Fix nested button hydration error in filtering report mobile view
- Color project titles by outcome in filtering report (green/red/amber)
- Redesign CrossStageComparisonChart: funnel viz + metrics table with attrition %
- Center doughnut chart in StatusBreakdownChart
- Remove redundant RoundTypeStatsCards from evaluation report
- Move evaluation tab bar below overview header, rename to "Juror Assignments"
- Dev email override system (DEV_EMAIL_OVERRIDE env var)
- Session refresh on role change without re-login
- Role switcher in user dropdown menu
- formatCategory() utility for consistent category display
- Activity feed max height constraint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Communication hub now supports selecting multiple rounds when sending
to Round Jury or Round Applicants (checkbox list instead of dropdown)
- Recipients are unioned across selected rounds with deduplication
- Recipient details grouped by round when multiple rounds selected
- Added MOPC logo above "Welcome back" on login page
- Added matt@letsbe.solutions as seeded APPLICANT account
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New roundLanguageSummary query in file router aggregates per-round document
language data from existing detectedLang/langConfidence fields
- Document Languages card on round overview tab shows analysis status and
flags non-English documents grouped by project with confidence scores
- Green border when all documents are English, amber when issues detected
- Project names link to project detail page for easy navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Audit log: user names link to /admin/members/{id}, entity IDs link to
relevant detail pages (projects, rounds, awards, users)
- Communication hub: expandable "View Recipients" section in sidebar shows
actual users/projects that will receive the message, with collapsible
project-level detail for applicants and juror assignment counts
- Email link type selector: choose between no link, messages inbox, login
page, or invite/accept link (auto-detects new vs existing members)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Version guard:
- Replace API route with prebuild-generated public/build-id.json
- Captures build ID on first load, only notifies on mismatch
- Fixes false positive refresh prompts from env mismatch
Members table (applicants):
- Show project name + round badge instead of round name + state
- Red badge for rejected, gray for withdrawn, green for passed,
outline for active rounds
- Include projectName in applicantRoundInfo from backend
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- /error?error=Verification: shows "Link Expired" with amber icon,
auto-redirects to /login?expired=1 after 5 seconds
- /accept-invite: expired/invalid/already-accepted tokens auto-redirect
to login after 4 seconds with "Redirecting..." message
- /login: amber banner when ?expired=1 explains the link expired and
prompts to sign in again or request a new magic link
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Inject NEXT_PUBLIC_BUILD_ID at build time via next.config.ts
- /api/version static route returns current build ID
- VersionGuard client component checks on tab focus + every 5 min
- Shows persistent toast with Refresh button (no auto-reload)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New previewRecipients query shows live project/applicant counts as you
compose, with per-state breakdown for Round Applicants
- Exclude Rejected/Withdrawn checkboxes filter out terminal-state projects
- Compose form now has 2/3 + 1/3 layout with always-visible recipient
summary sidebar showing project counts, applicant counts, state badges
- Preview dialog enlarged (max-w-3xl) with split layout: email preview
on left, recipient/delivery summary on right
- Send button now shows recipient count ("Send to N Recipients")
- resolveRecipients accepts excludeStates param for ROUND_APPLICANTS
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
For APPLICANT users in the admin members list, the Assignments column now
shows the project's current round name and state badge (Active, Pending,
Rejected, etc.) instead of "0 assigned".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Sort files by round sortOrder first (via requirement.round.sortOrder)
- Admin project detail now uses grouped file view with round headers
- Files without a round requirement appear under "General" group
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Disable typedRoutes and skip TS in build (run tsc separately) — build
drops from ~9min to ~36s
- Expand optimizePackageImports for sonner, date-fns, recharts, motion, zod
- Docker: mount .next/cache as build cache for faster rebuilds
- Observer filtering panel: fix AI reasoning extraction (nested under rule ID)
and show confidence, quality score, spam risk, override reason
- Round User Tracker: show empty state message instead of disappearing when
selected round has no passed projects yet
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Admin projects: status summary badges are clickable to filter by round state
with ring highlight, opacity fade, and clear button
- Add roundStates filter param to project.list backend query
(filters by latest round state per project, consistent with counts)
- Observer status dropdown now uses ProjectRoundState values
(Pending/In Progress/Completed/Passed/Rejected/Withdrawn)
- Observer status derived from latest ProjectRoundState instead of stale Project.status
- Observer CSV export fetches all matching projects, not just current page
- Add PENDING and PASSED styles to StatusBadge component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace flat team names list with proper cards showing roles and badges
- Hide TeamMembers from metadata display, remove Withdraw from header
- Add inline-editable project description (admin-toggleable setting)
- Move applicant feedback visibility from per-round config to admin settings
- Support EVALUATION, LIVE_FINAL, DELIBERATION round types in feedback
- Backwards-compatible: falls back to old per-round config if no settings exist
- Add observer team tab toggle and 10 new SystemSettings seed entries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Team tab to observer project detail (configurable via admin settings)
- Move applicant jury feedback visibility from per-round config to admin settings
- Add per-round-type controls: evaluation, live final, deliberation
- Support anonymous LiveVote and DeliberationVote display for applicants
- Add fine-grained toggles: scores, criteria, written feedback, hide from rejected
- Backwards compatible: falls back to old per-round config if admin settings not set
- New admin settings section under Analytics tab with all visibility controls
- Seed new SystemSettings keys for observer/applicant visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add updateTeamMemberRole mutation for admins to change team member roles
- Allow any team member (not just lead) to change project logo
- Add visible "Add logo"/"Change" label under logo for discoverability
- Pre-check email existence before sending magic link (show error)
- Add "forgot which email" contact link on login page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Messages were sent in a tight for-loop with no throttling. Now uses
sendBatchNotifications (10/batch, 150ms per email, 500ms between
batches) and fires in the background so the admin gets instant response.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Award-specific rounds (e.g. Spotlight on Africa) were mixed into the
main pipeline as a flat list, making them look like sequential steps
after Deliberation. Now they render in their own amber-tinted card
sections below the main pipeline, each with a header showing award
name, pool size, eligibility mode, and status.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix impersonation by bypassing useSession().update() loading gate with direct session POST
- Fix dashboard account counter defaulting to latest round with PASSED projects
- Add clickToEnlarge lightbox for project logos on admin detail page
- Remove submission eligibility config (all passed projects must upload)
- Suppress CredentialsSignin auth errors in production (minified name check)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Status counts now show each project's latest round state only
(no more inflated counts from projects passing multiple rounds)
- Add pagination controls at top of projects, members, and observer lists
- Add sortable column headers to admin projects table (title, category,
program, assignments, status) and members table (name, role, status,
last login)
- Backend: add sortBy/sortDir params to project.list and user.list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously counted distinct projects per state across ALL rounds,
inflating counts (e.g., 215 Passed when many were later rejected).
Now picks each project's latest round state (highest sortOrder) to
determine its current status.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix session auto-refresh causing re-render cascade by using useRef
instead of useState and delaying the refresh by 3s
- Make project logo clickable on dashboard and team page for team leads
with hover pencil overlay and ProjectLogoUpload dialog
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>