- See [implementation-plan.md](./implementation-plan.md) for phased delivery, ticket breakdown, and recommended sprint sequencing.
## Purpose
Build a standalone Forms product for schools and education teams to collect structured responses such as student feedback, course reviews, enrollment requests, parent intake, event registrations, and internal staff forms, while reusing as much of the existing exercise question engine as possible.
## Executive Summary
ClassroomIO already has a strong reusable question engine:
-`@cio/question-types` already centralizes question contracts, answer serialization, renderer contracts, and form-state helpers, and explicitly calls out reuse for future products such as forms/assessments.
-`@cio/ui/custom/exercise-question` already supports question rendering in `edit`, `take`, `preview`, `review`, and `submission` modes across 12 question types.
- The current exercise stack already handles templates, file upload answers, submission storage, and submission summary visualizations.
The main constraint is that the current product is still deeply shaped around courses, lessons, grading, and enrolled students. That makes it a poor fit for Google Forms / Typeform style workflows if we try to ship Forms by directly reusing the `exercise` and `submission` entities as-is.
### Recommendation
Build Forms as a new top-level product domain, but reuse the existing question engine and renderer stack aggressively.
Today, exercises can technically ask many of the same questions that a form would ask, but they are optimized for assessment, not response collection.
That creates hard product limits for schools and organizations that want to collect:
1. Student feedback after a course or session.
2. School review forms and satisfaction surveys.
3. Enrollment and application forms.
4. Parent intake and consent forms.
5. Staff requests and internal administrative forms.
6. Event registration forms.
The current exercise product does not map cleanly to these workflows because:
1. Exercises are attached to a `course`, often also a `lesson` and `section`.
2. Exercise access is enforced through course membership middleware.
3. Submissions are tied to `groupmember` and carry grading state, total score, reviewer, and feedback fields.
4. The UX assumes students, instructors, grading, due dates, and completion rather than respondents, publish/share, response settings, and collection workflows.
5. There is no public share-link, embeddable form, thank-you page, response export, response caps, or form lifecycle model.
## Product Thesis
If ClassroomIO turns the current exercise question engine into a standalone Forms product, the platform gets a second major content primitive:
-`Learning content` for teaching and assessment.
-`Response collection` for school operations and feedback.
This expands ClassroomIO from LMS-only workflows into school operations workflows without throwing away the investment already made in the exercise system.
The goal is **not** to build another Google Forms. The value of ClassroomIO Forms is that the **trigger, the respondent identity, and the consequence of a form all live inside the platform**:
- **Trigger**: a lesson is completed, a course ends, a buyer clicks "Buy", a landing page is visited — the form appears at the right moment without anyone sending a link.
- **Identity**: authenticated respondents are already known (`profile`, course membership); no re-typing who someone is, and responses attach to the person's record.
- **Consequence**: a response can enroll a student, create a purchase request an admin approves, feed reviews onto a course landing page, or surface per-lesson feedback to the instructor.
A standalone form with a share link is table stakes. Every roadmap decision should be weighed against this loop, not against feature parity with Google Forms or Typeform.
| Area | Current State | Reuse Value | Forms Gap |
| --- | --- | --- | --- |
| `@cio/question-types` | Shared package for question keys, answer payloads, render contracts, serializer, form state | High | Naming is exercise-centric, but architecture is already reusable |
| `@cio/ui/custom/exercise-question` | Full renderers for `edit`, `take`, `preview`, `review`, `submission` | High | Needs forms shell, branding, page layout, respondent UX |
| Question type coverage | 12 types: radio, checkbox, textarea, true/false, short answer, numeric, fill blank, file upload, matching, ordering, hotspot, link | High | Some types are assessment-first; forms also need presentational blocks and contact fields |
| Exercise builder UI | Existing authoring flow with question reorder, media, templates, preview | Medium | Assumes course exercise editing and points/correctness |
| Submission summary UI | Already shows aggregated response charts and individual response replay | Medium | Needs forms response inbox, filters, export, respondent metadata |
6. Public forms are required for enrollment and feedback use cases, but must include abuse protection.
7. Authenticated internal forms are also required for staff and student-only workflows.
8. File upload is supported in Forms, but public file upload must be guarded with rate limits and storage controls.
9. Existing exercise question types are reused first; new form-only block types can be added later.
10. The first implementation prioritizes reuse over perfect naming cleanup. Renaming `exercise-question` to a broader package is optional follow-up, not a blocker.
11.**Binding is attachment-level, not form-level.** A form is a reusable org-owned asset. A `form_attachment` binds a form to a platform context (course, lesson, landing page, checkout). Every response records which attachment it came through, so course pages show only their own responses while the form library shows all responses with a per-context breakdown. The attachment model is intentionally extensible: new platform features can add additional `context_type` values, per-context settings, and integration consequences in their own PRDs without reworking the core Forms product.
12.**Per-attachment settings vs per-form settings**: lifecycle windows (open/close dates), response limits, and one-response-per-respondent live on the **attachment**. Question content and identity settings (anonymous, collect name/email) live on the **form**. Editing a form's questions applies everywhere it is attached; question versioning is deferred. Future platform features may add new attachment contexts and consequence hooks (verified-email access grants, enrollment handoffs, etc.). Those integrations extend `form_attachment` and the runner/submission layer; they are specified in the consuming feature's PRD, not enumerated here. **Precedence**: for any integrated attachment, services read lifecycle, limit, and dedup fields from `form_attachment` only — never from the reusable `form` row. Legacy `form.starts_at`, `form.ends_at`, `form.response_limit`, and `form.allow_multiple_responses` columns may remain for bare share-link forms with no attachment, but must not be consulted when a response is tied to an attachment.
13.**MVP ships two integration loops**: (a) application → enrollment, including the purchase-request pipeline that replaces the hardcoded course payment modal, and (b) per-lesson pulse feedback. A landing-page form section and a reviews display block are part of MVP surface work.
14.**Create-in-context authoring**: every attachable surface (course settings, lesson, landing-page builder, checkout config) gets an "Attach a form" picker that lists existing org forms and offers "Create new", which opens the builder in a modal/drawer pre-seeded with a context-appropriate template. The form is saved to the org Forms library and attached in one motion. The Forms area is the library: manage forms, see where each is attached, view global results.
15.**Default purchase-request form**: every org is seeded with a default "Course purchase request" form attached to the checkout context of paid courses. It replaces the hardcoded fullname/email payment modal. Submit stores a pending response, then redirects to the course's `paymentLink`. Admins review pending purchase requests in the response inbox and approve to enroll the student. This also fixes the current theme-based paid path, which dead-ends on a "requires payment or invite" message with no purchase flow at all.
16.**Public review display requires consent + curation**: forms whose responses can be shown publicly (course reviews) must include a respondent consent checkbox, and only responses an admin has marked "approved for display" render on landing pages.
17.**Author-side draft lifecycle stays** (DRAFT → PUBLISHED → CLOSED → ARCHIVED). **Respondent-side save-and-resume** of a partially filled response is phase 2, as are edit-submitted-response and one-question-per-page mode. Anonymous public forms stay in v1.
18.**Commercial model: free standalone, paid integrations.** Free orgs get **unlimited forms** — creation, share-link collection, response inbox, analytics, CSV export — a full Google-Forms-style experience with no caps. What is gated behind paid plans is the **platform integration layer**: attaching forms to courses/lessons/landing pages/checkout, triggers, and consequences (approve→enroll, lesson pulse aggregation, landing-page form/reviews blocks). Self-hosted is ungated.
- **Checkout exception**: the default purchase-request form works on free orgs for *collection* — buyers fill it, responses land in the inbox, and the admin enrolls manually (parity with today's manual flow, no regression). The one-click **approve→enroll** action is paid.
19.**Permissions**: org **admins and teachers** can create forms and manage/view/export responses. **Students cannot create forms**; they only respond.
A form can be attached to platform contexts through `form_attachment` records:
1.**Course** — e.g. a course review or end-of-course survey; responses surface in that course's context.
2.**Lesson** — per-lesson pulse feedback (1–2 questions) shown after lesson completion, aggregated per lesson so instructors see which lessons need work.
3.**Landing page** — a form section block on the org landing page (contact, waitlist, enrollment interest).
4.**Checkout** — the purchase-request form on paid courses (see FR-9).
Requirements:
1. Each attachable surface exposes an "Attach a form" picker with existing forms plus inline "Create new" (builder opens in a modal/drawer, pre-seeded with a context-matching template; saved to the library and attached in one motion).
2. Attachment carries its own settings: open/close window, response limit, one-response-per-respondent.
3. Responses record `attachment_id`; context views filter by attachment, the library view aggregates across attachments with a per-context breakdown.
Future platform features may add new attachment contexts and consequence hooks (verified-email access grants, enrollment handoffs, etc.). Those integrations extend `form_attachment` and the runner/submission layer; they are specified in the consuming feature's PRD, not enumerated here.
1. Every org is seeded with a default "Course purchase request" form, auto-attached to the checkout context of paid courses.
2. Buyer flow: click Buy → fill the form → response stored with status `PENDING` → redirect to the course `paymentLink` → confirmation copy telling the buyer to complete payment.
3. Admin flow: pending purchase requests appear in the response inbox (and course context); admin approves a request → student is enrolled in the course; admin can also reject with an optional reason. On free plans the inbox shows the requests but the one-click approve→enroll action is gated (admin enrolls manually, as today).
4. Teacher/student notification emails are preserved from the current flow.
5. Admins can edit the default form's questions (org-wide) like any other form.
6. This replaces the hardcoded `payment-modal.svelte` two-field modal and gives theme-based landing pages a working paid path (they currently dead-end on a block message).
### FR-10: Public Review Display
1. Review-type forms include a respondent consent checkbox ("you may display my review publicly").
2. Admins can mark individual responses "approved for display".
3. A landing-page reviews block renders approved responses only, filterable to a single course or org-wide.
Binds a reusable form to a platform context and holds per-attachment settings. The `form` entity stays standalone; integration coupling is isolated here.
- When a response records `form_attachment_id`, attachment fields are authoritative. Do not read timing/limit columns from `form` for that submission path.
- New `context_type` values, per-context `settings`, DB check constraints, and integration consequences (access grants, context-specific identity overrides) are introduced by consuming feature PRDs and implemented alongside those features — not as implicit scope inside this MVP document. The first such extension is the Events product (`prd/events/README.md`): `EVENT` context bindings, tenant/uniqueness invariants, `form_response_access_grant`, scanner-safe redemption routes, and token-to-session exchange.
1.**One-response-per-email enforcement** — yes, as a per-attachment setting.
2.**Forms on public site/subdomain contexts** — yes; the landing-page form section and checkout attachment render on org public sites.
3.**Section/page support** — phase 2; v1 is one-page only.
4.**Staff routing after submission** — the purchase-request pipeline covers approve → enroll; general assignment/routing stays out of MVP.
5.**Presentation modes / respondent drafts / response editing** — one-question-per-page, respondent save-and-resume, and edit-submitted-response are all phase 2. Author-side DRAFT → PUBLISHED lifecycle is unaffected and stays in v1.
6.**Commercial model** — free tier gets unlimited standalone forms (no caps); the platform integration layer (attachments, triggers, consequences) is paid. Checkout exception: the default purchase-request form collects responses on free; one-click approve→enroll is paid. Self-hosted ungated.
7.**Permissions** — admins and teachers create forms and manage/view/export responses; students only respond.
2. Do we need respondent notification emails on submit in v1? (Admin/teacher emails exist for the purchase pipeline; existing ZeptoMail infra makes respondent confirmations cheap — leaning yes.)
3. Runner i18n: which locales must the public form runner support at launch?
4. PII/retention policy for enrollment/intake forms (minors' data, file uploads): retention window, deletion story, and what "anonymous" guarantees about stored metadata.
1. Percentage of forms attached to a platform context (course, lesson, landing page, checkout) vs bare share links — target majority attached.
2. Purchase-request conversion: Buy click → form submitted → approved → enrolled, measured end-to-end (baseline today is zero, since no purchase record exists).
3. Per-lesson pulse response rate vs a plain shared feedback link.
4. Number of orgs displaying approved reviews on a landing page.
Product/engineering metrics:
5. At least 3 high-value school workflows can be completed without abusing exercises as forms.
6. Form creation to publish time is under 10 minutes for common templates (create-in-context should make this under 2 minutes for attached forms).
7. Public form submission success rate stays above 98%.
8. At least 70% of question rendering code is shared with the exercise product.
9. Zero regressions in exercise creation, submission, and grading flows.
It is realistic to build a strong Forms product on top of the current exercise implementation, but only if the reuse happens at the **question engine layer**, not by forcing form workflows into the existing course exercise and grading model.
The existing investment already removed the hardest part, which is rich question rendering and answer modeling. What remains is the product work that makes something feel like Google Forms or Typeform: publishing, sharing, respondent identity, response management, analytics, and public-safe submission infrastructure.