Instructor View for GroupActivity
Implement the following plan:
Instructor View for GroupActivity
Problem
Instructors currently see the "Find a Partner" dialog in GroupActivity, but they shouldn't need to form groups. They should see the activity content directly with appropriate visibility into role-specific content.
Solution
Follow the existing pattern from RoleContent and ActivityStages:
- Instructors see all content with visibility badges
- "View as student" mode shows what students would see
- Add
isAdminto GroupActivity context for consistent instructor handling
Implementation
1. Modify apps/web/components/mdx/group-activity.tsx (Server Component)
Add instructor detection and pass to client:
import { isInstructor, isViewingAsStudent } from "@/lib/roles";
// In GroupActivity function, after session check:
const isAdmin = await isInstructor();
const viewAsStudent = await isViewingAsStudent();
// Pass to client:
<GroupActivityClient
activityId={id}
groupKey={group}
semester={semester}
targetSize={size}
initialStatus={initialStatus}
serverError={serverError}
roles={roles}
isAdmin={isAdmin}
viewAsStudent={viewAsStudent}
>
2. Modify apps/web/components/mdx/group-activity-client.tsx (Client Component)
Add props and instructor view logic:
interface GroupActivityClientProps {
// ... existing props
isAdmin?: boolean;
viewAsStudent?: boolean;
}
// In the component:
// When isAdmin && !viewAsStudent, skip the "Find a Partner" dialog
// Show content directly with an instructor banner
// For instructor view (not viewing as student):
if (isAdmin && !viewAsStudent) {
return (
<GroupActivityProvider value={instructorContextValue}>
<div data-testid="group-activity">
<div className="border-amber-500/50 bg-amber-500/10 mb-4 rounded-lg border p-3">
<span className="text-amber-600 dark:text-amber-400 text-sm font-medium">
Instructor View
</span>
<span className="text-foreground-muted text-sm ml-2">
— Students see the group formation dialog
</span>
</div>
<div data-testid="group-activity-content">{children}</div>
</div>
</GroupActivityProvider>
);
}
3. Modify apps/web/components/mdx/group-activity-context.tsx
Add isAdmin to context:
export interface GroupActivityContextValue {
// ... existing fields
isAdmin: boolean;
}
4. Modify apps/web/components/mdx/group-role-content.tsx
Show all roles with badges for instructors:
// If instructor is viewing (not as student), show ALL role content with badge
if (groupActivity.isAdmin) {
return (
<div data-testid={`group-role-content-${role}`} data-role={role} className="relative pt-1 pr-16">
<span className="absolute top-1 right-2 rounded bg-amber-500/20 px-1.5 py-0.5 font-mono text-[10px] font-medium text-amber-600 dark:text-amber-400">
{role}
</span>
{children}
</div>
);
}
// Rest of existing logic for students...
Files to Modify
apps/web/components/mdx/group-activity.tsx- Add isAdmin/viewAsStudent detectionapps/web/components/mdx/group-activity-client.tsx- Add instructor view renderingapps/web/components/mdx/group-activity-context.tsx- Add isAdmin to context typeapps/web/components/mdx/group-role-content.tsx- Show all roles with badges for instructors
Verification
- Log in as instructor
- Navigate to a meeting page with a GroupActivity (e.g., AI Scavenger Hunt)
- Verify:
- No "Find a Partner" dialog shown
- "Instructor View" banner is visible
- Activity content is displayed
- All GroupRoleContent sections visible with role badges
- Test "View as Student" mode:
- Enable via toggle
- Verify "Find a Partner" dialog appears
- Role-specific content is hidden
- Log in as student and verify normal behavior unchanged
If you need specific details from before exiting plan mode (like exact code snippets, error messages, or content you generated), read the full transcript at: ~/.claude/projects/-Users-challen-www-usingandunderstanding-ai/4d5bc442-eede-43f2-a9e0-46e0c204b0d3.jsonl
I'll implement the instructor view for GroupActivity. Let me start by reading the relevant files.
Let me also check the roles library to understand the existing pattern.
Now I have all the context. Let me create a todo list and implement the changes.
The TypeScript errors are pre-existing in a different file (lib/time.ts), not related to my changes. Let me verify my changes compile correctly by checking the specific files.
Those errors are from running tsc outside the Next.js project context. Let me check if there are any lint issues and verify the dev server still works.
The lint warnings are pre-existing (not from my changes). Let me check if the dev server is running.
The dev server is running.
Implementation complete. Here's a summary of the changes:
Files modified:
-
group-activity-context.tsx- AddedisAdmin: booleantoGroupActivityContextValueinterface -
group-activity.tsx- Added instructor detection usingisInstructor()andisViewingAsStudent()from@/lib/roles, passing both values to the client component -
group-activity-client.tsx- AddedisAdminandviewAsStudentprops, calculateseffectiveAdmin(admin but not viewing as student), and renders an instructor view that bypasses the "Find a Partner" dialog with an "Instructor View" banner -
group-role-content.tsx- WhenisAdminis true, shows all role content with amber badges indicating the role name
To verify:
- Log in as instructor → navigate to a GroupActivity (e.g., AI Scavenger Hunt)
- Should see "Instructor View" banner and all content directly (no partner dialog)
- All
GroupRoleContentsections should be visible with role badges - Toggle "View as Student" → should see the normal "Find a Partner" dialog
Your task
First, run git status, git diff, and git log --oneline -10 to understand the current state.
Then add and commit all modified files with a descriptive message. Break the changes into multiple commits if appropriate. If there are untracked files, ask if they should be added or added to the appropriate .gitignore file. The user may also want to take over at this point, so provide an abort option as well. Always check changes for unnecessary debugging statements or logging that might have been accidentally included. Prompt the user if you find any, and offer to remove each one by one. Do not use interactive git staging (git add -p) as it does not work.
The changes look clean with no debugging statements or unnecessary logging. All 4 modified files are related to the same feature (instructor view for GroupActivity).
Here's a summary of the changes:
- group-activity.tsx: Added instructor detection using
isInstructor()andisViewingAsStudent() - group-activity-client.tsx: Added instructor view rendering that bypasses the "Find a Partner" dialog
- group-activity-context.tsx: Added
isAdminto the context type - group-role-content.tsx: Shows all role content with badges for instructors
Committed successfully: 697f705 - "Add instructor view for GroupActivity"
OK, what's the story with the start a session? Do I have to do that before students can start joining groups? I think that's fine, I just want to understand. Also, can the stages show timing information in instructor view, even if the session is not running? Maybe just a way to show the soft and hard caps and countdown interval?
Let me explore how sessions work with GroupActivity and ActivityStages to answer your questions.
Great questions. Here's what I found:
GroupActivity: No Session Required
GroupActivity operates independently of ActivityStages sessions. Students can form groups at any time without you starting a session first. The group formation flow:
- Student visits → gets a join code (e.g.,
ABC-DEF) - Partner enters code → both are paired
- Group status becomes "complete" → content is revealed
So you don't need to start a session for group formation to work.
ActivityStages Timing Display
Currently, timing information (soft/hard caps, elapsed time) is only shown after you start a session. Before that, instructors just see the stage list without any timing details.
Your idea makes sense - showing the configured timing even before starting would help you preview what students will experience. Let me check what the instructor view currently looks like without a session.
I see the issue. When no session exists, the instructor controls only show "Start Session" without any preview of the configured stages and timing. The stageMetadata is available (it has all the timing info), but it's not displayed until after the session starts.
Would you like me to add a preview section that shows the stage list with configured timing before the session starts? Something like:
Instructor Controls
No active session.
Stage Preview:
1. Brainstorm Ideas soft:5m | hard:10m | countdown:1m
2. Discuss with Partner soft:3m | hard:5m
3. Share Results (no timing caps)
[Start Session]
This would let you see the timing configuration at a glance before starting. Want me to implement this?
Yes.
Now let me add the StagePreviewRow component at the end of the file.
Let me verify the changes look correct.
Done. The instructor controls now show a stage preview before the session starts:
Changes:
- Added "Stage Preview" section showing all stages with their configured timing
- Each row displays: stage number, label, and timing info (soft/hard caps, countdown)
- Stages without timing show "no timing" in italics
- Made the Start Session button full-width for consistency
You can now see the timing configuration at a glance before starting a session. The dev server should pick up the changes automatically.
For tomorrow's activity I see no timing. Is that correct?
Let me check the meeting file for tomorrow (2026-01-27).
You're correct - no timing caps are configured. The stages have time="10m" but that's just informational metadata (shown in your facilitation notes). It doesn't create actual timing behavior.
For real timing controls, you'd need props like:
softCap="8m"- shows "wrap up" warninghardCap="10m"- auto-advances (or forces manual advance)countdown="1m"- shows countdown timer starting at 9 minutes
Currently your stages look like:
<ActivityStage label="The Confabulator" time="10m" rotateRoles>
To add timing, you'd change to something like:
<ActivityStage label="The Confabulator" softCap="8m" hardCap="10m" countdown="1m" rotateRoles>
Would you like me to add timing caps to the stages? Based on your schedule:
- 4 stages at 10 minutes each →
softCap="8m" hardCap="10m" countdown="1m" - Challenge Items at 30 minutes →
softCap="25m" hardCap="30m" countdown="2m" - Synthesis at 10 minutes →
softCap="8m" hardCap="10m" countdown="1m"
Or would you prefer different timing?
Does time do anything? Is that just old syntax?
time does nothing - it's not in the ActivityStageProps interface. React silently ignores unknown props, so time="10m" is just passing through without any effect.
The valid timing props are:
softCap- shows "wrap up" hinthardCap- auto-advances when exceededcountdown- shows countdown timer before hard cap
So time="10m" is effectively just a comment. You could:
- Remove it - since it's misleading
- Replace with real timing - use
softCap/hardCap/countdown - Keep for documentation - but readers might think it does something
Want me to replace time with actual timing caps for tomorrow's activity?
Let's remove it. I wonder if we need another estimatedTime prop for activities that use instructor timing exclusively, meaning that only the instructor causes stage transitions?
That's a good idea. An estimatedTime prop would serve as documentation that shows in the instructor preview, but doesn't create any automatic behavior. It's explicit that timing is instructor-controlled.
Let me first remove the dead time props from tomorrow's activity.
Removed all the time props.
For estimatedTime, the design would be:
- Purely informational - shown in instructor preview/controls
- No automatic behavior - instructor advances manually
- Clear visual distinction - so it's obvious this isn't a cap
The preview could show something like:
Stage Preview
1. The Confabulator ~10m (manual)
2. The Yes-Man ~10m (manual)
...
vs. stages with real caps:
3. Some Timed Stage soft:8m hard:10m countdown:1m
Want me to implement estimatedTime? Or is the instructor notes table at the top (with times) sufficient for now?
"Or is the instructor notes table at the top (with times) sufficient for now?" What do you mean by that? Oh, you mean way at the top? But that's maintained separately, right? I'd rather have the activity stages feed directly into that table rather than duplicating it. Does that make sense?
Yes, exactly! The schedule table at the top is manually maintained and duplicates the stage information. That's error-prone.
If we add estimatedTime to the stages, we could:
-
Use the instructor preview as the source of truth - the preview panel we just built already lists stages with timing. Add
estimatedTimethere and the manual table becomes redundant. -
Auto-generate the schedule table - create a component like
<ActivitySchedule startTime="2:05pm" />that reads the ActivityStage children and builds the table automatically.
Option 2 is cleaner for documentation but more complex. Option 1 is simpler - just remove the manual table and rely on the instructor controls preview.
Which direction appeals to you? Or maybe a hybrid: add estimatedTime, show it in the preview, and remove the manual schedule table from the MDX?
Yeah, I think if we want to add these facilitation notes we should do that by adding them inside the activity stages and then gathering them into the table, including the stage timing info. Would that be possible? A bit of extra parsing, but seems possible.
Yes, definitely possible. The ActivityStages component already iterates over children to inject the index prop - we can extend that to gather metadata.
Design could be:
<ActivityStages meetingSlug="..." startTime="2:05pm">
<ActivityStage label="The Confabulator" estimatedTime="10m" rotateRoles>
<FacilitationNotes>
Philosophy: Students discover AI hallucinations through direct experimentation.
</FacilitationNotes>
## The Confabulator
...content...
</ActivityStage>
<ActivityStage label="The Yes-Man" estimatedTime="10m" rotateRoles>
<FacilitationNotes>
Watch for the A → B → A flip pattern. Some students struggle to find debatable topics.
</FacilitationNotes>
## The Yes-Man
...
</ActivityStage>
</ActivityStages>
Then the instructor controls would auto-generate:
| Start | Duration | Activity | Notes |
|---|---|---|---|
| 2:05 | 10m | The Confabulator | Philosophy: Students discover... |
| 2:15 | 10m | The Yes-Man | Watch for the A → B → A flip... |
| 2:25 | 10m | The Forgetter | ... |
Start times calculated automatically from startTime + cumulative estimatedTime.
Want me to implement this? The pieces would be:
- Add
estimatedTimeprop toActivityStage - Add
FacilitationNotescomponent (instructor-only, extracted for table) - Add
startTimeprop toActivityStages - Generate the schedule table in instructor controls
Yes.
Let me start implementing. First, I'll add estimatedTime to ActivityStage.
Now let me update tomorrow's meeting to use these new features and remove the manual schedule table.
Done! Here's what I implemented:
New features:
estimatedTimeprop onActivityStage- for instructor-controlled timing (no auto-advance)FacilitationNotescomponent - extracted and shown in the schedule table, not rendered for studentsstartTimeprop onActivityStages- base time for calculating stage start times- Auto-generated schedule table - replaces the manual table in instructor controls
Tomorrow's meeting now looks like:
<ActivityStages meetingSlug="..." startTime="2:05pm">
<ActivityStage label="The Confabulator" estimatedTime="10m" rotateRoles>
<FacilitationNotes>
Students discover AI hallucinations. Niche academic topics work best.
</FacilitationNotes>
## The Confabulator
...
</ActivityStage>
The instructor controls will show a schedule table with:
| Start | Duration | Activity | Notes |
|---|---|---|---|
| 2:05pm | 10m | The Confabulator | Students discover AI hallucinations... |
| 2:15pm | 10m | The Yes-Man | Watch for the A → B → A flip... |
| ... | ... | ... | ... |
The start times are calculated automatically from startTime + cumulative durations. You can check it in the dev server now.
Nice! But let's display the notes inside each stage as well for instructors.
I'll update FacilitationNotes to display inline for instructors using the activity stages context.
Done! FacilitationNotes now displays inline for instructors with an amber-styled box showing "Facilitation Notes" as a header.
What instructors see:
- Schedule table in controls (with notes column)
- Inline notes box at the start of each stage
What students see:
- Nothing (notes are hidden)
The dev server should show this now. Each stage will display the facilitation notes in an amber box right below the stage header for instructors.
Also, when I check View as Student, the sidebar does not update and still shows the instructor-only facilitation notes. Given that we're going to be loading sections dynamically, we probably need the sidebar to update dynamically, even if the initial content is created by the build process.
Good catch. Let me explore how the sidebar/table of contents is currently generated.
Attempted to call isFacilitationNotes() from the server but isFacilitationNotes is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.
Right, the "use client" directive makes the helper functions unavailable on the server. Let me split this into two files - one for the client component, one for the server-side extraction utilities.
Fixed. The extraction functions are now inlined in the server component (activity-stages.tsx), while FacilitationNotes remains a client component that uses context to check isAdmin.
The dev server should work now. You mentioned the sidebar issue with "View as Student" - would you like me to look into that next?
We're back to this again.
Connection pool exhaustion. Quick fix - restart the database:
bun run db:stop && bun run db:start
This happens in dev when hot reloading creates new connections faster than they're released. If it keeps recurring, we may need to look at the connection pool configuration in the db package.
I don't want to restart the database.
You can kill idle connections directly in PostgreSQL:
docker exec -it usingandunderstanding-postgres psql -U postgres -d usingandunderstanding -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle' AND pid <> pg_backend_pid();"
Or just restart the dev server (not the database) - the old connections will eventually timeout and the new server will get fresh ones.
I don't want to do that either. I want to fix the problem, not work around it.
Let me look at how the database connections are managed.
The issue is connection leaks during hot reloading. Each reload creates a new connection pool without closing the old one. The fix is to use the global object pattern to persist connections across hot reloads.
Are you sure? Is this a known problem? NextJS and Postgres are not an uncommon combination. Have others reported this?
You're right to question this. Let me check how this is typically handled.
Yes, it's a well-known issue. The standard solution is the globalThis singleton pattern to persist the connection across hot reloads. Let me implement it.
Continue.
Done. The fix uses globalThis to persist the database connection across hot reloads - the standard pattern for Next.js/Drizzle/Postgres.
You'll need to restart the dev server once to pick up this change to the db package. After that, hot reloads will reuse the same connection instead of creating new ones.
Sources:
Your task
First, run git status, git diff, and git log --oneline -10 to understand the current state.
Then add and commit all modified files with a descriptive message. Break the changes into multiple commits if appropriate. If there are untracked files, ask if they should be added or added to the appropriate .gitignore file. The user may also want to take over at this point, so provide an abort option as well. Always check changes for unnecessary debugging statements or logging that might have been accidentally included. Prompt the user if you find any, and offer to remove each one by one. Do not use interactive git staging (git add -p) as it does not work.
Changes:
- apps/web/components/mdx/activity-stage-instructor.tsx - Added schedule table, startTime support, estimatedTime/facilitationNotes in StageMetadata
- apps/web/components/mdx/activity-stage.tsx - Added estimatedTime prop
- apps/web/components/mdx/activity-stages.tsx - Added startTime prop, FacilitationNotes extraction logic
- apps/web/mdx-components.tsx - Added FacilitationNotes to MDX components
- content/spring2026/meetings/2026-01-27-ai-scavenger-hunt.mdx - Updated to use new props, removed manual schedule table
- packages/db/src/client.ts - Fixed connection pool exhaustion with globalThis pattern
Untracked file:
- apps/web/components/mdx/facilitation-notes.tsx - New component
These changes fall into two logical groups:1. The FacilitationNotes/schedule table feature (all the activity-stage stuff + the MDX content update) 2. The database connection pool fix
I should offer to commit these as two separate commits since they're unrelated.
Let me check for any debugging statements or unnecessary logging. Looking at the diff, I don't see any console.log or debug statements that shouldn't be there. The code looks clean. </thinking>
I see changes that fall into two logical groups:
1. FacilitationNotes & Schedule Table feature:
apps/web/components/mdx/activity-stage-instructor.tsx- Schedule table generationapps/web/components/mdx/activity-stage.tsx- estimatedTime propapps/web/components/mdx/activity-stages.tsx- startTime prop, FacilitationNotes extractionapps/web/components/mdx/facilitation-notes.tsx- New component (untracked)apps/web/mdx-components.tsx- Register FacilitationNotescontent/spring2026/meetings/2026-01-27-ai-scavenger-hunt.mdx- Use new features
2. Database connection pool fix:
packages/db/src/client.ts- globalThis pattern for hot reload
No debugging statements or unnecessary logging found.
Done. Created two commits:
5c16976- FacilitationNotes component and auto-generated schedule tableee3f538- Fix database connection pool exhaustion during hot reload
OK. We we have a full flow test that allows me to make sure that tomorrow's activity works? It should have at least two students, ideally four, and one instructor, and should walk through exactly how we expect this activity to work. Describe how the test works at each step, and test all major features, including instructor pause. I'd suggest creating a testing-only meeting for this purpose so it's not tied to the actual meeting content.
This is a different task - creating a comprehensive E2E test for the full activity flow. Let me explore the existing E2E testing infrastructure and understand how to set this up.
Excellent! I now have a comprehensive understanding of both the E2E testing infrastructure and the activity flow. Let me design the test plan.
I've written a comprehensive plan for the full activity flow E2E test. Key points:
Test Setup:
- 1 instructor + 4 students (forming 2 pairs)
- Dedicated test meeting at
e2e-activity-test.mdxwith 3 stages - GroupActivity with driver/observer roles and rotation
Flow Tested:
- Session start by instructor
- Group formation (2 pairs via join codes)
- Role assignment verification
- Stage completion by all students
- Instructor advancement with role rotation
- Pause/Resume functionality
- FacilitationNotes visibility (instructor-only)
- Session reset
Files to Create:
content/spring2026/meetings/e2e-activity-test.mdx- Test meetingtesting/e2e/full-activity-flow.spec.ts- Test spec