Adds VisaStatus enum, VisaApplication model 1:1 with AttendingMember
(cascade-delete), and Program.visaStatusVisibleToMembers Boolean
toggle. The model intentionally stores process metadata only — status,
optional nationality, key dates, free-text notes. Sensitive documents
(passport scans, invitation letters, decision papers) continue to flow
over email and are never persisted in the platform.
Migration is purely additive: CREATE TYPE / CREATE TABLE / ADD COLUMN /
ADD FK. No DROP / ALTER on existing data.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This edition is being handled manually via email — admins need to
record what each finalist replied. Adds:
- finalist.adminConfirm — flips PENDING → CONFIRMED with attendees +
visa flags. Same cap and team-membership checks as the public flow,
audit-logged as FINALIST_ADMIN_CONFIRM.
- finalist.adminDecline — flips PENDING → DECLINED with optional
reason and triggers waitlist promotion. Audit-logged as
FINALIST_ADMIN_DECLINE.
- finalist.getConfirmationDetail — feeds the admin attendee picker.
- Per-row Confirm / Decline actions on the Logistics > Confirmations
table (PENDING rows only) backed by a shared dialog that switches
between attendee-picker and reason-input modes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mentor.autoAssignBulkForRound now skips any project whose finalist
confirmation isn't CONFIRMED — there's no point assigning a mentor to
a team that won't be at the grand finale. Other eligibility rules
(wantsMentorship, admin_selected, already-assigned) are preserved.
Updated existing requested_only and skip-already-assigned tests to seed
CONFIRMED confirmations so they continue to isolate their target gate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The platform now covers the full 2026 edition lifecycle — invite-driven
auth, multi-round jury voting, mentor workspace + self-drop, AI
services, finalist confirmation + waitlist, logistics + travel + visa
self-declare, and a public confirmation flow. Marking 1.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds applicant.getMyFinalistConfirmation query (returns roster + cutoff
metadata for the team's confirmation, or null). New AttendingMembersCard
shows the confirmed attendee list and surfaces an Edit dialog to the
team lead — disabled past the editable cutoff. Card auto-hides until the
confirmation reaches CONFIRMED status.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Team-lead-only mutation that replaces the AttendingMember roster on a
CONFIRMED finalist confirmation. Diffs the requested user list against
existing rows: kept rows are updated in place (preserving FlightDetail),
removed rows are deleted, added rows are created. Enforces:
- team-lead role
- CONFIRMED status
- defaultAttendeeCap
- team-membership of every supplied userId
- cutoff = LIVE_FINAL.windowOpenAt − attendeeEditCutoffHours (default 48)
Audit-logged as FINALIST_EDIT_ATTENDEES with the diff payload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DropAssignmentDialog with required reason (10-1000 chars) calls
mentor.dropAssignment, redirects to /mentor on success. Button surfaces
in the project header only when the viewer is the assigned mentor and
the assignment is neither dropped nor completed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds mentor.dropAssignment mutation (mentor-only, ownership-checked, reason
min 10 chars). Filters dropped MentorAssignment rows out of getMyProjects,
getCandidates mentor count, getMentorPool, and getMenteeActivity so they
no longer surface in the mentor or admin UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Admin can un-confirm a CONFIRMED finalist (e.g. to allow a category
quota decrease). Sets status to SUPERSEDED, cascades to drop the active
mentor assignment (if any) with droppedBy='finalist_unconfirmed' and
the reason embedded. Mentor receives a MENTEE_DROPPED notification.
Already-completed assignments are preserved untouched.
Adds 3 nullable fields to MentorAssignment for drop lifecycle:
- droppedAt: timestamp of drop (null while active)
- droppedReason: free text (required when droppedAt is set)
- droppedBy: 'mentor' | 'admin' | 'finalist_unconfirmed'
Migration is purely additive: no DROP, no ALTER COLUMN, no RENAME.
All existing rows automatically get NULL for the new columns.
- /admin/logistics page with shadcn Tabs (3 active + 5 disabled "(soon)"
placeholder tabs for Visas / Lunch / Documents / Email Templates / Settings).
- Sidebar entry "Logistics" between Mentors and Awards (Plane icon).
- Confirmations tab: read-only table with status filter pills, browser-
local-time deadline display, attendee count, decline reason snippet.
- Hotels tab: single-hotel form (name/address/link/notes) with live
email-preview card showing what teams will see.
- Travel tab: per-attendee flight tracker with arrival/departure
datetimes, flight numbers, IATA airports, click-to-toggle status badge,
edit Sheet, and unfilled/pending/confirmed filter pills.
Smoke-tested end-to-end: navigation, sidebar entry, all three tabs
render, hotel save persists to DB and renders in preview card.
Adds 2 new models for grand-finale logistics PR 2:
- Hotel: 1:1 with Program (one per edition); name + address + link + notes
- FlightDetail: 1:1 with AttendingMember; arrival + departure datetimes,
flight numbers, airports, admin status (PENDING/CONFIRMED), admin notes
Migration is purely additive: no DROP/ALTER COLUMN/RENAME. FKs point
FROM new tables TO existing tables (Program, AttendingMember) with
ON DELETE CASCADE only firing on parent deletion.
- New components/admin/grand-finale/finalist-slots-card: per-category
quota editor with confirmed/pending counts, dirty-tracking, save button.
Renders an empty editor for both Startup and Business Concept categories
even when no quota exists yet.
- New components/admin/grand-finale/waitlist-card: per-category ranked
waitlist display with status badges + manual-promote AlertDialog
(audit-logged via FINALIST_MANUAL_PROMOTE).
- Round detail page: embeds both cards conditionally when
roundType === 'LIVE_FINAL'.
- New finalist router queries: listQuotas, listCategoryCounts (groupBy
on category+status), listWaitlist (rank-ordered with project relation).
Smoke-tested: setting Startup quota to 3 persists to DB; UI renders
quota editor and waitlist card cleanly with empty state.
- /finalist/confirm/[token] under (public) route group
- Browser-local-time deadline + zone label + live countdown
- Default-selects up to defaultAttendeeCap team members
- Per-member "Needs visa?" toggle that surfaces only when selected
- Decline AlertDialog with optional reason textarea
- Distinct friendly states for invalid / expired / already-confirmed /
already-declined / superseded tokens (not generic errors)
- Smoke-tested end-to-end against live dev server: confirmation row
flipped to CONFIRMED, AttendingMember row created with correct visa flag
- expirePendingPastDeadline service: scans PENDING confirmations past
deadline, marks each EXPIRED + audit-logs, then promotes the next
waitlist entry per affected category (using each program's grand-final
round configJson for windowHours).
- /api/cron/finalist-confirmations: hourly cron entrypoint (CRON_SECRET
header gate), wraps the service.
- finalist.addToWaitlist: insert at a specific rank, shifting later
entries down (transactional).
- finalist.reorderWaitlist: rewrite a category's rank order in one go,
using a temp-rank trick to avoid unique-constraint conflicts mid-update.
- finalist.manualPromote: out-of-rank-order admin promote with audit log
(FINALIST_MANUAL_PROMOTE) + fresh confirmation email.
2 new tests. Suite at 14/14 for finalist-confirmation.
- finalist.getByToken: public lookup of a confirmation by signed token,
with all the data the public page needs (project, team members, current
state). Throws on expired/tampered tokens.
- finalist.confirm: validates team membership of every selected user,
checks against program.defaultAttendeeCap, atomically writes
status=CONFIRMED + AttendingMember rows in a transaction.
- finalist.decline: captures optional reason, then promotes the next
WAITING waitlist entry in the same category (no-op if waitlist empty).
Resolves the new windowHours from the LIVE_FINAL round configJson.
- promoteNextWaitlistEntry service: encapsulates the cascade (mark
PROMOTED, create fresh PENDING confirmation, send email).
- New service module createPendingConfirmation: writes a PENDING
FinalistConfirmation row with a signed token whose exp matches the
computed deadline.
- selectFinalists admin mutation: reads windowHours from the round's
configJson.confirmationWindowHours (default 24), validates category
match + quota, then creates one confirmation per selected project
and sends a notification email to the team lead. Email failures are
logged but never roll back the row creation.
- New email helpers: getFinalistConfirmationTemplate +
sendFinalistConfirmationEmail.
Adds 4 new models for grand-finale logistics PR 1:
- FinalistSlotQuota: per-category mutable quotas
- WaitlistEntry: ranked per-category waitlist
- FinalistConfirmation: token-gated confirmation lifecycle (PENDING /
CONFIRMED / DECLINED / EXPIRED / SUPERSEDED) with optional decline reason
- AttendingMember: who from each team is attending, with visa flag
Plus Program.defaultAttendeeCap (default 3) for the per-edition team
attendance cap.
Migration is purely additive: no DROP/ALTER COLUMN/RENAME on existing
schema. All FKs ON DELETE CASCADE only fire on parent deletion.
Adds a project-centric ops view for mentor management:
- New mentor.getMenteeActivity tRPC procedure aggregates every project
with wantsMentorship=true and derives a status (unassigned / assigned
/ active / stalled) from the latest message + file activity.
- /admin/mentors becomes a tabbed page: existing Mentor list +
new Mentees & Activity table with status pills, search, and a
per-row Assign/Open CTA linking to /admin/projects/[id]/mentor.
- Includes 2 unit tests covering classification + program scoping.
Also: ignore .remember/ (plugin scratch dir).
- Switcher trigger now shows the current view's icon + label with a
chevron (e.g. "Admin View ⌄") instead of the vague "Switch View".
Dropdown adds a header, marks the current view with a checkmark,
and lists each accessible alternative explicitly.
- Adds a "Mentors" entry to the admin sidebar between Juries and
Awards so the existing /admin/mentors page is reachable from nav.
formatEnumLabel was leaving inputs uppercase ("TECHNOLOGY_INNOVATION"
became "TECHNOLOGY INNOVATION"); lowercasing first yields proper
title case ("Technology Innovation") and improves labels app-wide.
Apply it on the project mentor page for Ocean Issue + Category.
mentor.getRecentMessages: last N unread messages from teams across all
of a mentor's assignments. Drives a Recent Messages card on /mentor.
applicant.getMentorConversationPreview: last 3 messages + unread count
for a given project. Drives a 'Conversation with [Mentor]' card on
/applicant — auto-hides when no mentor is assigned.
Both procedures use the existing MentorMessage(projectId, createdAt)
composite index — no new index needed.
Plan: docs/superpowers/plans/2026-04-28-pr6-multi-role-and-workspace-previews.md
Extract ROLE_SWITCH_OPTIONS + switchableRoles computation from the two
duplicated copies (role-nav.tsx + admin-sidebar.tsx) into a single
src/components/layouts/role-switcher.tsx module.
Adds a RoleSwitcherPill component placed top-right of every dashboard:
- Hidden for single-role users
- Hidden during impersonation
- Same visual + click target across /jury, /mentor, /applicant,
/observer, /award-master AND /admin (admin layout gains a small
top-bar to host the pill)
Removes the duplicate role-switcher items from the admin sidebar's
bottom user-menu — one source of truth instead of three.
Plan: docs/superpowers/plans/2026-04-28-pr6-multi-role-and-workspace-previews.md
Banner wrapper now uses pointer-events-none so it doesn't intercept clicks
on the user-menu dropdown sitting underneath; the 'Return to Admin' button
re-enables pointer events on itself only.
Banner also lists every role the impersonated user holds (e.g.
'JURY MEMBER, MENTOR') instead of just the primary role, matching how
multi-role users are surfaced everywhere else.
Plan: docs/superpowers/plans/2026-04-28-pr6-multi-role-and-workspace-previews.md
user.bulkUpdateRoles({userIds, addRole?, removeRole?}) batches role
changes across up to 200 users with a SUPER_ADMIN self-demote guard.
When MENTOR is freshly added, fires sendMentorOnboardingEmail once per
user, gated by User.mentorOnboardingSentAt for idempotency. Audit log
entry per user changed.
UI: 'Add MENTOR role' button surfaces in the existing /admin/members
bulk-selection toolbar when ≥1 user is selected. Other roles
(OBSERVER / AWARD_MASTER) supported by the procedure but not yet wired
to UI; one button keeps the toolbar minimal until a clear need arises.
Tests cover happy path, idempotency on second call, removeRole semantics,
and the SUPER_ADMIN self-demote guard.
Plan: docs/superpowers/plans/2026-04-28-pr6-multi-role-and-workspace-previews.md
user.getDefaultDashboard returns the highest-priority role for which the
user has actionable work right now — pending eval in active round, active
mentoring assignment, applicant project in active round, etc. — falling
back to static priority order if nothing is actionable.
src/app/page.tsx now reads roles[] (multi-role array) instead of just the
primary role, fixing the bug where mentor+juror users always landed on
their primary role's dashboard. Uses static priority for simplicity in
the server component; the context-aware procedure remains available for
client surfaces.
Tests cover six cases: super-admin, juror with active eval, juror+observer
fallback, mentor+juror in mentoring round, both-active-priority-tiebreak,
observer-only.
Plan: docs/superpowers/plans/2026-04-28-pr6-multi-role-and-workspace-previews.md
One-shot email sent when a user is first granted the MENTOR role.
Subject: 'Welcome to MOPC mentoring'. Includes a CTA to /mentor and
a hint about the Switch View pill for multi-role users.
Idempotency lives at the call site (User.mentorOnboardingSentAt
checked in user.bulkUpdateRoles / user.updateRoles).
Plan: docs/superpowers/plans/2026-04-28-pr6-multi-role-and-workspace-previews.md
Single nullable DateTime column. No backfill. Catalog-only ALTER TABLE —
sub-millisecond on PostgreSQL regardless of table size. The column is
unused until the bulk role-update flow wires it up as an idempotency
stamp for the mentor-onboarding email.
Plan: docs/superpowers/plans/2026-04-28-pr6-multi-role-and-workspace-previews.md
Spec: docs/superpowers/specs/2026-04-28-mentor-round-readiness-design.md §D
mentor.getCandidates and mentor.getMentorPool both filtered on
status='ACTIVE', which excluded every seeded mentor (status=NONE)
and any INVITED mentor. Production scenario: PR 4's Manual Picker and
PR 5's pool counts both rendered empty against real data.
Filter changed to status != SUSPENDED — admins want to see all mentors
they manage (including INVITED + NONE), but not suspended ones.
Found via Playwright smoke of PR 5: pool count read 0 against 4 seeded
mentors with roles[]=['MENTOR'], status='NONE'.
Replaces the redirect-to-/admin/members stub with a sortable, searchable
list of all MENTOR-role users powered by mentor.getMentorPool. Columns:
name, expertise tags, country, active count, completed count, capacity
remaining, last activity. Header summary cards show pool size, total
active assignments, and average load.
Row links continue to /admin/members/[id]; /admin/mentors/[id] remains
a redirect (mentor-detail view deferred to a future PR).
Plan: docs/superpowers/plans/2026-04-28-pr5-mentor-round-overview.md
Test runs that crash before reaching afterAll leave orphan @test.local
users + programs (Test Program / getCandidates- / bulk- / source-flag-
/ mentor-files- name patterns). Mirrors tests/helpers.ts cleanupTestData
cascade order. Idempotent — safe to re-run any time the dev DB picks up
test pollution.
Run: npx tsx scripts/cleanup-test-pollution.ts
Replaces single-section AI-only stub with three sections (Project Context,
Currently Assigned, Pick a Mentor). Pick a Mentor is a tab strip:
- Manual Picker (default): all MENTOR-role users sorted by expertise
overlap %, with search + load/capacity columns. Assign sends
method=MANUAL.
- AI Suggestions: existing pane, with an amber 'AI matching unavailable'
banner + 'Tag overlap' pills when OPENAI_API_KEY is unset.
Plan: docs/superpowers/plans/2026-04-28-pr4-mentor-assignment-ux.md
Pure function reused by upcoming mentor.getCandidates + AI fallback path.
Refactors getAlgorithmicMatches to call it. No behavior change.
Plan: docs/superpowers/plans/2026-04-28-pr4-mentor-assignment-ux.md
Adds a PROJECT_TEAM recipient type to the message router (resolver
returns team members + project lead) and an "Email Team" button on
the admin project detail page that opens a self-contained dialog
matching the look of /admin/messages: subject, body (pre-filled
with "Hello [Project Title] team,\n\n"), live HTML preview iframe,
"Send test to me" + "Send to N" actions.
The composer reuses the existing message.previewEmail and
message.send tRPC procedures end-to-end — no parallel email
infrastructure introduced.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaces every MentoringConfigSchema field on the round Config tab:
- Adds "Mentoring Request Window" card with mentoringRequestDeadlineDays
numeric input (1-90 days, default 14) and passThroughIfNoRequest toggle
(default ON; OFF holds projects PENDING until manual mentor assignment).
- Adds inline help-text for the Eligibility dropdown explaining each
option's effect on auto-PASS behavior.
- Hides the General Settings card on MENTORING rounds (it only renders
Advancement Targets, which don't apply to a pass-through round).
- Relaxes the Launch Readiness "File requirements set" gate for MENTORING
rounds without filePromotionEnabled + a target window — file requirements
only matter when files will be promoted to a downstream submission window.
Spec: docs/superpowers/specs/2026-04-28-mentor-round-readiness-design.md §A
Plan: docs/superpowers/plans/2026-04-28-pr3-mentoring-config-completeness.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds generateMentorObjectKey helper producing
<projectName>/mentorship/<timestamp>-<file>. Replaces the
client-supplied bucket/objectKey on workspaceUploadFile with an
HMAC-signed upload token that binds bucket, objectKey, uploader,
and a 1h expiry — paths can no longer be forged from the client.
Adds workspaceGetUploadUrl, workspaceGetFiles,
workspaceGetFileDownloadUrl, workspaceDeleteFile procedures with
mentor-or-team-member auth. Builds <WorkspaceFilesPanel> and
wires it into the mentor workspace Files tab and the applicant
/applicant/mentor page. Replaces the file-promotion-panel mock
array with a real workspaceGetFiles query.
Tests cover token sign/verify (5), key construction (5), and
end-to-end procedure flow including auth + tampered tokens (7).
Spec: docs/superpowers/specs/2026-04-28-mentor-round-readiness-design.md §F.1
Plan: docs/superpowers/plans/2026-04-28-pr2-mentor-workspace-files.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bundle backend security (HMAC-signed upload tokens, server-built
objectKeys, mentor-or-team-member auth) with the actual file UI
that didn't exist yet (Files tab placeholder, file-promotion-panel
mock array, and applicant-side gap).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The "Confirm Your Evaluation Preferences" banner was including jury
group memberships whose only rounds are LIVE_FINAL or DELIBERATION.
Those ceremonies don't use cap+category preferences, so the sliders
were meaningless. Filter getOnboardingContext to memberships in
groups with at least one INTAKE/FILTERING/EVALUATION/SUBMISSION/
MENTORING round.
Spec: docs/superpowers/specs/2026-04-28-mentor-round-readiness-design.md §E
Plan: docs/superpowers/plans/2026-04-28-pr1-jury-preferences-filter.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step-by-step plan for §E. Single-procedure change to filter
getOnboardingContext memberships by linked-round type, plus a
new test file covering review-only, LIVE_FINAL-only, and mixed
group cases.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After review, two additions to the multi-role UX section:
1. Replace static-priority post-login redirect with context-aware
"go where the work is" via new user.getDefaultDashboard() — a
juror+observer landing during an active jury round goes to /jury
even though observer has no work; falls back to static priority
when no role has actionable work.
2. Standardize the role switcher's location across all dashboards.
Extract shared useRoleSwitcher hook + new RoleSwitcherPill that
renders in the top-right of every layout, including admin (which
currently puts switching in the bottom-left sidebar pill).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>