Generic partner roles with index-based content routing
Implement the following plan:
Generic partner roles with index-based content routing
Summary
Replace driver/observer with { "partner": "*" } role config. Add an index prop to GroupRoleContent for routing different content to different partners by member position. Add an overflow prop to GroupActivity controlling how 3rd partners are handled.
Scope: Component changes are general; MDX changes are today's activity only (2026-02-03).
Design
roles={{ "partner": "*" }}— everyone gets role "partner", no role labels to display<GroupRoleContent role="partner" index={0}>— shows to first partner only<GroupRoleContent role="partner" index={1}>— shows to second partner only- 3rd partner overflow controlled by
<GroupActivity overflow="random">(default) oroverflow="expand""random": 3rd member sees one of the existing partners' content (deterministic hash of groupId)"expand": 3rd member keeps index 2, for explicitindex={2}bonus content blocks
The existing role assignment algorithm (calculateUserRole/calculateRoleAssignments) needs no changes — with { "partner": "*" } everyone gets role "partner". The index-based filtering is purely a rendering concern in GroupRoleContent.
File changes
1. apps/web/components/mdx/group-activity-context.tsx
Add targetSize and overflow to GroupActivityContextValue:
export interface GroupActivityContextValue {
// ... existing fields ...
targetSize: number; // NEW
overflow: "random" | "expand"; // NEW
}
2. apps/web/components/mdx/group-activity.tsx
Add overflow prop to GroupActivityProps and pass through:
interface GroupActivityProps {
// ... existing ...
overflow?: "random" | "expand"; // NEW, default "random"
}
Pass overflow={overflow ?? "random"} to GroupActivityClient.
3. apps/web/components/mdx/group-activity-client.tsx
- Accept
overflowprop inGroupActivityClientProps - Add
targetSizeandoverflowto the context value (lines 302-313)
4. apps/web/components/mdx/group-role-content.tsx
Add index prop and position-based matching logic:
interface GroupRoleContentProps {
children: React.ReactNode;
role: string;
index?: number; // NEW — match by member position within group
}
When index is provided:
- Calculate member's position in sorted joined members (by roleIndex)
- If position >= targetSize (overflow member):
overflow === "random":effectiveIndex = hash(groupId) % targetSizeoverflow === "expand":effectiveIndex = position(raw)
- Match if
userRole === role AND effectiveIndex === index
When index is not provided: current behavior (match on role only).
Add a simpleStringHash helper for deterministic overflow remapping:
function simpleStringHash(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0;
}
return Math.abs(hash);
}
5. content/spring2026/meetings/2026-02-03-assessments-and-agents.mdx
Stage 1 — Review Assessment Design (lines 44-77):
roles={{ driver: 1, observer: "*" }} → roles={{ partner: "*" }}
<GroupRoleContent role="driver">→<GroupRoleContent role="partner" index={0}><GroupRoleContent role="observer">→<GroupRoleContent role="partner" index={1}>- Remove "Driver:" / "Observer:" prefixes from content
- Add brief context about what partner is doing (e.g., "While your partner reads the blog post...")
<TextSubmission>stays insideindex={1}block (the blog reader captures observations)
Stage 4 — Agents Talking to Agents (lines 131-177):
Same pattern: roles={{ partner: "*" }}, replace role props with index props, remove prefixes.
Files NOT modified
group-activity-context.tsxcalculateUserRole/calculateRoleAssignments— no algorithm changes needed;{ "partner": "*" }works with existing fill-role logicapps/api/src/routes/group-activities.ts— server role calculation unaffectedgroup-activity-role-display.tsx— not used in any MDX content- Other meeting MDX files — scoped to today only
Verification
bun run tsc— type checkbun run lint— lint checkCI=true bun run test:e2e— existing E2E tests pass (group-activity, full-activity-flow)- Manual: dev server shows today's activity with no role labels, each partner sees only their own instructions
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/ac3e57f2-fad0-4273-a526-5a358b26e14e.jsonl
I'll start by reading the key files that need to be modified to understand the current code structure.
Now I have a clear picture of all the files. Let me create the task list and start implementing.
Now let me implement all the changes. I'll start with the component changes in parallel where possible.
Now update the context value to include targetSize and overflow:
Now the main logic change — GroupRoleContent with index-based filtering:
Now add the hash helper and update the component function:
Now update the component signature and the preview/admin badges to show index info:
Update the admin badge too:
Now update the matching logic at the end of the component to handle the index prop:
Now update the MDX content. Stage 1 (Review Assessment Design):
Now Stage 4 (Agents Talking to Agents):
Now let me run type check and lint in parallel.
TypeScript passes. The lint error is a pre-existing issue in login/page.tsx (unescaped '), not related to my changes. Let me verify this is pre-existing:
Confirmed — the lint error is pre-existing (same error on the stashed clean branch). Let me restore my changes:
All done. Here's a summary of the changes:
Context & types (group-activity-context.tsx):
- Added
targetSize: numberandoverflow: "random" | "expand"toGroupActivityContextValue
Server component (group-activity.tsx):
- Added
overflow?: "random" | "expand"prop toGroupActivityProps - Passes
overflow ?? "random"toGroupActivityClient
Client component (group-activity-client.tsx):
- Added
overflowtoGroupActivityClientProps, destructured with default"random" - Included
targetSizeandoverflowin the context value and its dependency array
Role content (group-role-content.tsx):
- Added
index?: numberprop for position-based matching - Added
simpleStringHashhelper for deterministic overflow remapping - Preview/admin badges now show
partner[0]/partner[1]when index is set - When
indexis provided: calculates member position among sorted joined members, handles overflow (3rd partner mapped via hash for"random", kept raw for"expand"), matches onrole AND index - When
indexis omitted: existing behavior (role-only match)
MDX content (2026-02-03-assessments-and-agents.mdx):
- Stage 1 and Stage 4:
roles={{ driver: 1, observer: "*" }}→roles={{ partner: "*" }} <GroupRoleContent role="driver">→<GroupRoleContent role="partner" index={0}><GroupRoleContent role="observer">→<GroupRoleContent role="partner" index={1}>- Removed "Driver:" / "Observer:" prefixes, added contextual phrases like "While your partner reads the design document..."
- Changed "what the Driver is seeing" → "what your partner is seeing"
TypeScript passes cleanly. The one lint error (login/page.tsx unescaped quote) is pre-existing.
Do we need to update the e2e test for today's activity?
(no content)
The e2e tests don't reference the specific meeting MDX we changed. But mixed-stages.spec.ts and full-activity-flow.spec.ts test "driver"/"observer" roles extensively. Let me check whether those tests use their own test fixtures or the real content.
No. The e2e tests use their own test fixture pages (/testing/components/full-activity-flow and /testing/components/mixed-stages), not the real meeting MDX. Those fixtures still use driver/observer roles — which is correct, since driver/observer is still a valid role configuration and nothing about its behavior changed.
Our changes only affect:
- The context interface (additive — new fields
targetSizeandoverflow) GroupRoleContentbehavior when the newindexprop is provided (existing behavior unchanged whenindexis omitted)- The specific meeting MDX for today
None of the existing tests exercise index-based routing or roles={{ partner: "*" }}, so they won't break. And since no test navigates to the real meeting page, the MDX changes are irrelevant to the test suite.
Ah, I thought we had an e2e test specifically for today's activity.
(no content)
Let me look more carefully.
Having per-activity tests would ease my mind before class starts, yes. We can set these up to only run when requested and not every time. Try to test a complete flow using multiple students, maybe two groups, one pair and one trio, and the instructor controls, and so on. For the assessment part maybe just make sure that link works and the backend responds, but no need to say anything intelligent.
Now I have enough context to write the plan. Let me also quickly check the assessment page route testids.
Good. Now let me check how the full-activity-flow.spec.ts structures its serial tests — it's the closest pattern to what I need.
Now I have a thorough understanding of the test infrastructure. Let me write the plan.