This monorepo contains the core platform for offline-first data collection and synchronization. Use this file when you work across packages or need the big picture. For deep dives, open the AGENTS.md in the package you are changing.
Published architecture (users and external readers): Architecture overview on opendataensemble.org.
ODE is a clearinghouse model: data is collected on devices, synchronized through Synkronus, and is intended to flow through the system for local analysis and stewardship—not to live only on the server.
- Formulus — React Native mobile app: runs forms (JSON Forms) and custom app bundles in WebViews, offline-first, syncs with Synkronus.
- Formulus Formplayer — React web app embedded in Formulus: renders forms inside a WebView; shares the same bridge contract as custom apps.
- Synkronus — Go backend: auth, sync, app bundle distribution, export, shared HTTP API.
- Synkronus Portal — Web admin UI (React + Vite): same API as other clients; no privileged backend channel.
- Synkronus CLI —
synkcommand-line client: automation, bundles, sync, export. - ODE Desktop — Tauri app: Data management + Forms / app workbench; source in
desktop/. See ROADMAP.md.
flowchart LR
Formulus[Formulus_RN]
Formplayer[Formulus_Formplayer]
Synkronus[Synkronus_API]
Portal[Portal]
CLI[CLI]
Formulus -->|sync| Synkronus
Portal -->|same_API| Synkronus
CLI -->|same_API| Synkronus
Formulus -->|hosts_WebView| Formplayer
Design principle: One backend, many clients — prefer the public API for all user-facing tools.
| Profile | Typical focus | Where to work |
|---|---|---|
| Platform developer | You are editing this repo: RN, Go, React, shared packages, CI. | Package AGENTS.md below. |
| Custom app author | You ship an HTML/JS/CSS app bundle and JSON forms for Formulus; you may not clone this monorepo. | Custom app template (AI + author context) and documentation. |
Do not assume custom app authors have local checkouts of ODE or internal example repos.
| Package | Role | Stack | Agent guide |
|---|---|---|---|
| formulus | Mobile runtime, WebViews, native bridge | React Native | formulus/AGENTS.md |
| formulus-formplayer | Form UI in WebView | React, Vite, JSON Forms | formulus-formplayer/AGENTS.md |
| synkronus | Sync API and coordination | Go | synkronus/AGENTS.md |
| synkronus-cli | CLI for API operations | Go | synkronus-cli/AGENTS.md |
| synkronus-portal | Web administration | React, TypeScript, Vite | synkronus-portal/AGENTS.md |
| packages/tokens | Design tokens (@ode/tokens) |
Style Dictionary | packages/tokens/AGENTS.md |
| packages/components | Shared UI (@ode/components) |
React | packages/components/AGENTS.md |
| desktop | Data management + Forms / app workbench (Tauri) | React, Rust | desktop/AGENTS.md |
Use this when preparing a new ODE release (pre-release or stable). Full tagging and CI behaviour: RELEASE.md. Android Play/F-Droid versionCode rules: formulus/android/ANDROID_RELEASE.md.
| Layer | Pre-release (e.g. v1.1.1-alpha.3) |
Stable (e.g. v1.1.1) |
|---|---|---|
Client manifests (package.json, versionName, CLI, Desktop, Portal) |
Target semver without suffix (1.1.1) |
Same (1.1.1) |
| Git tag + GitHub release | v1.1.1-alpha.3 (mark pre-release) |
v1.1.1 |
Synkronus Docker / server BuildVersion() |
From release tag via CI ldflags | From release tag |
For stable, you usually do not re-bump client manifests if they already match the target version; bump Android versionCode only when shipping a new Play build.
| File | Field | Purpose |
|---|---|---|
formulus/package.json |
version |
Source for ODE_VERSION / x-ode-version (formulus/src/version.ts) |
formulus/android/app/build.gradle |
versionCode, versionName |
Google Play; run pnpm run sync:version from formulus/ after package.json bump for versionName |
formulus/ios/Formulus.xcodeproj/project.pbxproj |
MARKETING_VERSION, CURRENT_PROJECT_VERSION |
iOS display + build number (align CURRENT_PROJECT_VERSION with Android versionCode) |
synkronus-cli/internal/cmd/version.go |
Version |
synk version output |
synkronus-cli/versioninfo.json |
Windows file/product version | Windows binary metadata |
desktop/package.json, desktop/src-tauri/tauri.conf.json, desktop/src-tauri/Cargo.toml |
version |
ODE Desktop app version (keep all three in sync) |
desktop/src/lib/synkConstants.ts |
SYNKRONUS_CLIENT_VERSION |
Desktop x-ode-version header |
synkronus-portal/package.json |
version |
Portal x-ode-version (synkronus-portal/src/version.ts) |
Synkronus server version is not edited in source for releases — CI injects it from the git tag (.github/workflows/synkronus-docker.yml).
- Semver: bump
MAJOR.MINOR.PATCHin client manifests to match the release line (e.g.1.1.1). - Android
versionCode: must increase monotonically for Google Play (+10 per release is a common convention; +1 per shipped alpha build is also fine). - In-app version display: Formulus About/Settings use native
versionName+versionCodeviaAppVersionService; Desktop About uses TaurigetVersion().
# After bumping formulus/package.json
cd formulus && pnpm run sync:version
# Pre-flight on touched JS packages
cd formulus-formplayer && pnpm run lint && pnpm run format:check
cd formulus && pnpm run lint && pnpm run format:check
cd desktop && pnpm run lint && pnpm run format:check && pnpm run typecheck && pnpm test
cd synkronus-cli && go build ./cmd/synkronus && ./synk version # or synkronus-cli.exe on WindowsFORMULUS_INTERFACE_VERSIONinformulus/src/webview/FormulusInterfaceDefinition.ts— WebView bridge API contract, not app release versionformulus-formplayer/package.json— embedded library semver- OpenAPI document version comments in generated API clients
synkronus-cli/internal/config/config.godefaultapi.version— Synkronus API contract major version for compatibility checks, not CLI display version
git tag v1.1.1-alpha.3 # or v1.1.1 for stable
git push origin v1.1.1-alpha.3
# GitHub → Releases → publish (pre-release checkbox for alpha/rc tags)- Formulus ↔ WebView (custom apps + formplayer):
formulus/src/webview/FormulusInterfaceDefinition.tsis the source of truth for the injected JavaScript API. Formplayer copies a synced TypeScript snapshot viapnpm run sync-interfaceinformulus-formplayer(see formulus-formplayer/AGENTS.md). - ODE Desktop workbench developer mode: local custom app mirror under
bundles/dev-local/(profile-scoped); see desktop/AGENTS.md and developer mode guide. - Built-in attachment fields:
photo,audio,video, and generic file (select_file) persist attachment basenames (and metadata) in observation JSON while binaries live under Formulusattachments/storage and sync via the attachment pipeline—see published docs (form specifications, form design guide) andFormulusInterfaceDefinition.ts. - Custom app bridge (v1.1.0+):
persistObservation(headless write),sync,getConnectivityStatus,getCurrentDataRevisionCount, andopenFormplayeroptionsskipFinalize/skipDraftSelection— contract inFormulusInterfaceDefinition.ts; runpnpm run sync-interfacein formplayer after changes. - Sub-observations: Each nested Formplayer session validates its own schema;
skipFinalizeonly skips the Finalize page. Custom validators are per-session — see Custom Extensions — nested sessions (docs site). - Shared UI tokens: Install tokens before components / formplayer where the docs require it (see package READMEs and formplayer AGENTS).
- Pipelines: .github/CICD.md.
- Lint/format: Run the relevant scripts in the package you touch (see root README.md and each package).
- Pre-flight before opening a PR: each package
AGENTS.mdlists the locallint/format/format:check/test/buildcommands that match CI — run them in every package you changed (e.g. formulus-formplayer/AGENTS.md). - Commits/PRs: Conventional Commits and PR expectations are documented in formulus-formplayer/AGENTS.md (project-wide convention).
ODE Desktop ships in desktop/ (see desktop/AGENTS.md). Broader product direction: ROADMAP.md and opendataensemble.org.
Authoritative public documentation: opendataensemble.org.
Optional AI-focused context (no ODE clone required): custom_app on GitHub (README.md, AGENTS.md, CONTEXT_*.md).