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>
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>