Comprehensive round system audit: fix 27 logic bugs, add manual project/assignment features, improve UI/UX
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m23s

## Critical Logic Fixes (Tier 1)
- Fix requiredReviews config key mismatch (always defaulted to 3)
- Fix double-email + stageName/roundName metadata mismatch in notifications
- Fix snake_case config reads in peer review (peerReviewEnabled was always blocked)
- Add server-side COI check to evaluation submit (was client-only)
- Fix hard-coded feedbackText.min(10) — now uses config values
- Fix binaryDecision corruption in non-binary scoring modes
- Fix advanceProjects: add competition/sort-order/status validations, move autoPass into tx
- Fix removeFromRound: now cleans up orphaned Assignment records
- Fix 3-day reminder sending wrong email template (was using 24h template)

## High-Priority Logic Fixes (Tier 2)
- Add project state transition whitelist (prevent invalid transitions like REJECTED→PASSED)
- Scope AI assignment job to jury group members (was querying all JURY_MEMBERs)
- Add COI awareness to AI assignment generation
- Enforce requireAllCriteriaScored server-side
- Fix expireIntentsForRound nested transaction (now uses caller's tx)
- Implement notifyOnEntry for advancement path
- Implement notifyOnAdvance (was dead config)
- Fix checkRequirementsAndTransition for SubmissionFileRequirement model

## New Features (Tier 3)
- Add Project to Round: dialog with "Create New" and "From Pool" tabs
- Assignment "By Project" mode: select project → assign multiple jurors
- Backend: project.createAndAssignToRound procedure

## UI/UX Improvements (Tier 4+5)
- Add AlertDialog confirmation to header status dropdown
- Replace native confirm() with AlertDialog in assignments table
- Jury stats card now display-only with "Change" link
- Assignments tab restructured into logical card groups
- Inline-editable round name in header
- Back button shows destination label
- Readiness checklist: green check instead of strikethrough
- Gate assignments tab when no jury group assigned
- Relative time on window stats card
- Toast feedback on date saves
- Disable advance button when no target round
- COI section shows placeholder when empty
- Round position shown as "Round X of Y"
- InlineMemberCap edit icon always visible
- Status badge tooltip with description
- Add REMINDER_3_DAYS email template
- Fix maybeSendEmail to respect notification preferences
- Optimize bulk notification email loop

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt
2026-02-19 12:59:35 +01:00
parent ee8b12e59c
commit baca483fcb
12 changed files with 1814 additions and 609 deletions

View File

@@ -1083,6 +1083,60 @@ Together for a healthier ocean.
}
}
/**
* Generate "3 Days Remaining" email template (for jury)
*/
function getReminder3DaysTemplate(
name: string,
pendingCount: number,
roundName: string,
deadline: string,
assignmentsUrl?: string
): EmailTemplate {
const greeting = name ? `Hello ${name},` : 'Hello,'
const urgentBox = `
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin: 20px 0;">
<tr>
<td style="background-color: #fef3c7; border-left: 4px solid #f59e0b; border-radius: 0 8px 8px 0; padding: 16px 20px;">
<p style="color: #92400e; margin: 0; font-size: 14px; font-weight: 600;">&#9888; 3 Days Remaining</p>
</td>
</tr>
</table>
`
const content = `
${sectionTitle(greeting)}
${urgentBox}
${paragraph(`This is a reminder that <strong style="color: ${BRAND.darkBlue};">${roundName}</strong> closes in 3 days.`)}
${statCard('Pending Evaluations', pendingCount)}
${infoBox(`<strong>Deadline:</strong> ${deadline}`, 'warning')}
${paragraph('Please plan to complete your remaining evaluations before the deadline to ensure your feedback is included in the selection process.')}
${assignmentsUrl ? ctaButton(assignmentsUrl, 'Complete Evaluations') : ''}
`
return {
subject: `Reminder: ${pendingCount} evaluation${pendingCount !== 1 ? 's' : ''} due in 3 days`,
html: getEmailWrapper(content),
text: `
${greeting}
This is a reminder that ${roundName} closes in 3 days.
You have ${pendingCount} pending evaluation${pendingCount !== 1 ? 's' : ''}.
Deadline: ${deadline}
Please plan to complete your remaining evaluations before the deadline.
${assignmentsUrl ? `Complete evaluations: ${assignmentsUrl}` : ''}
---
Monaco Ocean Protection Challenge
Together for a healthier ocean.
`,
}
}
/**
* Generate "1 Hour Reminder" email template (for jury)
*/
@@ -1457,6 +1511,14 @@ export const NOTIFICATION_EMAIL_TEMPLATES: Record<string, TemplateGenerator> = {
ctx.metadata?.deadline as string | undefined,
ctx.linkUrl
),
REMINDER_3_DAYS: (ctx) =>
getReminder3DaysTemplate(
ctx.name || '',
(ctx.metadata?.pendingCount as number) || 0,
(ctx.metadata?.roundName as string) || 'this round',
(ctx.metadata?.deadline as string) || 'Soon',
ctx.linkUrl
),
REMINDER_24H: (ctx) =>
getReminder24HTemplate(
ctx.name || '',