Studies & participation
How participants enroll, complete tasks, and receive completion codes.
Public studies live at /studies. Each study has a marketing page, an enrollment flow, a task hub, and per-task runners. This guide covers how that works and how to configure it from the admin panel.
Public routes
| Route | Who | Purpose |
|---|---|---|
/studies |
Anyone | Catalog of recruiting/active public studies |
/studies/[slug] |
Anyone | Study summary, enrollment panel |
/studies/[slug]/participate |
Enrolled member | Task list and progress hub |
/studies/[slug]/participate/[taskSlug] |
Enrolled member | Single task runner |
/research/code |
Assigned rater | Dual-rater coding queue |
/research/code/[assignmentId] |
Assigned rater | Score a blinded response |
When a study appears publicly
A study is listed on /studies when both are true:
publicVisibleis checked (Settings → Public study page)- Status is
RECRUITING,ACTIVE, orPUBLISHED
New enrollments are accepted when status is RECRUITING or ACTIVE.
Participant journey
Browse /studies
↓
/studies/[slug] — read summary, review consent
↓
Sign in (or sign up)
↓
Enroll — POST /api/research/enroll
↓
/studies/[slug]/participate — see unlocked tasks
↓
/studies/[slug]/participate/[taskSlug] — complete each task
↓
All required tasks done → completion code displayed
Step 1: Study page
The public page shows:
- Title, public summary, methodology excerpt
- Recruitment status and study metadata
- Linked frameworks, media, and artifacts (from dissemination links)
- Enrollment panel (
StudyEnrollPanel)
If the visitor is not signed in, the panel prompts them to sign in with a callback URL back to the study page.
Step 2: Consent and enrollment
Signed-in members see consent checkboxes:
| Consent | Required? | Purpose |
|---|---|---|
| Study participation | Yes | Core research consent (study_participation) |
| Telemetry | Optional | Behavioral event tracking on study pages |
| AI interaction logging | Optional | Log AI prompt/accept events for AI tasks |
| Anonymized data | Always recorded | Standard anonymized-data consent on enroll |
Consent version: 2026-06-01 (see CONSENT_VERSION in code).
Enrollment calls:
POST /api/research/enroll
Content-Type: application/json
{
"studyId": "...",
"telemetryOptIn": false,
"aiInteractionOptIn": false
}
What happens server-side:
- Validates study exists and is recruiting
- Blocks duplicate enrollment (unless prior status was
WITHDRAWN— then re-enroll with new random condition) - Randomly assigns a condition if the study has arms
- Creates
Participantwith statusENROLLED - Writes
ConsentRecordrows and updatesUserResearchProfile - Sends enrollment confirmation email if Resend is configured
Step 3: Task hub
After enrollment, /studies/[slug]/participate shows:
- Assigned condition name
- List of tasks with locked/unlocked/completed states
- Link to the recommended next task
Which tasks appear:
- Global tasks (
conditionId= null) — all participants - Arm-specific tasks — only participants in that condition
Unlock rules:
- Tasks with
requiresTaskSlugin config stay locked until that slug is completed - Optional tasks (
optional: true) can be skipped for study completion
Step 4: Task runner
Each task page renders based on task type:
| Type | Behavior |
|---|---|
SURVEY |
Structured fields from config.fields[] |
WRITING |
Prompt + textarea, word count validation |
REFLECTION |
Similar to writing with reflection framing |
AI_ASSISTED |
AI suggestion panel + final response (requires OPENAI_API_KEY or falls back to simulated text) |
TECHNICAL |
Technical exercise template |
LEARNING |
Learning-oriented task template |
On submit, the server:
- Validates response (word counts, required fields)
- Marks
TaskResponseasCOMPLETED - Sets participant status to
ACTIVE - Resolves the next task via branch rules /
nextTaskSlug/ linear order - If all required tasks are complete → participant
COMPLETED, session closed, completion code generated
Step 5: Completion code
When all required tasks finish:
- Participant status →
COMPLETED - A completion code is generated and stored on the
Participantrow - Format:
{PREFIX}-{8-char hash}
PREFIX = study.completionCodePrefix (e.g. PFDF)
or derived from study slug if blank
hash = first 8 hex chars of SHA256(participantId:studySlug:completion)
Example: PFDF-A1B2C3D4
The participate hub displays the code plus any external credit instructions configured on the study (SONA, course credit, etc.).
Configuring participation (admin checklist)
Use this checklist when preparing a study for live participants:
- Write a clear public summary on the study settings page
- Set status to
RECRUITINGand enable Public study page - Create conditions if using an experimental design
- Build tasks with correct
orderIndex,requiresTaskSlug, and arm assignments - Test the full chain locally with a test member account
- Set completion code prefix and external credit instructions if using SONA or similar
- Configure
RESEND_API_KEY+RESEARCH_ADMIN_EMAILfor enrollment/completion emails - Set
OPENAI_API_KEYif the study includesAI_ASSISTEDtasks
Task sequencing examples
Linear chain
Three global tasks with increasing orderIndex and explicit prerequisites:
| Slug | orderIndex | requiresTaskSlug |
|---|---|---|
screening |
0 | — |
agency-baseline |
1 | screening |
debrief |
2 | agency-baseline |
Per-arm tasks
Same screening for everyone, different writing task per condition:
| Slug | Condition | requiresTaskSlug |
|---|---|---|
screening |
All | — |
critical-analysis-non-ai |
non-ai | screening |
critical-analysis-frictionless |
frictionless-ai | screening |
critical-analysis-purposeful |
purposeful-friction-ai | screening |
Branching
After a survey task with a confidence scale field:
{
"branchRules": [
{
"field": "confidence",
"operator": "gte",
"value": "4",
"nextTaskSlug": "deep-dive"
}
],
"nextTaskSlug": "standard-followup"
}
If confidence ≥ 4 → deep-dive; otherwise → standard-followup.
AI-assisted tasks
AI tasks call POST /api/research/ai-interaction during the task runner:
| Action | Purpose |
|---|---|
generate |
Request an AI suggestion from aiSystemPrompt |
accept / reject / revise |
Log participant interaction with the suggestion |
Requires enrollment and (when configured) AI interaction consent. Events (ai_prompt, ai_accept, etc.) feed the event queue for analysis.
Configure:
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o-mini
Without an API key, the platform returns simulated placeholder text so flows can be tested locally.
Participant data model
| Model | Key fields |
|---|---|
Participant |
status, conditionId, enrolledAt, completedAt, completionCode, cohort |
ParticipantSession |
Active session per enrollment |
TaskResponse |
status, responseData (JSON), wordCount, durationSeconds |
UserResearchProfile |
consentStatus, telemetryOptIn, participationHistory, researchRole |
ConsentRecord |
Per-consent-type grant/revoke audit trail |
Participant statuses:
SCREENING → ENROLLED → ACTIVE → COMPLETED
Also: WITHDRAWN, ABANDONED
Admin participant management
Per-study list
/admin/studies/[id]/participants — filter by status, view condition, enrollment dates, link to detail.
Participant detail
/admin/studies/[id]/participants/[participantId] — admin controls:
- Change assigned condition
- Force status (setting
COMPLETEDgenerates a completion code if missing) - View event timeline
Cross-study search
/admin/participants — search by name, email, study, status.
Event tracking
Client events
Study pages emit events to POST /api/events (page views, task interactions). Telemetry requires opt-in at enrollment or on the research profile.
Server events
Enrollment, task start/submit, AI interactions, and study completion are logged server-side via logEvent() → event queue → event_logs table.
Process the queue with cron:
curl -H "Authorization: Bearer $CRON_SECRET" \
https://your-domain.com/api/cron/process-events
Set EVENT_QUEUE_DISABLED=true to write events synchronously during debugging.
Email notifications
| Trigger | Requires |
|---|---|
| Enrollment confirmation | RESEND_API_KEY, participant email |
| Study completion | Same |
| Scheduled export ready | Notify email on export schedule |
Without Resend, emails are skipped silently in production — check server logs in dev.
Member account integration
Participants manage research preferences at /account → Research profile section:
- Consent status
- Telemetry opt-in
- Study interest areas
- Participation history
Saved studies and task progress are tied to the member account — participants must sign in before enrolling.
Rater participation (separate from enrollment)
Dual-rater coding uses a different flow from study participation:
- Admin adds rater by email on Coding tab (user must exist)
- Admin generates assignments from completed responses
- Rater visits
/research/code(not the study participate URL) - Rater scores blinded responses; κ computed when ≥ 2 raters finish the same response
Platform role RESEARCHER grants research admin access — it does not auto-assign rater duties. Rater assignment is per-study via StudyRater.
Demo and test accounts
After npm run db:seed:
| Study | URL |
|---|---|
| PFDF pilot | /studies/purposeful-friction-pilot |
| Account | Password | |
|---|---|---|
| Demo participant | pfdf-demo-non-ai-1@thehclab.com |
pfdf-demo |
| Rater B | pfdf-rater-b@thehclab.com |
pfdf-demo |
| Admin | lab@thehclab.com |
ADMIN_PASSWORD |
Demo participants have pre-completed responses across all three arms for analytics and coding smoke tests.
Local test workflow:
- Sign in as a fresh member (or use a demo account)
- Enroll in the PFDF study
- Walk through
/participatetasks - Confirm completion code format
PFDF-XXXXXXXX - In admin, run ANALYSIS_WIDE export and check Analytics ANOVA panel
Related documentation
- Research operations — admin setup: conditions, exports, coding, webhooks
- Background jobs — cron and event queue
- Roles & permissions — who can manage vs participate