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

1761 lines
78 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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