CMP-DC
Form runtime documentation
This document describes what the form-runtime package can do for end users and integrators, and how it is structured technically. It is generated from the current codebase; for normative ODK expression semantics, see the monorepo spec specs/odk-phase0/PHASE2_EXPRESSION_SEMANTICS.md.
Overview
Form runtime is a standalone Next.js application that renders interactive questionnaires from a JSON definition (FormJson), validates answers with ODK-like rules (relevance, constraints, calculate fields), supports internationalization and accessibility, and submits answers to your backend or a local API. It can run as a public fill experience, an embedded demo, or a builder preview target.
Functional capabilities
Form definition
- Contract:
FormJson—id,meta(title, description, tags, version, locale, optionali18nOverrides, optionalsubmitUrl/submitJwt), and a tree ofquestions. - Normalization: persisted API payloads (including survey export shapes with
surveyMeta) are coerced vianormalizePersistedFormToFormJson.
Question types with dedicated UI
These types are wired in widgetRegistry and render full controls: text, integer, decimal, select_one, select_multiple, rating, acknowledge, note, calculate, image, audio, video, file.
Containers: group and repeat nest children; repeats support row add/remove, per-row validation, and parameters such as repeat count / ODK template flags where implemented in schema and storage.
Other declared types (e.g. date, time, datetime, geo types, range, barcode) currently resolve to UnsupportedWidget — they appear as an unsupported notice unless you extend the registry.
Visibility and logic
- Relevance:
relevantExpressionand builder skip logic drive which questions are shown (visibility.ts). - Constraints:
constraintExpressionwith optionalconstraintMessage/ per-localeconstraintMessages; messages support${field}-style interpolation after locale resolution. - Calculate: read-only fields from
calculation; derived values are merged into the answer map for visibility, validation, and submit. - Expression engine: documented subset (not full XPath 2.0) — see
validation/expression.tsand the Phase 2 semantics doc in the repo.
Validation behavior
FormRuntimesupportsvalidationMode:submit(default),change(debounced), orblur.- On submit failure: field errors, an error summary (ordered by render order), and scroll/focus to the first invalid field.
- Required fields, regex helper in constraints, and rating uniqueness rules are enforced in
validateForm.ts.
Internationalization
Built-in bundles for validation and UI copy (formRuntime namespace), with meta.locale and optional meta.i18nOverrides merging author strings. The document lang attribute follows the resolved language.
Accessibility
Field regions use stable data-field-key, composed aria-describedby, aria-errormessage when invalid, and appropriate roles for groups/ratings/media. Inline errors use live regions consistent with the error summary pattern.
Drafts and offline
- Drafts: answers persisted in IndexedDB per form id/version; can be restored on reload until a successful submit clears them.
- Offline submission: when the network fails or the device is offline, submissions can be queued in an outbox and retried (client drain + status messaging).
Submission
If meta.submitUrl and meta.submitJwt are set (typical when Django serves fill JSON), POST is sent to that URL with the JWT header expected by the backend. Otherwise the runtime posts to /api/submissions on the same origin with JSON body including form_id and answers (buildSubmitRequest).
Routes and surfaces
| Path | Purpose |
|---|---|
/ | Marketing / entry; links to demo and documentation. |
/docs | This documentation page. |
/demo | Loads a form via FormLoader + /api/forms/[formId] and renders FormRuntime (JSON demo). |
/fill/[formId] | Respondent fill flow: fetches normalized form from /api/fill-forms/[formId] (backend proxy), then FormRuntime. |
/preview | Iframe target for the survey builder: accepts postMessage with form JSON from an allowed origin. |
/validate-xform | UI to validate raw ODK XForm XML via POST /api/validate-xform (monorepo validator package). |
Technical architecture
Stack
Next.js (App Router), React, TypeScript, i18next / react-i18next for translations, Tailwind CSS for styling. Client components drive the form; API routes handle fill proxy, submissions, ping, and XForm validation.
Main modules (under src/)
domain/formSchema.ts— TypeScript types forFormJsonand questions.domain/normalizeFormDocument.ts— Coercion from API / export payloads.runtime/FormRuntime.tsx— Shell: state, submit, drafts, outbox, validation orchestration.runtime/QuestionRenderer.tsx— Dispatches group, repeat, and leaf widgets.runtime/widgets/*— Per-type UI;widgetChromefor shared a11y and errors.validation/*— Expressions, visibility, repeat paths,validateForm/validateField, interpolation.i18n/*— Locale bundles,createFormRuntimeI18n, fill-shell i18n for loading states.lib/*— Draft storage, outbox, submit request, scroll-to-field, CORS helpers for builder origins.
Data flow (simplified)
- Load
FormJson(static API, fill proxy, or postMessage). - Initialize answers (defaults + optional draft restore).
- On change: merge calculate outputs; re-evaluate visibility; optional debounced/blur validation.
- On submit: full validation; POST answers; queue on failure when policy allows.
APIs and integration
HTTP routes (selection)
GET /api/forms/[formId]— Demo form JSON for/demo.GET /api/fill-forms/[formId]— Server-side fetch from Django fill-json (secrets + backend URL from env); used by/fill/[formId].POST /api/submissions— Default same-origin submit target when JWT URL is not embedded in the form.GET /api/ping— Health / CORS probe for builder embedding.POST /api/validate-xform— Validates XForm XML (monorepo validator); CORS restricted to configured builder origins.
Environment variables (typical)
FORM_RUNTIME_BACKEND_URL/DJANGO_API_URL— Django base for fill proxy.FORM_FILL_EXPORT_SECRET— Shared secret for fill-json requests (must match backend).NEXT_PUBLIC_FORM_BUILDER_ORIGINS— Extra allowed origins for preview postMessage and CORS.
See each route file and .env.example in this package for the full list and dev defaults.
Limitations and non-goals
- Not a full ODK XPath 1.0 engine — expressions are an intentional subset aligned with Phase 2 semantics.
- Question types not in
widgetRegistryrender as unsupported until implemented. - External CSV itemsets and other advanced ODK features may require additional product design and schema work.
- XForm validation in
/api/validate-xformloads the sibling package underpackages/xform-validator/distrelative to the app working directory — monorepo deployments must include that build output or adjust wiring.