Prestarts
The Prestarts module handles daily safety checklists created by supervisors before each work shift. Prestarts capture shift details, weather forecasts, dynamic custom fields, activities with assigned resources, file attachments, sign-ons, and comments. This is the first “operational data entry” module — building on the custom fields configuration layer.
Lifecycle
Prestarts follow a two-stage lifecycle:
- Draft — Core fields (supervisor, datetime, duration, shift type, location) are editable. Activities, sign-ons, and comments can be added.
- Published — Core fields are locked. The briefed activities keep their plan, but their cost code and actual quantity can still change. Activities added after publish can be changed and deleted. Photos and files can still be added (they show as “Added after publish”), but not removed. Comments remain open for ongoing discussion.
Publishing is a one-way transition. Published prestarts will later create linked diary entries automatically.
Draft prestarts can be changed many at once from the list (supervisor, shift, duration, location) — see Bulk Edit & Undo.
Duplicated and new prestarts start at the project’s configured prestart time for the shift (Shift Configuration), falling back to a fixed time when none is set.
Data Model
Enums
| Enum | Values | Description |
|---|---|---|
prestart_status | draft, published | Prestart lifecycle stage |
prestart_duration | 0, 15, 30, 60 | Toolbox talk duration (minutes) |
shift_type | day, afternoon, night | Shift type |
Prestarts
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
project_id | UUID | Foreign key to projects |
org_id | UUID | Foreign key to organizations (tenant isolation) |
supervisor_id | UUID | Foreign key to users (supervisor running the prestart) |
prestart_datetime | TIMESTAMPTZ | When the prestart occurs |
duration | prestart_duration | Toolbox talk duration in minutes |
shift_type | shift_type | Day, afternoon, or night shift |
site_location | TEXT | Free-text location description |
weather_data | JSONB | Stored weather forecast from Open Meteo |
custom_field_values | JSONB | Map of field definition UUID to value |
status | prestart_status | Draft or published |
published_at | TIMESTAMPTZ | When the prestart was published (NULL if draft) |
diary_id | UUID | Future FK to diaries table |
metadata | JSONB | Extensible unstructured data |
deleted_at | TIMESTAMPTZ | Soft-delete timestamp |
created_at | TIMESTAMPTZ | Auto-set on insert |
updated_at | TIMESTAMPTZ | Auto-updated via trigger |
Indexes:
(org_id, project_id, status)WHEREdeleted_at IS NULL— list queries with status filter(org_id, project_id, supervisor_id)— filter by supervisor(project_id, prestart_datetime DESC)WHEREdeleted_at IS NULL— chronological listing
Activities
Activities are reusable work items linked to either a prestart or a diary (future). They describe the work to be performed during a shift.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
prestart_id | UUID | FK to prestarts (nullable, for diaries) |
diary_id | UUID | Future FK to diaries |
project_id | UUID | FK to projects |
org_id | UUID | FK to organizations |
description | TEXT | Activity description (required) |
custom_field_values | JSONB | Dynamic field values |
sort_order | INTEGER | Display ordering |
metadata | JSONB | Extensible data |
deleted_at | TIMESTAMPTZ | Soft-delete |
created_at | TIMESTAMPTZ | Auto-set |
updated_at | TIMESTAMPTZ | Auto-updated via trigger |
Activity Resources
Many-to-many link between activities and users (workers assigned to the activity).
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
activity_id | UUID | FK to activities |
user_id | UUID | FK to users |
org_id | UUID | FK to organizations |
created_at | TIMESTAMPTZ | Auto-set |
Unique constraint on (activity_id, user_id).
Prestart Files
Attachments linked to either a prestart or an activity.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
prestart_id | UUID | FK to prestarts (nullable) |
activity_id | UUID | FK to activities (nullable) |
org_id | UUID | FK to organizations |
file_name | TEXT | Original filename |
file_url | TEXT | Server path to stored file |
file_size | BIGINT | Size in bytes |
mime_type | TEXT | MIME type |
uploaded_by | UUID | FK to users |
metadata | JSONB | Extensible data |
deleted_at | TIMESTAMPTZ | Soft-delete |
created_at | TIMESTAMPTZ | Auto-set |
CHECK constraint: at least one of prestart_id or activity_id must be set.
Prestart Sign-Ons
Records which workers attended the prestart meeting.
Anyone with company-wide access (for example a Master Admin) can sign themselves on to any project in their organisation, including the office QR sign-on, without being added to the project first.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
prestart_id | UUID | FK to prestarts |
user_id | UUID | FK to users |
org_id | UUID | FK to organizations |
project_id | UUID | FK to projects (denormalized for NOTIFY) |
signed_at | TIMESTAMPTZ | When the user signed on |
metadata | JSONB | Extensible data |
created_at | TIMESTAMPTZ | Auto-set |
Unique constraint on (prestart_id, user_id).
Prestart Comments
Discussion threads on prestarts, available to any project member.
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
prestart_id | UUID | FK to prestarts |
user_id | UUID | FK to users (comment author) |
org_id | UUID | FK to organizations |
project_id | UUID | FK to projects (denormalized for NOTIFY) |
content | TEXT | Comment text |
metadata | JSONB | Extensible data |
deleted_at | TIMESTAMPTZ | Soft-delete |
created_at | TIMESTAMPTZ | Auto-set |
updated_at | TIMESTAMPTZ | Auto-updated via trigger |
Design note:
project_idis denormalized onprestart_sign_onsandprestart_commentsto enable zero-cost LISTEN/NOTIFY triggers. Without it, the trigger function would need to JOIN through theprestartstable to find theproject_id, which is not possible in a simple trigger function.
API Endpoints
All endpoints are under /v1/orgs/{orgId}/projects/{projectId}/.
Prestarts
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /prestarts | Any project member | List with filters |
| POST | /prestarts | Supervisor+ or admin | Create draft |
| GET | /prestarts/{prestartId} | Any project member | Full detail |
| GET | /prestarts/{prestartId}/adjacent | Any project member | Previous and next day (details) |
| PUT | /prestarts/{prestartId} | Creator or admin | Update draft |
| POST | /prestarts/{prestartId}/publish | Creator or admin | Publish |
| DELETE | /prestarts/{prestartId} | Creator or admin | Soft delete |
Activities
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /prestarts/{prestartId}/activities | Creator or admin | Add activity |
| PUT | /prestarts/{prestartId}/activities/{activityId} | Creator or admin | Update activity |
| DELETE | /prestarts/{prestartId}/activities/{activityId} | Creator or admin | Remove activity |
Activity Resources
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /prestarts/{prestartId}/activities/{activityId}/resources | Creator or admin | Assign user |
| DELETE | /prestarts/{prestartId}/activities/{activityId}/resources/{userId} | Creator or admin | Unassign user |
Files
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /prestarts/{prestartId}/files | Creator or admin | Upload to prestart |
| POST | /prestarts/{prestartId}/activities/{activityId}/files | Creator or admin | Upload to activity |
| DELETE | /files/{fileId} | Creator or admin | Remove file |
Sign-Ons
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /prestarts/{prestartId}/sign-ons | Creator or admin | Add sign-on |
| DELETE | /prestarts/{prestartId}/sign-ons/{signOnId} | Creator or admin | Remove sign-on |
Comments
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /prestarts/{prestartId}/comments | Any project member | List comments |
| POST | /prestarts/{prestartId}/comments | Any project member | Add comment |
| PUT | /prestarts/{prestartId}/comments/{commentId} | Author only | Edit comment |
| DELETE | /prestarts/{prestartId}/comments/{commentId} | Author or admin | Delete comment |
Weather
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /weather?date=YYYY-MM-DD | Any project member | Fetch forecast using project lat/lng |
Architecture
Real-Time Updates
All prestart tables fire LISTEN/NOTIFY triggers on the project_changes channel:
Postgres trigger fires → pg_notify('project_changes', payload)
Go WebSocket hub receives notification → broadcasts to org room
Browser WebSocket client receives message → TanStack Query cache invalidationThe INVALIDATION_MAP in ws-provider.tsx maps table names to query keys:
prestarts→["prestarts", orgId, projectId]+["prestart", orgId, projectId, prestartId]activities→["prestart", orgId, projectId]prestart_sign_ons→["prestart", orgId, projectId]prestart_comments→["prestart-comments", orgId, projectId]+["prestart", orgId, projectId]
Weather Integration
The weather endpoint proxies the Open Meteo API. Projects must have latitude, longitude, and timezone configured (via the project detail Overview tab) for weather to work. The forecast is stored as JSONB in weather_data on the prestart.
Custom Field Values
Dynamic custom fields (configured at the org/project level) are stored as { "field-def-uuid": value } in the custom_field_values JSONB column on both prestarts and activities. Field definitions come from the custom_field_definitions table with entity_type = 'prestart_diary' or 'activity'.
Security
Tenant Isolation
Every query includes AND org_id = $org_id. The org_id is extracted from the verified session, never from request parameters.
Role-Based Access
| Action | Allowed Roles |
|---|---|
| List/View prestarts | Any project member |
| Create prestart | supervisor, senior_supervisor, master_admin, organisational_manager |
| Edit/Publish/Delete | Prestart creator OR admin (master_admin, organisational_manager) |
| Manage activities/files | Prestart creator OR admin |
| Add/remove sign-ons | Prestart creator OR admin |
| Add comment | Any project member |
| Edit comment | Comment author only |
| Delete comment | Comment author OR admin |
Input Validation
- BFF (Next.js): Zod schemas validate all inputs before proxying to Go
- Go API: Struct validation + UUID parsing on all path parameters
- Database: Foreign key constraints, CHECK constraints, NOT NULL constraints
Key Files
| File | Purpose |
|---|---|
backend/db/migrations/007_prestarts.sql | Schema: 3 enums, 6 tables, indexes, triggers |
backend/db/queries/prestarts.sql | 7 prestart queries (sqlc) |
backend/db/queries/activities.sql | 8 activity + resource queries |
backend/db/queries/prestart_files.sql | 3 file queries |
backend/db/queries/prestart_sign_ons.sql | 3 sign-on queries |
backend/db/queries/prestart_comments.sql | 4 comment queries |
backend/api/internal/prestart/service.go | Business logic (~930 lines) |
backend/api/internal/prestart/handler.go | HTTP handlers + routes (~860 lines) |
backend/api/internal/weather/service.go | Open Meteo API client |
backend/api/internal/weather/handler.go | Weather HTTP handler |
apps/web/src/actions/prestarts.ts | 20 BFF server actions |
apps/web/src/actions/weather.ts | Weather BFF action |
apps/web/src/app/_components/prestart-detail.tsx | Detail view with tabs |
apps/web/src/app/_components/prestart-form.tsx | Create/edit form |
apps/web/src/app/_components/prestart-comments.tsx | Comments section |
apps/web/src/app/admin/prestarts/_components/prestart-table-v2.tsx | Prestart list table |