MOPC-App/docs/claude-architecture-redesign/18-mentor-ui.md

1761 lines
78 KiB
Markdown
Raw Permalink Normal View History

# Mentor UI Redesign (Complete Workspace Experience)
## 1. Overview
Mentors are a **first-class role** in the MOPC platform. They guide finalist teams through the mentoring round (Round 6) — a collaboration layer between Jury 2 finalist selection and the Live Finals. The redesigned mentor experience provides a dedicated dashboard and full workspace for file collaboration, threaded comments, and file promotion to official submissions.
### What Changes
| Area | Current State | Redesigned State |
|------|---------------|------------------|
| **Dashboard** | Basic list of assigned projects | Rich dashboard with activity feed, status cards, metrics |
| **Messaging** | Simple chat via `MentorMessage` model | Enhanced messaging integrated into workspace |
| **File Sharing** | None — teams upload to official submission only | Full private workspace with file upload/download |
| **Comments** | None | Threaded file comments with @mentions |
| **File Promotion** | None | Promote workspace files to official submission slots |
| **Milestones** | Basic `MentorMilestone` tracking | Interactive milestone progress UI |
| **Navigation** | `/mentor` and `/mentor/projects` | Full IA with team-specific workspaces |
### Design Principles
1. **Privacy-first** — Workspace is private (mentor + team + admin only; jury can't see it)
2. **No duplication** — Promoted files reuse MinIO objects (no storage duplication)
3. **Provenance tracking** — Full audit trail from workspace upload → promotion → submission
4. **Mobile-friendly** — File review on tablets is a primary use case
5. **Integrated** — Workspace is tightly integrated with existing submission system
---
## 2. Current Mentor Experience (Baseline)
### Existing Components
| File | Purpose |
|------|---------|
| `src/components/layouts/mentor-nav.tsx` | Top navigation (Dashboard, My Projects, Resources) |
| `src/components/shared/mentor-chat.tsx` | Chat component for mentor-team messaging |
| `src/server/routers/mentor.ts` | tRPC procedures (assign, getSuggestions, etc.) |
| `src/server/services/mentor-matching.ts` | AI-based mentor-project matching |
### Current Data Models
```
MentorAssignment {
id, projectId, mentorId, method, assignedAt, assignedBy
aiConfidenceScore, expertiseMatchScore, aiReasoning
completionStatus, lastViewedAt
}
MentorMessage {
id, mentorAssignmentId, senderId, message, createdAt, isRead
}
MentorNote {
id, mentorAssignmentId, note, isVisibleToAdmin, createdAt
}
MentorMilestone {
id, competitionId, name, description, sortOrder
}
MentorMilestoneCompletion {
id, mentorAssignmentId, milestoneId, completedAt, notes
}
```
### Current Mentor Procedures (src/server/routers/mentor.ts)
| Procedure | Purpose |
|-----------|---------|
| `getSuggestions` | Get AI-suggested mentors for a project |
| `assign` | Manually assign mentor to project |
| `unassign` | Remove mentor assignment |
| `getMyAssignments` | List mentor's assigned projects |
| `getAssignmentDetail` | Get detailed view of a single assignment |
| `sendMessage` | Send message to team |
| `getMessages` | Retrieve chat history |
| `addNote` | Add private mentor note |
| `getNotes` | Retrieve mentor's notes |
| `completeMilestone` | Mark a milestone as complete |
| `getMilestones` | Get milestone list + completion status |
### What's Missing
- No file workspace
- No threaded comments
- No file promotion mechanism
- No visual file preview
- No mentor dashboard metrics
- No admin monitoring of mentor activity
- No file versioning in workspace
- No mobile-optimized file review
---
## 3. Mentor Dashboard (Landing Page)
The mentor dashboard is the entry point for all mentoring activities. It provides an at-a-glance view of assigned teams, workspace activity, and upcoming deadlines.
### Route
```
/mentor (dashboard)
```
### ASCII Mockup
```
┌──────────────────────────────────────────────────────────────────────────┐
│ MOPC 2026 — Mentor Dashboard [🔔 3] [Profile ▾]│
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ Mentoring Period: June 1 June 30, 2026 │
│ ⏱ 18 days remaining │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ 3 │ │ 12 │ │ 5 │ │
│ │ Assigned Teams │ │ Unread Messages│ │ Files Reviewed │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
│ │
│ ┌─ My Assigned Teams ───────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────────────┐ │ │
│ │ │ 🚀 OceanClean AI — Startup │ │ │
│ │ │ Team Lead: Sarah Johnson · sjohnson@oceanclean.ai │ │ │
│ │ │ │ │ │
│ │ │ 💬 2 unread messages · 📁 3 files uploaded │ │ │
│ │ │ 🔄 Last activity: 2 hours ago │ │ │
│ │ │ │ │ │
│ │ │ Status: ✅ Active Milestones: 2/4 completed │ │ │
│ │ │ │ │ │
│ │ │ [Open Workspace] [View Details] │ │ │
│ │ └────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────────────┐ │ │
│ │ │ 💡 Blue Carbon Hub — Business Concept │ │ │
│ │ │ Team Lead: Dr. Maria Santos · msantos@bluecarbonhub.org │ │ │
│ │ │ │ │ │
│ │ │ 💬 0 unread · 📁 1 file uploaded │ │ │
│ │ │ 🔄 Last activity: 2 days ago │ │ │
│ │ │ │ │ │
│ │ │ Status: ⚠ Needs Attention Milestones: 1/4 completed │ │ │
│ │ │ │ │ │
│ │ │ [Open Workspace] [View Details] │ │ │
│ │ └────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────────────┐ │ │
│ │ │ 🌊 SeaWatch Monitor — Startup │ │ │
│ │ │ Team Lead: John Chen · jchen@seawatch.io │ │ │
│ │ │ │ │ │
│ │ │ 💬 0 unread · 📁 0 files │ │ │
│ │ │ 🔄 Last activity: Never │ │ │
│ │ │ │ │ │
│ │ │ Status: ⚠ No Activity Yet Milestones: 0/4 completed │ │ │
│ │ │ │ │ │
│ │ │ [Open Workspace] [View Details] │ │ │
│ │ └────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Milestone Progress ───────────────────────────────────────────┐ │
│ │ │ │
│ │ ☑ Initial Review (3/3 teams completed) │ │
│ │ ├─ OceanClean AI: ✅ Jun 2 │ │
│ │ ├─ Blue Carbon Hub: ✅ Jun 3 │ │
│ │ └─ SeaWatch Monitor: ✅ Jun 4 │ │
│ │ │ │
│ │ ☐ Business Plan Feedback (1/3 teams completed) │ │
│ │ ├─ OceanClean AI: ✅ Jun 5 │ │
│ │ ├─ Blue Carbon Hub: ⏳ In progress │ │
│ │ └─ SeaWatch Monitor: ⏳ Not started │ │
│ │ │ │
│ │ ☐ Pitch Deck Review (0/3 teams) │ │
│ │ ☐ Final Submission Review (0/3 teams) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Recent Activity ────────────────────────────────────────────┐ │
│ │ │ │
│ │ 2 hours ago │ │
│ │ 📁 Sarah Johnson uploaded "Business Plan v3.pdf" │ │
│ │ in OceanClean AI workspace │ │
│ │ │ │
│ │ 5 hours ago │ │
│ │ 💬 You sent a message to Blue Carbon Hub │ │
│ │ │ │
│ │ Yesterday │ │
│ │ ✅ You completed "Initial Review" for SeaWatch Monitor │ │
│ │ │ │
│ │ 2 days ago │ │
│ │ 📁 You uploaded "Financial Model Template.xlsx" │ │
│ │ in OceanClean AI workspace │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
```
### Data Requirements
**Team Cards:**
```typescript
// Query: mentor.getMyDashboard
{
teams: [
{
projectId: string,
projectTitle: string,
projectCategory: "STARTUP" | "BUSINESS_CONCEPT",
teamLead: { name, email },
mentorAssignmentId: string,
status: "active" | "needs_attention" | "no_activity" | "completed",
unreadMessageCount: number,
fileCount: number,
lastActivity: Date | null,
milestonesCompleted: number,
milestonesTotal: number,
}
],
stats: {
totalTeams: number,
unreadMessages: number,
filesReviewed: number,
filesPromoted: number,
},
mentoring: {
windowOpenAt: Date,
windowCloseAt: Date,
daysRemaining: number,
}
}
```
**Status Logic:**
- `no_activity` — lastActivity is null (team hasn't engaged)
- `needs_attention` — lastActivity > 3 days ago OR unreadMessageCount > 0
- `active` — Regular engagement
- `completed` — All milestones done
**Activity Feed:**
```typescript
// Query: mentor.getRecentActivity (limit: 10)
{
activities: [
{
id: string,
type: "FILE_UPLOADED" | "MESSAGE_SENT" | "MILESTONE_COMPLETED" | "FILE_PROMOTED",
actor: { id, name },
projectTitle: string,
timestamp: Date,
metadata: { fileName?, milestoneId? }
}
]
}
```
### Component Breakdown
```
MentorDashboard (page)
├─ MentorHeader (period info, countdown, stats cards)
├─ TeamCardList
│ └─ TeamCard (reusable, shows status + quick actions)
├─ MilestoneProgressSection
│ └─ MilestoneExpandableList (shows per-team breakdown)
└─ RecentActivityFeed
└─ ActivityItem (icon + text + timestamp)
```
---
## 4. Workspace (Per-Team Private Collaboration Space)
The workspace is the heart of the mentor experience. It's a split-pane interface with files on the left and detail view on the right.
### Route
```
/mentor/team/[projectId]
/mentor/team/[projectId]/files
/mentor/team/[projectId]/file/[fileId]
/mentor/team/[projectId]/chat
/mentor/team/[projectId]/milestones
```
### ASCII Mockup — File List View
```
┌──────────────────────────────────────────────────────────────────────────┐
│ ← Back to Dashboard [🔔 3] [Profile ▾]│
├──────────────────────────────────────────────────────────────────────────┤
│ OceanClean AI — Workspace │
│ Team Lead: Sarah Johnson · sjohnson@oceanclean.ai │
│ Mentoring Period: June 1 June 30 · ⏱ 18 days remaining │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ [📁 Files] [💬 Chat (2)] [📋 Milestones (2/4)] │
│ │
│ ┌─────────────────────┬────────────────────────────────────────────────┐│
│ │ Files (5) │ File Detail ││
│ │ [📤 Upload File] │ ││
│ │ │ ┌────────────────────────────────────────────┐ ││
│ │ ───────────────────│ │ 📄 Business Plan v3.pdf │ ││
│ │ │ │ Uploaded by Sarah Johnson (Team) │ ││
│ │ 📄 Business Plan.. │ │ Jun 8, 2026 · 2.4 MB │ ││
│ │ Sarah · Jun 8 │ │ │ ││
│ │ 💬 4 comments │ │ [📥 Download] [💬 Comments (4)] │ ││
│ │ [Open →] │ │ [🚀 Promote to Submission] │ ││
│ │ │ │ │ ││
│ │ 📊 Financial Mod.. │ │ ───────────────────────────────────────── │ ││
│ │ You · Jun 6 │ │ │ ││
│ │ 💬 2 comments │ │ 💬 Comments (4) │ ││
│ │ [Open →] │ │ │ ││
│ │ │ │ Dr. Martin Duval (Mentor) · Jun 8, 14:30 │ ││
│ │ 📄 Market Analys.. │ │ Section 3.2 needs stronger market │ ││
│ │ Sarah · Jun 5 │ │ analysis. Consider adding competitor │ ││
│ │ 💬 1 comment │ │ comparisons and market size estimates. │ ││
│ │ [Open →] │ │ │ ││
│ │ │ │ └─ Sarah Johnson (Team) · Jun 8, 16:00 │ ││
│ │ 📄 Pitch Deck Dr.. │ │ Good point! We'll add a competitive │ ││
│ │ Sarah · Jun 4 │ │ landscape section in the next ver. │ ││
│ │ ✅ PROMOTED │ │ │ ││
│ │ [View] │ │ Dr. Martin Duval (Mentor) · Jun 9, 10:00 │ ││
│ │ │ │ Financial projections look much better. │ ││
│ │ 🎥 Video Pitch │ │ Revenue model is solid. Ready to promote? │ ││
│ │ Sarah · Jun 3 │ │ │ ││
│ │ 💬 0 comments │ │ └─ Sarah Johnson (Team) · Jun 9, 11:30 │ ││
│ │ [Open →] │ │ Yes! Please promote to the Business │ ││
│ │ │ │ Plan requirement slot. │ ││
│ │ │ │ │ ││
│ │ │ │ [Add comment... ] │ ││
│ │ │ │ [Post Comment] │ ││
│ │ │ └────────────────────────────────────────────┘ ││
│ │ │ ││
│ └─────────────────────┴────────────────────────────────────────────────┘│
│ │
└──────────────────────────────────────────────────────────────────────────┘
```
### Split-Pane Behavior
| State | Left Pane | Right Pane |
|-------|-----------|------------|
| **Default** | File list | File upload instructions + quick stats |
| **File selected** | File list (selected file highlighted) | File detail (preview, comments, actions) |
| **Mobile** | Stack vertically — file list on top, detail below when file selected |
### File List Item Structure
```typescript
interface WorkspaceFile {
id: string
fileName: string
uploadedBy: { id, name, role: "MENTOR" | "TEAM" }
uploadedAt: Date
size: number
mimeType: string
commentCount: number
isPromoted: boolean
promotedToSlot?: { requirementName: string, submissionWindowName: string }
}
```
### File Actions
| Action | Visibility | Behavior |
|--------|------------|----------|
| **Download** | Always | Get MinIO pre-signed URL, trigger browser download |
| **Comment** | Always | Open comment thread in right pane |
| **Promote** | Only if not already promoted | Show promotion dialog (see section 5) |
| **Delete** | Uploader or admin only | Soft delete (mark as deleted, don't remove from MinIO) |
---
## 5. File Review & Comments (Threaded Discussion)
### Comment Thread UI
```
┌──────────────────────────────────────────────────────────────┐
│ 💬 Comments on: Business Plan v3.pdf │
├──────────────────────────────────────────────────────────────┤
│ │
│ Dr. Martin Duval (Mentor) Jun 8, 14:30 │
│ Section 3.2 needs stronger market analysis. Consider │
│ adding competitor comparisons and market size estimates. │
│ │
│ └─ Sarah Johnson (Team) Jun 8, 16:00 │
│ Good point! We'll add a competitive landscape │
│ section in the next version. @Dr. Martin would you │
│ prefer a SWOT matrix or Porter's 5 Forces analysis? │
│ │
│ └─ Dr. Martin Duval (Mentor) Jun 8, 17:00 │
│ Porter's 5 Forces would be stronger for this │
│ type of technology play. Focus on threat of │
│ substitutes and competitive rivalry. │
│ │
├──────────────────────────────────────────────────────────────┤
│ │
│ Dr. Martin Duval (Mentor) Jun 9, 10:00 │
│ Financial projections look much better. Revenue model is │
│ solid. Ready to promote this to official submission? │
│ │
│ └─ Sarah Johnson (Team) Jun 9, 11:30 │
│ Yes! Please promote to the Business Plan slot. │
│ │
├──────────────────────────────────────────────────────────────┤
│ │
│ [Add comment... ] │
│ │
│ [@mention team member] [Post Comment] │
└──────────────────────────────────────────────────────────────┘
```
### Comment Features
**Threading:**
- Top-level comments (parentCommentId = null)
- Nested replies (parentCommentId = parent's ID)
- Maximum nesting depth: 3 levels (prevents deep threading hell)
**@Mentions:**
- Typing `@` triggers autocomplete of team members + mentor
- Mentioned users get in-app notification
- Mention is rendered as a link to user profile
**Formatting:**
- Plain text only (no rich text editor — keep it simple)
- Auto-linkify URLs
- Preserve line breaks
**Deletion:**
- Author can delete own comments within 1 hour of posting
- Admin can delete any comment
- Deleted comments show "[Comment deleted]" placeholder (preserve thread structure)
### Comment API
| Procedure | Purpose |
|-----------|---------|
| `mentorWorkspace.addComment(fileId, content, parentCommentId?)` | Add comment |
| `mentorWorkspace.listComments(fileId)` | Get threaded comments |
| `mentorWorkspace.deleteComment(commentId)` | Delete comment |
### Comment Data Structure
```typescript
interface MentorFileComment {
id: string
mentorFileId: string
authorId: string
author: { id, name, role }
content: string
parentCommentId: string | null
replies: MentorFileComment[] // Nested replies
createdAt: Date
updatedAt: Date
}
```
---
## 6. File Promotion (Workspace → Official Submission)
File promotion is the **key value add** of the mentor workspace. It allows refined documents to become official submissions without manual re-upload.
### Promotion Flow
```
┌────────────────────────────────────────────────────────────────┐
│ Step 1: Mentor/Team clicks "Promote →" on a workspace file │
└────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│ Step 2: Promotion Dialog Appears │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Promote File to Official Submission │ │
│ │ │ │
│ │ File: Business Plan v3.pdf │ │
│ │ Size: 2.4 MB │ │
│ │ │ │
│ │ Target Submission Window: │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ Round 2 Docs (Semi-finalist Submissions) ▾ │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Replaces Requirement Slot: │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ Business Plan ▾ │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Current file in this slot: │ │
│ │ "Business Plan v2.pdf" uploaded Jun 3, 2026 │ │
│ │ │ │
│ │ ⚠ This will replace the existing file. The old file │ │
│ │ will remain in version history but won't be shown │ │
│ │ to jurors. │ │
│ │ │ │
│ │ [Cancel] [Promote & Replace File] │ │
│ └────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│ Step 3: Server Executes Promotion │
│ │
│ 1. Validate: │
│ ✓ Workspace is active │
│ ✓ File not already promoted │
│ ✓ Submission window exists and is accessible │
│ ✓ User has permission (team lead or admin) │
│ │
│ 2. Find existing ProjectFile for that requirement slot: │
│ ProjectFile.where(projectId, requirementId) │
│ │
│ 3. Create new ProjectFile: │
│ { │
│ projectId: project.id, │
│ submissionWindowId: selected window, │
│ requirementId: selected requirement, │
│ fileName: "Business Plan v3.pdf", │
│ mimeType: "application/pdf", │
│ size: 2.4MB, │
│ bucket: same as MentorFile, │
│ objectKey: same as MentorFile, // NO DUPLICATION │
│ version: 3, // increment from previous │
│ } │
│ │
│ 4. Update old file (if exists): │
│ oldFile.replacedById = newFile.id │
│ │
│ 5. Update MentorFile: │
│ { │
│ isPromoted: true, │
│ promotedToFileId: newFile.id, │
│ promotedAt: now, │
│ promotedByUserId: actor.id, │
│ } │
│ │
│ 6. Audit log entry: │
│ { │
│ action: "MENTOR_FILE_PROMOTED", │
│ actorId: actor.id, │
│ targetType: "PROJECT_FILE", │
│ targetId: newFile.id, │
│ metadata: { │
│ mentorFileId: mentorFile.id, │
│ submissionWindowId: window.id, │
│ requirementId: requirement.id, │
│ replacedFileId: oldFile?.id, │
│ } │
│ } │
└────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│ Step 4: UI Updates │
│ │
│ - MentorFile shows "✅ PROMOTED" badge │
│ - Promote button disabled │
│ - Toast: "File promoted to Business Plan slot" │
│ - ProjectFile now visible to jury in evaluation round │
└────────────────────────────────────────────────────────────────┘
```
### Promotion Dialog Component
```tsx
// components/mentor/file-promotion-dialog.tsx
interface FilePromotionDialogProps {
mentorFile: WorkspaceFile
availableWindows: SubmissionWindow[]
currentWindowId: string // Default selection
onPromote: (windowId: string, requirementId: string | null) => Promise<void>
}
// Logic:
// 1. User selects target submission window
// 2. Load requirements for that window (via tRPC query)
// 3. Show dropdown of available requirement slots
// 4. Show warning if slot already has a file
// 5. Confirm and execute promotion
```
### Promotion Rules
| Rule | Enforcement |
|------|-------------|
| **Only unpromoted files** | isPromoted = false |
| **Workspace must be active** | workspaceOpenAt <= now <= workspaceCloseAt |
| **User must have permission** | Team lead OR admin OR (mentor if config allows) |
| **Target window must exist** | SubmissionWindow.id valid |
| **No file duplication** | ProjectFile.objectKey = MentorFile.objectKey (same MinIO object) |
| **Versioning** | New file increments version number |
| **Audit trail** | Full provenance logged |
### Unpromote (Revert)
Admin can revert a promotion:
```
1. Find ProjectFile created by promotion (via MentorFile.promotedToFileId)
2. If it replaced a previous file, restore that file (set replacedById = null)
3. Delete the promoted ProjectFile
4. Reset MentorFile flags:
- isPromoted = false
- promotedToFileId = null
- promotedAt = null
- promotedByUserId = null
5. Audit log: "MENTOR_FILE_UNPROMOTED"
```
**API:**
- `mentorWorkspace.unpromoteFile(mentorFileId)` — admin only
---
## 7. Messaging (Team Communication)
The workspace includes integrated chat for real-time communication between mentor and team.
### Chat Tab UI
```
┌──────────────────────────────────────────────────────────────┐
│ [📁 Files] [💬 Chat (2)] [📋 Milestones] │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Dr. Martin Duval (Mentor) Jun 8, 10:30 AM │ │
│ │ Welcome to the mentoring workspace! I've reviewed │ │
│ │ your initial application. Let's focus on strengthening │ │
│ │ the financial projections first. I'll upload a │ │
│ │ template to the Files tab. │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Jun 8, 2:15 PM │ │
│ │ Sarah Johnson (Team) │ │
│ │ Thank you! We've uploaded the │ │
│ │ revised business plan to the │ │
│ │ Files section. Looking forward │ │
│ │ to your feedback. │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Dr. Martin Duval (Mentor) Jun 9, 9:00 AM │ │
│ │ Excellent work on the revisions! I've left detailed │ │
│ │ comments on the file. The market analysis is much │ │
│ │ stronger now. One more iteration should get us to │ │
│ │ a promotion-ready version. │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ─────────────────────────────────────────────────────────── │
│ │
│ [Type a message... ] │
│ │
│ [📎 Attach File] [Send] │
└──────────────────────────────────────────────────────────────┘
```
### Message Features
**Real-time Updates:**
- Polling every 10 seconds for new messages
- WebSocket support if available
- Unread count badge on tab
**Message Types:**
| Type | Description |
|------|-------------|
| `text` | Plain text message |
| `file_reference` | Link to a workspace file ("I've uploaded X — see Files tab") |
| `milestone` | Milestone completion notification |
**Notifications:**
- In-app notification on new message
- Email notification if recipient hasn't viewed workspace in 24 hours
- Push notification if enabled
**Read Receipts:**
- Messages marked as read when chat tab is viewed
- No "typing..." indicator (keep it simple)
---
## 8. Milestones (Progress Tracking)
Milestones are optional admin-defined checkpoints for mentoring progress. They're displayed in the workspace and on the mentor dashboard.
### Milestones Tab UI
```
┌──────────────────────────────────────────────────────────────┐
│ [📁 Files] [💬 Chat] [📋 Milestones (2/4)] │
├──────────────────────────────────────────────────────────────┤
│ │
│ Mentoring Progress │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ✅ Initial Review │ │
│ │ Completed: Jun 2, 2026 by Dr. Martin Duval │ │
│ │ Notes: Reviewed application materials. Strong concept │ │
│ │ but needs financial model refinement. │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ✅ Business Plan Feedback │ │
│ │ Completed: Jun 5, 2026 by Dr. Martin Duval │ │
│ │ Notes: Provided detailed comments on v2. Team responded │ │
│ │ with v3 incorporating all feedback. │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ⏳ Pitch Deck Review │ │
│ │ │ │
│ │ [Mark as Complete] │ │
│ │ │ │
│ │ Optional Notes: │ │
│ │ [Reviewed draft deck. Suggested stronger opening... ] │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ⏳ Final Submission Review │ │
│ │ │ │
│ │ [Mark as Complete] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
```
### Milestone Data Model
```prisma
model MentorMilestone {
id String @id @default(cuid())
competitionId String
name String // "Initial Review", "Business Plan Feedback"
description String? @db.Text
sortOrder Int @default(0)
isRequired Boolean @default(false) // If true, must be completed before workspace closes
competition Competition @relation(...)
completions MentorMilestoneCompletion[]
}
model MentorMilestoneCompletion {
id String @id @default(cuid())
mentorAssignmentId String
milestoneId String
completedAt DateTime @default(now())
notes String? @db.Text
mentorAssignment MentorAssignment @relation(...)
milestone MentorMilestone @relation(...)
@@unique([mentorAssignmentId, milestoneId])
}
```
### Milestone API
| Procedure | Purpose |
|-----------|---------|
| `mentor.getMilestones(mentorAssignmentId)` | Get milestones + completion status for a team |
| `mentor.completeMilestone(assignmentId, milestoneId, notes)` | Mark milestone complete |
| `mentor.uncompleteMilestone(completionId)` | Revert completion (admin only) |
---
## 9. Navigation & Information Architecture
### Route Structure
```
/mentor/ → Dashboard (landing page)
/mentor/team/[projectId] → Workspace (redirects to /files)
/mentor/team/[projectId]/files → File list + detail split-pane
/mentor/team/[projectId]/file/[fileId] → Full-screen file detail (mobile)
/mentor/team/[projectId]/chat → Chat tab
/mentor/team/[projectId]/milestones → Milestones tab
/mentor/notifications → Notification center
/mentor/settings → Mentor profile/preferences
```
### Navigation Component
```tsx
// components/layouts/mentor-workspace-nav.tsx
interface MentorWorkspaceNavProps {
projectId: string
currentTab: "files" | "chat" | "milestones"
unreadMessageCount: number
milestonesCompleted: number
milestonesTotal: number
}
// Renders:
// [📁 Files] [💬 Chat (2)] [📋 Milestones (2/4)]
```
### Breadcrumb
```
Dashboard > My Projects > OceanClean AI > Files
```
---
## 10. Admin View of Mentoring (Monitoring & Override)
Admin can monitor all mentoring activity and intervene when needed.
### Admin Dashboard — Mentoring Overview
```
┌──────────────────────────────────────────────────────────────────────────┐
│ Admin Dashboard > Mentoring Overview │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ Mentoring Period: June 1 June 30, 2026 · ⏱ 18 days remaining │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ 12 │ │ 8 │ │ 4 │ │
│ │ Active Pairs │ │ Files Promoted │ │ Inactive Teams │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
│ │
│ ┌─ Mentor-Team Pairings ────────────────────────────────────────────┐ │
│ │ │ │
│ │ Search: [Filter by mentor, project, or status...] │ │
│ │ │ │
│ │ Mentor | Project | Files | Msgs | Last Act │ │
│ │ ────────────────────┼──────────────────┼───────┼──────┼────────── │ │
│ │ Dr. Martin Duval | OceanClean AI | 5 | 8 | 2 hrs ago │ │
│ │ | Blue Carbon Hub | 1 | 3 | 2 days │ │
│ │ | SeaWatch Monitor | 0 | 0 | Never │ │
│ │ | | | | [Reassign]│ │
│ │ │ │
│ │ Dr. Lisa Chen | WaveGen Pro | 3 | 12 | 5 hrs ago │ │
│ │ | Reef Restore AI | 2 | 6 | 1 day │ │
│ │ │ │
│ │ Prof. Ahmed Hassan | PlasticTrack | 4 | 10 | 3 hrs ago │ │
│ │ | CoralSense | 1 | 2 | 4 days │ │
│ │ | | | | ⚠ Flag │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Alerts ──────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ⚠ 4 teams have no activity in the last 5 days │ │
│ │ ⚠ 2 mentors are at max capacity (3 teams each) │ │
│ │ ⚠ 1 team has unread messages for 48+ hours │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ [Export Mentoring Report] [Bulk Reassign] [Extend Deadline] │
└──────────────────────────────────────────────────────────────────────────┘
```
### Admin Actions
| Action | Purpose |
|--------|---------|
| **View any workspace** | Full read/write access to all workspaces |
| **Reassign mentor** | Change mentor for a project mid-stream |
| **Promote files on behalf of team** | Force promotion if team is unresponsive |
| **Extend deadline for specific team** | Grant extra time |
| **Flag inactive pairs** | Mark for follow-up |
| **Export workspace data** | Download all files, messages, comments for audit |
### Admin Workspace Access
Admin sees an additional "Admin Actions" section in any workspace:
```
┌─ Admin Actions ─────────────────────────────────────────┐
│ │
│ [Reassign Mentor] [Extend Deadline] [Export Data] │
│ [Promote File (Override)] [Close Workspace] │
│ │
└─────────────────────────────────────────────────────────┘
```
---
## 11. Mobile Experience (Tablet-Optimized)
The workspace is designed to work well on tablets (iPad, Android tablets) for file review on the go.
### Mobile Layout
**Portrait Mode:**
```
┌─────────────────────────────┐
│ ← Back OceanClean AI │
├─────────────────────────────┤
│ [Files] [Chat] [Milestones] │
├─────────────────────────────┤
│ │
│ Files (5) │
│ │
│ 📄 Business Plan v3.pdf │
│ Sarah · Jun 8 · 💬 4 │
│ [Open] │
│ │
│ 📊 Financial Model.xlsx │
│ You · Jun 6 · 💬 2 │
│ [Open] │
│ │
│ 📄 Market Analysis.pdf │
│ Sarah · Jun 5 · 💬 1 │
│ [Open] │
│ │
│ ... │
│ │
└─────────────────────────────┘
Tap a file →
┌─────────────────────────────┐
│ ← Back to Files │
├─────────────────────────────┤
│ 📄 Business Plan v3.pdf │
│ Sarah Johnson (Team) │
│ Jun 8, 2026 · 2.4 MB │
├─────────────────────────────┤
│ │
│ [📥 Download] │
│ [🚀 Promote] │
│ │
│ ─────────────────────── │
│ │
│ 💬 Comments (4) │
│ │
│ Dr. Martin · Jun 8, 14:30 │
│ Section 3.2 needs stronger │
│ market analysis... │
│ │
│ └─ Sarah · Jun 8, 16:00 │
│ Good point! We'll add │
│ a competitive section. │
│ │
│ [Add comment...] │
│ │
└─────────────────────────────┘
```
**Landscape Mode:**
```
┌───────────────────────────────────────────────────────────┐
│ ← Back to Dashboard OceanClean AI [🔔 3] [Menu ▾]│
├──────────────┬────────────────────────────────────────────┤
│ Files (5) │ 📄 Business Plan v3.pdf │
│ │ Sarah Johnson · Jun 8 · 2.4 MB │
│ 📄 Business..│ [📥 Download] [🚀 Promote] │
│ 💬 4 │ │
│ │ ────────────────────────────────────── │
│ 📊 Financia..│ 💬 Comments (4) │
│ 💬 2 │ │
│ │ Dr. Martin · Jun 8, 14:30 │
│ 📄 Market A..│ Section 3.2 needs stronger... │
│ 💬 1 │ └─ Sarah · Jun 8, 16:00 │
│ │ Good point! We'll add... │
│ │ │
└──────────────┴────────────────────────────────────────────┘
```
### Mobile-Specific Features
- **Swipe gestures** — Swipe left on file to reveal delete/promote actions
- **Pull to refresh** — Refresh file list and comments
- **Touch-friendly targets** — Minimum 44px tap targets
- **Offline mode** — Download files for offline review, sync comments when back online
- **File preview** — In-app PDF preview (no external app required)
---
## 12. Accessibility
### WCAG 2.1 AA Compliance
| Feature | Implementation |
|---------|----------------|
| **Keyboard navigation** | Full keyboard access to all workspace features |
| **Screen reader support** | ARIA labels on all interactive elements |
| **Color contrast** | 4.5:1 minimum for all text |
| **Focus indicators** | Visible focus ring on all focusable elements |
| **Alt text** | File type icons have descriptive labels |
| **Error messaging** | Clear, descriptive error messages on validation failures |
### File Preview Accessibility
For PDF file previews:
- Use `<iframe>` with PDF.js (accessible)
- Provide "Download" button as alternative to in-browser preview
- Alt text: "PDF preview of [filename]"
For image files:
- Show full-size image with alt text from file description
- Zoom controls for low vision users
For other file types (Excel, PowerPoint):
- No in-browser preview — download only
- Clear file type indicator and size
---
## 13. Component Library
### New Components
| Component | Path | Purpose |
|-----------|------|---------|
| `MentorDashboard` | `src/app/(mentor)/mentor/page.tsx` | Landing page |
| `TeamCard` | `src/components/mentor/team-card.tsx` | Reusable team summary card |
| `WorkspaceLayout` | `src/app/(mentor)/mentor/team/[projectId]/layout.tsx` | Workspace shell with tabs |
| `FileList` | `src/components/mentor/file-list.tsx` | Left pane file list |
| `FileDetail` | `src/components/mentor/file-detail.tsx` | Right pane file detail |
| `FilePromotionDialog` | `src/components/mentor/file-promotion-dialog.tsx` | Promotion confirmation dialog |
| `FileUploadZone` | `src/components/mentor/file-upload-zone.tsx` | Drag-and-drop upload area |
| `CommentThread` | `src/components/mentor/comment-thread.tsx` | Threaded comment display |
| `CommentInput` | `src/components/mentor/comment-input.tsx` | Comment textarea with @mentions |
| `MilestoneList` | `src/components/mentor/milestone-list.tsx` | Milestone progress view |
| `MentorActivityFeed` | `src/components/mentor/activity-feed.tsx` | Recent activity stream |
| `WorkspaceStatusBadge` | `src/components/mentor/workspace-status-badge.tsx` | Status indicator (active, needs attention, etc.) |
### Modified Existing Components
| Component | Changes |
|-----------|---------|
| `MentorChat` | Integrate into workspace tabs, add file reference support |
| `MentorNav` | Add "Workspaces" navigation item |
---
## 14. API Reference (New tRPC Procedures)
### Router: `mentorWorkspace`
All procedures require mentor, team lead, or admin authentication.
```typescript
// src/server/routers/mentor-workspace.ts
export const mentorWorkspaceRouter = router({
// File Upload
getUploadUrl: mentorOrTeamProcedure
.input(z.object({
mentorAssignmentId: z.string(),
fileName: z.string(),
mimeType: z.string(),
}))
.mutation(async ({ ctx, input }) => {
// Generate MinIO pre-signed PUT URL
return { uploadUrl, objectKey }
}),
saveFile: mentorOrTeamProcedure
.input(z.object({
mentorAssignmentId: z.string(),
fileName: z.string(),
mimeType: z.string(),
size: z.number(),
bucket: z.string(),
objectKey: z.string(),
description: z.string().optional(),
}))
.mutation(async ({ ctx, input }) => {
// Create MentorFile record
return mentorFile
}),
// File Management
listFiles: mentorOrTeamProcedure
.input(z.object({
mentorAssignmentId: z.string(),
}))
.query(async ({ ctx, input }) => {
// Return MentorFile[] with comment counts
return { files }
}),
getFileDownloadUrl: mentorOrTeamProcedure
.input(z.object({
mentorFileId: z.string(),
}))
.query(async ({ ctx, input }) => {
// Generate MinIO pre-signed GET URL
return { downloadUrl, expiresAt }
}),
deleteFile: mentorOrTeamProcedure
.input(z.object({
mentorFileId: z.string(),
}))
.mutation(async ({ ctx, input }) => {
// Soft delete (mark as deleted)
// Only uploader or admin can delete
return { success: true }
}),
// Comments
addComment: mentorOrTeamProcedure
.input(z.object({
mentorFileId: z.string(),
content: z.string().min(1),
parentCommentId: z.string().optional(),
}))
.mutation(async ({ ctx, input }) => {
// Create MentorFileComment
// Parse @mentions and create notifications
return comment
}),
listComments: mentorOrTeamProcedure
.input(z.object({
mentorFileId: z.string(),
}))
.query(async ({ ctx, input }) => {
// Return threaded comments (with nested replies)
return { comments }
}),
deleteComment: mentorOrTeamProcedure
.input(z.object({
commentId: z.string(),
}))
.mutation(async ({ ctx, input }) => {
// Only author or admin can delete
// Replace with "[Comment deleted]" placeholder
return { success: true }
}),
// File Promotion
promoteFile: protectedProcedure
.input(z.object({
mentorFileId: z.string(),
submissionWindowId: z.string(),
requirementId: z.string().optional(),
}))
.mutation(async ({ ctx, input }) => {
// Validate permissions (team lead or admin)
// Execute promotion (create ProjectFile, update MentorFile)
// Audit log
return { mentorFile, projectFile }
}),
unpromoteFile: adminProcedure
.input(z.object({
mentorFileId: z.string(),
}))
.mutation(async ({ ctx, input }) => {
// Revert promotion (admin only)
return { success: true }
}),
// Workspace Status
getWorkspaceStatus: mentorOrTeamProcedure
.input(z.object({
mentorAssignmentId: z.string(),
}))
.query(async ({ ctx, input }) => {
// Summary stats for workspace
return {
fileCount,
unreadMessageCount,
milestonesCompleted,
milestonesTotal,
lastActivity,
}
}),
})
```
### Authorization Helper
```typescript
// Check if user can access workspace
async function canAccessWorkspace(
userId: string,
mentorAssignmentId: string,
prisma: PrismaClient
): Promise<boolean> {
const assignment = await prisma.mentorAssignment.findUnique({
where: { id: mentorAssignmentId },
include: {
mentor: true,
project: {
include: {
teamMembers: true,
},
},
},
})
if (!assignment) return false
// Mentor
if (assignment.mentorId === userId) return true
// Team lead
const teamLead = assignment.project.teamMembers.find(
(tm) => tm.role === 'LEAD'
)
if (teamLead?.userId === userId) return true
// Any team member (for read access)
const isTeamMember = assignment.project.teamMembers.some(
(tm) => tm.userId === userId
)
if (isTeamMember) return true
// Admin (checked in middleware)
return false
}
```
---
## 15. Service Layer
### New Service: `mentor-workspace.ts`
```typescript
// src/server/services/mentor-workspace.ts
import { PrismaClient, MentorFile, ProjectFile } from '@prisma/client'
import { generatePresignedUrl } from './minio-client'
import { logAudit } from '../utils/audit'
/**
* Generate MinIO pre-signed PUT URL for workspace file upload
*/
export async function getWorkspaceUploadUrl(
mentorAssignmentId: string,
fileName: string,
mimeType: string,
actorId: string,
prisma: PrismaClient
): Promise<{ uploadUrl: string; objectKey: string }> {
// Validate workspace is active
const assignment = await prisma.mentorAssignment.findUniqueOrThrow({
where: { id: mentorAssignmentId },
})
if (!assignment.workspaceEnabled) {
throw new Error('Workspace is not enabled for this assignment')
}
const now = new Date()
if (
assignment.workspaceOpenAt &&
assignment.workspaceCloseAt &&
(now < assignment.workspaceOpenAt || now > assignment.workspaceCloseAt)
) {
throw new Error('Workspace is not currently open')
}
// Generate object key
const objectKey = `mentor-workspace/${mentorAssignmentId}/${Date.now()}-${fileName}`
// Generate pre-signed PUT URL (15-minute expiry)
const uploadUrl = await generatePresignedUrl(
process.env.MINIO_BUCKET!,
objectKey,
'PUT',
15 * 60,
mimeType
)
return { uploadUrl, objectKey }
}
/**
* Save file metadata after upload
*/
export async function saveWorkspaceFile(
mentorAssignmentId: string,
uploadedByUserId: string,
file: {
fileName: string
mimeType: string
size: number
bucket: string
objectKey: string
},
description: string | null,
prisma: PrismaClient
): Promise<MentorFile> {
const mentorFile = await prisma.mentorFile.create({
data: {
mentorAssignmentId,
uploadedByUserId,
fileName: file.fileName,
mimeType: file.mimeType,
size: file.size,
bucket: file.bucket,
objectKey: file.objectKey,
description,
},
})
// Audit log
await logAudit(prisma, {
action: 'MENTOR_FILE_UPLOADED',
actorId: uploadedByUserId,
targetType: 'MENTOR_FILE',
targetId: mentorFile.id,
metadata: {
fileName: file.fileName,
size: file.size,
mentorAssignmentId,
},
})
return mentorFile
}
/**
* Promote workspace file to official submission
*/
export async function promoteFileToSubmission(
mentorFileId: string,
submissionWindowId: string,
requirementId: string | null,
actorId: string,
prisma: PrismaClient
): Promise<{ mentorFile: MentorFile; projectFile: ProjectFile }> {
// 1. Validate mentor file
const mentorFile = await prisma.mentorFile.findUniqueOrThrow({
where: { id: mentorFileId },
include: {
mentorAssignment: {
include: {
project: true,
},
},
},
})
if (mentorFile.isPromoted) {
throw new Error('This file has already been promoted')
}
// 2. Validate workspace is active
const { mentorAssignment } = mentorFile
const now = new Date()
if (
!mentorAssignment.workspaceEnabled ||
(mentorAssignment.workspaceCloseAt && now > mentorAssignment.workspaceCloseAt)
) {
throw new Error('Workspace is closed — cannot promote files')
}
// 3. Find existing file in target slot (if requirementId provided)
let replacedFile: ProjectFile | null = null
if (requirementId) {
replacedFile = await prisma.projectFile.findFirst({
where: {
projectId: mentorAssignment.projectId,
requirementId,
replacedById: null,
},
})
}
// 4. Determine version number
const existingVersions = await prisma.projectFile.findMany({
where: {
projectId: mentorAssignment.projectId,
submissionWindowId,
requirementId,
},
orderBy: { version: 'desc' },
take: 1,
})
const nextVersion = existingVersions.length > 0 ? existingVersions[0].version + 1 : 1
// 5. Create new ProjectFile (reusing same MinIO object)
const projectFile = await prisma.projectFile.create({
data: {
projectId: mentorAssignment.projectId,
submissionWindowId,
requirementId,
fileType: 'SUBMISSION',
fileName: mentorFile.fileName,
mimeType: mentorFile.mimeType,
size: mentorFile.size,
bucket: mentorFile.bucket,
objectKey: mentorFile.objectKey, // NO DUPLICATION
version: nextVersion,
isLate: false,
},
})
// 6. Update old file if it exists
if (replacedFile) {
await prisma.projectFile.update({
where: { id: replacedFile.id },
data: { replacedById: projectFile.id },
})
}
// 7. Update MentorFile
const updatedMentorFile = await prisma.mentorFile.update({
where: { id: mentorFileId },
data: {
isPromoted: true,
promotedToFileId: projectFile.id,
promotedAt: now,
promotedByUserId: actorId,
},
})
// 8. Audit log
await logAudit(prisma, {
action: 'MENTOR_FILE_PROMOTED',
actorId,
targetType: 'PROJECT_FILE',
targetId: projectFile.id,
metadata: {
mentorFileId,
submissionWindowId,
requirementId,
replacedFileId: replacedFile?.id,
version: nextVersion,
},
})
return { mentorFile: updatedMentorFile, projectFile }
}
/**
* Revert promotion (admin only)
*/
export async function unpromoteFile(
mentorFileId: string,
actorId: string,
prisma: PrismaClient
): Promise<void> {
const mentorFile = await prisma.mentorFile.findUniqueOrThrow({
where: { id: mentorFileId },
include: { promotedFile: true },
})
if (!mentorFile.isPromoted || !mentorFile.promotedFile) {
throw new Error('File is not promoted')
}
const promotedFile = mentorFile.promotedFile
// 1. If promoted file replaced a previous file, restore that file
if (promotedFile.replacedById) {
const previousFile = await prisma.projectFile.findUnique({
where: { replacedById: promotedFile.id },
})
if (previousFile) {
await prisma.projectFile.update({
where: { id: previousFile.id },
data: { replacedById: null },
})
}
}
// 2. Delete the promoted ProjectFile
await prisma.projectFile.delete({
where: { id: promotedFile.id },
})
// 3. Reset MentorFile flags
await prisma.mentorFile.update({
where: { id: mentorFileId },
data: {
isPromoted: false,
promotedToFileId: null,
promotedAt: null,
promotedByUserId: null,
},
})
// 4. Audit log
await logAudit(prisma, {
action: 'MENTOR_FILE_UNPROMOTED',
actorId,
targetType: 'MENTOR_FILE',
targetId: mentorFileId,
metadata: {
deletedProjectFileId: promotedFile.id,
},
})
}
```
---
## 16. Edge Cases & Error Handling
| Scenario | Handling |
|----------|----------|
| **Team doesn't want mentoring but admin assigns anyway** | Assignment created; team sees mentor in dashboard with explanation |
| **Mentor goes inactive during period** | Admin can reassign; previous workspace preserved (read-only for old mentor) |
| **File promoted then mentor period closes** | Promoted file remains as official submission; MentorFile remains in workspace |
| **Team tries to promote file for requirement that doesn't exist** | Error — must select valid requirement or leave requirementId null |
| **Two files promoted to same requirement slot** | Second promotion replaces first (versioning applies) |
| **Mentoring file is larger than requirement maxSizeMB** | Warning shown but promotion allowed (admin override implicit) |
| **Workspace closed but team needs one more upload** | Admin can extend via round window adjustment or grant grace period |
| **Promoted file deleted from workspace** | ProjectFile remains (separate record); MentorFile keeps audit trail |
| **Mentor uploads file then leaves program** | Files remain accessible in workspace; admin can reassign mentor |
| **File upload fails mid-way** | MinIO object exists but no MentorFile record → cleanup job deletes orphaned objects weekly |
| **Comment with @mention for user not in workspace** | @mention rendered as plain text, no notification sent |
| **Workspace accessed after mentoring round ends** | Read-only mode — can view files and messages, can't upload or comment |
---
## 17. Performance Considerations
### File Upload Optimization
- **Direct MinIO upload** — Files never touch the Next.js server
- **Pre-signed URLs** — 15-minute expiry, client uploads directly
- **Chunked upload** — For files > 5 MB, use multipart upload
- **Progress feedback** — Real-time upload progress bar
### File List Performance
- **Pagination** — Load 20 files at a time (infinite scroll)
- **Comment count caching** — Denormalized comment count on MentorFile
- **Thumbnail generation** — For images, generate thumbnails on upload
### Real-time Updates
- **Polling strategy** — Poll every 10s for new messages, 30s for file list
- **WebSocket upgrade** — If available, use WebSocket for real-time updates
- **Optimistic UI** — Show uploaded file immediately, sync in background
---
## 18. Security
### Access Control
```typescript
// Every workspace procedure checks:
1. User is authenticated
2. User is mentor OR team member OR admin
3. Workspace is active (or admin override)
```
### File Storage Security
- **Private bucket** — All workspace files in private MinIO bucket
- **Pre-signed URLs** — Expire after 15 minutes
- **No directory listing** — Can't enumerate workspace files without database access
- **Audit trail** — Every file upload, download, promotion logged
### XSS Prevention
- **Sanitize filenames** — Strip HTML, escape special chars
- **Sanitize comments** — Plain text only, no HTML tags
- **Validate MIME types** — Only allow whitelisted file types
---
## 19. Testing Strategy
### Unit Tests
```typescript
// Test file promotion logic
describe('promoteFileToSubmission', () => {
it('should create ProjectFile with same objectKey', async () => {
const { mentorFile, projectFile } = await promoteFileToSubmission(...)
expect(projectFile.objectKey).toBe(mentorFile.objectKey)
expect(mentorFile.isPromoted).toBe(true)
})
it('should increment version number', async () => {
// Upload file v1
// Promote to create ProjectFile v1
// Upload file v2
// Promote to create ProjectFile v2
expect(projectFile.version).toBe(2)
})
it('should replace previous file in slot', async () => {
// Promote file1 to slot A
// Promote file2 to slot A
expect(file1.replacedById).toBe(file2.id)
})
})
```
### Integration Tests
```typescript
// Test full workspace flow
describe('Workspace Integration', () => {
it('should allow mentor to upload, team to comment, and promote', async () => {
// 1. Mentor uploads file
const uploadUrl = await trpc.mentorWorkspace.getUploadUrl(...)
// Upload to MinIO
const file = await trpc.mentorWorkspace.saveFile(...)
// 2. Team adds comment
const comment = await trpc.mentorWorkspace.addComment(...)
expect(comment.authorId).toBe(teamMemberId)
// 3. Promote file
const { projectFile } = await trpc.mentorWorkspace.promoteFile(...)
expect(projectFile.projectId).toBe(project.id)
})
})
```
### E2E Tests (Playwright)
```typescript
// Test workspace UI
test('Mentor can upload file and view comments', async ({ page }) => {
await page.goto('/mentor/team/project-123/files')
// Upload file
await page.click('text=Upload File')
await page.setInputFiles('input[type=file]', 'test-file.pdf')
await page.click('text=Upload')
// Wait for file to appear
await page.waitForSelector('text=test-file.pdf')
// Open file detail
await page.click('text=test-file.pdf')
// Add comment
await page.fill('textarea[placeholder="Add comment..."]', 'Looks good!')
await page.click('text=Post Comment')
// Verify comment appears
await page.waitForSelector('text=Looks good!')
})
```
---
## 20. Rollout Plan
### Phase 1: Foundation (Week 1-2)
- [ ] Create data models (MentorFile, MentorFileComment)
- [ ] Migration script
- [ ] Basic tRPC procedures (upload, list, download)
- [ ] Service layer (`mentor-workspace.ts`)
### Phase 2: Core UI (Week 3-4)
- [ ] Mentor dashboard
- [ ] Workspace layout (split-pane)
- [ ] File list component
- [ ] File detail component
- [ ] File upload zone
### Phase 3: Comments & Chat (Week 5)
- [ ] Comment thread component
- [ ] @mention support
- [ ] Enhanced chat integration
### Phase 4: File Promotion (Week 6)
- [ ] Promotion dialog
- [ ] Promotion service logic
- [ ] Unpromote (admin override)
- [ ] Audit trail
### Phase 5: Admin Tools (Week 7)
- [ ] Admin monitoring dashboard
- [ ] Reassignment flow
- [ ] Workspace export
- [ ] Activity alerts
### Phase 6: Polish & Mobile (Week 8)
- [ ] Mobile responsive design
- [ ] Accessibility audit
- [ ] Performance optimization
- [ ] E2E tests
---
## 21. Success Metrics
| Metric | Target |
|--------|--------|
| **Mentor engagement** | 80%+ of mentors upload at least 1 file |
| **Team engagement** | 90%+ of finalist teams engage in workspace |
| **File promotion rate** | 50%+ of workspace files promoted to submission |
| **Comment activity** | Average 3+ comments per promoted file |
| **Milestone completion** | 70%+ of mentors complete all milestones |
| **Mobile usage** | 30%+ of workspace access from mobile/tablet |
| **Time to first file upload** | < 24 hours after mentoring round opens |
| **Admin intervention rate** | < 10% of assignments require admin reassignment |
---
## 22. Future Enhancements (Post-MVP)
### Video Integration
- Loom/Zoom integration for mentor video feedback
- Record screen share reviews of files
### Real-time Collaboration
- Live document editing (Google Docs-style)
- Shared whiteboard for brainstorming
### AI-Powered Features
- AI-generated file summaries
- Suggested comments based on file content
- Auto-tagging files by topic
### Analytics Dashboard
- Mentor performance metrics
- Team engagement heatmaps
- File promotion success rates
### Templates Library
- Mentor-created templates (financial models, pitch decks)
- Template marketplace
---
## Document Complete
This document provides a comprehensive blueprint for the Mentor UI redesign. It covers:
- Current baseline (existing mentor features)
- Dashboard design (landing page with team cards, metrics, activity feed)
- Workspace architecture (split-pane file management)
- File review & comments (threaded discussion)
- File promotion (workspace → official submission)
- Messaging integration
- Milestones tracking
- Navigation & IA
- Admin monitoring
- Mobile experience
- Accessibility
- Component library
- API reference
- Service layer
- Edge cases & security
- Testing strategy
- Rollout plan
- Success metrics
The redesigned mentor experience transforms mentoring from basic messaging into a full collaboration workspace with file management, threaded comments, and seamless integration with the submission system.