diff --git a/architecture-goals.md b/architecture-goals.md
deleted file mode 100644
index a4b005e4a..000000000
--- a/architecture-goals.md
+++ /dev/null
@@ -1,233 +0,0 @@
-# Architecture Goals
-
-Date: 2025-11-24
-Author: Automation (drafted by GitHub Copilot assistant)
-
----
-
-## Goal (summary)
-
-Implement a Yjs-based CRDT sync layer backed by per-project Durable Objects (DOs). Clients will use Yjs and persist locally with y-indexeddb for offline support. Users will be stored centrally in D1 and authenticated through a dedicated worker (BetterAuth). One project => one Durable Object is the recommended partitioning strategy.
-
-We will standardize on UUIDs for entity IDs. D1 is used only for the global users table. R2 will be used to store PDF documents uploaded by users for projects.
-
----
-
-## High-level architecture (target)
-
-Client (browser) -- WebSocket/HTTP/WebAuth --> Edge Worker (auth check) --> Durable Object (per-project)
-
-Durable Object: holds Y.Doc (Yjs) for the project, validates domain constraints, and broadcasts updates to connected clients. Also holds user roles for the project: owner or member.
-
-D1 Persistence: Users table is handled globally by the BetterAuth worker + D1.
-
-R2 is used for storing PDF documents uploaded by users for projects.
-
-UI Local Persistence: Client persists Y.Doc to IndexedDB using y-indexeddb for offline support. When device reconnects, the Y.Doc merges with the DO and resolves conflicts automatically.
-
-Official diagram (conceptual):
-
-Client A <-> Edge Worker (auth) <-> Durable Object (Y.Doc)
-^
-Client B ----------------------------------------|
-
----
-
-## Data model mapping
-
-Hierarchical Y.Doc structure per project:
-
-```
-Project (1 Durable Object per project)
- - meta: Y.Map { name, description, createdAt, updatedAt }
- - members: Y.Map (userId => { role, joinedAt })
- - reviews: Y.Map (reviewId => {
- name, description, createdAt, updatedAt,
- checklists: Y.Map (checklistId => {
- title, assignedTo, status, createdAt, updatedAt,
- answers: Y.Map (questionKey => { value, notes, updatedAt, updatedBy })
- })
- })
-```
-
-Example Yjs usage:
-
-```js
-const ydoc = new Y.Doc();
-
-// Create a review
-const reviews = ydoc.getMap('reviews');
-const reviewId = crypto.randomUUID();
-const reviewMap = new Y.Map();
-reviewMap.set('name', 'Sleep Study Review');
-reviewMap.set('description', 'AMSTAR2 evaluation');
-reviewMap.set('createdAt', Date.now());
-reviewMap.set('checklists', new Y.Map());
-reviews.set(reviewId, reviewMap);
-
-// Add a checklist to the review
-const checklistsMap = reviewMap.get('checklists');
-const checklistId = crypto.randomUUID();
-const checklistMap = new Y.Map();
-checklistMap.set('title', 'Study 1 Assessment');
-checklistMap.set('assignedTo', 'user-uuid'); // reviewer userId
-checklistMap.set('status', 'pending'); // pending, in-progress, completed
-checklistMap.set('createdAt', Date.now());
-checklistMap.set('answers', new Y.Map());
-checklistsMap.set(checklistId, checklistMap);
-
-// Record an answer (AMSTAR2 format with boolean arrays per column)
-const answersMap = checklistMap.get('answers');
-answersMap.set(
- 'q1',
- new Y.Map([
- // Each question stores: answers (nested boolean arrays), critical flag, notes
- ['answers', [[false, false, false, false], [false], [false, true]]], // matches column structure
- ['critical', false],
- ['notes', ''],
- ['updatedAt', Date.now()],
- ['updatedBy', 'user-uuid'],
- ]),
-);
-```
-
-Why this structure:
-
-- Hierarchical: Projects -> Reviews -> Checklists -> Answers
-- Fine-grained CRDT: Each level is a Y.Map for efficient merges
-- Assignments: Each checklist has `assignedTo` for reviewer assignment
-- Status tracking: Checklists have status (pending/in-progress/completed)
-
----
-
-## ID strategy
-
-- Use UUIDs across clients and servers to avoid temp-id swap headaches. Generates deterministic unique ids (v4) on creation.
-
----
-
-## Durable Object responsibilities
-
-- Own the authoritative Y.Doc for the project.
-- Authenticate and authorize connecting clients (validate tokens via BetterAuth worker/service).
-
----
-
-## Client responsibilities
-
-- Use Yjs (Y.Doc) and y-indexeddb for local persistence and offline use.
-- Connect to DO via secure WebSocket provider (or a worker that routes to DO and validates auth token).
-- Translate Yjs document to the UI data model (you can either use Yjs directly in UI or maintain a lightweight local mirror if necessary).
-
----
-
-## Authentication & Users
-
-- Global users table and authentication lives outside project DOs.
-- Store users in D1 (Cloudflare). BetterAuth worker handles secure token minting and validation.
-- On client connect, the worker validates the user and checks membership/permission for requested project. Connection allowed only for authorized member roles.
-
-## Project Documents (PDFs)
-
-- User-uploaded PDF documents for projects are stored in R2 (Cloudflare object storage).
-
----
-
-## Architecture Implementation Roadmap
-
-This roadmap outlines the step-by-step plan for implementing the new architecture in this project, transitioning from previous approaches to a Yjs + Durable Objects + D1 model.
-
-**Phase 1 — Foundation & Pilot**
-
-- Establish UUID strategy and implement generation utilities with tests.
-- Build initial Durable Object (DO) for a single project, syncing a simple `checklist_answers` array using Yjs.
-
-**Phase 2 — Core Expansion**
-
-- Extend DO to support reviews, checklists, and assignments.
-- Add permission checks and role logic within DOs (owner/member rules).
-
-**Phase 3 — Authentication & Integration**
-
-- Deploy BetterAuth worker and global D1 user table.
-- Integrate client authentication flow to obtain tokens for WebSocket connections.
-
-**Phase 4 — Testing & Hardening**
-
-- Develop and run tests for offline reconciliation and conflict scenarios.
-- Harden system for reliability and edge cases.
-
-**Phase 5 — Analytics & Monitoring**
-
-- Add monitoring and metrics for DO performance.
-
----
-
-## Example minimal DO + client pseudocode (PoC)
-
-DO (high-level pseudocode):
-
-```js
-// Durable Object pseudocode
-class ProjectDoc {
- constructor() {
- this.ydoc = new Y.Doc();
- this.clients = [];
- }
-
- onConnect(client, user) {
- // Auth done by Edge Worker
- this.clients.push(client);
- // Send current snapshot to client
- client.send(Y.encodeStateAsUpdate(this.ydoc));
- }
-
- onMessage(client, update) {
- // update is a Yjs update (Uint8Array)
- Y.applyUpdate(this.ydoc, update);
- // DO-side validation hooks here for domain invariants
-
- // Broadcast to other clients
- this.clients.forEach(c => c !== client && c.send(update));
- }
-}
-```
-
-Client (pseudocode):
-
-```js
-const ydoc = new Y.Doc();
-const provider = new WebsocketProvider('wss://example.com/project', projectId, ydoc, { params: { token } });
-
-// Persist locally
-const persistence = new IndexeddbPersistence(projectKey, ydoc);
-
-// UI can read from ydoc.getMap('checklist_answers') directly
-const answers = ydoc.getMap('checklist_answers');
-
-// create a new answer
-const ansId = uuidv4();
-answers.set(
- ansId,
- new Y.Map({ id: ansId, checklist_id, question_key, answers: new Y.Array(['yes']), critical: false }),
-);
-
-// Yjs + provider handles sync, DO enforces validation.
-```
-
-## Project-level Y.Doc (checklist PoC)
-
-To start small we keep _one Y.Doc per project_ (a Durable Object). Each project doc exposes a top-level `checklists` Y.Map where each checklist is a `Y.Map` with fields like `title` and `items` (a `Y.Array`).
-
-Server (DO): implement a `ProjectDoc` Durable Object which:
-
-- holds a Y.Doc for the project
-- persists the Y.Doc to the DO state
-- handles WebSocket connections for real-time sync
-
-Client: connect to the worker WebSocket at `/api/projects/:projectId`. When connected the server sends the current state as a Yjs update; clients apply updates and push local updates back as encoded Yjs updates.
-
-Notes:
-
-- We intentionally do not provide per-checklist HTTP fetch routes; clients read the container Y.Doc and use the `checklistId` inside the document.
-- You will need an environment binding for `PROJECT_DOC` in your worker configuration to enable the DO route.
diff --git a/packages/web/src/ROBINS-I/checklist-map.js b/packages/web/src/ROBINS-I/checklist-map.js
index 1fb90a817..a7dc3ab1e 100644
--- a/packages/web/src/ROBINS-I/checklist-map.js
+++ b/packages/web/src/ROBINS-I/checklist-map.js
@@ -67,19 +67,32 @@ export const INFORMATION_SOURCES = [
'Journal article(s)',
'Study protocol',
'Statistical analysis plan (SAP)',
- 'Non-commercial registry record (e.g. ClinicalTrials.gov)',
- 'Company-owned registry record',
+ 'Non-commercial registry record (e.g. ClinicalTrials.gov record)',
+ 'Company-owned registry record (e.g. GSK Clinical Study Register record)',
'Grey literature (e.g. unpublished thesis)',
'Conference abstract(s)',
- 'Regulatory document (e.g. CSR, approval package)',
+ 'Regulatory document (e.g. Clinical Study Report, Drug Approval Package)',
'Individual participant data',
'Research ethics application',
- 'Grant database summary (e.g. NIH RePORTER)',
+ 'Grant database summary (e.g. NIH RePORTER, Research Councils UK Gateway to Research)',
'Personal communication with investigator',
'Personal communication with sponsor',
- 'Other',
];
+// Section D: Information sources
+export const SECTION_D = {
+ title: 'Part D: Information Sources',
+ description:
+ 'Which of the following sources have you obtained to help you inform your risk of bias judgements (tick as many as apply)?',
+ otherField: {
+ id: 'otherSpecify',
+ label: 'Please specify any additional sources not listed above',
+ placeholder: 'e.g., Additional data sources, correspondence, supplementary materials...',
+ type: 'textarea',
+ stateKey: 'otherSpecify',
+ },
+};
+
// Checklist type definition
export const CHECKLIST_TYPES = {
ROBINS_I: {
@@ -88,6 +101,50 @@ export const CHECKLIST_TYPES = {
},
};
+// Planning Stage: List confounding factors
+export const PLANNING_SECTION = {
+ title: 'The ROBINS-I V2 Tool',
+ subtitle: 'At planning stage: list confounding factors',
+ p1: {
+ id: 'p1',
+ label: 'P1',
+ text: 'List the important confounding factors relevant to all or most studies on this topic. Specify whether these are particular to specific intervention-outcome combinations.',
+ placeholder:
+ 'e.g., Age, baseline disease severity, comorbidities, concomitant medications, socioeconomic status...',
+ type: 'textarea',
+ stateKey: 'confoundingFactors',
+ },
+};
+
+// Section A: Specify the result being assessed for risk of bias
+export const SECTION_A = {
+ a1: {
+ id: 'a1',
+ label: 'A1',
+ text: 'Specify the numerical result being assessed',
+ placeholder: 'e.g., OR = 1.5 (95% CI: 1.2-1.9)',
+ type: 'textarea',
+ stateKey: 'numericalResult',
+ },
+ a2: {
+ id: 'a2',
+ label: 'A2',
+ text: 'Provide further details about this result (for example, location in the study report, reason it was chosen)',
+ optional: true,
+ placeholder: 'e.g., Table 3, primary outcome analysis',
+ type: 'textarea',
+ stateKey: 'furtherDetails',
+ },
+ a3: {
+ id: 'a3',
+ label: 'A3',
+ text: 'Specify the outcome to which this result relates',
+ placeholder: 'e.g., All-cause mortality at 12 months',
+ type: 'textarea',
+ stateKey: 'outcome',
+ },
+};
+
// Section B: Decide whether to proceed with a risk-of-bias assessment
export const SECTION_B = {
b1: {
@@ -109,6 +166,47 @@ export const SECTION_B = {
},
};
+// Section C: Specify the (hypothetical) target randomized trial specific to the study
+export const SECTION_C = {
+ description:
+ "The target randomized trial is either explicitly described by the primary study investigators or implied by the study's design and analysis.",
+ c1: {
+ id: 'c1',
+ label: 'C1',
+ text: 'Specify the participants and eligibility criteria',
+ placeholder: 'e.g., Adults aged 18+ with type 2 diabetes, no prior cardiovascular disease',
+ type: 'textarea',
+ stateKey: 'participants',
+ },
+ c2: {
+ id: 'c2',
+ label: 'C2',
+ text: 'Specify the intervention strategy',
+ placeholder: 'e.g., Initiation of metformin 500mg twice daily',
+ type: 'textarea',
+ stateKey: 'interventionStrategy',
+ },
+ c3: {
+ id: 'c3',
+ label: 'C3',
+ text: 'Specify the comparator strategy',
+ placeholder: 'e.g., Initiation of sulfonylurea therapy',
+ type: 'textarea',
+ stateKey: 'comparatorStrategy',
+ },
+ c4: {
+ id: 'c4',
+ label: 'C4',
+ text: 'Did the analysis account for switches during follow-up between the intervention strategies being compared, or for other protocol deviations during follow-up?',
+ type: 'radio',
+ stateKey: 'isPerProtocol',
+ options: [
+ { value: false, label: 'No (the analysis is estimating the intention-to-treat effect)' },
+ { value: true, label: 'Yes (the analysis is estimating the per-protocol effect)' },
+ ],
+ },
+};
+
// Domain 1A: Bias due to confounding (Intention-to-Treat Effect)
export const DOMAIN_1A = {
id: 'domain1a',
diff --git a/packages/web/src/components/checklist-ui/ROBINSIChecklist/DomainJudgement.jsx b/packages/web/src/components/checklist-ui/ROBINSIChecklist/DomainJudgement.jsx
index ec310b4e8..3a4af9e79 100644
--- a/packages/web/src/components/checklist-ui/ROBINSIChecklist/DomainJudgement.jsx
+++ b/packages/web/src/components/checklist-ui/ROBINSIChecklist/DomainJudgement.jsx
@@ -1,4 +1,4 @@
-import { For, createUniqueId } from 'solid-js';
+import { For } from 'solid-js';
import { ROB_JUDGEMENTS, BIAS_DIRECTIONS, DOMAIN1_DIRECTIONS } from '@/ROBINS-I/checklist-map.js';
/**
@@ -14,13 +14,14 @@ import { ROB_JUDGEMENTS, BIAS_DIRECTIONS, DOMAIN1_DIRECTIONS } from '@/ROBINS-I/
* @param {boolean} [props.disabled] - Whether the selector is disabled
*/
export function DomainJudgement(props) {
- const uniqueId = createUniqueId();
const directionOptions = () => (props.isDomain1 ? DOMAIN1_DIRECTIONS : BIAS_DIRECTIONS);
const getJudgementColor = judgement => {
switch (judgement) {
case 'Low':
return 'bg-green-100 border-green-400 text-green-800';
+ case 'Low (except for concerns about uncontrolled confounding)':
+ return 'bg-green-100 border-green-400 text-green-800';
case 'Moderate':
return 'bg-yellow-100 border-yellow-400 text-yellow-800';
case 'Serious':
@@ -39,43 +40,33 @@ export function DomainJudgement(props) {