Files
Rotimi Best 634ffeab0f docs(prd): integration-first revision of forms product PRD
Lock core product decisions: attachment-level binding model with
form_attachment table, create-in-context authoring, purchase-request
pipeline replacing the hardcoded payment modal, per-lesson pulse
feedback, landing-page form and reviews blocks, admin/teacher-only
form creation, and free-standalone/paid-integrations commercial model.
Defer respondent save-and-resume, edit-submitted-response, and
one-question-per-page to phase 2. Add FORM-F1 to FORM-F9 tickets.
2026-07-18 12:17:40 +01:00

19 KiB

Forms Product Implementation Plan

Scope Lock

  1. Forms is a new top-level product domain and must not be implemented by repurposing course exercise routes or submission entities directly.
  2. Shared question rendering and answer modeling must be reused from @cio/question-types and @cio/ui/custom/exercise-question.
  3. Exercise behavior must remain backward compatible throughout delivery.
  4. MVP includes public forms and authenticated organization forms.
  5. Invite-only / explicitly targeted forms are phase 2, not MVP.
  6. MVP includes draft, published, closed, and archived lifecycle states.
  7. MVP includes response inbox, per-question aggregates, and CSV export.
  8. MVP excludes grading, points, reviewer workflow, and correctness semantics.
  9. MVP may ship with form-specific question tables rather than a generalized cross-product questionnaire schema.
  10. Public forms must ship with abuse controls before general rollout.
  11. Binding is attachment-level: form_attachment binds a reusable form to a context (course, lesson, landing page, checkout) and holds lifecycle/limit/dedup settings; responses record their attachment.
  12. MVP includes the purchase-request pipeline (default org form replacing payment-modal.svelte, pending → approve → enroll) and per-lesson pulse feedback.
  13. MVP is one-page presentation only; respondent save-and-resume (FORM-B6) and edit-submitted-response are deferred to phase 2. Author-side DRAFT → PUBLISHED lifecycle stays.
  14. Commercial model is free-standalone / paid-integrations: free orgs get unlimited forms with share-link collection, inbox, analytics, and export; attachments, triggers, and consequences (approve→enroll, lesson pulse, landing-page blocks) require a paid plan. Checkout exception: the default purchase-request form collects on free; one-click approve→enroll is paid. Self-hosted ungated.
  15. Form creation and response access are restricted to org admins and teachers; students can only respond.

Delivery Phases

  1. Phase A: Shared foundations and schema.
  2. Phase B: API validation, services, and public/internal response flow.
  3. Phase C: Dashboard builder and management UI.
  4. Phase D: Response inbox, analytics, and export.
  5. Phase E: Hardening, abuse controls, rollout, and follow-up platform extraction.

Ticket Breakdown

ID Phase Area Task Key Files Depends On Done When
FORM-A1 A Shared Contracts Audit @cio/question-types and @cio/ui/custom/exercise-question for exercise-only assumptions and document the locked reusable surface packages/question-types/*, packages/ui/src/custom/exercise-question/*, prd/forms-product/README.md None Reuse boundaries are explicit and no hidden coupling remains undiscovered
FORM-A2 A Shared Labels Extend shared render contracts so host apps can inject forms-specific labels and submit copy without introducing hardcoded strings packages/question-types/src/exercise-types.ts, packages/question-types/src/render-contract.ts, packages/question-types/src/index.ts FORM-A1 Forms runner can use shared components with product-appropriate copy
FORM-A3 A DB Schema Add form table with lifecycle, access, response settings, timing, slug, and org ownership packages/db/src/schema.ts None Schema compiles and form entity supports draft/publish/close flows
FORM-A4 A DB Schema Add formQuestion and formQuestionOption tables aligned to shared question contracts packages/db/src/schema.ts FORM-A3 Form questions/options persist independently of exercises
FORM-A5 A DB Schema Add formResponse and formResponseAnswer tables for respondent identity and answer storage packages/db/src/schema.ts FORM-A3, FORM-A4 Responses can be stored without grading or groupmember coupling
FORM-A6 A DB Relations/Types Add DB relations and exported inferred types for forms entities packages/db/src/relations.ts, packages/db/src/types.ts FORM-A3, FORM-A4, FORM-A5 Query layer can consume typed forms entities cleanly
FORM-A7 A DB Migration Generate migration SQL and metadata for all new forms tables and constraints packages/db/src/migrations/* FORM-A3, FORM-A4, FORM-A5 Migration applies cleanly against local DB
FORM-A8 A Validation Add form CRUD schemas, publish/close schemas, public response schemas, and response query schemas packages/utils/src/validation/form/form.ts, packages/utils/src/validation/form/index.ts FORM-A3, FORM-A4, FORM-A5 API can validate form authoring and response payloads consistently
FORM-A9 A Query Layer Add CRUD queries for forms, form questions/options, and response storage/read paths packages/db/src/queries/form/* FORM-A6 Query layer supports form create/read/update/publish/respond flows
FORM-B1 B API Errors Add forms-specific error codes for unavailable form, closed form, invalid responder, response limit reached, and duplicate response apps/api/src/utils/errors.ts None Forms routes can return precise failure codes
FORM-B2 B API Service Add org-side forms service for create, get, list, update, duplicate, archive, publish, and close apps/api/src/services/form/form.ts, apps/api/src/services/form/index.ts FORM-A8, FORM-A9 Admin lifecycle actions work through a single service layer
FORM-B3 B API Service Add public/internal form runner service to resolve form by slug and compute access rules apps/api/src/services/form/runner.ts FORM-A8, FORM-A9 Public and authenticated forms load through one normalized response model
FORM-B4 B API Service Add response submission service with typed answer serialization using shared question contracts apps/api/src/services/form/response.ts FORM-A8, FORM-A9 Shared renderer payloads can be submitted and persisted end-to-end
FORM-B5 B API Service Implement one-response-per-user/email and response-limit enforcement apps/api/src/services/form/response.ts FORM-B4 Duplicate/over-limit submissions are blocked server-side
FORM-B6 B API Service Add authenticated draft save/resume support only if retained in MVP; otherwise defer and mark explicitly apps/api/src/services/form/response.ts, apps/api/src/services/form/runner.ts FORM-B4 Scope decision is resolved and API shape is stable
FORM-B7 B API Routes Add organization management routes for forms CRUD and lifecycle actions apps/api/src/routes/organization/form.ts, route exports, apps/api/src/app.ts FORM-B2 Org admins can manage forms over typed RPC routes
FORM-B8 B API Routes Add public routes for GET /forms/:slug and POST /forms/:slug/respond apps/api/src/routes/public/form.ts, apps/api/src/app.ts FORM-B3, FORM-B4, FORM-B5 Public runner is reachable without course context
FORM-B9 B API Routes Add org response routes for list/detail/export preparation apps/api/src/routes/organization/form.ts FORM-B2, FORM-B4 Dashboard can load responses and analytics data
FORM-B10 B File Upload Reuse upload answer payload model for forms file upload and add size/type restrictions by access mode apps/api/src/services/form/response.ts, shared upload helpers, form routes FORM-B4 File-upload questions work safely for forms
FORM-C1 C Dashboard Types Add forms feature request/response types inferred from API contracts apps/dashboard/src/lib/features/forms/utils/types.ts FORM-B7, FORM-B8, FORM-B9 Dashboard code uses inferred API types only
FORM-C2 C Dashboard API Add forms API classes for management, runner, and responses apps/dashboard/src/lib/features/forms/api/*.svelte.ts, .server.ts where needed FORM-C1 Components can call form APIs through feature-local classes
FORM-C3 C Navigation Add forms entry point and routing in dashboard navigation apps/dashboard/src/lib/features/ui/navigation/*, new routes under apps/dashboard/src/routes/forms/* FORM-C2 Admin can discover and enter forms product from dashboard
FORM-C4 C Builder State Create forms builder store/state using shared question contracts without course/exercise dependencies apps/dashboard/src/lib/features/forms/store/*, util files FORM-C2 Builder state supports question add/edit/reorder/preview
FORM-C5 C Builder UI Build forms editor page reusing shared question renderer/edit mode patterns apps/dashboard/src/lib/features/forms/components/builder/*, routes FORM-C4 Admin can author a form end-to-end
FORM-C6 C Settings UI Build form settings panel for lifecycle, access mode, identity collection, limits, and success screen copy apps/dashboard/src/lib/features/forms/components/settings/* FORM-C5 Admin can configure MVP settings without raw JSON
FORM-C7 C Public Runner UI Build form runner page for public and authenticated response flows using shared question renderer in take mode apps/dashboard/src/routes/forms/respond/[slug]/+page.svelte or public site equivalent, feature components FORM-C2, FORM-B8 Respondents can complete and submit forms
FORM-C8 C Preview UI Add builder preview mode matching shared runner behavior apps/dashboard/src/lib/features/forms/components/preview/* FORM-C5, FORM-C7 Admin preview matches live runner closely
FORM-C9 C Templates UI Add starter templates for feedback, review, enrollment, intake, RSVP, and staff request apps/dashboard/src/lib/features/forms/components/templates/*, template seed/query source as needed FORM-C5 Admin can create common forms quickly
FORM-C10 C Translations Add forms translation keys and remove hardcoded UI copy from forms feature apps/dashboard/src/lib/utils/translations/en.json and peer locales as needed FORM-C5, FORM-C6, FORM-C7 Forms UI follows translation conventions
FORM-D1 D Response Inbox Build responses list with filters, counts, completion metadata, and detail drill-in apps/dashboard/src/lib/features/forms/components/responses/*, routes FORM-B9, FORM-C2 Admin can inspect submissions without export
FORM-D2 D Response Detail Build single-response review view using shared question replay patterns apps/dashboard/src/lib/features/forms/components/responses/response-detail.svelte FORM-D1 Admin can inspect each answer question-by-question
FORM-D3 D Analytics Build per-question aggregate analytics using submission summary/chart patterns already used in exercises apps/dashboard/src/lib/features/forms/components/analytics/* FORM-D1 Admin can see distribution charts and basic trends
FORM-D4 D Export Implement CSV export service and dashboard trigger apps/api/src/services/form/export.ts, form routes, dashboard responses UI FORM-B9, FORM-D1 Export produces one row per response with respondent metadata and question columns
FORM-D5 D Permissions Ensure only authorized org users can view responses and exports forms services/routes, auth middleware wiring FORM-B7, FORM-B9 Response data is not exposed to unauthorized users
FORM-E1 E Abuse Controls Add rate limiting and bot protection for public response endpoints public form routes, middleware/config FORM-B8 Public forms are protected against basic spam/abuse
FORM-E2 E Monitoring Add logging and monitoring hooks for submit failures, file-upload abuse, and export usage forms services/routes FORM-B8, FORM-D4, FORM-E1 Operational visibility exists before rollout
FORM-E3 E QA/Tests Add validation, query, API, and dashboard tests for form create/publish/respond/export flows tests across packages/utils, packages/db, apps/api, apps/dashboard FORM-D4 Core flows have regression coverage
FORM-E4 E Rollout Add feature flag, rollout checklist, and production enablement notes forms config/docs, PRD references FORM-E1, FORM-E2, FORM-E3 Safe staged rollout is documented and executable
FORM-E5 E Follow-up Platforming Evaluate whether to generalize forms/exercises into a shared questionnaire persistence layer after MVP stability follow-up PRD/docs FORM-E4 Decision is made with real usage data instead of pre-optimizing
FORM-F1 A DB Schema Add formAttachment table (context type, course/lesson refs, lifecycle window, response limit, dedup flag) and formResponse.formAttachmentId + approvedForDisplay packages/db/src/schema.ts, relations/types FORM-A3, FORM-A5 Reusable forms can bind to contexts and responses resolve to their attachment
FORM-F2 B API Service Attachment CRUD + response filtering by attachment/context; library view aggregates across attachments apps/api/src/services/form/attachment.ts, form routes FORM-F1, FORM-B2 Course context shows only its responses; library shows per-context breakdown
FORM-F3 C Attach Picker "Attach a form" picker component with inline create-in-context (builder in modal/drawer, template pre-seeded by context) apps/dashboard/src/lib/features/forms/components/attach-picker/*, course/lesson settings surfaces FORM-C5, FORM-F2 Admin can attach or create+attach a form without leaving the course
FORM-F4 B/C Purchase Pipeline Seed default "Course purchase request" form per org; checkout attachment on paid courses; submit stores PENDING response then redirects to paymentLink; preserve teacher/student emails seed logic, apps/api/src/services/form/purchase.ts, org-site course routes FORM-F2 Buy click leads to the org form and a stored purchase request; legacy payment-modal.svelte retired
FORM-F5 C/D Approve → Enroll Response inbox actions for purchase requests: approve (enrolls student), reject with reason forms inbox components, enrollment service wiring FORM-F4, FORM-D1 Admin approval enrolls the student without leaving the inbox
FORM-F6 C Lesson Pulse Per-lesson pulse feedback attachment shown after lesson completion, with per-lesson aggregate view for instructors lesson completion surface, apps/dashboard/src/lib/features/forms/components/analytics/* FORM-F2, FORM-D3 Instructor sees pulse results grouped by lesson
FORM-F7 C Landing Page Blocks Landing-page form section block and approved-reviews display block (consent checkbox + approvedForDisplay curation) packages/ui landing themes, org landing-page editor FORM-F2, FORM-C7 Org can embed a form and display curated reviews on its public site
FORM-F8 E Plan Gating Gate the integration layer (attachment creation, approve→enroll, lesson pulse, landing-page blocks) behind paid plans; standalone forms stay ungated and unlimited; default checkout form collects on free with approve→enroll gated; self-hosted ungated plan config, form/attachment services, dashboard upsell states FORM-B2, FORM-F2, FORM-F4 Free orgs can collect without limits; every integration touchpoint shows a paid upsell instead of failing silently
FORM-F9 B/C Permissions Restrict form creation, editing, response viewing, and export to org ADMIN and TUTOR roles across routes and dashboard nav; students only respond forms routes middleware, dashboard nav/route guards FORM-B7, FORM-C3 Student accounts cannot reach form authoring or response data

Route Targets

Organization Management Routes

  1. GET /organization/:orgId/forms
  2. POST /organization/:orgId/forms
  3. GET /organization/:orgId/forms/:formId
  4. PUT /organization/:orgId/forms/:formId
  5. POST /organization/:orgId/forms/:formId/publish
  6. POST /organization/:orgId/forms/:formId/close
  7. GET /organization/:orgId/forms/:formId/responses
  8. GET /organization/:orgId/forms/:formId/responses/:responseId
  9. GET /organization/:orgId/forms/:formId/export

Public / Runner Routes

  1. GET /forms/:slug
  2. POST /forms/:slug/respond

Optional Authenticated Draft Routes

Only if MVP keeps save/resume:

  1. POST /forms/:slug/draft
  2. PUT /forms/:slug/draft/:draftId

Acceptance Criteria by Phase

Phase A

  1. New forms schema exists without altering exercise semantics.
  2. Shared question/render contracts are usable by forms without hardcoded exercise wording.
  3. Query layer can create and read forms, questions, and responses independently from courses.
  4. Build passes for @cio/db, @cio/utils, and @cio/question-types.

Phase B

  1. Org admins can create, update, publish, close, and fetch forms through API routes.
  2. Public and authenticated forms can be loaded by slug without course membership.
  3. Response submission persists typed answers and enforces lifecycle and limit rules.
  4. File upload questions work with safe restrictions.

Phase C

  1. Dashboard has a discoverable Forms area.
  2. Admin can author a form, configure settings, preview it, and publish it.
  3. Respondent-facing runner works for public and authenticated forms.
  4. No hardcoded user-facing copy is introduced in dashboard forms UI.

Phase D

  1. Admin can list responses, open a single response, and inspect answers.
  2. Admin can see basic aggregate analytics for supported structured question types.
  3. CSV export includes response metadata and one column per question.

Phase E

  1. Public endpoints have rate limiting and bot mitigation.
  2. Submit, export, and failure paths are monitored.
  3. Automated tests cover create/publish/respond/export paths.
  4. Rollout can be staged safely behind a feature flag.

Verification Commands

  1. pnpm --filter @cio/question-types build
  2. pnpm --filter @cio/db build
  3. pnpm --filter @cio/utils build
  4. pnpm --filter @cio/api build
  5. pnpm --filter @cio/dashboard build
  6. Run targeted tests for forms validation, API routes, and dashboard feature flows.

Suggested First Sprint Slice

  1. FORM-A1 to FORM-A9, FORM-F1
  2. FORM-B1 to FORM-B5, FORM-F2
  3. FORM-B7, FORM-B8
  4. FORM-C1, FORM-C2

This gets the core platform decision made early, lands the schema/query/API foundation, and proves the highest-risk assumption: that the shared question engine can power a new standalone product without breaking exercises.

Suggested Second Sprint Slice

  1. FORM-C3 to FORM-C8
  2. FORM-D1, FORM-D2
  3. FORM-E1
  4. FORM-F3 to FORM-F5

This delivers an MVP loop: create form, publish form, submit response, inspect response, and keep public endpoints safe enough for controlled rollout — plus the flagship integration demo: attach a form from a course, and buy a paid course through the purchase-request pipeline with approve → enroll.

Post-MVP Follow-Up Candidates

  1. Invite-only / explicitly targeted forms.
  2. Save and resume drafts if not shipped in MVP.
  3. Edit submitted response.
  4. Multi-section/page builder if one-page MVP ships first.
  5. Shared questionnaire persistence model for exercises and forms.