Appearance
Forms / Core Client Management
Reusable form definitions, versions, assignments, and submissions power onboarding questionnaires and recurring check-ins.
The questionnaire builder is implemented as a Forms bounded context, not as hard-coded profile fields. Coaches create reusable templates, publish immutable versions, assign or schedule them for clients, and review submissions. Profile and progress updates happen only through explicit mappings from submitted answers.
Tags: NestJS, Forms Module, Drizzle + PostgreSQL, Web + Coach Mobile + Client Mobile
V1 baseline decisions
all explicit answer mappings are allowed, including profile, measurement, progress photo, nutrition, task, and communication targets, but mapped updates stay pending until coach review approves them. Clients can amend a submitted form only while it is still unreviewed. Recurring schedules support weekly, every other week, and monthly rules. Photo upload questions use the same compressed image profile as progress photos: JPEG/WebP, 1600px max long edge, 3 MB hard max, and 960px/640px/320px server variants.
Form Builder And Submission Flows
The Forms context owns templates, versions, assignments, schedules, submissions, and review state. Onboarding, profile, progress, and communication contexts consume events or mapped results.
Coach Builds And Publishes A Form
- Coach creates a draft form from web or coach mobile and selects one V1 baseline purpose: onboarding, check-in, or custom.
- Coach adds sections and questions using supported component types: text, date, number, measurement, range, choice, upload, contact, consent, and instructions.
- Application service validates the draft definition: stable question IDs, supported component config, required labels, allowed units, upload limits, and explicit mapping rules.
- Coach previews the client-facing version, then publishes.
- Publishing creates an immutable
FormVersionsnapshot and emitsFormVersionPublished.
Coach Assigns Or Schedules A Form
- Coach selects a published form version and one or more active coach-client relationships.
- For one-time onboarding or custom tasks, API creates
FormAssignmentrecords with due dates and task metadata. - For repeating check-ins, API creates a
FormScheduleusing an V1 baseline recurrence rule: weekly, every other week, or monthly. - DailyExecution or Communication receives assignment events to show client tasks and reminders.
- Coach can pause, cancel, or archive future assignments without deleting historical submissions.
Client Completes A Form
- Client opens a form task from onboarding, Today view, check-in reminder, or message attachment.
- Client mobile loads the published version snapshot and validates answers locally where practical.
- Draft progress is saved locally and synced with idempotency keys; media answers upload through signed URLs, use the configured media profile, and store asset references.
- Submission validates required answers, answer types, units, choice IDs, consent flags, and active assignment status.
- Accepted submission marks the assignment submitted and emits
FormSubmitted. - Client can amend the submitted answers while coach review is still pending. After the coach records a review or clarification decision, changes require a clarification or replacement submission flow.
Coach Reviews A Submission
- Coach opens submissions from task inbox, client profile, progress timeline, or form detail.
- System displays answer values, mapped profile/progress updates, uploaded media, submission metadata, and previous responses.
- Coach marks the submission reviewed, requests clarification, sends feedback, and approves or rejects pending mapped updates.
- Review action emits
FormSubmissionReviewed, applies approved mappings through target context commands, and resolves related task inbox items.
Note
Published form versions are immutable. Editing a published form creates a new draft/version so historical submissions always reference the exact definition the client answered.
Domain Model
Model form definitions separately from assignments and submitted answers. A template may have many versions; an assignment always targets one version.
Aggregates & Entities
FormTemplate: aggregate root for coach/workspace-owned form identity, purpose, status, draft definition, and latest published version.FormVersion: immutable published snapshot of the form definition, component schema, mappings, and locale labels.FormQuestion: stable definition object inside a draft/version; identified by client-stablequestionId.FormAssignment: aggregate root linking a published version to a client relationship as a one-time task.FormSchedule: weekly, every-other-week, or monthly recurring rule that generates assignments for repeated check-ins or custom follow-up forms.FormSubmission: aggregate root for client answers, submission status, idempotency key, and review status.FormSubmissionRevision: immutable snapshot of submitted or amended answers before review, used to audit client amendments.FormSubmissionReview: coach review decision, clarification request, or feedback state for a submission.
Value Objects & Rules
FormPurpose:ONBOARDING,CHECK_IN,CUSTOM. Progress, nutrition, and other specialized questions can be modeled inside those forms instead of creating separate V1 baseline purpose types.FormStatus:DRAFT,PUBLISHED,ARCHIVED.QuestionComponentType: short text, long text, date, age, number, measurement, range, single choice, multiple choice, yes/no, dropdown, photo upload, file upload, phone/email, consent, instruction block.QuestionValidationRule: required, min/max, allowed units, allowed file/media types, max upload count, choice constraints, and consent acknowledgement.FormFieldMapping: optional explicit mapping from answer to client profile, health profile, nutrition profile, measurement entry, progress photo set, task metadata, check-in summary, message attachment, or future target context that exposes a typed mapping adapter.FormRecurrenceRule:WEEKLY,EVERY_OTHER_WEEK, orMONTHLY. Cron-like and arbitrary weekday sets are out of Public V1.SubmissionStatus:DRAFT,SUBMITTED,AMENDED,REVIEWED,NEEDS_CLARIFICATION,SUPERSEDED.MappingApprovalStatus:PENDING_REVIEW,APPROVED,REJECTED,APPLIED,FAILED.- Only published versions can be assigned. Only active assignments can accept submissions. Only explicitly mapped answers can update other contexts, and all mapped updates require coach review approval before becoming visible.
- A client can amend answers only before a coach review or clarification decision is recorded. Amendment creates a new submitted revision and replaces the current answer payload; reviewed submissions are immutable except through clarification or replacement submission flows.
Component Catalog
| Component | Use |
|---|---|
| Short text | Names, brief answers, short notes, simple free text. |
| Large text | Long explanations, goals, medical background, coach-specific context. |
| Date | Birth date, target date, injury date, event date. |
| Age | Age input where the coach does not need exact date of birth. |
| Number | General numeric answers such as weekly training days or experience years. |
| Measurement | Weight, height, waist, body fat, and other unit-based values. |
| Range or slider | Energy, stress, pain, adherence, confidence, difficulty, or satisfaction scores. |
| Single choice | One answer from predefined options, such as primary goal or training level. |
| Multiple choice | Multiple answers, such as available equipment, dietary restrictions, or injuries. |
| Yes/No | Binary questions such as medical clearance or supplement use. |
| Dropdown | Long option lists where radio buttons would be too noisy. |
| Photo upload | Progress photos, food photos, medical or body-composition screenshots. |
| File upload | PDFs, lab results, doctor notes, previous programs, or other documents. |
| Phone/email | Contact fields with validation where needed. |
| Consent/acknowledgement | Client confirms agreement, disclaimer, readiness, or data consent. |
| Section title/instruction block | Separates the form into readable groups and gives context before questions. |
Rules Per Question
- Help text or examples.
- Minimum and maximum values for numbers, ranges, and measurements.
- Allowed units where relevant.
- Answer visibility for coach/client.
| Model | Owned By | Important Methods | Emits |
|---|---|---|---|
FormTemplate | Forms | createDraft(), updateDraftDefinition(), publish(), archive() | FormTemplateCreated, FormVersionPublished, FormTemplateArchived |
FormAssignment | Forms | assign(), markOpened(), markSubmitted(), cancel(), expire() | FormAssigned, FormAssignmentSubmitted, FormAssignmentCanceled |
FormSchedule | Forms | createRecurring(), pause(), resume(), generateNextOccurrence() | FormScheduleCreated, FormOccurrenceGenerated |
FormSubmission | Forms | saveDraft(), submit(), amendBeforeReview(), validateAnswers(), requestClarification(), markReviewed() | FormSubmitted, FormSubmissionAmended, FormSubmissionReviewed, FormClarificationRequested |
Table Structure
Use JSONB for the versioned form definition and answer payload, but keep lifecycle, assignment, schedule, and review data relational for querying.
V1 Baseline Mapping Policy
- Allow all explicit mappings that have a typed target adapter: profile, health profile, nutrition profile, measurement entry, progress photo set, task metadata, check-in summary, and communication reference.
- Submission creates pending mapping results only. Target contexts are not mutated until the coach approves the submission or specific mapping rows during review.
- Failed mapping applications stay retryable with audit status and never silently overwrite target records.
Upload Profile
- Photo answers: JPEG/WebP, client-compressed, EXIF stripped, 1600px max long edge, 3 MB hard max, with server variants at 960px, 640px, and 320px.
- File answers: PDF, image, or document uploads only; 10 MB hard max per file; no executable archives; malware scan and content-type sniffing required before visibility.
- Media assets inherit submission visibility and retention, and signed read URLs are generated per request.
| Table | Purpose | Important Columns | Constraints & Indexes |
|---|---|---|---|
form_templates | Coach/workspace-owned form identity and editable draft metadata. | id, workspace_id, owner_user_id, title, purpose, status, draft_definition jsonb, latest_version_id, created_at, updated_at, archived_at | Index (workspace_id, purpose, status); workspace_id and owner_user_id are external identity references, not navigable domain relations; draft can be edited only while not archived. |
form_versions | Immutable published snapshot used by assignments and submissions. | id, template_id, version_number, definition jsonb, field_mappings jsonb, upload_constraints jsonb, published_by_user_id, published_at, checksum | Unique (template_id, version_number); unique checksum per template optional; never update definition after publish. |
form_assignments | One-time client task for a published version. | id, form_version_id, relationship_id, assigned_by_user_id, source_type, source_id, status, due_at, opened_at, submitted_at, canceled_at | Index (relationship_id, status, due_at); index (form_version_id); relationship_id is an external aggregate identity from CoachClientManagement. |
form_schedules | Recurring check-in or custom follow-up assignment rules. | id, form_version_id, relationship_id, recurrence_kind, recurrence_rule jsonb, status, starts_at, ends_at, next_run_at, created_by_user_id | Index (status, next_run_at) for scheduler; generated assignments reference schedule as source; recurrence_kind accepts WEEKLY, EVERY_OTHER_WEEK, or MONTHLY. |
form_submissions | Client answers for one assignment/version. | id, assignment_id, form_version_id, relationship_id, client_user_id, answers jsonb, status, current_revision, client_idempotency_key, submitted_at, last_amended_at, reviewed_at, created_at, updated_at | Unique (assignment_id, client_idempotency_key); index (relationship_id, submitted_at desc); answer values keep question IDs from the version snapshot. |
form_submission_revisions | Audit trail for submitted and pre-review amended answer snapshots. | id, submission_id, revision_number, answers jsonb, asset_refs jsonb, created_by_user_id, created_at, reason | Unique (submission_id, revision_number); immutable after insert; used to explain pre-review amendments. |
form_submission_assets | References uploaded photos, files, or documents attached to answers. | id, submission_id, question_id, asset_id, asset_kind, upload_status, processing_profile, byte_size, variant_keys jsonb, created_at | Index (submission_id, question_id); files live in media storage and are permissioned through the submission/relationship; photos use FORM_PHOTO_V1 baseline_1600. |
form_submission_reviews | Coach review decision, clarification request, or feedback for submitted answers. | id, submission_id, reviewed_by_user_id, review_status, feedback_message, internal_notes, created_at | Index (submission_id, created_at desc); internal notes are coach-only and never included in client response payloads. |
form_mapping_results | Audit of explicit answer mappings pending, approved, rejected, or applied to target contexts. | id, submission_id, submission_revision, question_id, target_context, target_type, target_id, approval_status, approved_by_user_id, approved_at, apply_status, error_message, applied_at | Index (submission_id); retry failed mappings through outbox/job process; mappings are created as PENDING_REVIEW and cannot apply before coach approval. |
Lifecycle Policy
Forms need stable history because clients answer a specific published version. Deletion behavior should protect submissions and coaching decisions.
Form Lifecycle
Draft: editable by authorized coaches; can be previewed but not assigned.Published version: immutable and assignable; edit creates a new draft based on the latest version.Archived template: hidden from new assignments but retained for version/submission lookup.Assignment: active until submitted, canceled, expired, or superseded by a newer onboarding/check-in task.Submission: historical evidence; clients may amend while review is pending, which creates a new revision. After review or clarification is recorded, corrections use clarification or replacement submissions, not in-place answer edits.
Retention And Deletion
- Templates use
archived_atinstead of normal soft delete so history remains navigable. - Published versions are retained while any assignment or submission references them.
- Submission answers may contain sensitive health, nutrition, body, and media data. Mark sensitive questions at definition time and carry that classification into submissions, exports, retention, and mapping review.
- During an active relationship, retain submitted answers and media for coach review, client history, audit, and downstream mapped updates.
- After a relationship ends, sensitive answer bodies and media assets become read-only for authorized coaches for 30 days, then are purged or anonymized from normal access. Keep only minimal audit metadata such as submission id, form version id, timestamps, and anonymized mapping status.
- Account deletion or explicit privacy deletion can shorten the 30-day window and must purge object storage keys, thumbnails, derived variants, and raw answer payloads where policy requires.
- Media/file answer assets inherit submission visibility and retention. Remove storage objects only after retention and privacy checks pass.
- Ended relationships remove normal coach edit/review access immediately and remove normal coach read access after the retention cleanup window.
DDD boundary rule
Forms stores external IDs such as workspace_id, owner_user_id, and relationship_id as identity references. The Forms domain model must not navigate or mutate CoachWorkspace, User, or CoachClientRelationship aggregates directly. Database foreign keys across bounded contexts are optional infrastructure constraints; if they create migration/runtime coupling, prefer plain UUID columns plus application-level policy checks.
Amendment rule
A client can amend one submitted response per assignment only until a coach review or clarification decision is recorded. The latest pre-review revision is the one the coach reviews; earlier revisions stay available for audit and are excluded from mapping application.
API Contracts
Coach endpoints manage templates, versions, assignments, schedules, and reviews. Client endpoints load assigned forms and submit answers for active relationships.
| Endpoint | Use Case | Request | Response |
|---|---|---|---|
POST /api/v1/coach/forms | Create a draft form template. | title, purpose, optional starter definition, locale defaults. | Draft template, editable permissions, and validation warnings. |
PATCH /api/v1/coach/forms/{formId}/draft | Save draft builder changes. | Draft definition, client-side version, optional idempotency key. | Updated draft, validation result, next client-side version. |
POST /api/v1/coach/forms/{formId}/publish | Publish immutable version. | Expected draft version, publish note, optional assignment intent. | Published version, checksum, version number, and assignment eligibility. |
GET /api/v1/coach/forms | List coach/workspace forms. | Filters: purpose, status, search, updated range. | Form cards with latest version, assignment counts, and archive status. |
POST /api/v1/coach/forms/{formId}/assignments | Assign a published version to clients. | versionId, relationship IDs, due date, source metadata, notification options. | Created assignments, rejected clients with reasons, emitted task references. |
POST /api/v1/coach/forms/{formId}/schedules | Create recurring check-in schedule. | versionId, relationship IDs, recurrenceKind of WEEKLY, EVERY_OTHER_WEEK, or MONTHLY, start/end, due offset. | Created schedules and next generated occurrence time. |
GET /api/v1/coach/form-submissions | Coach reviews submitted forms. | Filters: relationship, form, purpose, status, submitted range. | Submission summaries, client, mapped updates, review state, and due context. |
POST /api/v1/coach/form-submissions/{submissionId}/review | Record coach review or clarification request. | reviewStatus, optional feedback, internal notes, mapping approval flags per target, optional clarification task. | Review record, updated submission status, resolved task references, and approved mappings queued for application. |
GET /api/v1/client/form-assignments | Client loads active form tasks. | Client token, optional relationship/status filters. | Assignments with form titles, purpose, due date, progress, and mobile cache metadata. |
GET /api/v1/client/form-assignments/{assignmentId} | Client opens a form for completion. | Assignment id and client bearer token. | Published version definition, previous draft progress, upload constraints, and submit permissions. |
PUT /api/v1/client/form-assignments/{assignmentId}/draft | Save in-progress answers. | Partial answers, local version, client idempotency key. | Draft state, validation errors, and sync token. |
POST /api/v1/client/form-assignments/{assignmentId}/submit | Submit completed form. | Answers, asset references, version checksum, client idempotency key. | Submission, validation result, mapped updates pending-review summary, next task. |
PATCH /api/v1/client/form-submissions/{submissionId} | Client amends a submitted form before coach review. | Full replacement answers, asset references, expected current revision, version checksum, client idempotency key. | Updated submission revision, validation result, pending-review mapping summary, or conflict if a coach review or clarification decision has been recorded. |
Key Code Snippets
Implementation sketches for the planned TypeScript/NestJS/Drizzle stack. Keep form validation in application/domain services so all surfaces share the same rules.
Drizzle schema shape
apps/api/src/forms/db/forms.schema.ts
ts
export const formTemplates = pgTable("form_templates", {
id: uuid("id").primaryKey().defaultRandom(),
// External identity references. Do not import/navigate other bounded-context aggregates here.
workspaceId: uuid("workspace_id").notNull(),
ownerUserId: uuid("owner_user_id").notNull(),
title: text("title").notNull(),
purpose: text("purpose").$type<FormPurpose>().notNull(),
status: text("status").$type<"DRAFT" | "PUBLISHED" | "ARCHIVED">().notNull().default("DRAFT"),
draftDefinition: jsonb("draft_definition").$type<FormDefinition>().notNull(),
latestVersionId: uuid("latest_version_id"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
archivedAt: timestamp("archived_at", { withTimezone: true }),
}, (table) => ({
workspacePurposeStatusIdx: index("form_templates_workspace_purpose_status_idx")
.on(table.workspaceId, table.purpose, table.status),
}));
export const formVersions = pgTable("form_versions", {
id: uuid("id").primaryKey().defaultRandom(),
templateId: uuid("template_id").notNull().references(() => formTemplates.id),
versionNumber: integer("version_number").notNull(),
definition: jsonb("definition").$type<FormDefinition>().notNull(),
fieldMappings: jsonb("field_mappings").$type<FormFieldMapping[]>().notNull().default([]),
uploadConstraints: jsonb("upload_constraints").$type<FormUploadConstraints>().notNull().default({
photo: { profile: "FORM_PHOTO_V1 baseline_1600", maxBytes: 3_000_000, maxLongEdgePx: 1600 },
file: { maxBytes: 10_000_000 },
}),
publishedByUserId: uuid("published_by_user_id").notNull(),
publishedAt: timestamp("published_at", { withTimezone: true }).notNull().defaultNow(),
checksum: text("checksum").notNull(),
}, (table) => ({
templateVersionUnique: uniqueIndex("form_versions_template_version_uq")
.on(table.templateId, table.versionNumber),
}));
export const formAssignments = pgTable("form_assignments", {
id: uuid("id").primaryKey().defaultRandom(),
formVersionId: uuid("form_version_id").notNull().references(() => formVersions.id),
relationshipId: uuid("relationship_id").notNull(),
assignedByUserId: uuid("assigned_by_user_id").notNull(),
sourceType: text("source_type").notNull(),
sourceId: uuid("source_id"),
status: text("status").$type<FormAssignmentStatus>().notNull().default("ACTIVE"),
dueAt: timestamp("due_at", { withTimezone: true }),
openedAt: timestamp("opened_at", { withTimezone: true }),
submittedAt: timestamp("submitted_at", { withTimezone: true }),
canceledAt: timestamp("canceled_at", { withTimezone: true }),
}, (table) => ({
relationshipStatusDueIdx: index("form_assignments_relationship_status_due_idx")
.on(table.relationshipId, table.status, table.dueAt),
}));Submit form use case
apps/api/src/forms/use-cases/submit-form-assignment.ts
ts
@Injectable()
export class SubmitFormAssignmentUseCase {
constructor(
private readonly assignments: FormAssignmentRepository,
private readonly versions: FormVersionRepository,
private readonly submissions: FormSubmissionRepository,
private readonly validator: FormAnswerValidator,
private readonly policy: FormAccessPolicy,
private readonly outbox: OutboxPort,
) {}
async execute(command: SubmitFormAssignmentCommand): Promise<FormSubmissionDto> {
const assignment = await this.assignments.getById(command.assignmentId);
await this.policy.assertClientCanSubmit(command.clientUserId, assignment);
const version = await this.versions.getById(assignment.formVersionId);
if (command.versionChecksum !== version.checksum) {
throw new ConflictException("The form version changed. Reload before submitting.");
}
const validation = this.validator.validate(version.definition, command.answers, command.assets);
if (!validation.ok) return FormSubmissionDto.withValidationErrors(validation.errors);
const submission = FormSubmission.submit({
assignment,
version,
clientUserId: command.clientUserId,
answers: validation.normalizedAnswers,
assetRefs: command.assets,
idempotencyKey: command.idempotencyKey,
});
submission.createPendingMappingResults(version.fieldMappings);
await this.submissions.save(submission);
assignment.markSubmitted(submission.id, submission.submittedAt);
await this.assignments.save(assignment);
await this.outbox.publish(new FormSubmitted({
submissionId: submission.id,
assignmentId: assignment.id,
relationshipId: assignment.relationshipId,
formVersionId: version.id,
}));
return FormSubmissionDto.fromDomain(submission);
}
}Events & Background Jobs
Use domain events and outbox records to connect forms to onboarding tasks, mobile sync, explicit profile/progress mappings, and coach notifications.
Domain Events
FormVersionPublished: update form catalog/search read models.FormAssigned: create client task, notification, and mobile sync record.FormOccurrenceGenerated: generated from schedule into assignment.FormSubmitted: resolve assignment, create coach inbox item, create pending mapping results, and update form timelines.FormSubmissionAmended: update pending review state, invalidate previous pending mapping results, and refresh the coach inbox summary.FormSubmissionReviewed: resolve coach task, approve or reject mapping results, and optionally notify client.FormClarificationRequested: create client follow-up task and message/notification.
Jobs
generate-due-form-assignments: scans active schedules bynext_run_atand creates occurrences idempotently.send-form-reminders: notifies clients before due dates and when overdue.process-form-mappings: applies coach-approved explicit mappings to target contexts and records mapping results.expire-overdue-assignments: marks expired tasks when product policy requires expiry.purge-or-anonymize-form-data: handles 30-day ended-relationship cleanup, account deletion, privacy deletion, and object storage purge for sensitive answers and media assets.
Permissions & Access Rules
All access is relationship-scoped. Form templates are workspace-scoped; assignments and submissions are coach-client relationship-scoped.
Coach Rules
- Coach can create/edit/archive templates in their workspace based on role permissions.
- Coach can assign forms only to active or allowed trial relationships in the same workspace.
- Coach can review submissions for relationships they are authorized to access; recording a review or clarification decision locks the submission against client amendments.
- Coach review is required before any mapped profile, progress, nutrition, task, or communication update is applied or visible outside the submission review workspace.
- Coach cannot mutate a published version definition; they must publish a new version.
- Coach internal review notes are excluded from client-facing endpoints.
Client Rules
- Client can see only assignments tied to their own active/allowed relationship.
- Client can submit only active assignments that have not been canceled or expired.
- Client can amend a submitted form only while coach review is still pending. Once review or clarification is recorded, changes require a coach clarification or replacement submission.
- Client cannot see other clients' submissions or coach-private review notes.
- Client media uploads must be bound to an assignment/question and verified before submission.
Note
Security tests must cover relationship boundaries, archived forms, ended coach-client relationships, stale assignment links, and signed media URL access.
Web, Coach Mobile, Client Mobile
All surfaces share API contracts and validation rules, but each surface uses interaction patterns that fit the device.
Coach Web
Primary dense builder: drag-and-drop canvas, side settings, component catalog, mapping setup, preview, publish, assignment, weekly/every-other-week/monthly schedule management, submissions list, and review workspace with mapping approval controls.
Coach Mobile
Parity creation/editing with step-based flow, bottom sheets, reorder handles, focused question settings, preview, publish, assign, schedule, and quick review. Review can approve or reject mapped updates; draft edits should be offline-capable through local outbox.
Client Mobile
Guided completion with required-state indicators, local draft autosave, retryable signed uploads, photo compression, RTL/localized labels, submit confirmation, pre-review amendment, and clear feedback after coach review or clarification request.
Test Scenarios & V1 Baseline Decisions
Validate versioning, answer schema, relationship permissions, pre-review amendments, offline retries, media uploads, review-gated mappings, retention cleanup, and recurrence before implementation.
Tests
- Domain: publishing freezes version definition and increments version number.
- Domain: validator rejects missing required answers, unsupported units, stale choice IDs, invalid media refs, invalid recurrence kinds, and unmapped target adapters.
- Domain: recurring schedules support only weekly, every other week, and monthly rules in Public V1.
- Domain: client can amend a submitted form before coach review; amendment creates a new revision and invalidates previous pending mapping rows.
- Domain: client amendment fails once coach review or clarification has been recorded.
- API: coach can create, edit draft, publish, assign, schedule, cancel, and archive with correct permissions.
- API: client submission idempotency prevents duplicate submissions from mobile retries.
- API: photo answers reject unsupported content type, photos over 3 MB after compression, images over 1600px long edge, and checksum mismatch.
- API: file answers reject executable types, unsupported content types, files over 10 MB, and failed malware/content sniffing checks.
- API/security: coach/client cannot access forms outside their workspace/relationship.
- Integration:
FormSubmittedcreates coach inbox item and pending mapping results without mutating profile or progress history. - Integration:
FormSubmissionReviewedapplies only coach-approved explicit mappings and records applied/rejected/failed mapping statuses. - Retention: ended relationship cleanup purges or anonymizes sensitive health answers and media assets after 30 days while preserving minimal audit metadata.
- Web E2E: coach builds form with multiple component types, reorders questions, previews, publishes, and assigns.
- Mobile E2E: client saves draft offline, uploads media, reconnects, submits once, and sees submitted state.
- Localization/RTL: Arabic question labels and RTL layout render correctly for client submission flow.
Resolved V1 Baseline Decisions
- Answer mappings: all explicit mappings are allowed in Public V1 when a typed target adapter exists.
- Mapping visibility: coach review approval is required before mapped profile, progress, nutrition, task, or communication updates become visible outside the submission review workspace.
- Amendments: clients can amend a submitted form only before coach review or clarification is recorded; after that, changes use clarification or replacement submissions.
- Recurring schedules: V1 baseline supports weekly, every other week, and monthly recurrence only.
- Photo uploads: use the same profile as progress photos: JPEG/WebP, EXIF stripped, 1600px max long edge, 3 MB hard max, and 960px/640px/320px variants.
- File uploads: V1 baseline allows safe document/image file types up to 10 MB, with malware scanning, content sniffing, and no executable archives.
- Retention: sensitive health answers and media assets are retained during active relationships, read-only for 30 days after relationship end, then purged or anonymized from normal access while minimal audit metadata remains.