Group Photo Awareness on Instructor Activity Page
Implement the following plan:
Group Photo Awareness on Instructor Activity Page
Context
During in-class activities with grouped stages, the instructor controls panel shows completion counts like "2 of 10 groups" but doesn't show WHO is working with whom. The goal is to display student photos clustered by group so the instructor can see at a glance which students are paired together. Names appear on hover.
Data Flow
- Activity sessions (
activitySessions) track meeting-level stage progress - Groups (
activityGroups+activityGroupMembers) track who is in which group, keyed bygroupKey+activityId+semester - Completions (
activityStageCompletions) link sessions to groups viagroupIdcolumn - Photos served at
/api/admin/roster/photo/:uin, with email→UIN mapping from roster CSV - The status endpoint already detects grouped stages and counts groups, but doesn't return membership details
Files to Create
1. apps/api/src/lib/roster-utils.ts — Shared roster helpers
Extract parseRosterCsv, getDataDir, and getCurrentSemesterId from apps/api/src/routes/admin/roster.ts so both the roster route and the new groups endpoint can reuse them.
2. New API route: groups for a session
File: apps/api/src/routes/admin/activity-stages.ts (add new route)
Add GET /:meetingSlug/groups?semester=... to the existing admin activity stages router.
Logic:
- Find the session by
meetingSlug+semester - Get all
activityStageCompletionswith non-nullgroupId, grouped bystageIndex - For each stage's group IDs, look up the
activityIdfromactivityGroups - For each
activityId, get ALL groups and members (including groups that haven't completed yet — this catches groups that formed but are still working) - Parse the roster CSV to build email→UIN map
- Return:
{
stageGroups: {
[stageIndex: number]: Array<{
groupId: string;
members: Array<{
name: string | null;
email: string;
uin: string | null;
joined: boolean;
}>;
completed: boolean; // whether this group has completed this stage
}>;
}
}
Files to Modify
3. apps/api/src/routes/admin/roster.ts — Use extracted helpers
Replace inline parseRosterCsv, getDataDir, getCurrentSemesterId with imports from apps/api/src/lib/roster-utils.ts.
4. apps/web/components/mdx/activity-stage-instructor.tsx — Add group photos UI
Changes to ActivityStageInstructorControls:
- Add a second
useSWRcall for the groups endpoint:/api/admin/activity-stages/${meetingSlug}/groups?semester=${semester}, polling every 3s (same as status) - Only fetch when session exists (conditional SWR)
- Pass group data to
StageRowvia newgroupsprop
Changes to StageRow:
- New optional prop:
groups: Array<{ groupId, members[], completed }> - When
stage.isGrouped && groups?.length > 0, render a group visualization below the existing stage info - Layout: A flex-wrap row of group clusters
- Each group cluster: An avatar stack of 2-3 small circular photos (h-8 w-8), slightly overlapping with negative margin (-ml-2 for 2nd+ photo)
- Completed groups: green ring/border on the cluster
- Non-completed groups: default border
- Photos with UINs:
<img>withsrc="${env.NEXT_PUBLIC_API_URL}/api/admin/roster/photo/${uin}",title="${name}"for hover - Photos without UINs (no photo): Show initials circle with gray background,
title="${name}" - The whole group cluster is compact — something like a pill/chip containing the overlapping avatars
- Add
data-testidattributes for testing:group-cluster-{groupId},group-member-{email}
5. testing/e2e/tests/full-activity-flow.spec.ts — Add group visualization test
Add a new test that extends the existing full-flow pattern:
test("instructor sees group clusters after students form groups and complete a stage")
Steps:
- Create instructor + 4 students (to get 2 groups)
- Instructor starts session
- Two pairs of students form groups
- Both pairs complete Stage 1
- Verify instructor view shows group clusters via
data-testid="stage-group-clusters-0" - Verify 2 group clusters are visible (one per pair)
- Verify each cluster contains 2 member avatars with
data-testid="group-member-avatar" - Verify member names are accessible via
titleattribute
Note: Test users won't have actual photos (no UIN/photo file), so the test verifies the initials-fallback UI. The structure and grouping logic is fully testable.
Implementation Order
- Create
apps/api/src/lib/roster-utils.ts(extract helpers) - Update
apps/api/src/routes/admin/roster.ts(use extracted helpers) - Add groups endpoint to
apps/api/src/routes/admin/activity-stages.ts - Update
apps/web/components/mdx/activity-stage-instructor.tsx(fetch + display) - Add E2E test to
testing/e2e/tests/full-activity-flow.spec.ts
Verification
- Check dev server is running:
curl -sk -o /dev/null -w "%{http_code}" https://[local-hostname]/ - Navigate to a meeting page with group activities as instructor (e.g.,
/spring2026/meetings/2026-02-17-alphago-discussion) - Start the activity session
- Open the meeting page in another browser/incognito as a student, form a group
- Verify the instructor view shows photo clusters for formed groups
- Verify hovering a photo shows the student name
- Verify completed groups get a visual distinction
- Run E2E test:
CI=true cd testing/e2e && bunx playwright test --project=full-activity-flow - Run type checking:
cd apps/web && bun run tscandcd apps/api && bun run tsc
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/da526847-cef4-4927-90df-fba4cbf7473c.jsonl
I'll start by reading the key files I need to understand before implementing the changes.
Let me also check the DB schema for activity groups and understand the existing patterns.
Now let me create tasks and start implementing. Let me first check the activity stage completions schema.
Good, I now have a thorough understanding of the data model. Let me create the implementation tasks and start working.
Creating the shared roster utils file.
Now update roster.ts to use the extracted helpers.
Now I'll add the groups endpoint to activity-stages.ts. I need to add the route at the end of the file, before the export.
Now add the groups endpoint before the export statement.
Now let me update the instructor controls component. I need to add the SWR fetch for groups and render group photo clusters in StageRow.
Now I'll add the group types and modify the component. First, add the types after AdminStatusResponse:
Now add the SWR call for groups in the ActivityStageInstructorControls component. I'll add it right after the existing SWR call:
Now pass the group data to StageRow. I need to update the StageRow invocation in the JSX:
Now update the StageRow interface and component to accept and render groups:
Now update the StageRow function signature and add the groups rendering. I need to add groups to the destructured props and then add the visualization after the existing row content:
Now I need to change the StageRow from a single div with flex items-center justify-between to a wrapper that contains the existing row content plus the groups visualization below. Let me replace the entire return block of StageRow:
Now I need to add the GroupCluster and MemberAvatar components. I'll add them right before the SessionTimingIndicator function:
Now add the E2E test. I'll add a new test describe block to the existing file:
Now let me run the TypeScript checks to verify the code compiles.
Both pass. Let me now check if the dev server is running, then run the E2E tests.
Dev server is running. Let me run the E2E tests.
Let me check the playwright config to see how the full-activity-flow project is defined:
Let me check if the test file is being picked up:
The test is listed. Let me try running it with the exact test name:
All tests pass. Let me also run the full activity flow test suite to make sure I haven't broken anything:
All 20 tests pass, including the new group photo clusters test.
All done. Here's a summary of what was implemented:
Changes Made
New file: apps/api/src/lib/roster-utils.ts
Extracted shared helpers (getCurrentSemesterId, getDataDir, parseRosterCsv) from the roster route so both the roster and the new groups endpoint can reuse them.
Modified: apps/api/src/routes/admin/roster.ts
Replaced the inline helper functions with imports from roster-utils.ts.
Modified: apps/api/src/routes/admin/activity-stages.ts
Added GET /:meetingSlug/groups?semester=... endpoint that:
- Finds the session and all completions with non-null
groupId - Looks up the
activityIdfrom completed groups to find ALL formed groups - Joins with group members and users to get names/emails
- Maps emails to UINs via the roster CSV (gracefully handles missing roster in test environments)
- Returns
{ stageGroups: { [stageIndex]: Array<{ groupId, members[], completed }> } }
Modified: apps/web/components/mdx/activity-stage-instructor.tsx
- Added second SWR call to the groups endpoint (conditional on session existing + having grouped stages, polling every 3s)
- Added
GroupClustercomponent: a rounded pill with overlapping member avatars, green border when completed - Added
MemberAvatarcomponent: shows photo from/api/admin/roster/photo/:uinif UIN available, otherwise shows initials circle with gray background - Names appear on hover via
titleattribute - All elements have
data-testidattributes for testing
Modified: testing/e2e/tests/full-activity-flow.spec.ts
Added "Group Photo Clusters" test that creates 4 students in 2 pairs, has them complete Stage 1, then verifies:
- Instructor sees
stage-group-clusters-0container - 2 group clusters are visible (one per pair)
- 4 member avatars total (2 per cluster)
- Member names accessible via
titleattribute
All 20 tests pass (including the new one).
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.
No debug statements. The untracked apps/api/src/lib/ directory contains the new roster-utils.ts file — that should be committed. All changes form one coherent feature, so a single commit makes sense.
Committed as 7f9fd5c. All 5 files in one coherent commit covering the full feature: shared roster utils, the groups API endpoint, the instructor UI, and the E2E test.
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.
All clean — formatting/lint fixes and a tailwind-merge patch bump. No debug statements. One commit covers it all.
Committed as 5dd3183.
Can we check if the backup task is working properly? It should have run overnight.
The backup is working perfectly. It ran today at 8:00 AM UTC (2:00 AM Central) and completed in 8 seconds:
- Job:
db-backup-29523360— Status: Complete (1/1), Duration: 18s - Prod backup:
prod-2026-02-18-080008.sql.gzuploaded to Box - Dev backup:
dev-2026-02-18-080008.sql.gzuploaded to Box
The CronJob was created yesterday (~22h ago) on schedule 0 8 * * * (8 AM UTC / 2 AM Central). One note: the CLAUDE.md says it runs at 2 AM Central, but the schedule is 0 8 * * * UTC with no timezone set — that's correct during CST (UTC-6) but will shift to 3 AM Central during daylight saving time (CDT, UTC-5). If you want it pinned to 2 AM Central year-round, you could add a timeZone: "America/Chicago" field and change the schedule to 0 2 * * *.
Amazing! Can we check for those files using local rclone and make sure the contents look sane?
Your local rclone Box token has expired. You'll need to re-authenticate:
rclone config reconnect box:
This will open a browser for the Box OAuth flow. Once that's done, we can list and inspect the backup files.
Alternatively, we could verify via the K8s pod's rclone (which clearly has a valid token since the backup succeeded). Want me to try that route instead, or do you want to reconnect your local rclone first?
I reconnected.
Both files are there. Let me pull them down and check the contents look sane.
Both backups look great:
- Prod (841 KB) and Dev (344 KB) — reasonable sizes
- Valid
pg_dumpformat (PostgreSQL 16.11, dumped by 16.12) - Schema includes
drizzle,pgvectorextension, custom types - 50 CREATE TABLE / COPY statements in prod — full schema + data
- Ends with
PostgreSQL database dump complete— no truncation \restrict/\unrestrictguards present for safe restore
Everything checks out — the backup pipeline is working correctly.
Nice! Thanks. This works perfectly.
Glad to hear it! The backup system is solid — automated, fast (8 seconds), and producing clean full dumps to Box every night. Enjoy the peace of mind!
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.
A version bump to 2026.2.17 across all 23 packages. Clean — nothing else changed.
Done — e087a45.
Nice! OK. Now we need another conversational assessment with questions about creativity drawn from our recent discussion and the AlphaGo documentary. This one should also be set up for students to take on their own, similar to the Turing Test one. Actually let's make it publicly accessible as well. Start by picking a few questions. For this assessment, it may be best to choose a few that ask students to choose a position, maybe similar to or identical to questions explored during yesterday's discussion. Design the questions and the assessment configuration for the two-agent approach.
That's from the previous task. This is a completely different task. Let me explore the existing assessment system and the AlphaGo discussion content.
Let me read the existing Turing Test assessment as a concrete template, and check how assessments are linked from meeting pages.
Now let me read the AlphaGo discussion meeting content to see the exact discussion questions used.
Now I have everything I need. Let me also quickly check how assessments are accessed via routes to confirm the public URL pattern.
Good — assessments are linked as /assessments/{slug}. Now let me design the assessment and write the plan.
Now let me ask about a key design question before finalizing.
Good. The plan is solid — 3 opinion portions, all publicly accessible, matching the confirmed selection. The plan file already reflects this design with full detail on criteria, evaluator context, and verification steps.
One concern here is that we didn't really get to Question 4 yesterday in class, so students might not be familiar with the metaphors. Perhaps we can make this less tied to the discussion, and ask instead simply about how they think humans and AI can or can't work together effectively?
Good point. Let me update the plan to make Portion 3 a broader question about human-AI collaboration that doesn't depend on the centaur/reverse-centaur framing from Round 4.
Also update the corresponding portion description in the file-to-create section:
Updated. Portion 3 is now a general question about human-AI collaboration effectiveness grounded in students' own experience, not the centaur/reverse-centaur framing they may not have discussed.
Note that I'm gone again tomorrow so I'm going to post this for students to do outside of class, so it won't be linked off of a meeting.
Updated — no meeting page modification, just the standalone assessment file accessible at /assessments/creativity.