Monorepo: See also ../AGENTS.md for the full ODE map and cross-package contracts.
This file gives AI assistants and developers enough context to work effectively in this repo. For user-facing docs, see README.md.
- Formulus Formplayer is a web app that renders and submits forms using the JSON Forms spec. It is not a standalone site: it runs inside a WebView in the Formulus React Native app.
- The RN app (sibling repo
../formulus) loads the formplayer bundle from file:// (e.g.file:///android_asset/formplayer_dist/on Android). The formplayer is the “form UI”; the RN app handles sync (Synkronus), storage, and native capabilities (camera, GPS, etc.). - The formplayer exposes a JavaScript API (
window.formulus.formplayer) that the host and custom apps use to add or edit observations. The formplayer is initialized and configured by the Formulus app (renderers, cells, formSpecs, etc.); custom apps only call the API.
- Parent repo:
formulus-formplayerlives under a monorepo (e.g.ODE). Sibling projects include:formulus— React Native app that hosts the formplayer WebView and provides the native bridge.packages/tokens—@ode/tokens(design tokens; Style Dictionary).packages/components—@ode/components(shared UI; may use@ode/tokens).
- Install order (from repo root):
cd packages/tokens && npm installthencd formulus-formplayer && npm install && npm start.
Installing only in formulus-formplayer can break the tokenspreparescript.
- Scripts (from
formulus-formplayer/):npm run build—sync-interface→tsc→vite build(output:build/).npm run build:rn— build then copybuild/into the Formulus app:- Android:
../formulus/android/app/src/main/assets/formplayer_dist/ - iOS:
../formulus/ios/formplayer_dist/
- Android:
npm run build:ode-desktop— one command: same asbuild:rn, then copiesbuild/into../desktop/public/formplayer_dist/for ODE Desktop (Tauri). Use this when you need both React Native assets and the desktop embed refreshed. Alternatively, fromdesktop/only:pnpm copy:formplayer(requires an existingformulus-formplayer/build/).
- Interface sync:
scripts/sync-interface.jscopies one shared TypeScript file from the Formulus app into the formplayer:
formulus/src/webview/FormulusInterfaceDefinition.ts→formulus-formplayer/src/types/FormulusInterfaceDefinition.ts.
So the single source of truth for the bridge contract is in formulus; formplayer consumes a copy. Runnpm run sync-interface(ornpm run build) when that file changes. - Vite:
vite.config.tsis tuned for WebView:base: './'so assets resolve under file://.- Single bundle (
inlineDynamicImports: true) so the WebView doesn’t fail loading multiple chunks. - No
crossoriginon script/link so file:// loading works. - Source maps enabled for debugging.
| Area | Purpose |
|---|---|
src/App.tsx |
Main app: JsonForms setup, renderer/cell registration, theme, init from FormInitData. |
src/index.tsx |
Entry: mounts React app; exposes React and MaterialUI on window for custom question type renderers. |
src/renderers/* |
JSON Forms renderers (e.g. signature, photo, file, GPS, swipe layout, finalize). Each has a tester (when to use) and a component. |
src/theme/ |
MUI theme from @ode/tokens via tokens-adapter.ts; material wrappers for consistent look. |
src/services/ |
FormulusInterface.ts (bridge client), DraftService, ExtensionsLoader, custom question type/validator loaders and registries. |
src/types/ |
FormulusInterfaceDefinition.ts (synced from formulus), CustomQuestionTypeContract.ts, etc. |
src/components/ |
Shared UI (e.g. QuestionShell, FormLayout, DraftSelector). |
src/builtinExtensions.ts |
Built-in extension functions (e.g. getDynamicChoiceList) used in forms. |
src/mocks/ |
webview-mock.ts and DevTestbed for local dev without RN. |
scripts/ |
sync-interface.js, copy-to-rn.js, clean-rn-assets.js. |
- WebView environment: No real
window.ReactNativeWebViewin browser dev; use the mock. Assume file:// and single JS bundle when changing Vite/build. - Bridge: Communication with RN is via postMessage and the contract in
FormulusInterfaceDefinition.ts. The formplayer usesFormulusClient(singleton) inFormulusInterface.tsto call native (camera, signature, submit, etc.). - Custom question types: Loaded from a manifest (source strings) from the RN app, evaluated in a sandbox with
ReactandMaterialUIonwindow. They use format in the schema (e.g."format": "signature"), not onlytype. Contract:src/types/CustomQuestionTypeContract.ts. - Design tokens: Use
@ode/tokensviasrc/theme/tokens-adapter.tsand the theme insrc/theme/theme.ts; avoid hardcoding colors/spacing that exist in tokens.
- New question type (built-in)
Add a renderer insrc/renderers/with a tester (e.g.formatIs('myFormat')) and component; register it inApp.tsx(renderers array). If it needs a new AJV format, register it where other formats are registered inApp.tsx. - New question type (custom / from Synkronus)
Custom types are loaded byCustomQuestionTypeLoaderfrom the manifest; they must comply withCustomQuestionTypeContract.tsand export a default component. No change in formplayer code needed for new custom types that follow the contract. - New native capability (e.g. new “requestX” from RN)
- Extend the contract in formulus (
FormulusInterfaceDefinition.ts). - Run
npm run sync-interfacein formulus-formplayer. - Implement the client side in
FormulusInterface.tsand use it in the relevant renderer or service.
- Extend the contract in formulus (
- Build / bundle issues
Keep one main bundle; avoid dynamic imports that create extra chunks unless you’ve verified loading under file:// in the RN WebView. Keepbase: './'and the no-crossorigin plugin.
npm start— Vite dev server; useswebview-mockand (optionally)DevTestbedso you can test without the RN app.- Tests:
npm test(Vitest). Lint/format:npm run lint,npm run lint:fix,npm run format,npm run format:check.
- Commit messages must follow Conventional Commits (e.g.
feat(scope): add X,fix(scope): resolve Y). - Before opening a PR, run
npm run formatso Prettier has formatted the files. - PRs should use the following template:
- Bug Fix
- New Feature / Enhancement
- Refactor / Code Cleanup
- Documentation Update
- Maintenance / Chore
- Other (please specify):
- formulus (React Native mobile app)
- formulus-formplayer (React web app)
- synkronus (Go backend server)
- synkronus-cli (Command-line utility)
- Documentation
- DevOps / CI/CD
- Other:
Closes/Fixes/Resolves:
- Unit tests added/updated
- Integration tests added/updated
- Manually tested
- Tested on multiple platforms (if applicable)
- Not applicable
- This PR introduces breaking changes
- This PR does NOT introduce breaking changes
If breaking changes, please describe migration steps:
- Documentation has been updated
- Documentation update is not required
- Code follows project style guidelines
- All existing tests pass
- New tests added for new functionality
- PR title follows Conventional Commits format
Thank you for contributing to Open Data Ensemble (ODE)!
- Form init: RN sends
FormInitData(e.g. viaonFormInit); seeFormulusInterfaceDefinition.ts. - Submit: Use
FormulusClient.submitObservationWithContext(formInitData, finalData)so create vs update is correct. - Renderers: Use
QuestionShellfor consistent layout and use the theme/tokens (e.g.tokensfromtheme/tokens-adapter) for spacing and colors where applicable.
Using this file, an AI or new developer can reason about the formplayer’s role in the monorepo, where to change code for new features, and what not to break (single bundle, file://, bridge contract, tokens).