Commit Graph

533 Commits

Author SHA1 Message Date
Matt
1f24f5539c feat: dish CRUD on lunch router
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 02:30:49 +02:00
Matt
7da4200e72 feat: lunch.getEvent + lunch.updateEvent procedures
Lazy-creates LunchEvent on first read or update. Audit-logs every
update with the patched fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 02:30:06 +02:00
Matt
1a0afd8c6e feat: auto-create MemberLunchPick on attendee writes
Adds ensureLunchPickForAttendingMember helper called from confirm,
adminConfirm, and editAttendees attendee-creation paths. No-ops when
the program has no LunchEvent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 02:28:51 +02:00
Matt
eb19cb11a1 chore: drop dead Logistics tabs + move visa toggle to settings
- Remove the Documents tab — visa documents are out of scope for this
  edition and there is no other concrete document need.
- Remove the Logistics > Settings disabled tab — every per-edition
  configuration knob now lives at /admin/settings > Edition.
- Replace the inline "Visible to teams" toggle on the Visas tab with a
  small "Edition settings" button that links straight to the
  consolidated settings page. The toggle itself moved to that page in
  the previous commit.
- Drop the now-unused getVisaVisibility / setVisaVisibility wiring
  inside VisasTab. (The procedures still exist server-side; the new
  Edition tab uses program.updateEditionSettings instead.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 20:09:50 +02:00
Matt
2f59b87e4f feat: Edition tab on /admin/settings
New top-level Edition tab in the admin settings sidebar (under General,
between Defaults and Branding). Driven by the EditionSettingsTab
component which uses the EditionContext to scope to the current edition
and calls program.getEditionSettings / updateEditionSettings.

Three sub-sections:
  - Grand-finale logistics: defaultAttendeeCap, confirmationWindowHours,
    attendeeEditCutoffHours.
  - Visa: visaStatusVisibleToMembers toggle (will be removed from the
    Logistics > Visas tab in the next commit).
  - Coming soon: placeholders for Lunch and Email Templates.

Each numeric input commits on blur; the visa toggle commits immediately.
All writes invalidate the query so the rest of the UI reflects changes
without a refresh.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 20:01:48 +02:00
Matt
78992a493a feat: program.getEditionSettings + updateEditionSettings
Backs the new consolidated Edition tab on /admin/settings.

getEditionSettings returns a merged view of Program-level fields
(defaultAttendeeCap, visaStatusVisibleToMembers) plus LIVE_FINAL round
config (attendeeEditCutoffHours, confirmationWindowHours, with
sensible defaults). Round-derived values are null when the round
doesn't exist yet.

updateEditionSettings is partial — only supplied fields are written.
Round config writes merge into the existing configJson so other keys
are preserved. Audit-logged as PROGRAM_EDITION_SETTINGS_UPDATE.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:59:31 +02:00
Matt
62ab27a05a feat: mentor detail side sheet + Teams column
The mentor list now ends with a Teams column showing chips of each
mentor's active assignments (truncated at 2 + overflow badge). Clicking
any row opens a right-side Sheet with the mentor's profile (expertise,
country, joined date, max assignments) and a per-team activity feed —
project, status (active / completed / dropped), assignment date, and
counts of messages / files / milestones with their last timestamp.

Stat cards on both the Mentor and Mentee panels were stale and not
particularly informative, so they're gone — the table itself is now
the focal element on each panel.

getMentorPool gained an activeTeams[] field; new getMentorDetail query
backs the side sheet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:52:17 +02:00
Matt
030db533e1 fix: size logistics tab bar to fit buttons + horizontal scrollbar
The fixed h-10 wasn't tall enough to fit a 32px tab button plus the
overflow-x scrollbar, so buttons clipped and a vertical scroll appeared
inside the bar. Switching to h-auto + pb-2 lets the bar size naturally
and reserves space below the row for the scrollbar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:45:52 +02:00
Matt
7824b00ff4 fix: horizontal scroll on logistics tab bar instead of wrapping
Stacking 8 tabs onto two rows looked rough. Switching the TabsList to
w-full + justify-start + overflow-x-auto keeps every tab on one line
and lets the bar scroll horizontally on narrower viewports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:44:38 +02:00
Matt
46a78c3a74 feat: render visa status + next date on AttendingMembersCard
Each member now sees their own visa status (status badge + next
upcoming date) on the applicant dashboard, sourced from
applicant.getMyVisaApplications. Other teammates' rows still show the
generic "Visa support" badge if they need a visa, since the platform
deliberately scopes visa visibility to the caller. The whole visa
surface auto-hides if the program toggle is off.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:40:25 +02:00
Matt
fe630e0e2d feat: admin Visas tab — table + edit dialog + visibility toggle
Activates the previously-disabled Visas tab on /admin/logistics.

VisasTab renders a flat table joined per attendee per project, sorted
by status priority. Status filter pills mirror the Confirmations tab.
The header carries a "Visible to teams" Switch backed by a new
logistics.getVisaVisibility query and the existing setVisaVisibility
mutation; toggling it controls whether members see their own status.

VisaEditDialog is a per-row editor with a status dropdown,
nationality input, three native date inputs (invitation / appointment
/ decision), and a notes textarea. No file uploads — the platform
deliberately holds zero document artifacts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:37:55 +02:00
Matt
7c86e42413 feat: applicant.getMyVisaApplications gated by program toggle
Returns the caller's visa application rows when the program's
visaStatusVisibleToMembers toggle is on; returns null when it's off
(so the UI can hide the section entirely); returns an empty array
when the toggle is on but the caller has no needsVisa attendees.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:33:40 +02:00
Matt
0e104e0b6f feat: admin visa CRUD procedures
logistics router gains three procedures for the Visas tab:
  - listVisaApplications: program-scoped, joined with project + attendee,
    sorted by status priority (REQUESTED first → NOT_NEEDED last).
  - updateVisaApplication: partial update of status / dates / nationality /
    notes; clears nullable fields on null. Audit-logged as VISA_UPDATE
    with previous + next snapshots.
  - setVisaVisibility: flips Program.visaStatusVisibleToMembers. Audit-
    logged as VISA_VISIBILITY_SET.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:32:52 +02:00
Matt
bdfd99874a feat: auto-create/sync VisaApplication on attendee writes
confirm and adminConfirm now create REQUESTED VisaApplication rows for
every attendee with needsVisa=true, in the same Prisma transaction as
the AttendingMember inserts. editAttendees was extended into a fully
diff-aware sync: existing attendees whose needsVisa flips on get a new
VisaApp; flipping off deletes it; staying true preserves the row (and
its status / notes / dates). Removed attendees cascade automatically
via the FK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:31:28 +02:00
Matt
6e5f607425 feat: admin can confirm/decline attendance on team behalf
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>
2026-04-28 19:03:01 +02:00
Matt
ff355ee10e feat: gate mentor auto-assign on CONFIRMED finalist status
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>
2026-04-28 18:57:18 +02:00
Matt
a6284e5c66 feat: edit-attendees dialog + roster card on applicant dashboard
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>
2026-04-28 18:54:40 +02:00
Matt
5b642c3d50 feat: finalist.editAttendees with cutoff and diff-based update
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>
2026-04-28 18:50:52 +02:00
Matt
3d8aab46f1 feat: mentor self-drop dialog on project detail page
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>
2026-04-28 18:48:09 +02:00
Matt
3bc1cc14c7 feat: mentor self-drop with required reason
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>
2026-04-28 18:44:45 +02:00
Matt
5bdb65181d feat: finalist.unconfirm with mentor cascade
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.
2026-04-28 18:37:34 +02:00
Matt
e706913a57 feat: add MENTEE_DROPPED + MENTOR_DROPPED notification types 2026-04-28 18:35:50 +02:00
Matt
57ec28edad feat: logistics page shell + Confirmations/Travel/Hotels tabs
- /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.
2026-04-28 18:25:29 +02:00
Matt
d1f29a149a feat: list-confirmations admin query 2026-04-28 18:20:40 +02:00
Matt
b1e6eb81eb feat: flight-detail CRUD on logistics router 2026-04-28 18:19:39 +02:00
Matt
497145b983 feat: hotel CRUD on logistics router 2026-04-28 18:18:16 +02:00
Matt
95055e0dae feat: admin UI for finalist slot quotas + waitlist on grand-finale round
- 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.
2026-04-28 18:07:55 +02:00
Matt
437bed2326 feat: public finalist confirmation page UI
- /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
2026-04-28 18:04:25 +02:00
Matt
14a81cd6ec feat: auto-cascade cron + admin waitlist management procedures
- 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.
2026-04-28 18:00:47 +02:00
Matt
19ef364c71 feat: public confirm/decline procedures with waitlist auto-promotion
- 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).
2026-04-28 17:58:31 +02:00
Matt
895be93678 feat: selectFinalists creates PENDING confirmations and sends emails
- 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.
2026-04-28 17:55:09 +02:00
Matt
3ea36296b9 feat: per-category finalist slot quotas with confirmed-count guard 2026-04-28 17:52:22 +02:00
Matt
53a1e62614 feat: HMAC-signed finalist confirmation token 2026-04-28 17:50:17 +02:00
Matt
d0058b46ed feat: Mentees & Activity tab on /admin/mentors
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).
2026-04-28 16:47:53 +02:00
Matt
11ab0943f6 feat: name current view in role-switcher pill, add Mentors sidebar entry
- 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.
2026-04-28 16:32:51 +02:00
Matt
e37f3a5874 fix: render enum labels as proper title case
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.
2026-04-28 16:28:30 +02:00
Matt
26ff8ed111 feat(workspace): mentor + applicant message previews (§F.2)
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
2026-04-28 16:14:11 +02:00
Matt
70a9752d73 refactor(layouts): shared RoleSwitcherPill across dashboards (§D.6)
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
2026-04-28 16:09:40 +02:00
Matt
6475d5c418 fix(impersonation): pointer-events + show all roles (§D.4-5)
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
2026-04-28 16:05:51 +02:00
Matt
432470083c feat(admin): bulk role updates + mentor-onboarding email (§D.2-3)
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
2026-04-28 16:05:16 +02:00
Matt
0c2b2d1f96 feat(user): context-aware default dashboard (§D.1)
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
2026-04-28 16:00:56 +02:00
Matt
cedd188328 feat(email): mentor-onboarding email template + sender (§D)
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
2026-04-28 15:58:17 +02:00
Matt
08829df54d fix(mentor): show non-suspended users in pool + picker (§B/§C)
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'.
2026-04-28 15:32:28 +02:00
Matt
34bd267c32 feat(admin): real /admin/mentors list page (§B)
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
2026-04-28 15:28:09 +02:00
Matt
a0a2c5f06a feat(mentor): mentoring-specific Round Overview card grid (§B)
Renders above Round Details when round.roundType === 'MENTORING':
  - Top-line counts: requested + assigned (with awaiting badge)
  - Request window: countdown pill (amber <48h, red <12h)
  - Mentor pool: size + avg load + 'View all' link to /admin/mentors
  - Workspace activity: msgs / files / milestones / last activity

Round Details panel now shows 'Mentor Pool: N members' (linked) instead
of an always-empty 'Jury Group' row on MENTORING rounds.

Plan: docs/superpowers/plans/2026-04-28-pr5-mentor-round-overview.md
2026-04-28 15:26:31 +02:00
Matt
f9bffabf05 feat(mentor): getRoundStats + getMentorPool procedures (§B)
- getRoundStats(roundId): totals + requested/assigned/awaiting counts +
  request-window deadline (windowOpenAt + mentoringRequestDeadlineDays) +
  workspace activity (msgs / files / milestones / lastActivityAt).
- getMentorPool({programId?}): all MENTOR-role users with current/completed
  assignment counts, capacity remaining, last activity. Drives both the
  round-overview pool card and the /admin/mentors list page.
- Tests cover empty rounds, mixed-state rounds, and capacity arithmetic.

Plan: docs/superpowers/plans/2026-04-28-pr5-mentor-round-overview.md
Spec: docs/superpowers/specs/2026-04-28-mentor-round-readiness-design.md §B
2026-04-28 15:24:07 +02:00
Matt
2b07c12c18 feat(mentor): round-level auto-fill toolbar on Projects tab (§C)
Adds an 'Auto-fill remaining' button above ProjectStatesTable on the
MENTORING round Projects tab. Calls mentor.autoAssignBulkForRound,
respecting the round's configJson.eligibility:
  - requested_only / all_advancing: enabled, count from new
    round.getProjectsNeedingMentor query
  - admin_selected: disabled with explanatory copy

Plan: docs/superpowers/plans/2026-04-28-pr4-mentor-assignment-ux.md
2026-04-28 14:58:32 +02:00
Matt
ddae34c8f5 feat(mentor): rewrite project mentor-assignment page (§C)
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
2026-04-28 14:56:46 +02:00
Matt
4874491b18 feat(mentor): getCandidates + autoAssignBulkForRound procedures (§C)
- getCandidates: lists MENTOR-role users with expertise-overlap %, load,
  capacity. Drives the manual picker on /admin/projects/[id]/mentor.
- autoAssignBulkForRound: round-scoped bulk auto-fill respecting the
  round's configJson.eligibility (requested_only / all_advancing /
  admin_selected). Skips already-assigned projects.
- getSuggestions returns source: 'ai' | 'fallback' so the UI can label
  the AI tab when OPENAI_API_KEY is missing.
- Tests cover ordering, skip-already-assigned, eligibility refusal, and
  the source flag.

Plan: docs/superpowers/plans/2026-04-28-pr4-mentor-assignment-ux.md
Spec: docs/superpowers/specs/2026-04-28-mentor-round-readiness-design.md §C
2026-04-28 14:54:43 +02:00
Matt
c29410fd4e refactor(mentor): extract computeExpertiseOverlap helper (§C prep)
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
2026-04-28 14:50:50 +02:00