Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/developer/developer-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ This section is designed for:
- Architects evaluating ODE architecture
- Contributors wanting to improve the codebase

:::tip AI coding assistants
Building **custom applications** (HTML, JS, CSS bundles and JSON forms) without cloning the ODE monorepo? Use the **[custom_app](https://github.com/OpenDataEnsemble/custom_app)** repository on GitHub (`AGENTS.md` and `CONTEXT_*.md` for assistants and authors), together with the main **[documentation](https://opendataensemble.org/docs/)** site.
:::

:::info Not developing?
If you're collecting data, see the [Data Collector Guide](/docs/collector/collector-index).
If you're designing forms, see the [Implementer Guide](/docs/implementer/implementer-index).
Expand Down
45 changes: 25 additions & 20 deletions docs/guides/custom-applications.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,42 @@ Complete guide to building and deploying custom applications that integrate with

## Overview

Custom applications are React applications that run within the Formulus mobile app, providing specialized workflows and user experiences. They allow you to create custom navigation, integrate with the ODE form system, and build specialized interfaces for specific use cases.
Custom applications are **web applications** (HTML, CSS, and JavaScript) that run inside the Formulus mobile app’s WebView. You may author them with **any** stack—plain static files, **Vite**, **React**, **Vue**, **Svelte**, or another bundler—**as long as the build output** can be packaged as described in the [app bundle format](/docs/reference/app-bundle-format) (entry HTML, assets, and `forms/` layout). They provide specialized workflows, custom navigation, integration with the ODE form system, and interfaces tailored to your use case.

## Scaffolding

ODE does **not** require a special installer: start from a **standard** project scaffold (for example **`npm create vite@latest`** with React, Svelte, or Solid templates) and then align the **folder layout** with the app bundle spec. Copy-paste commands, a **Vite `outDir` example**, and a post-scaffold checklist are maintained in the **[custom_app](https://github.com/OpenDataEnsemble/custom_app)** repository README on GitHub (AI and author context for the Formulus API and forms live in that repo as well).

## Application Structure

Custom applications follow a standardized structure for consistency and maintainability:
There is **no single mandatory** project layout. The tree below is **one** common pattern (React + Vite + optional `app.config.json` for theming). You can use a simpler folder tree if you prefer hand-written HTML/JS or a different framework, provided the **zip** you upload matches the [bundle format](/docs/reference/app-bundle-format).

```
my-app/
├── app.config.json # App configuration and theme
├── forms/ # Form definitions
│ ├── survey/
├── app.config.json # Optional: app metadata and theme (if your template uses it)
├── forms/ # Form definitions (see bundle format)
│ ├── survey/ # One folder per form type (form name)
│ │ ├── schema.json # JSON Schema (draft-07)
│ │ └── ui.json # Formulus UI schema
│ └── forms-manifest.json # Form registry
├── src/
│ ├── components/ # Reusable components
│ ├── screens/ # Main screens
│ ├── utils/ # Utility functions
│ └── theme.js # Theme generation
├── scripts/ # Build and validation scripts
├── package.json
└── vite.config.js
│ │ └── ui.json # JSON Forms UI schema (ODE rules)
│ └── forms-manifest.json # Form registry (if used by your project)
├── src/ # Optional: only if you use a bundler (e.g. React)
│ ├── components/
│ ├── screens/
│ ├── utils/
│ └── theme.js
├── scripts/
├── package.json # Optional: if you use npm tooling
└── vite.config.js # Optional: example bundler config
```

## Configuration System

### app.config.json

The `app.config.json` file is the single source of truth for your application's configuration:
If your template uses **`app.config.json`** (common in React-based examples), it can hold application metadata and theme. Plain HTML apps may omit it and configure styling in CSS/JS instead. When present, it is typically the single place for those settings:

```json
{
"$schema": "https://ode.dev/schemas/app-config-v1.json",
"name": "My Application",
"version": "1.0.0",
"navigation": {
Expand Down Expand Up @@ -108,10 +111,12 @@ export function buildTheme(mode = 'light') {

### Form Structure

Each form consists of two files:
Each form is a **directory** named with the **form type** (for example `survey/`). Inside it, two files are required:

1. **`schema.json`**: [JSON Schema](https://json-schema.org/) (draft-07) defining data shape, validation, and question types (including ODE `format` values). See [Form specifications](/docs/reference/form-specifications).
2. **`ui.json`**: [JSON Forms](https://jsonforms.io/) **UI schema** defining layout (`VerticalLayout`, `Control`, `scope`, rules). ODE follows JSON Forms with project-specific rules—see [Form specifications](/docs/reference/form-specifications). The [app bundle format](/docs/reference/app-bundle-format) describes how these files sit inside the zip.

1. **schema.json**: JSON Schema (draft-07) defining data structure
2. **ui.json**: Formulus UI schema defining form layout and behavior
Synkronus accepts **`forms/<formType>/schema.json`** and **`ui.json`** at the **bundle root** (with **`forms/`** as a **sibling** of **`app/`**), or the alternate path **`app/forms/<formType>/...`** where **`forms`** sits **inside** **`app`**. See [App bundle format](/docs/reference/app-bundle-format).

### Example Form

Expand Down
1 change: 0 additions & 1 deletion docs/guides/quick-start-custom-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ my-app/

```json
{
"$schema": "https://ode.dev/schemas/app-config-v1.json",
"name": "My First App",
"version": "1.0.0",
"navigation": {
Expand Down
4 changes: 4 additions & 0 deletions docs/implementer/implementer-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ This section is designed for users who:
- Need guidance on deployment and data management
- Are responsible for project outcomes

:::tip AI coding assistants
If you use an AI assistant to build **custom applications** (bundles and JSON forms) for ODE, see **[custom_app](https://github.com/OpenDataEnsemble/custom_app)** on GitHub and the **[documentation](https://opendataensemble.org/docs/)** site—no need to clone the full ODE monorepo.
:::

:::info Not collecting data?
If you're collecting data with Formulus, see the [Data Collector Guide](/docs/collector/collector-index).
If you're developing or extending ODE, see the [Developer Guide](/docs/developer/developer-index).
Expand Down
114 changes: 63 additions & 51 deletions docs/reference/app-bundle-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,35 @@ App bundles are ZIP archives containing custom application files, form specifica

## Bundle Structure

An app bundle is a ZIP file with the following structure:
Synkronus validates bundles that use **top-level** folders **`app/`**, **`forms/`**, and optionally **`renderers/`**. The **`app`** directory (your HTML/JS/CSS) and the **`forms`** directory (JSON Forms) are **siblings** at the root of the ZIP—they are **not** nested inside each other in the default layout.

```
app-bundle.zip
├── index.html # Main entry point (required)
├── manifest.json # Bundle metadata (required)
├── assets/
│ ├── css/
│ │ └── styles.css
│ ├── js/
│ │ ├── app.js
│ │ └── formulus-load.js
│ └── images/
│ └── logo.png
├── forms/ # Form specifications (optional)
│ ├── survey-v1.json
│ └── health-v1.json
└── config/ # Configuration files (optional)
└── settings.json
├── app/ # Web UI (required): sibling of forms/, not parent of it
│ ├── index.html # Main entry point (required)
│ ├── assets/
│ │ ├── css/
│ │ ├── js/
│ │ │ ├── app.js
│ │ │ └── formulus-load.js
│ │ └── images/
│ └── public/ # Optional: e.g. app.config.json copied into build
├── forms/ # Form specs (optional; see Form specifications)
│ ├── survey/ # Folder name = form type id
│ │ ├── schema.json
│ │ └── ui.json
│ └── health/
│ ├── schema.json
│ └── ui.json
├── forms/ext.json # Optional: app-level extensions config
└── renderers/ # Optional: custom JSON Forms renderers
└── myRenderer/
└── renderer.jsx
```

**Alternate layout (nested forms):** Some projects put form folders under **`app/forms/<formType>/`** instead of top-level **`forms/<formType>/`**. In that case **`forms`** is a **subdirectory inside `app`** (path `app/forms/...`). That is **different** from having **`app/`** and **`forms/`** as **two separate top-level folders**. Synkronus accepts both; use one style consistently in a given bundle.

## Manifest Format

The `manifest.json` file defines bundle metadata:
Expand All @@ -42,7 +50,7 @@ The `manifest.json` file defines bundle metadata:
"version": "20250114-123456",
"name": "My Custom App",
"description": "Description of the custom application",
"entryPoint": "index.html",
"entryPoint": "app/index.html",
"createdAt": "2025-01-14T10:00:00Z"
}
```
Expand All @@ -59,13 +67,13 @@ The `manifest.json` file defines bundle metadata:

## Entry Point

The entry point (`index.html` by default) is the main HTML file loaded when the app bundle is opened. It should:
Validated bundles include **`app/index.html`** (path from the **ZIP root**). The manifest **`entryPoint`** should reference that file (commonly `app/index.html`). The entry HTML should:

1. Include the Formulus load script
2. Initialize the application
3. Use the Formulus JavaScript interface

**Example:**
**Example** (paths below assume assets live under `app/assets/`):

```html
<!DOCTYPE html>
Expand All @@ -84,38 +92,40 @@ The entry point (`index.html` by default) is the main HTML file loaded when the

## Form Specifications

Form specifications can be included in the `forms/` directory. Each form file should contain:
Use a **one-folder-per-form** layout. The **folder name** is the **form type** identifier (for example `survey`, `registration`). Synkronus validates that each form directory contains both required files.

- Schema definition
- UI schema definition
- Form metadata
**Folder structure:** **`forms/<formType>/`** at the **root** of the ZIP, as a **sibling** of **`app/`**.

**Example form file:**
### Required files per form

| File | Role |
|------|------|
| **`schema.json`** | **[JSON Schema](https://json-schema.org/)** (draft-07 in ODE) for the observation payload: properties, types, validation, and ODE-specific **`format`** values for question types. Defines *what* is collected. |
| **`ui.json`** | **[JSON Forms UI schema](https://jsonforms.io/docs/uischema/)** for layout: `VerticalLayout`, `Control` elements, `scope` pointers into the schema, rules, etc. Defines *how* the form is shown. ODE follows JSON Forms with additional rules documented in [Form specifications](/docs/reference/form-specifications). |

Upstream JSON Forms concepts and UI schema structure are described at **[jsonforms.io](https://jsonforms.io/)**. Always cross-check ODE-specific behavior in [Form specifications](/docs/reference/form-specifications)—not every JSON Forms feature is available in Formulus.

**Example paths (two files per form, not one combined JSON) — top-level `forms/` (sibling of `app/`):**

```json
{
"formType": "survey",
"version": "1.0.0",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"title": "Name"
}
}
},
"uischema": {
"type": "VerticalLayout",
"elements": [
{
"type": "Control",
"scope": "#/properties/name"
}
]
}
}
```
forms/
└── survey/
├── schema.json
└── ui.json
```

**Same form type using nested `app/forms/` instead:**

```
app/
├── index.html
└── forms/
└── survey/
├── schema.json
└── ui.json
```

See [Form specifications](/docs/reference/form-specifications) and [Form design](/docs/guides/form-design) for complete examples of `schema.json` and `ui.json` pairs.

## Versioning

Expand Down Expand Up @@ -143,21 +153,22 @@ synk app-bundle switch 20250114-123456
### Required Files

- `manifest.json`: Bundle metadata
- `index.html`: Main entry point (or file specified in `entryPoint`)
- `app/index.html`: Main entry point (or path given by `entryPoint`)

### Optional Files

- `assets/`: CSS, JavaScript, images, and other assets
- `forms/`: Form specification files
- `config/`: Configuration files
- `app/assets/` (or similar under `app/`): CSS, JavaScript, images, and other assets for the web UI
- `forms/<formType>/`: Form specifications at **ZIP root** (sibling of `app/`), **or** `app/forms/<formType>/` if you use the nested layout
- `forms/ext.json`: Optional extensions manifest
- `renderers/`: Optional custom JSON Forms renderers

## File Size Limits

| Component | Limit | Notes |
|-----------|-------|-------|
| **Total bundle size** | 100 MB | Maximum ZIP file size |
| **Individual file** | 50 MB | Maximum size per file |
| **Form specification** | 1 MB | Maximum size per form file |
| **Form specification** | 1 MB | Maximum size per form file (`schema.json` / `ui.json`) |

## Upload Process

Expand Down Expand Up @@ -213,3 +224,4 @@ Mobile devices download app bundles during synchronization:
- [Custom Applications Guide](/docs/guides/custom-applications)
- [API Reference](/docs/reference/rest-api/overview)
- [Form Design Guide](/docs/guides/form-design)
- [custom_app on GitHub](https://github.com/OpenDataEnsemble/custom_app) — AI/agent context (`AGENTS.md`, `CONTEXT_*.md`) and **scaffolding** recipes (`npm create vite@latest`, post-build checklist)
Loading