diff --git a/docs/06-development/01-overview.mdx b/docs/06-development/01-overview.mdx
new file mode 100644
index 00000000..63554612
--- /dev/null
+++ b/docs/06-development/01-overview.mdx
@@ -0,0 +1,108 @@
+---
+sidebar_position: 1
+title: Developer Documentation
+description: Technical reference for developers building on or contributing to StudyU
+---
+
+# Developer Documentation
+
+StudyU is an open-source platform for designing and running N-of-1 clinical trials on participants' smartphones. It consists of two Flutter applications sharing a common core library, backed by Supabase (PostgreSQL + Auth + Storage).
+
+## System Architecture
+
+```mermaid
+graph TB
+ subgraph "Client Layer"
+ APP["StudyU App
Flutter (iOS, Android, Web)
For participants"]
+ DESIGNER["StudyU Designer
Flutter Web
For researchers"]
+ end
+
+ subgraph "Shared Code"
+ CORE["studyu_core
Domain models, serialization,
Supabase ORM utilities"]
+ COMMON["studyu_flutter_common
Shared Flutter widgets,
localization, secure storage"]
+ end
+
+ subgraph "Backend (Supabase)"
+ AUTH["Auth (GoTrue)
Email/password"]
+ DB["PostgreSQL
RLS-protected tables"]
+ STORAGE["Storage
Blob bucket: observations"]
+ EDGE["Edge Functions
(minimal usage)"]
+ end
+
+ APP --> COMMON
+ APP --> CORE
+ DESIGNER --> COMMON
+ DESIGNER --> CORE
+ COMMON --> CORE
+
+ APP -->|"supabase_flutter SDK"| AUTH
+ APP -->|"REST API (PostgREST)"| DB
+ APP -->|"File uploads"| STORAGE
+
+ DESIGNER -->|"supabase_flutter SDK"| AUTH
+ DESIGNER -->|"REST API (PostgREST)"| DB
+
+ style APP fill:#4FC3F7,color:#000
+ style DESIGNER fill:#81C784,color:#000
+ style CORE fill:#FFB74D,color:#000
+ style COMMON fill:#FFB74D,color:#000
+ style AUTH fill:#CE93D8,color:#000
+ style DB fill:#CE93D8,color:#000
+ style STORAGE fill:#CE93D8,color:#000
+ style EDGE fill:#CE93D8,color:#000
+```
+
+## Tech Stack
+
+| Layer | Technology | Notes |
+|---|---|---|
+| **Mobile / Web apps** | Flutter / Dart 3.8+ | Two apps from one codebase |
+| **App state (participants)** | Provider + ChangeNotifier | Lightweight, mobile-first |
+| **App state (researchers)** | Riverpod + code gen | Complex async form workflows |
+| **Routing (participants)** | Navigator 1.0 | Named routes, imperative |
+| **Routing (researchers)** | GoRouter v17 | URL-driven, auth guards |
+| **Form handling (researchers)** | reactive_forms | Debounced auto-save |
+| **Serialization** | json_serializable | Compile-time safety, code-generated |
+| **Monorepo tooling** | Melos | Script runner, package linking |
+| **Backend** | Supabase | PostgreSQL + Auth + Storage + PostgREST |
+| **Database** | PostgreSQL 15 | RLS on all tables |
+| **Authentication** | GoTrue (Supabase) | Email/password only |
+| **Error tracking** | Sentry | Analytics opt-in |
+
+## Key Architectural Decisions
+
+| Decision | Choice | Rationale |
+|---|---|---|
+| Monorepo | Melos-managed workspace | Shared models and utilities across two apps |
+| Backend | Supabase (hosted PostgreSQL) | Auth, RLS, real-time, and storage out of the box |
+| State management (App) | Provider | Lightweight, sufficient for mobile-first participant flows |
+| State management (Designer) | Riverpod | Complex form workflows and async data management |
+| Serialization | json_serializable (code gen) | Compile-time safety for 30+ model classes |
+| Offline strategy | Exception-based fallback + cache sync | Pragmatic for clinical data entry in low-connectivity scenarios |
+
+## Reading Order for New Developers
+
+If you are new to StudyU, follow this sequence:
+
+1. **[Domain Model: N-of-1 Trials](./02-domain-model/01-n-of-1-trials.mdx)** — understand the clinical research concepts before touching code
+2. **[Domain Model: Core Entities](./02-domain-model/02-core-entities.mdx)** — learn what Study, Intervention, Observation, and Subject mean in the codebase
+3. **[Domain Model: Entity Relationships](./02-domain-model/03-entity-relationships.mdx)** — see how entities connect via diagrams
+4. **[Domain Model: Concrete Example](./02-domain-model/04-concrete-example.mdx)** — walk through the Vitamin D trial end-to-end
+5. **[Architecture: System Overview](./03-architecture/01-system-overview.mdx)** — high-level architecture and tech stack
+6. **[Architecture: Monorepo Structure](./03-architecture/02-monorepo-structure.mdx)** — packages, dependencies, Melos scripts
+7. **[Local Setup: Prerequisites](./06-local-setup/01-prerequisites.mdx)** — tools to install
+8. **[Local Setup: Clone and Bootstrap](./06-local-setup/02-clone-and-bootstrap.mdx)** — get the repo running
+9. **[Local Setup: Running Apps](./06-local-setup/04-running-apps.mdx)** — start the apps locally
+
+## Documentation Sections
+
+| Section | What it covers |
+|---|---|
+| [Domain Model](./02-domain-model/01-n-of-1-trials.mdx) | N-of-1 trial concepts, entity glossary, ERD diagrams, concrete walkthrough |
+| [Architecture](./03-architecture/01-system-overview.mdx) | System design, monorepo, frontend patterns, Supabase backend, offline sync |
+| [App Reference](./04-app-reference/01-screen-map.mdx) | Participant app screen flow, task system, widget reference |
+| [Designer Reference](./05-designer-reference/01-screen-map.mdx) | Researcher app navigation, design tabs, management screens, form patterns |
+| [Local Setup](./06-local-setup/01-prerequisites.mdx) | Prerequisites, clone, Supabase, running apps, environment config, code generation |
+| [Development Workflow](./07-development-workflow/01-edit-build-test.mdx) | Daily workflow, coding conventions, IDE setup, troubleshooting |
+| [Database Reference](./08-database-reference/01-schema.mdx) | Tables, columns, computed functions, RLS policies, migrations |
+| [JSON Schema Reference](./09-json-schema-reference/01-overview.mdx) | Complete JSON structure of every model class with examples |
diff --git a/docs/06-development/02-domain-model/01-n-of-1-trials.mdx b/docs/06-development/02-domain-model/01-n-of-1-trials.mdx
new file mode 100644
index 00000000..cdcaf932
--- /dev/null
+++ b/docs/06-development/02-domain-model/01-n-of-1-trials.mdx
@@ -0,0 +1,144 @@
+---
+sidebar_position: 1
+title: What is an N-of-1 Trial?
+description: The clinical research concept at the heart of StudyU, explained for developers
+---
+
+# What is an N-of-1 Trial?
+
+This document is the entry point for developers joining the StudyU project who know Flutter but have no background in clinical research. Understanding the research concept behind the product is the key to understanding why the data model looks the way it does.
+
+## The problem with traditional clinical trials
+
+A standard Randomized Controlled Trial (RCT) enrolls hundreds or thousands of participants, randomly assigns them to a treatment or a placebo group, and then measures the *average* treatment effect across the group. This answers the question "does this drug work for people *on average*?" — but it says nothing about whether the drug will work for *you specifically*.
+
+## What an N-of-1 trial is
+
+An N-of-1 trial studies a **single participant** across time. Instead of comparing two groups of different people, it compares the *same person* across different treatment phases. The participant alternates between an active treatment (Intervention A) and a control or alternative treatment (Intervention B), repeating the cycle multiple times. Because the same person experiences both conditions, many sources of individual variability are controlled automatically.
+
+This answers a different question: **"Does this treatment work for this individual?"**
+
+## Why this matters for StudyU
+
+StudyU is a platform that lets researchers design and run N-of-1 trials digitally, using participants' smartphones. The clinical insight translates directly into product requirements:
+
+- A study must have **at least two intervention arms** to alternate between.
+- A study has a **schedule** that dictates which arm is active on which days.
+- Data is collected through **tasks** that participants complete on each study day.
+- Results are meaningful *per participant*, not only in aggregate.
+
+## How a typical N-of-1 trial runs
+
+```mermaid
+flowchart TD
+ A([Participant opens StudyU app]) --> B[Browse study registry\nor enter invite code]
+ B --> C[View study details]
+ C --> D{Has eligibility check?}
+ D -- No --> F
+ D -- Yes --> E[Complete eligibility questionnaire]
+ E --> EE{All criteria\nsatisfied?}
+ EE -- No --> Z1([Not eligible — exit])
+ EE -- Yes --> F
+ F{Has consent items?}
+ F -- No --> H
+ F -- Yes --> G[Review and agree to\neach consent item]
+ G --> GG{All items\nagreed?}
+ GG -- No --> Z2([Cannot enroll — exit])
+ GG -- Yes --> H
+ H[Enrolled as StudySubject\nstartedAt recorded] --> I
+ I{includeBaseline?}
+ I -- Yes --> J[Baseline phase\nObservations only — no intervention tasks]
+ I -- No --> K
+ J --> K[Intervention phase A\nIntervention tasks + Observations]
+ K --> L[Intervention phase B\nIntervention tasks + Observations]
+ L --> M{More cycles?}
+ M -- Yes --> K
+ M -- No --> N[Study complete]
+ N --> O[View personal results report]
+```
+
+## Phase sequences
+
+The order of intervention phases within each cycle is configurable by the researcher:
+
+```mermaid
+gantt
+ title Study Phase Sequences (phaseDuration = 7 days, numberOfCycles = 2, includeBaseline = true)
+ dateFormat YYYY-MM-DD
+ axisFormat Day %j
+
+ section Alternating (ABAB)
+ Baseline :baseline1, 2024-01-01, 7d
+ Intervention A :a1, after baseline1, 7d
+ Intervention B :b1, after a1, 7d
+ Intervention A :a2, after b1, 7d
+ Intervention B :b2, after a2, 7d
+
+ section Counter-Balanced (ABBA)
+ Baseline :baseline2, 2024-01-01, 7d
+ Intervention A :c1, after baseline2, 7d
+ Intervention B :c2, after c1, 7d
+ Intervention B :c3, after c2, 7d
+ Intervention A :c4, after c3, 7d
+
+ section Randomized
+ Baseline :baseline3, 2024-01-01, 7d
+ Intervention A :d1, after baseline3, 7d
+ Intervention B :d2, after d1, 7d
+ Intervention B :d3, after d2, 7d
+ Intervention A :d4, after d3, 7d
+
+ section Customized (ABBA string)
+ Baseline :baseline4, 2024-01-01, 7d
+ Intervention A :e1, after baseline4, 7d
+ Intervention B :e2, after e1, 7d
+ Intervention B :e3, after e2, 7d
+ Intervention A :e4, after e3, 7d
+```
+
+> **Note:** Each bar is one phase lasting `phaseDuration` days. The counter-balanced and customized sequences shown above happen to produce the same output (ABBA) — the difference is that `customized` takes a researcher-provided string, while `counterBalanced` is computed by the `_generateCounterBalancedCycle` algorithm.
+
+## The study lifecycle
+
+```mermaid
+flowchart LR
+ A([Start]) --> B[Draft]
+ B --> |"Researcher designs study\nin Designer app"| B
+ B --> |"Researcher publishes"| C[Running]
+ C --> |"Participants enroll\nand complete tasks"| C
+ C --> |"Researcher closes\nor study period ends"| D[Closed]
+ D --> E([Analyze results])
+
+ subgraph "Draft phase — what the researcher configures"
+ F[Define interventions]
+ G[Add observations and questionnaires]
+ H[Set eligibility criteria and consent]
+ I[Configure schedule]
+ J[Set up report specification]
+ end
+
+ subgraph "Running phase — what participants do"
+ K[Enroll via app]
+ L[Complete daily tasks]
+ M[SubjectProgress records accumulate]
+ end
+
+ B -.-> F
+ C -.-> K
+```
+
+## What this means in code
+
+The N-of-1 structure maps directly to the domain model:
+
+| Clinical concept | Code entity | Where to find it |
+|---|---|---|
+| The trial protocol | `Study` | `core/lib/src/models/tables/study.dart` |
+| A treatment arm | `Intervention` | `core/lib/src/models/interventions/intervention.dart` |
+| A task the participant does per phase | `CheckmarkTask` | `core/lib/src/models/interventions/tasks/checkmark_task.dart` |
+| A measurement taken every phase | `QuestionnaireTask` | `core/lib/src/models/observations/tasks/questionnaire_task.dart` |
+| The trial schedule | `StudySchedule` | `core/lib/src/models/study_schedule/study_schedule.dart` |
+| An enrolled participant | `StudySubject` | `core/lib/src/models/tables/study_subject.dart` |
+| A single task completion | `SubjectProgress` | `core/lib/src/models/tables/subject_progress.dart` |
+
+Continue to [Core Entities](./02-core-entities.mdx) for the full glossary of each domain object.
diff --git a/docs/06-development/02-domain-model/02-core-entities.mdx b/docs/06-development/02-domain-model/02-core-entities.mdx
new file mode 100644
index 00000000..189c9f83
--- /dev/null
+++ b/docs/06-development/02-domain-model/02-core-entities.mdx
@@ -0,0 +1,337 @@
+---
+sidebar_position: 2
+title: Core Entities
+description: Glossary of every domain object in StudyU with field-by-field reference
+---
+
+# Core Entities
+
+This glossary defines every domain entity in StudyU, maps it to its code location, and lists all fields. Read [N-of-1 Trials](./01-n-of-1-trials.mdx) first if you haven't already.
+
+## Study
+
+**Research meaning:** The top-level protocol for a clinical study. It defines everything needed to conduct the trial: what treatments will be compared, what data will be collected, who is eligible to participate, what participants must consent to, and how long the trial runs.
+
+**In the codebase:** `Study` is the aggregate root. Everything else hangs off it. It maps to the `study` table in Supabase.
+
+**File:** `core/lib/src/models/tables/study.dart`
+
+| Field | Type | Description |
+|---|---|---|
+| `id` | `String` | UUID primary key |
+| `userId` | `String` | The researcher who owns the study |
+| `title` | `String?` | Display name |
+| `description` | `String?` | Free-text study description |
+| `status` | `StudyStatus` | `draft`, `running`, or `closed` |
+| `participation` | `Participation` | `open` (anyone can join) or `invite` (invite code required) |
+| `resultSharing` | `ResultSharing` | `public`, `private`, or `organization` |
+| `questionnaire` | `StudyUQuestionnaire` | Eligibility screening questions |
+| `eligibilityCriteria` | `List` | Rules evaluated against questionnaire answers |
+| `consent` | `List` | Items the participant must agree to |
+| `interventions` | `List` | The treatment arms |
+| `observations` | `List` | Data collection tasks that run throughout the study |
+| `schedule` | `StudySchedule` | Temporal structure of the trial |
+| `reportSpecification` | `ReportSpecification` | How results are visualized |
+| `collaboratorEmails` | `List` | Co-researchers with edit access |
+| `registryPublished` | `bool` | Whether the study appears in the public registry |
+
+**Key constant:** `Study.baselineID = '__baseline'` is the sentinel ID used for the baseline phase. The baseline is not stored as a real `Intervention` row — it is synthesized on the client using this ID.
+
+**Computed properties:**
+
+- `hasEligibilityCheck` — `true` if both `eligibilityCriteria` and `questionnaire.questions` are non-empty
+- `studyLength` — total number of days, calculated from `schedule`
+- `isOwner(user)`, `isEditor(user)`, `canEdit(user)` — access-control helpers
+
+---
+
+## Intervention
+
+**Research meaning:** One arm of the study — a treatment, therapy, supplement, or behavior that the participant follows during a phase. A study with two interventions alternates between them. The "baseline" is a special arm with no active tasks, used to measure the participant's resting state.
+
+**In the codebase:** `Intervention` owns a list of `InterventionTask` instances. Currently the only concrete `InterventionTask` type is `CheckmarkTask`.
+
+**File:** `core/lib/src/models/interventions/intervention.dart`
+
+| Field | Type | Description |
+|---|---|---|
+| `id` | `String` | UUID |
+| `name` | `String?` | Display name (e.g., "Vitamin D 2000 IU") |
+| `description` | `String?` | Instructions shown to the participant |
+| `icon` | `String` | Icon identifier string |
+| `tasks` | `List` | Activities the participant performs during this phase |
+
+**The baseline intervention** is constructed dynamically in `StudySubject.selectedInterventions`:
+
+```dart
+Intervention(Study.baselineID, 'Baseline')
+ ..tasks = []
+ ..icon = 'rayStart';
+```
+
+It has an empty task list because the baseline phase collects data only through Observations (which run across all phases), not through phase-specific tasks.
+
+**`CheckmarkTask`** (`core/lib/src/models/interventions/tasks/checkmark_task.dart`) is the only built-in intervention task type. It records a simple boolean: did the participant complete this activity today? It stores a `Result` in `SubjectProgress`.
+
+---
+
+## Observation
+
+**Research meaning:** A measurement taken from the participant. Unlike intervention tasks (which only run during their assigned phase), observations run during **every phase including baseline**. This is intentional — you need baseline data and data from both treatment arms to make comparisons.
+
+**In the codebase:** `Observation` is an abstract class extending `Task`. The only concrete implementation is `QuestionnaireTask`.
+
+**File:** `core/lib/src/models/observations/observation.dart`
+
+`QuestionnaireTask` (`core/lib/src/models/observations/tasks/questionnaire_task.dart`) contains a `StudyUQuestionnaire` (a list of `Question` objects). When a participant completes a `QuestionnaireTask`, it produces a `Result` in `SubjectProgress`.
+
+The key behavioral difference between tasks and observations in `StudySubject.scheduleFor()`:
+
+```dart
+// Intervention tasks — only for the active intervention today
+for (final task in activeIntervention.tasks) { ... }
+
+// Observations — always included, regardless of which intervention is active
+for (final observation in study.observations) { ... }
+```
+
+---
+
+## Subject (StudySubject)
+
+**Research meaning:** A participant who has enrolled in a study. Their enrollment record tracks which interventions they are assigned to, when they started, and all of their task completion data.
+
+**In the codebase:** `StudySubject` maps to the `study_subject` table. It holds references to its parent `Study` and its list of `SubjectProgress` records. The subject's trial sequence is generated lazily via `interventionOrder`.
+
+**File:** `core/lib/src/models/tables/study_subject.dart`
+
+| Field | Type | Description |
+|---|---|---|
+| `id` | `String` | UUID |
+| `studyId` | `String` | Foreign key to `study` |
+| `userId` | `String` | Foreign key to the auth user |
+| `startedAt` | `DateTime?` | The date the participant began the trial (stored UTC) |
+| `selectedInterventionIds` | `List` | The two intervention IDs assigned to this participant |
+| `inviteCode` | `String?` | Code used to join an invite-only study |
+| `isDeleted` | `bool` | Soft-delete flag |
+
+**Key methods:**
+
+| Method | Purpose |
+|---|---|
+| `getInterventionForDate(DateTime)` | Returns which `Intervention` is active on a given day |
+| `getDayOfStudyFor(DateTime)` | Returns how many days since `startedAt` |
+| `scheduleFor(DateTime)` | Returns all `TaskInstance` objects due on a given day (intervention tasks + observations) |
+| `completedTaskForDay(String taskId, DateTime)` | Returns `true` if the task was completed in any of its `CompletionPeriod` windows |
+| `interventionOrder` | The full ordered list of intervention IDs for the trial (generated by `StudySchedule`) |
+
+:::note
+`startedAt` is stored in UTC. When comparing dates, use the `differenceInDays` extension method rather than direct arithmetic to avoid timezone bugs.
+:::
+
+---
+
+## Questionnaire (StudyUQuestionnaire)
+
+**Research meaning:** A set of questions. In clinical research, questionnaires serve two purposes: screening (are you eligible?) and measurement (how are you feeling?). StudyU uses the same `StudyUQuestionnaire` class for both.
+
+**In the codebase:** `StudyUQuestionnaire` is a thin wrapper around `List`. It is used in two completely different contexts:
+
+1. **On `Study.questionnaire`** — shown to the participant during the eligibility check before enrollment. Answers are evaluated against `EligibilityCriterion` expressions.
+2. **Inside `QuestionnaireTask.questions`** — shown during the study as a daily observation task.
+
+**File:** `core/lib/src/models/questionnaire/questionnaire.dart`
+
+**Question types** (all extend `Question`, parsed via `Question.fromJson`):
+
+| Type | Answer value | Use case |
+|---|---|---|
+| `BooleanQuestion` | `bool` | Yes/no questions |
+| `ChoiceQuestion` | `String` | Single or multiple choice |
+| `ScaleQuestion` | `num` | Numeric rating scale |
+| `AnnotatedScaleQuestion` | `num` | Scale with labels at each step |
+| `VisualAnalogueQuestion` | `double` | Continuous slider |
+| `FreeTextQuestion` | `String` | Open-ended text entry |
+| `ImageCapturingQuestion` | `String` | Photo upload (stored as blob path) |
+| `AudioRecordingQuestion` | `String` | Audio upload (stored as blob path) |
+| `FitbitQuestion` | varies | Pulls data from Fitbit API |
+| `PainQuestion` | varies | Body map pain locator |
+
+Questions support **conditional display** via `QuestionConditional`: a question can be hidden unless a previous question's answer meets a condition.
+
+---
+
+## Eligibility Criteria (EligibilityCriterion)
+
+**Research meaning:** Rules that determine whether a participant qualifies for a study. A study might require participants to be non-smokers, within a certain age range, or have a specific diagnosis. If a participant violates any criterion, they cannot enroll.
+
+**In the codebase:** Each `EligibilityCriterion` has a `condition: Expression` that is evaluated against the participant's `QuestionnaireState` (their answers to the eligibility questionnaire).
+
+**File:** `core/lib/src/models/eligibility/eligibility_criterion.dart`
+
+| Field | Type | Description |
+|---|---|---|
+| `id` | `String` | UUID |
+| `reason` | `String?` | Human-readable explanation (shown to participant if they fail) |
+| `condition` | `Expression` | The logical condition that must be `true` for the participant to qualify |
+
+**Methods:**
+- `isSatisfied(QuestionnaireState)` — returns `true` if the condition passes
+- `isViolated(QuestionnaireState)` — returns `true` if the condition explicitly fails (note: `null` means the answer has not been given yet, which is distinct from `false`)
+
+**Expression types** (all extend `Expression`, parsed via `Expression.fromJson`):
+
+| Type | File | Purpose |
+|---|---|---|
+| `BooleanExpression` | `types/boolean_expression.dart` | Tests a boolean answer |
+| `ChoiceExpression` | `types/choice_expression.dart` | Tests a choice answer against a specific value |
+| `NumericExpression` | `types/numeric_expression.dart` | Tests a numeric answer with a comparison operator |
+| `TextExpression` | `types/text_expression.dart` | Tests a text answer |
+| `NotExpression` | `types/not_expression.dart` | Logical NOT of another expression |
+| `CompositeExpression` | `types/composite_expression.dart` | AND/OR of a list of sub-expressions |
+
+`CompositeExpression` is the recursive building block. It has a `logicType` (`and` or `or`) and a `List` that can itself contain further `CompositeExpression` instances, forming an arbitrary tree. An empty `CompositeExpression` evaluates to `true`.
+
+---
+
+## Consent (ConsentItem)
+
+**Research meaning:** Before a participant can enroll in a clinical study, they must give informed consent — they must confirm that they understand the study's purpose, risks, and their right to withdraw. Each `ConsentItem` is one statement they must explicitly agree to.
+
+**In the codebase:** `Study.consent` is a `List`. The app presents each item to the participant and requires them to check a box before proceeding to enrollment.
+
+**File:** `core/lib/src/models/consent/consent_item.dart`
+
+| Field | Type | Description |
+|---|---|---|
+| `id` | `String` | UUID |
+| `title` | `String?` | Short heading (e.g., "Data Storage") |
+| `description` | `String?` | Full explanation of what the participant is agreeing to |
+| `iconName` | `String` | Icon identifier, defaults to `'textBoxCheck'` |
+
+`Study.hasConsentCheck` returns `true` if `consent` is non-empty.
+
+---
+
+## Study Schedule (StudySchedule)
+
+**Research meaning:** The temporal blueprint of the trial. It defines how long each phase lasts, how many times the participant cycles through the interventions, whether there is a baseline period at the start, and in what order the interventions appear.
+
+**In the codebase:** `StudySchedule` is a value object embedded in `Study.schedule`. It is used both for display in the Designer and for generating the actual phase sequence for each `StudySubject`.
+
+**File:** `core/lib/src/models/study_schedule/study_schedule.dart`
+
+| Field | Type | Default | Description |
+|---|---|---|---|
+| `numberOfCycles` | `int` | `2` | How many times the intervention pair repeats |
+| `phaseDuration` | `int` | `7` | Number of days in each phase |
+| `includeBaseline` | `bool` | `true` | Whether a baseline phase precedes the interventions |
+| `sequence` | `PhaseSequence` | `alternating` | Order of interventions within each cycle |
+| `sequenceCustom` | `String` | `'ABAB'` | Custom sequence string, used only when `sequence == customized` |
+
+**`PhaseSequence` enum values:**
+
+| Value | Pattern | Description |
+|---|---|---|
+| `alternating` | A B A B ... | Interventions alternate every phase |
+| `counterBalanced` | A B B A ... | Order reverses each cycle to control for order effects |
+| `randomized` | varies | Order randomized per cycle (first cycle is always A B) |
+| `customized` | user-defined | Researcher provides a string like `'ABBA'` or `'AABB'` |
+
+**Key computed values:**
+- `length` — total days in the study (baseline + all cycles)
+- `numberOfPhases` — total number of phases including optional baseline
+- `generateInterventionIdsInOrder(List)` — given the two selected intervention IDs, returns the full ordered sequence of intervention IDs the subject will follow
+
+:::note
+`StudySchedule.numberOfInterventions = 2` is a constant. The platform currently assumes exactly two intervention arms per study.
+:::
+
+---
+
+## Task
+
+**Research meaning:** Any activity a participant must perform on a study day. Tasks have a schedule (when they are available) and may fire reminders.
+
+**In the codebase:** `Task` is the abstract base class for everything a participant does. It is not serialized directly — you always work with a concrete subtype.
+
+**File:** `core/lib/src/models/tasks/task.dart`
+
+| Field | Type | Description |
+|---|---|---|
+| `id` | `String` | UUID |
+| `type` | `String` | Discriminator string used for JSON polymorphism |
+| `title` | `String?` | Display name shown to participant |
+| `header` | `String?` | Optional instructional text shown above the task |
+| `footer` | `String?` | Optional text shown below the task |
+| `schedule` | `Schedule` | When the task is available and when reminders fire |
+
+**`Schedule`** (`core/lib/src/models/tasks/schedule.dart`) contains:
+- `completionPeriods: List` — one or more time windows (e.g., 08:00–12:00 and 18:00–20:00) during which the task can be completed. A task with multiple `CompletionPeriod` entries requires completion in each window.
+- `reminders: List` — times when the participant receives a push notification.
+
+**`CompletionPeriod`** has an `unlockTime` and `lockTime` (both `StudyUTimeOfDay`), plus an `id` UUID used to match a `SubjectProgress.result.periodId` back to the specific window.
+
+**Concrete subtypes:**
+
+```
+Task (abstract)
+├── InterventionTask (abstract) — phase-specific
+│ └── CheckmarkTask — records a bool (did participant do this?)
+└── Observation (abstract) — runs every phase
+ └── QuestionnaireTask — records a QuestionnaireState
+```
+
+---
+
+## SubjectProgress
+
+**Research meaning:** A single task completion event. Every time a participant completes a task, one record is written. The aggregate of all `SubjectProgress` records for a subject is the raw data used in analysis.
+
+**In the codebase:** `SubjectProgress` maps to the `subject_progress` table. Its primary key is `(completed_at, subject_id)`.
+
+**File:** `core/lib/src/models/tables/subject_progress.dart`
+
+| Field | Type | Description |
+|---|---|---|
+| `subjectId` | `String` | Foreign key to `study_subject` |
+| `interventionId` | `String` | Which intervention was active when this was completed |
+| `taskId` | `String` | Which task was completed |
+| `completedAt` | `DateTime?` | UTC timestamp of completion |
+| `resultType` | `String` | `'QuestionnaireState'` or `'bool'` — the discriminator |
+| `result` | `Result` | The typed payload |
+
+**`Result`** (`core/lib/src/models/results/result.dart`) is a generic wrapper with two concrete forms:
+- `Result` — produced by `QuestionnaireTask`. Contains the participant's answers keyed by question ID.
+- `Result` — produced by `CheckmarkTask`. Contains a single `true` value (the record existing implies the task was done).
+
+The `Result.periodId` field ties a completion back to the specific `CompletionPeriod` window it was completed in, enabling multi-period task tracking.
+
+:::note
+`completedAt` is stored in UTC. The code uses `isSameDate()` extension comparisons — be careful when doing date arithmetic directly.
+:::
+
+---
+
+## Report Specification (ReportSpecification)
+
+**Research meaning:** The configuration for how a study's results are presented to the researcher and participant. An N-of-1 trial's primary output is a comparison between phases — the report is how that comparison is communicated.
+
+**In the codebase:** `ReportSpecification` is embedded in `Study.reportSpecification`. It has one primary section and any number of secondary sections, each of a different statistical type.
+
+**File:** `core/lib/src/models/report/report_specification.dart`
+
+| Field | Type | Description |
+|---|---|---|
+| `primary` | `ReportSection?` | The headline analysis shown prominently |
+| `secondary` | `List` | Additional analyses shown below |
+
+**`ReportSection` subtypes** (all extend `ReportSection`, each with `id`, `title`, `description`):
+
+| Type | File | What it shows |
+|---|---|---|
+| `AverageSection` | `sections/average_section.dart` | Mean value per intervention phase |
+| `DescriptiveStatsSection` | `sections/descriptive_stats_section.dart` | Min, max, mean, standard deviation |
+| `GaugeComparisonSection` | `sections/gauge_comparison_section.dart` | Side-by-side gauge comparing two phases |
+| `LinearRegressionSection` | `sections/linear_regression_section.dart` | Trend line over time |
+| `TextualSummarySection` | `sections/textual_summary_section.dart` | Researcher-authored text interpretation |
diff --git a/docs/06-development/02-domain-model/03-entity-relationships.mdx b/docs/06-development/02-domain-model/03-entity-relationships.mdx
new file mode 100644
index 00000000..04ac1ee5
--- /dev/null
+++ b/docs/06-development/02-domain-model/03-entity-relationships.mdx
@@ -0,0 +1,367 @@
+---
+sidebar_position: 3
+title: Entity Relationships
+description: ERD diagrams and class diagrams showing how StudyU entities connect
+---
+
+# Entity Relationships
+
+This page shows the relationships between all StudyU domain entities via diagrams. For field-by-field descriptions, see [Core Entities](./02-core-entities.mdx).
+
+## Entity Relationship Diagram
+
+The full domain model as an ERD. `Study` is the aggregate root — all other entities hang off it directly or indirectly.
+
+```mermaid
+erDiagram
+ Study {
+ string id PK
+ string userId
+ string title
+ StudyStatus status
+ Participation participation
+ }
+ Intervention {
+ string id PK
+ string name
+ string description
+ string icon
+ }
+ InterventionTask {
+ string id PK
+ string type
+ string title
+ }
+ Observation {
+ string id PK
+ string type
+ string title
+ }
+ StudyUQuestionnaire {
+ list questions
+ }
+ Question {
+ string id PK
+ string type
+ string prompt
+ }
+ StudySchedule {
+ int numberOfCycles
+ int phaseDuration
+ bool includeBaseline
+ PhaseSequence sequence
+ string sequenceCustom
+ }
+ EligibilityCriterion {
+ string id PK
+ string reason
+ }
+ Expression {
+ string type
+ }
+ ConsentItem {
+ string id PK
+ string title
+ string description
+ string iconName
+ }
+ StudySubject {
+ string id PK
+ string studyId FK
+ string userId
+ datetime startedAt
+ list selectedInterventionIds
+ string inviteCode
+ }
+ SubjectProgress {
+ string subjectId FK
+ string interventionId
+ string taskId
+ datetime completedAt
+ string resultType
+ }
+ Result {
+ string type
+ string periodId
+ }
+ ReportSpecification {
+ ReportSection primary
+ list secondary
+ }
+ ReportSection {
+ string id PK
+ string type
+ string title
+ }
+
+ Study ||--o{ Intervention : "interventions"
+ Study ||--o{ Observation : "observations"
+ Study ||--|| StudySchedule : "schedule"
+ Study ||--|| StudyUQuestionnaire : "eligibility questionnaire"
+ Study ||--o{ EligibilityCriterion : "eligibilityCriteria"
+ Study ||--o{ ConsentItem : "consent"
+ Study ||--|| ReportSpecification : "reportSpecification"
+ Study ||--o{ StudySubject : "participants"
+ Intervention ||--o{ InterventionTask : "tasks"
+ Observation ||--|| StudyUQuestionnaire : "questions"
+ StudyUQuestionnaire ||--o{ Question : "questions"
+ EligibilityCriterion ||--|| Expression : "condition"
+ ReportSpecification ||--o{ ReportSection : "secondary"
+ StudySubject ||--o{ SubjectProgress : "progress"
+ SubjectProgress ||--|| Result : "result"
+```
+
+## Task and Data Collection Model
+
+This class diagram shows the task type hierarchy and how task completions produce typed results.
+
+```mermaid
+classDiagram
+ class Task {
+ <>
+ +String id
+ +String type
+ +String? title
+ +Schedule schedule
+ +extractPropertyResults()
+ +getAvailableProperties()
+ }
+
+ class Schedule {
+ +List~CompletionPeriod~ completionPeriods
+ +List~StudyUTimeOfDay~ reminders
+ }
+
+ class CompletionPeriod {
+ +String id
+ +StudyUTimeOfDay unlockTime
+ +StudyUTimeOfDay lockTime
+ }
+
+ class InterventionTask {
+ <>
+ }
+
+ class CheckmarkTask {
+ +String taskType = "checkmark"
+ }
+
+ class Observation {
+ <>
+ }
+
+ class QuestionnaireTask {
+ +String taskType = "questionnaire"
+ +StudyUQuestionnaire questions
+ }
+
+ class SubjectProgress {
+ +String subjectId
+ +String interventionId
+ +String taskId
+ +DateTime completedAt
+ +String resultType
+ +Result result
+ }
+
+ class Result~T~ {
+ +String type
+ +String? periodId
+ +T result
+ }
+
+ class QuestionnaireState {
+ +Map~String,Answer~ answers
+ }
+
+ Task <|-- InterventionTask
+ Task <|-- Observation
+ InterventionTask <|-- CheckmarkTask
+ Observation <|-- QuestionnaireTask
+ Task *-- Schedule
+ Schedule *-- CompletionPeriod
+ CheckmarkTask --> SubjectProgress : "produces\nResult"
+ QuestionnaireTask --> SubjectProgress : "produces\nResult"
+ SubjectProgress *-- Result
+ Result~QuestionnaireState~ *-- QuestionnaireState
+```
+
+## Eligibility Expression Tree
+
+The expression system is a recursive tree of conditions used for eligibility criteria and question conditionals.
+
+```mermaid
+classDiagram
+ class Expression {
+ <>
+ +String? type
+ +evaluate(QuestionnaireState) bool?
+ }
+
+ class CompositeExpression {
+ +String type = "composite"
+ +LogicType logicType
+ +List~Expression~ expressions
+ +evaluate() bool?
+ }
+
+ class NotExpression {
+ +String type = "not"
+ +Expression expression
+ +evaluate() bool?
+ }
+
+ class BooleanExpression {
+ +String type = "boolean"
+ +String questionId
+ +bool value
+ +evaluate() bool?
+ }
+
+ class ChoiceExpression {
+ +String type = "choice"
+ +String questionId
+ +String choice
+ +evaluate() bool?
+ }
+
+ class NumericExpression {
+ +String type = "numeric"
+ +String questionId
+ +String comparator
+ +num value
+ +evaluate() bool?
+ }
+
+ class TextExpression {
+ +String type = "text"
+ +String questionId
+ +String text
+ +evaluate() bool?
+ }
+
+ class EligibilityCriterion {
+ +String id
+ +String? reason
+ +Expression condition
+ +isSatisfied(QuestionnaireState) bool
+ +isViolated(QuestionnaireState) bool
+ }
+
+ Expression <|-- CompositeExpression
+ Expression <|-- NotExpression
+ Expression <|-- BooleanExpression
+ Expression <|-- ChoiceExpression
+ Expression <|-- NumericExpression
+ Expression <|-- TextExpression
+ CompositeExpression o-- Expression : "sub-expressions\n(recursive)"
+ NotExpression o-- Expression : "negated expression"
+ EligibilityCriterion *-- Expression : "condition"
+```
+
+**Example expression tree** for the criterion "participant is a non-smoker AND is between 18 and 65 years old":
+
+```
+CompositeExpression (AND)
+├── BooleanExpression(questionId: "q_smoker", value: false)
+└── CompositeExpression (AND)
+ ├── NumericExpression(questionId: "q_age", comparator: ">=", value: 18)
+ └── NumericExpression(questionId: "q_age", comparator: "<=", value: 65)
+```
+
+An empty `CompositeExpression` evaluates to `true` (no conditions = always passes).
+
+## Database Table Relationships
+
+The database-level ERD showing how the Supabase tables map to each other:
+
+```mermaid
+erDiagram
+ study {
+ uuid id PK
+ uuid user_id FK
+ text title
+ text description
+ enum status "draft | running | closed"
+ enum participation "open | invite"
+ enum result_sharing "public | private | organization"
+ jsonb interventions
+ jsonb observations
+ jsonb schedule
+ jsonb questionnaire
+ jsonb eligibility_criteria
+ jsonb consent
+ jsonb report_specification
+ jsonb results
+ jsonb contact
+ text_arr collaborator_emails
+ bool registry_published
+ timestamp created_at
+ timestamp updated_at
+ }
+
+ study_subject {
+ uuid id PK
+ uuid study_id FK
+ uuid user_id FK
+ timestamp started_at
+ text_arr selected_intervention_ids
+ text invite_code
+ bool is_deleted
+ }
+
+ subject_progress {
+ uuid id PK
+ uuid study_id FK
+ uuid user_id FK
+ text taskId
+ jsonb result
+ timestamp taskInstanceUTC
+ }
+
+ study_invite {
+ text code PK
+ uuid study_id FK
+ text_arr preselected_intervention_ids
+ }
+
+ app_config {
+ text id PK
+ jsonb app_min_version
+ jsonb app_privacy
+ jsonb app_terms
+ jsonb designer_privacy
+ jsonb designer_terms
+ jsonb imprint
+ jsonb contact
+ jsonb analytics
+ }
+
+ users {
+ uuid id PK
+ text email
+ jsonb preferences
+ }
+
+ repo {
+ uuid id PK
+ uuid study_id FK
+ text url
+ enum git_provider "gitlab"
+ }
+
+ study_fitbit_credentials {
+ uuid study_id PK
+ text oauth_tokens
+ }
+
+ study ||--o{ study_subject : "participants"
+ study ||--o{ study_invite : "invite codes"
+ study ||--o| repo : "code repository"
+ study ||--o| study_fitbit_credentials : "fitbit config"
+ study_subject ||--o{ subject_progress : "task completions"
+```
+
+:::info
+Most study configuration (interventions, observations, schedule, eligibility criteria, consent, report) is stored as **JSONB columns** on the `study` table. Only participant data (`study_subject`, `subject_progress`) is normalized into separate tables.
+:::
diff --git a/docs/06-development/02-domain-model/04-concrete-example.mdx b/docs/06-development/02-domain-model/04-concrete-example.mdx
new file mode 100644
index 00000000..07fb35e0
--- /dev/null
+++ b/docs/06-development/02-domain-model/04-concrete-example.mdx
@@ -0,0 +1,126 @@
+---
+sidebar_position: 4
+title: Concrete Example
+description: A full walkthrough of the Vitamin D study showing how all entities fit together
+---
+
+# Concrete Example: Vitamin D and Sleep Quality
+
+This walkthrough uses a realistic study to show how all domain entities fit together in practice. Read [Core Entities](./02-core-entities.mdx) first if you haven't already.
+
+## The scenario
+
+Dr. Smith wants to test whether taking 2000 IU of Vitamin D daily improves sleep quality in adults who report poor sleep. She opens StudyU Designer and creates a new study.
+
+## Step 1 — Configuring the Study
+
+Dr. Smith creates a `Study` with:
+- `title`: "Vitamin D and Sleep Quality"
+- `status`: `draft`
+- `participation`: `invite` (she will recruit participants herself)
+
+## Step 2 — Defining interventions
+
+She adds two `Intervention` objects to `study.interventions`:
+
+- **Intervention A:** `name = "Vitamin D 2000 IU"`, with one `CheckmarkTask` titled "Take your Vitamin D supplement". The `CheckmarkTask` has a `Schedule` with a `CompletionPeriod` from 07:00 to 10:00 and a reminder at 08:00.
+- **Intervention B:** `name = "Placebo"`, with one `CheckmarkTask` titled "Take your placebo tablet". Same schedule.
+
+## Step 3 — Setting up observations
+
+She adds one `Observation` to `study.observations`: a `QuestionnaireTask` titled "Sleep Diary" containing two questions:
+- A `ScaleQuestion` (id: `q_sleep_quality`, prompt: "How would you rate your sleep quality last night? (1–10)")
+- A `BooleanQuestion` (id: `q_woke_up`, prompt: "Did you wake up during the night?")
+
+This `QuestionnaireTask` will appear in the participant's task list every morning throughout the entire study — baseline, Vitamin D phase, and placebo phase — because observations are not phase-specific.
+
+## Step 4 — Eligibility screening
+
+Dr. Smith wants only adults aged 18+ who self-report poor sleep. She adds two questions to `study.questionnaire`:
+- A `ScaleQuestion` (id: `q_sleep_baseline`, prompt: "On average, how would you rate your sleep quality? (1–10)")
+- A `BooleanQuestion` (id: `q_age_ok`, prompt: "Are you 18 years of age or older?")
+
+She then adds two `EligibilityCriterion` entries to `study.eligibilityCriteria`:
+- Criterion 1: `reason = "You must be 18 or older"`, `condition = BooleanExpression(questionId: "q_age_ok", value: true)`
+- Criterion 2: `reason = "This study is for people who experience poor sleep"`, `condition = NumericExpression(questionId: "q_sleep_baseline", comparator: "<=", value: 5)`
+
+## Step 5 — Consent
+
+She adds two `ConsentItem` entries to `study.consent`:
+- "Data Storage": explains that anonymized responses are stored on secure servers
+- "Right to Withdraw": explains the participant can leave at any time
+
+## Step 6 — Schedule
+
+She configures `study.schedule`:
+- `phaseDuration = 14` (two-week phases)
+- `numberOfCycles = 2` (two full A/B cycles)
+- `includeBaseline = true` (two weeks of baseline before interventions start)
+- `sequence = PhaseSequence.alternating`
+
+Total study length: 14 (baseline) + 14×4 (four phases) = **70 days**.
+
+## Step 7 — Publish and enroll
+
+Dr. Smith changes `status` to `running` and distributes invite codes. When participant Alex joins:
+
+1. Alex answers the eligibility questionnaire. The app evaluates each `EligibilityCriterion.condition` against Alex's `QuestionnaireState`. Both pass (`q_age_ok = true`, `q_sleep_baseline = 3`).
+2. Alex reviews and accepts both `ConsentItem` entries.
+3. A `StudySubject` is created:
+ - `userId = Alex`
+ - `studyId = study.id`
+ - `selectedInterventionIds = [interventionA.id, interventionB.id]`
+ - `startedAt = today`
+4. `interventionOrder` is computed: `['__baseline', 'A', 'B', 'A', 'B']`.
+
+## Step 8 — Daily data collection
+
+On **day 3** (still baseline), Alex opens the app. `StudySubject.scheduleFor(today)` is called:
+- `getInterventionForDate(today)` returns the baseline `Intervention` (empty task list).
+- The intervention task loop produces nothing (baseline has no tasks).
+- The observation loop adds the "Sleep Diary" `QuestionnaireTask`.
+
+Alex completes the Sleep Diary. A `SubjectProgress` record is written:
+- `subjectId = Alex's subject id`
+- `interventionId = '__baseline'`
+- `taskId = sleepDiaryTask.id`
+- `completedAt = now (UTC)`
+- `resultType = 'QuestionnaireState'`
+- `result = Result` containing `{q_sleep_quality: 4, q_woke_up: true}`
+
+On **day 20** (first Vitamin D phase), the same `scheduleFor` call now returns both the "Take your Vitamin D supplement" `CheckmarkTask` and the "Sleep Diary" `QuestionnaireTask`. Two separate `SubjectProgress` records are written when Alex completes them.
+
+## Step 9 — Results
+
+After 70 days, Dr. Smith closes the study. She views the report, which uses `ReportSpecification` sections to compare Alex's average `q_sleep_quality` score during Vitamin D phases versus placebo phases. Because this is an N-of-1 trial, the comparison is meaningful for Alex specifically — it tells Dr. Smith whether Vitamin D helped *this person*, independent of what it does for the population average.
+
+## How the data maps to code
+
+```mermaid
+flowchart LR
+ subgraph "Study protocol (study table — JSONB)"
+ S[Study]
+ IA[Intervention A\nVitamin D]
+ IB[Intervention B\nPlacebo]
+ OBS[Observation\nSleep Diary]
+ SCHED[Schedule\n14d phases, 2 cycles]
+ end
+
+ subgraph "Participant data (separate tables)"
+ SUBJ[StudySubject\nAlex]
+ P1[SubjectProgress\nday 3, baseline,\nSleep Diary answers]
+ P2[SubjectProgress\nday 20, Vitamin D,\nCheckmark done]
+ P3[SubjectProgress\nday 20, Vitamin D,\nSleep Diary answers]
+ end
+
+ S --> IA
+ S --> IB
+ S --> OBS
+ S --> SCHED
+ S --> SUBJ
+ SUBJ --> P1
+ SUBJ --> P2
+ SUBJ --> P3
+```
+
+The key insight is that the study protocol lives in a single JSONB-heavy `study` row, while participant activity is normalized into `study_subject` and `subject_progress` rows. This design keeps the protocol immutable during the study while allowing participant data to accumulate over time.
diff --git a/docs/06-development/02-domain-model/_category_.json b/docs/06-development/02-domain-model/_category_.json
new file mode 100644
index 00000000..ab379787
--- /dev/null
+++ b/docs/06-development/02-domain-model/_category_.json
@@ -0,0 +1,5 @@
+{
+ "label": "Domain Model",
+ "position": 2,
+ "description": "Clinical research concepts and how they map to code"
+}
diff --git a/docs/06-development/03-architecture/01-system-overview.mdx b/docs/06-development/03-architecture/01-system-overview.mdx
new file mode 100644
index 00000000..9c3d80ff
--- /dev/null
+++ b/docs/06-development/03-architecture/01-system-overview.mdx
@@ -0,0 +1,140 @@
+---
+sidebar_position: 1
+title: System Overview
+description: High-level architecture and technology stack for the StudyU platform
+---
+
+# System Overview
+
+StudyU is a platform for running N-of-1 clinical trials digitally. The system consists of two Flutter applications sharing a common core library, backed by Supabase (PostgreSQL + Auth + Storage).
+
+## System diagram
+
+```mermaid
+graph TB
+ subgraph "Client Layer"
+ APP["StudyU App
Flutter (iOS, Android, Web)
For participants"]
+ DESIGNER["StudyU Designer
Flutter Web
For researchers"]
+ end
+
+ subgraph "Shared Code"
+ CORE["studyu_core
Domain models, serialization,
Supabase ORM utilities"]
+ COMMON["studyu_flutter_common
Shared Flutter widgets,
localization, secure storage"]
+ end
+
+ subgraph "Backend (Supabase)"
+ AUTH["Auth (GoTrue)
Email/password"]
+ DB["PostgreSQL
RLS-protected tables"]
+ STORAGE["Storage
Blob bucket: observations"]
+ EDGE["Edge Functions
(minimal usage)"]
+ end
+
+ APP --> COMMON
+ APP --> CORE
+ DESIGNER --> COMMON
+ DESIGNER --> CORE
+ COMMON --> CORE
+
+ APP -->|"supabase_flutter SDK"| AUTH
+ APP -->|"REST API (PostgREST)"| DB
+ APP -->|"File uploads"| STORAGE
+
+ DESIGNER -->|"supabase_flutter SDK"| AUTH
+ DESIGNER -->|"REST API (PostgREST)"| DB
+
+ style APP fill:#4FC3F7,color:#000
+ style DESIGNER fill:#81C784,color:#000
+ style CORE fill:#FFB74D,color:#000
+ style COMMON fill:#FFB74D,color:#000
+ style AUTH fill:#CE93D8,color:#000
+ style DB fill:#CE93D8,color:#000
+ style STORAGE fill:#CE93D8,color:#000
+ style EDGE fill:#CE93D8,color:#000
+```
+
+## Key architectural decisions
+
+| Decision | Choice | Rationale |
+|---|---|---|
+| Monorepo | Melos-managed workspace | Shared models and utilities across two apps |
+| Backend | Supabase (hosted PostgreSQL) | Auth, RLS, real-time, and storage out of the box |
+| State management (App) | Provider | Lightweight, sufficient for mobile-first participant flows |
+| State management (Designer) | Riverpod | Complex form workflows and async data management |
+| Serialization | json_serializable (code gen) | Compile-time safety for 30+ model classes |
+| Offline strategy | Exception-based fallback + cache sync | Pragmatic for clinical data entry in low-connectivity scenarios |
+
+## Technology stack
+
+```mermaid
+graph TB
+ subgraph "Frontend"
+ FLUTTER["Flutter / Dart 3.8+"]
+ PROVIDER["Provider (App)"]
+ RIVERPOD["Riverpod (Designer)"]
+ GOROUTER["GoRouter (Designer)"]
+ NAV1["Navigator 1.0 (App)"]
+ JSON_SER["json_serializable"]
+ REACTIVE["reactive_forms (Designer)"]
+ end
+
+ subgraph "Shared Libraries"
+ CORE_PKG["studyu_core
Models, API, utilities"]
+ COMMON_PKG["studyu_flutter_common
Storage, localization"]
+ SUPA_FL["supabase_flutter
Client SDK"]
+ end
+
+ subgraph "Backend"
+ SUPA["Supabase"]
+ PG["PostgreSQL 15"]
+ GOTRUE2["GoTrue (Auth)"]
+ POSTGREST["PostgREST (API)"]
+ STORAGE2["Storage (Blobs)"]
+ end
+
+ subgraph "Tooling"
+ MELOS["Melos (monorepo)"]
+ BUILD_RUNNER["build_runner (code gen)"]
+ SENTRY["Sentry (error tracking)"]
+ end
+
+ FLUTTER --> PROVIDER
+ FLUTTER --> RIVERPOD
+ FLUTTER --> GOROUTER
+ FLUTTER --> NAV1
+ FLUTTER --> JSON_SER
+ FLUTTER --> REACTIVE
+
+ PROVIDER --> CORE_PKG
+ RIVERPOD --> CORE_PKG
+ CORE_PKG --> SUPA_FL
+ COMMON_PKG --> SUPA_FL
+ SUPA_FL --> SUPA
+
+ SUPA --> PG
+ SUPA --> GOTRUE2
+ SUPA --> POSTGREST
+ SUPA --> STORAGE2
+
+ MELOS --> FLUTTER
+ BUILD_RUNNER --> JSON_SER
+
+ style FLUTTER fill:#4FC3F7,color:#000
+ style SUPA fill:#CE93D8,color:#000
+ style PG fill:#CE93D8,color:#000
+ style CORE_PKG fill:#FFB74D,color:#000
+ style COMMON_PKG fill:#FFB74D,color:#000
+```
+
+## The two apps compared
+
+| Aspect | App (Participants) | Designer (Researchers) |
+|---|---|---|
+| **State management** | Provider (`ChangeNotifier`) | Riverpod (`flutter_riverpod` + `riverpod_generator`) |
+| **Routing** | Navigator 1.0 (`onGenerateRoute`) | GoRouter v17 (declarative, route guards) |
+| **Target platforms** | iOS, Android, Web | Web only |
+| **Form handling** | Standard Flutter forms | `reactive_forms` |
+| **Layout** | Single-column, mobile-first | Responsive two-column |
+| **API pattern** | Direct `SupabaseQuery` calls | Repository pattern with `StudyUApiClient` |
+| **Code generation** | `json_serializable` | `json_serializable` + `riverpod_generator` |
+
+Continue to [Monorepo Structure](./02-monorepo-structure.mdx) for the package dependency graph and Melos scripts.
diff --git a/docs/06-development/03-architecture/02-monorepo-structure.mdx b/docs/06-development/03-architecture/02-monorepo-structure.mdx
new file mode 100644
index 00000000..769f5281
--- /dev/null
+++ b/docs/06-development/03-architecture/02-monorepo-structure.mdx
@@ -0,0 +1,171 @@
+---
+sidebar_position: 2
+title: Monorepo Structure
+description: Package layout, dependency hierarchy, and Melos scripts
+---
+
+# Monorepo Structure
+
+StudyU uses [Melos](https://melos.invertase.dev/) to manage a Dart/Flutter monorepo. The four packages have a strict dependency hierarchy.
+
+## Package dependency graph
+
+```mermaid
+graph TD
+ APP["app/
studyu_app v2.12.3
Participant mobile app"]
+ DESIGNER["designer_v2/
studyu_designer_v2 v1.15.3
Researcher web app"]
+ COMMON["flutter_common/
studyu_flutter_common v1.10.1
Shared Flutter utilities"]
+ CORE["core/
studyu_core v4.7.1
Domain models & API"]
+ DB["database/
SQL migrations"]
+ SUPA["supabase/
Config, seeds, local dev"]
+
+ APP --> COMMON
+ APP --> CORE
+ DESIGNER --> COMMON
+ DESIGNER --> CORE
+ COMMON --> CORE
+
+ DB -.->|"schema for"| CORE
+ SUPA -.->|"local dev for"| DB
+
+ style APP fill:#4FC3F7,color:#000
+ style DESIGNER fill:#81C784,color:#000
+ style COMMON fill:#FFB74D,color:#000
+ style CORE fill:#FFB74D,color:#000
+ style DB fill:#E0E0E0,color:#000
+ style SUPA fill:#E0E0E0,color:#000
+```
+
+## Package responsibilities
+
+| Package | Directory | Purpose |
+|---|---|---|
+| `studyu_core` | `core/` | Domain models (`Study`, `Intervention`, `SubjectProgress`, etc.), JSON serialization, Supabase ORM base classes (`SupabaseObject`, `SupabaseQuery`), CSV export, Fitbit integration |
+| `studyu_flutter_common` | `flutter_common/` | Secure storage wrapper, environment loader (`.env` files), `AppLanguage` ChangeNotifier, Supabase initialization with URL failover |
+| `studyu_app` | `app/` | Participant-facing app: study browsing, enrollment, daily task completion, offline caching, result reports |
+| `studyu_designer_v2` | `designer_v2/` | Researcher-facing web app: study design forms, participant monitoring, recruitment, data analysis and export |
+
+## Directory layout
+
+```
+studyu/
+├── app/ # studyu_app — participant app
+├── core/ # studyu_core — domain models
+├── database/
+│ └── migration/ # 16 SQL migration files
+├── designer_v2/ # studyu_designer_v2 — researcher app
+├── flutter_common/ # studyu_flutter_common — shared Flutter code
+│ └── lib/envs/ # .env files for all environments
+├── supabase/
+│ ├── config.toml # Supabase local dev config
+│ ├── seed.sql # Test data seeding
+│ └── functions/ # Edge functions (minimal)
+├── melos.yaml # Monorepo scripts
+└── pubspec.yaml # Root dev dependencies (lint config)
+```
+
+## Melos scripts
+
+All scripts are defined in `melos.yaml` and run from the repo root with `melos run