HCTHE HCLAB

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:

  1. publicVisible is checked (Settings → Public study page)
  2. Status is RECRUITING, ACTIVE, or PUBLISHED

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:

  1. Validates study exists and is recruiting
  2. Blocks duplicate enrollment (unless prior status was WITHDRAWN — then re-enroll with new random condition)
  3. Randomly assigns a condition if the study has arms
  4. Creates Participant with status ENROLLED
  5. Writes ConsentRecord rows and updates UserResearchProfile
  6. 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 requiresTaskSlug in 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:

  1. Validates response (word counts, required fields)
  2. Marks TaskResponse as COMPLETED
  3. Sets participant status to ACTIVE
  4. Resolves the next task via branch rules / nextTaskSlug / linear order
  5. 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 Participant row
  • 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 RECRUITING and 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_EMAIL for enrollment/completion emails
  • Set OPENAI_API_KEY if the study includes AI_ASSISTED tasks

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:

SCREENINGENROLLEDACTIVECOMPLETED

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 COMPLETED generates 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:

  1. Admin adds rater by email on Coding tab (user must exist)
  2. Admin generates assignments from completed responses
  3. Rater visits /research/code (not the study participate URL)
  4. 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 Email 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:

  1. Sign in as a fresh member (or use a demo account)
  2. Enroll in the PFDF study
  3. Walk through /participate tasks
  4. Confirm completion code format PFDF-XXXXXXXX
  5. 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