From 1a4aef7b589dc3b5da13cd9b9b2fa1cfd3a80caa Mon Sep 17 00:00:00 2001 From: Emil Rossing Date: Mon, 15 Jun 2026 23:41:15 +0000 Subject: [PATCH 1/6] docs(formplayer): document mobile UX and bridge API pack --- docs/guides/choice-lists.md | 14 ++++ docs/guides/custom-extensions.md | 1 + docs/guides/form-design.md | 100 +++++++++++++++++++++++++- docs/reference/formplayer-contract.md | 30 +++++++- docs/reference/formplayer.md | 14 +++- docs/reference/formulus.md | 53 +++++++++++++- 6 files changed, 205 insertions(+), 7 deletions(-) diff --git a/docs/guides/choice-lists.md b/docs/guides/choice-lists.md index 2bee3b0..7962c83 100644 --- a/docs/guides/choice-lists.md +++ b/docs/guides/choice-lists.md @@ -68,6 +68,20 @@ Example structure: Use **snake_case** names for lists (`yes_no`, `region_list`, `priority_level`). +### How shared lists render on device + +By default, a plain `oneOf` / `$ref` field renders as a **native HTML ``** (keyboard-free, reliable in Formulus WebViews). Opt back into the searchable Autocomplete with `"autocomplete": true`. + +| Option | Values | Applies to | +|--------|--------|------------| +| `display` | `"radio"` \| `"buttons"` | Single-select | +| `display` | `"checkboxes"` \| `"buttons"` | Multi-select (`type: array` with `uniqueItems`) | +| `orientation` | `"vertical"` (default) \| `"horizontal"` \| `"flow"` | Radio, checkbox, and button modes | +| `buttonGroup` | `"segmented"` (default) \| `"separated"` | Button modes | +| `autocomplete` | `true` | Single-select — use searchable Autocomplete instead of native select | +| `placeholder` | string | Placeholder for native select (e.g. `"Select…"`) | + +**Tap-to-clear:** In `radio` and `buttons` single-select modes, tapping the already-selected option clears the answer. + +**Example — horizontal Yes/No buttons:** + +```json +{ + "type": "Control", + "scope": "#/properties/consent", + "options": { + "display": "buttons", + "orientation": "horizontal" + } +} +``` + +### Sticky fields + +Set `"sticky": true` to remember the last submitted scalar value for that field. Values are stored in device `localStorage`, keyed by form type + version + field path, and applied as the **lowest-precedence** default on **new** observations only (not edits, drafts, or sub-observation sessions). + +```json +{ + "type": "Control", + "scope": "#/properties/interviewer_id", + "options": { "sticky": true } +} +``` + +### Dynamic schema defaults + +For new observations, Formplayer resolves a small set of **default tokens** in `schema.json` property `default` values (only when the field is still empty after params / `defaultData`): + +| Token | Resolves to | +|-------|-------------| +| `$today` | Local calendar date `YYYY-MM-DD` (`format: "date"`) | +| `$now` | ISO 8601 date-time (`format: "date-time"`) | + +Static `default` literals are unchanged. + +### Deferred validation + +New observations start with validation errors hidden (`ValidateAndHide`). Errors appear after the user navigates forward or reaches Finalize. Edits and draft resumes show validation immediately. Override via `params.validationMode`: `"ValidateAndShow"`, `"ValidateAndHide"`, or `"NoValidation"`. + +### Number bounds + +`minimum` / `maximum` are **validation constraints**, not per-keystroke clamps. Users can type out-of-range values; AJV surfaces errors on blur and at finalize. Stepper +/- buttons still respect bounds. ## Working with Media & Special Field Types @@ -934,6 +1010,28 @@ Show field when boolean is true: } ``` +#### Multi-select choice check + +For **array** multi-select fields, use `contains` with `const` (not `const` on the array itself): + +```json +{ + "type": "Control", + "scope": "#/properties/other_symptoms", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/symptoms", + "schema": { + "contains": { "const": "other" } + } + } + } +} +``` + +Custom question types (`format` renderers) and the root `SwipeLayout` element also honour `SHOW` / `HIDE` rules. + ### Unsafe Patterns #### ❌ Rule Referencing Missing Field diff --git a/docs/reference/formplayer-contract.md b/docs/reference/formplayer-contract.md index 4271dc2..69b2d36 100644 --- a/docs/reference/formplayer-contract.md +++ b/docs/reference/formplayer-contract.md @@ -303,6 +303,21 @@ addFormats(ajv); // Standard format validators (date, email, etc.) - Each element in `elements` becomes a page - Progress indicator shows current page - Navigation buttons (Previous/Next) provided automatically +- Root `SwipeLayout` honours `SHOW` / `HIDE` rules (same as nested pages) +- Default `labelLayout` is `"inline"` (compact two-column rows); set `"stacked"` for classic layout +- Optional `headerFields` (up to two schema keys) show read-only context under the progress bar +- `showInnerTitle` defaults to `false` (form title lives in Formulus chrome unless opted in) + +**SwipeLayout `options`:** + +| Option | Values | Notes | +|--------|--------|-------| +| `labelLayout` | `"inline"` \| `"stacked"` | Default `"inline"` | +| `headerFields` | `string[]` | Max 2 field keys | +| `showInnerTitle` | `boolean` | Default `false` | +| `autoFocusFirstInput` | `boolean` | Default `true` | +| `nextButtonLabel` | `string` | Custom Next label | +| `finalizeButtonLabel` | `string` | Last content page label | #### VerticalLayout @@ -394,9 +409,22 @@ addFormats(ajv); // Standard format validators (date, email, etc.) **Optional:** - `label`: String or `false` (to hide label) -- `options`: Object with renderer-specific options +- `options`: Object with renderer-specific options (see below) - `rule`: Conditional display/enable rule +**Common `options`:** + +| Option | Values | Description | +|--------|--------|-------------| +| `labelLayout` | `"inline"` \| `"stacked"` | Per-field layout override (inherits SwipeLayout default) | +| `sticky` | `true` | Remember last submitted scalar value for new observations | +| `display` | `"radio"` \| `"buttons"` \| `"checkboxes"` | Choice layout (single- or multi-select) | +| `orientation` | `"vertical"` \| `"horizontal"` \| `"flow"` | Choice layout direction | +| `buttonGroup` | `"segmented"` \| `"separated"` | Button-group styling | +| `autocomplete` | `true` | Searchable Autocomplete for single-select (default is native `` dropdown by default for `oneOf` / `$ref` lists; optional Autocomplete (`options.autocomplete`), radio, or button groups +- **Multi Select**: Vertical checkboxes by default; optional checkbox or button groups with `options.display` + +### Form UX (2026) + +- **Inline layout**: SwipeLayout forms default to compact two-column rows (`labelLayout: "inline"`) +- **Sticky fields**: Opt-in per-control value memory (`options.sticky`) +- **Deferred validation**: New forms hide errors until first forward navigation +- **Sub-observation fast path**: `skipFinalize` skips the Finalize page for nested child forms +- **Dynamic defaults**: Schema `default: "$today"` / `"$now"` for new observations + +See [Form design guide](../guides/form-design) for `ui.json` examples. ### Boolean diff --git a/docs/reference/formulus.md b/docs/reference/formulus.md index 30cc057..bbfe08d 100644 --- a/docs/reference/formulus.md +++ b/docs/reference/formulus.md @@ -181,15 +181,62 @@ const observations = await api.getObservationsByQuery({ **Returns:** Promise resolving to an array of observations -#### sync() +#### sync(options?) Trigger manual synchronization. ```javascript -await api.sync(); +const { version } = await api.sync(); +// Optional: include attachments (slower) +await api.sync({ includeAttachments: true }); ``` -**Returns:** Promise that resolves when sync completes +**Returns:** `Promise<{ version: number }>` — the server's data revision after sync completes. + +#### getConnectivityStatus() + +Probe whether the configured Synkronus server answers `GET /health`. Never rejects for offline devices — returns `{ online: false }`. + +```javascript +const status = await api.getConnectivityStatus(); +// { online: boolean, serverUrl: string | null, checkedAt: number } +``` + +Use for "verify when online, fall back when offline" workflows in custom apps. + +#### getCurrentDataRevisionCount() + +Read the device's last-known Synkronus data revision (`current_version` from the most recent successful sync). + +```javascript +const revision = await api.getCurrentDataRevisionCount(); // number, 0 if never synced +``` + +Reflects **server-stream alignment only** — not unsynced local edits. Poll after `sync()` or on an interval to detect remote changes from other devices. + +#### persistObservation(input) + +Persist an observation **without opening Formplayer** (headless write). Uses the same path as a Formplayer submit. + +```javascript +const result = await api.persistObservation({ + formType: 'survey', + finalData: { name: 'Ada', age: 30 }, + observationId: null, // omit or null to create; provide id to update +}); +// { observationId, formData } +``` + +#### openFormplayer options + +When opening forms programmatically, `openFormplayer` accepts: + +| Option | Description | +|--------|-------------| +| `subObservationMode` | Nested child form for embedded sub-observations | +| `skipFinalize` | Skip Finalize page; auto-submit from last content page | + +**Form init `params` reserved keys** (not persisted as observation data): `defaultData`, `theme`, `darkMode`, `themeColors`, `context` (read-only session context exposed in Formplayer as `window.formulusSessionContext`), `validationMode`. ## Database Schema From 82289f7f2bc7679c355e7eddc8070f0a8c5ec9c8 Mon Sep 17 00:00:00 2001 From: Emil Rossing Date: Wed, 17 Jun 2026 21:07:12 +0000 Subject: [PATCH 2/6] docs: document sub-observation itemLabel and addButtonLabel Describe optional schema itemLabel and ui.json addButtonLabel override for embedded repeat add-button copy. Co-authored-by: Cursor --- docs/guides/custom-extensions.md | 81 ++++++++++++++++++++++++++- docs/reference/form-specifications.md | 4 +- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/docs/guides/custom-extensions.md b/docs/guides/custom-extensions.md index e704048..8cf585f 100644 --- a/docs/guides/custom-extensions.md +++ b/docs/guides/custom-extensions.md @@ -40,15 +40,55 @@ Use sub-observations when related answers should live **inside the parent observ |----------|----------|-------------| | `format` | yes | Must be `"sub-observation"`. | | `linkedForm` | yes | Child **form type** opened for add/edit (non-empty string). | -| `parentKey` | yes | Field name written on child payloads linking back to the parent (for example a foreign key into the parent entity id). | -| `parentValuePath` | recommended | Dot path into **current parent form data** for that key’s value (falls back to parent `observationId` when absent). | +| `parentKey` | optional | When set, field name written on **new** child payloads linking back to the parent (for example a foreign key). When omitted, embedded repeats rely on data already nested in the parent JSON — typical when the child form does not need an injected parent id. | +| `parentValuePath` | recommended when `parentKey` is set | Dot path into **current parent form data** for that key’s value (falls back to parent `observationId` when absent). | | `columns` | optional | `{ key, label }[]` entries for the on-screen summary list; if omitted, `displayField` drives a single summary column. | | `displayField` | optional | Fallback field key for the summary column (default `observationId`). | +| `itemLabel` | optional | Singular name for each embedded item (for example `"room"`). When set, the add button shows `+ Add {itemLabel}`, the empty table shows `No {itemLabel}`, and delete confirmations fall back to `this {itemLabel}`. When omitted, legacy copy is unchanged (`+ Add observation`, etc.). | | `orderBy` | optional | Sort embedded items by field: string field name or `{ key, direction }` (`asc` / `desc`). Without `key`, sorts by `createdAt` descending when present on payloads. | | `allowDelete` | optional | Default `true`. | | `subObservationInitValues` | optional | Map merged into **initial params** when **adding** a new embedded child. Values support templates `{{parentValue}}`, `{{currentInstanceId}}`, or `{{dot.path}}` into parent data. | | `subObservationEditInitValues` | optional | Map merged **on top of** the saved child payload when **opening an existing** embedded item for edit—useful when parent-derived fields must be refreshed each time (often omitted). | -| `skipFinalize` | optional | When `true`, the nested child form skips the injected Finalize page and auto-submits from the last content page. Formulus also skips GPS `beginObservationSession()` and suppresses the success modal for this fast path. Can be set on the schema property or passed via `openFormplayer(..., { skipFinalize: true })`. | +| `skipFinalize` | optional | When `true`, the nested child form **omits the Finalize page**; **Done** on the last content page runs the same submit path as Finalize. The child is still validated against **its own** `schema.json` (AJV + that form’s custom validators) before `formData` is returned to the parent. Formulus also skips GPS `beginObservationSession()` and suppresses the success modal for this fast path. Can be set on the schema property or passed via `openFormplayer(..., { skipFinalize: true })`. | + +**`openFormplayer` options (custom apps):** `{ subObservationMode?, skipFinalize?, skipDraftSelection? }`. Use `skipDraftSelection: true` on **root** forms when the custom app orchestrates the session and must not show the draft picker (for example headless follow-up after `persistObservation`). Sub-observation sessions never offer the draft picker. + +**UI schema override:** On the parent `ui.json` Control, `options.addButtonLabel` sets the **full** add-button text (JSON Forms array convention). It takes precedence over `itemLabel` when both are set — useful for localized phrasing (for example `"+ Adicionar quarto"`). + +### Validation and `skipFinalize` + +`skipFinalize` does **not** defer validation to the root form. Each nested session is a separate Formplayer instance with its own `ui.json` and schema: + +| When | What validates | +|------|----------------| +| Child **Done** / submit (`skipFinalize` or Finalize page) | Child form only — required fields, AJV, `options.customValidators` on **that** form | +| Parent data change / parent Finalize | Parent form — including validators on embedded arrays at the parent level | + +On success, `SubObservationQuestionRenderer` merges `result.formData` into the parent array and closes the child modal immediately. Parent-level logic (denormalized indexes, cross-row rules, global sequence numbers) does **not** run inside the child session unless you duplicate it there or pass context in (see below). + +### Nested sessions and custom validators + +Custom validators run in the **active Formplayer session only**. For a multi-level embedded tree (for example `household → rooms[] → beds[] → persons[]`): + +- A validator on the **root** form’s `rooms` control runs when **root** `data` changes — not when the enumerator adds a bed inside an open **room** sub-form. +- Put validators on **each form** where rows are added if numbering or summary columns must update as soon as the child returns (typical with `skipFinalize`). +- Use **config** (for example `scope: "household" | "quarto" | "cama"`) so one validator module can serve multiple form types. + +**Authoring checklist for auto-numbering embedded rows:** + +1. Root form — validator on the top-level sub-observation array; rebuild parent-only indexes (for example a flattened lookup array). +2. Each nested child form — validator on its own sub-observation array for local sequence fields (`bed_num`, `person_num`, …). +3. **Global** sequences across the whole tree — pass a read-only snapshot from the parent via flat `subObservationInitValues` / `subObservationEditInitValues` (single-token templates preserve JSON types), or wait for platform **parent context** (below). Strip ephemeral snapshot fields on root finalize so they are not persisted. + +See [Custom validators](#custom-validators-validators) and [Parent context across nesting levels](#parent-context-across-nesting-levels). + +### Parent context across nesting levels + +Nested `openFormplayer` sessions receive only the **current row** as `core.data`, not the full parent observation. That limits cross-sibling validation, global numbering, and extension helpers that need ancestor fields. + +**Today (workaround):** Copy needed parent slices into child **data** with flat init templates, for example `"household_rooms": "{{rooms}}"` on `subObservationInitValues`. Formplayer resolves **top-level string templates only** — not nested object maps. Mark snapshot fields `readOnly` and remove them in a root-level validator before persist. Distinct from `format: "form_context"` / `params.context`, which are better for session metadata than large tree copies. + +**Proposed (not yet in ODE):** `subObservationContext` — read-only parent snapshot resolved at open time, exposed to validators/extensions, **not** validated against the child schema and **not** merged into persisted child JSON. Until then, use init templates or duplicate validators per level. Example property on the parent schema: @@ -74,6 +114,41 @@ Example property on the parent schema: Some forms use `type: ["array", "string"]` with `"format": "sub-observation"` for migration compatibility; Formplayer activates the control whenever `format` matches. ::: +## Custom validators (`validators/`) + +Bundle **custom validators** alongside custom question types. Register them in the app manifest (`validators//index.js`); reference them from `ui.json` control `options.customValidators`. + +```json +{ + "type": "Control", + "scope": "#/properties/quartos", + "options": { + "customValidators": [ + { "name": "assignRepeatPositions", "config": { "quartosField": "quartos" } } + ] + } +} +``` + +**Mutating validators:** A validator may update `data` in place (for example auto-numbering embedded sub-observation rows or rebuilding a denormalized index array). Formplayer detects mutations after each change and before finalize, then refreshes form state so summary tables and dependent fields update immediately. Return validation errors in the usual way; returning patches is not required. + +**Per-session scope:** Mutations apply to the **current** form session. Nested sub-observations need validators on each level where rows are added, or a parent snapshot field (see [Parent context across nesting levels](#parent-context-across-nesting-levels)). Root-only validators are not enough for deep embedded trees. + +```json +{ + "type": "Control", + "scope": "#/properties/beds", + "options": { + "customValidators": [ + { + "name": "assignRepeatPositions", + "config": { "scope": "room", "bedsField": "beds" } + } + ] + } +} +``` + See also [Form specifications](../reference/form-specifications.md) and [Formplayer](../reference/formplayer.md). ## Quick Start diff --git a/docs/reference/form-specifications.md b/docs/reference/form-specifications.md index 07145e4..f9a3c6f 100644 --- a/docs/reference/form-specifications.md +++ b/docs/reference/form-specifications.md @@ -197,7 +197,9 @@ Question types are specified using the `format` property in the schema: ### Sub-observations (embedded observations of another form type) -Collect **multiple related observations as JSON objects inside one parent observation** using `"format": "sub-observation"`. Each embedded item is edited in a nested Formplayer session (`openFormplayer` with `subObservationMode`). +Collect **multiple related observations as JSON objects inside one parent observation** using `"format": "sub-observation"`. Each embedded item is edited in a nested Formplayer session (`openFormplayer` with `subObservationMode`). Only **`linkedForm`** is required; **`parentKey`** is optional. Optional **`itemLabel`** customizes add-button and empty-table copy (see [Custom Extensions](../guides/custom-extensions.md#sub-observations-format-sub-observation)). + +Nested sessions validate the **child** schema on submit (`skipFinalize` only skips the Finalize page). Parent-level validators and denormalized fields run in the **parent** session. For multi-level trees, use validators on each form where rows are added, or parent snapshot init fields — see [Nested sessions and custom validators](../guides/custom-extensions.md#nested-sessions-and-custom-validators). See the full schema options and examples in [Custom Extensions](../guides/custom-extensions.md#sub-observations-format-sub-observation). From 7d96e0fd92f49ac8f645d1c1e41e8ecef00e3146 Mon Sep 17 00:00:00 2001 From: Emil Rossing Date: Wed, 17 Jun 2026 22:33:13 +0000 Subject: [PATCH 3/6] formplayer, updated docs --- docs/guides/form-design.md | 8 +++++++- docs/reference/formplayer.md | 11 +++++++++-- docs/reference/formulus.md | 3 ++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/guides/form-design.md b/docs/guides/form-design.md index dcc9dad..fffd964 100644 --- a/docs/guides/form-design.md +++ b/docs/guides/form-design.md @@ -523,7 +523,11 @@ ODE supports various question types through the Formplayer component. Question t Use **`format: sub-observation`** on an array property for **embedded repeats**: each nested completion stores JSON on the parent observation. Adding or editing opens the linked child form in **sub-observation mode**, so Synkronus still receives **one** parent observation payload. -See [Custom Extensions](./custom-extensions.md#sub-observations-format-sub-observation) for schema keys (`linkedForm`, `parentKey`, `parentValuePath`, `subObservationInitValues`, `skipFinalize`, templates, etc.). +See [Custom Extensions](./custom-extensions.md#sub-observations-format-sub-observation) for schema keys (`linkedForm`, optional `parentKey`, `parentValuePath`, `subObservationInitValues`, `skipFinalize`, templates, etc.). + +**`skipFinalize`:** Skips only the Finalize **page**; the child form still validates on **Done** before returning `formData` to the parent. Validation is **not** deferred to the root form. + +**Custom validators** (for example auto-numbering embedded rows) are declared in `ui.json` via `options.customValidators`. Validators run in the **active** form session — for nested trees, attach validators on **each** level where rows are added, not only on the root array. See [Custom Extensions — nested sessions](./custom-extensions.md#nested-sessions-and-custom-validators) and [parent context](./custom-extensions.md#parent-context-across-nesting-levels). ## Control options @@ -584,6 +588,8 @@ Static `default` literals are unchanged. New observations start with validation errors hidden (`ValidateAndHide`). Errors appear after the user navigates forward or reaches Finalize. Edits and draft resumes show validation immediately. Override via `params.validationMode`: `"ValidateAndShow"`, `"ValidateAndHide"`, or `"NoValidation"`. +**Draft picker:** On a new root form, Formplayer offers to resume local drafts when any exist. Custom apps pass `skipDraftSelection: true` in `openFormplayer` options to open directly (see [Formulus bridge API](../reference/formulus.md#openformplayer-options)). + ### Number bounds `minimum` / `maximum` are **validation constraints**, not per-keystroke clamps. Users can type out-of-range values; AJV surfaces errors on blur and at finalize. Stepper +/- buttons still respect bounds. diff --git a/docs/reference/formplayer.md b/docs/reference/formplayer.md index 245f3e1..0c4f1d4 100644 --- a/docs/reference/formplayer.md +++ b/docs/reference/formplayer.md @@ -276,7 +276,11 @@ Formplayer supports various question types through custom renderers: - **Inline layout**: SwipeLayout forms default to compact two-column rows (`labelLayout: "inline"`) - **Sticky fields**: Opt-in per-control value memory (`options.sticky`) - **Deferred validation**: New forms hide errors until first forward navigation -- **Sub-observation fast path**: `skipFinalize` skips the Finalize page for nested child forms +- **Sub-observation fast path**: `skipFinalize` omits the Finalize page; child still validates on Done before returning data to the parent +- **Nested validators**: Custom validators are per Formplayer session — deep embedded trees need validators on each nesting level (see [Custom Extensions](../guides/custom-extensions.md#nested-sessions-and-custom-validators)) +- **Optional `parentKey`**: Sub-observation arrays require only `linkedForm`; parent id injection is optional +- **Mutating custom validators**: In-place `data` updates from bundle validators refresh the UI automatically +- **Draft bypass**: `openFormplayer(..., { skipDraftSelection: true })` for orchestrated root sessions - **Dynamic defaults**: Schema `default: "$today"` / `"$now"` for new observations See [Form design guide](../guides/form-design) for `ui.json` examples. @@ -335,9 +339,10 @@ Formplayer validates form responses against: Custom validation rules can be added: +- **Bundle validators**: `ui.json` → `options.customValidators` referencing `validators//` modules in the app bundle (see [Custom Extensions](../guides/custom-extensions.md#custom-validators-validators)) +- **In-place updates**: Validators may mutate form `data` (for example auto-numbering repeat rows); Formplayer refreshes state when mutations are detected - **Conditional Validation**: Rules based on other field values - **Cross-field Validation**: Validation across multiple fields -- **Async Validation**: Server-side validation support ## Draft Management @@ -351,6 +356,8 @@ Formplayer can save incomplete forms as drafts: ### Loading Drafts +When opening a **new** root observation, Formplayer may show a **draft selector** if local drafts exist. Custom apps can bypass this with `openFormplayer(formType, params, savedData, { skipDraftSelection: true })` when they orchestrate the session (for example after preparing `defaultData` programmatically). Sub-observation sessions and edits with `savedData` never show the picker. + When editing an observation: 1. **Load Data**: Fetch observation data from Formulus diff --git a/docs/reference/formulus.md b/docs/reference/formulus.md index bbfe08d..70cfcd1 100644 --- a/docs/reference/formulus.md +++ b/docs/reference/formulus.md @@ -234,7 +234,8 @@ When opening forms programmatically, `openFormplayer` accepts: | Option | Description | |--------|-------------| | `subObservationMode` | Nested child form for embedded sub-observations | -| `skipFinalize` | Skip Finalize page; auto-submit from last content page | +| `skipFinalize` | Omit Finalize page; **Done** on last content page submits after child-schema validation; returns `formData` to parent | +| `skipDraftSelection` | Skip draft picker on new root sessions (custom-app orchestration) | **Form init `params` reserved keys** (not persisted as observation data): `defaultData`, `theme`, `darkMode`, `themeColors`, `context` (read-only session context exposed in Formplayer as `window.formulusSessionContext`), `validationMode`. From 28d378ba3e0fe9dcc2f99f884df3b99b103f4d1d Mon Sep 17 00:00:00 2001 From: Emil Rossing Date: Wed, 17 Jun 2026 22:47:51 +0000 Subject: [PATCH 4/6] v1.1.0 docs refresh --- docs/development/architecture.md | 16 +++ docs/development/ode-desktop-development.md | 118 +++++++++++++++++ docs/development/setup.md | 17 +-- docs/getting-started/architecture-overview.md | 21 +++- .../installation/installing-ode-desktop.md | 118 +++++++++++++++++ docs/guides/ode-desktop-developer-mode.md | 6 +- docs/implementer/implementer-index.md | 12 ++ docs/index.md | 11 +- docs/reference/components.md | 33 +++++ docs/reference/index.md | 11 ++ docs/reference/ode-desktop.md | 119 ++++++++++++++++++ docs/using/app-bundles.md | 4 +- docs/using/custom-applications.md | 4 +- docs/using/ode-desktop/about.md | 37 ++++++ docs/using/ode-desktop/developer-mode.md | 35 ++++++ docs/using/ode-desktop/import.md | 41 ++++++ docs/using/ode-desktop/index.md | 69 ++++++++++ docs/using/ode-desktop/observations.md | 67 ++++++++++ docs/using/ode-desktop/profiles.md | 79 ++++++++++++ docs/using/ode-desktop/sync.md | 71 +++++++++++ docs/using/ode-desktop/workbench-bundles.md | 59 +++++++++ .../using/ode-desktop/workbench-custom-app.md | 58 +++++++++ .../ode-desktop/workbench-form-preview.md | 50 ++++++++ docusaurus.config.ts | 9 ++ sidebars.ts | 25 ++++ src/components/AnnouncementBanner/index.tsx | 8 +- 26 files changed, 1071 insertions(+), 27 deletions(-) create mode 100644 docs/development/ode-desktop-development.md create mode 100644 docs/getting-started/installation/installing-ode-desktop.md create mode 100644 docs/reference/ode-desktop.md create mode 100644 docs/using/ode-desktop/about.md create mode 100644 docs/using/ode-desktop/developer-mode.md create mode 100644 docs/using/ode-desktop/import.md create mode 100644 docs/using/ode-desktop/index.md create mode 100644 docs/using/ode-desktop/observations.md create mode 100644 docs/using/ode-desktop/profiles.md create mode 100644 docs/using/ode-desktop/sync.md create mode 100644 docs/using/ode-desktop/workbench-bundles.md create mode 100644 docs/using/ode-desktop/workbench-custom-app.md create mode 100644 docs/using/ode-desktop/workbench-form-preview.md diff --git a/docs/development/architecture.md b/docs/development/architecture.md index 4dc7cb2..7c545a4 100644 --- a/docs/development/architecture.md +++ b/docs/development/architecture.md @@ -83,6 +83,22 @@ Go command-line utility providing: - Go 1.24+ - Cobra CLI framework +### ODE Desktop + +Tauri desktop application providing: + +- **Local observation store**: SQLite per profile workspace +- **Sync console**: Pull, push, conflict visibility, index rebuild +- **Workbench**: Bundle download, form preview, custom app embed +- **Developer mode**: Local custom app mirror for iteration + +**Technology Stack:** +- Tauri 2 (Rust) +- React, TypeScript, Vite +- Embedded formplayer + +See [ODE Desktop Development](/development/ode-desktop-development). + ## Data Flow ### Observation Creation diff --git a/docs/development/ode-desktop-development.md b/docs/development/ode-desktop-development.md new file mode 100644 index 0000000..d0be217 --- /dev/null +++ b/docs/development/ode-desktop-development.md @@ -0,0 +1,118 @@ +--- +sidebar_position: 7 +--- + +# ODE Desktop Development + +Complete guide for developing **ODE Desktop** from the ODE monorepo. + +## Prerequisites + +- **Node.js** 20+ and **pnpm** 10+ +- **Rust** toolchain ([rustup](https://rustup.rs/)) +- Platform build tools per [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) + +Install shared design tokens before the desktop package: + +```bash +cd packages/tokens && pnpm install && pnpm run build && cd ../.. +``` + +## Local development setup + +```bash +cd desktop +pnpm install +pnpm tauri dev +``` + +:::warning Use the Tauri window + +`pnpm tauri dev` opens the **Tauri desktop window** with the Rust backend. Do not use a regular browser at `http://localhost:1420` — IPC commands such as `invoke` only work inside the Tauri shell. + +::: + +For frontend-only work without Tauri IPC: + +```bash +pnpm dev +``` + +This starts the Vite dev server only; most Data management and Workbench features require `pnpm tauri dev`. + +## Scripts + +| Script | Purpose | +|--------|---------| +| `pnpm dev` | Vite dev server (frontend only) | +| `pnpm build` | Typecheck + Vite production build | +| `pnpm build:formplayer` | Build `formulus-formplayer` and copy into `public/formplayer_dist/` | +| `pnpm build:tauri` | Prepare Formplayer assets, then build the desktop frontend | +| `pnpm tauri build` | Full desktop bundle (runs `build:tauri` first) | +| `pnpm tauri dev` | Development with hot reload in the Tauri shell | +| `pnpm lint` / `pnpm lint:fix` | ESLint | +| `pnpm format` / `pnpm format:check` | Prettier | +| `pnpm test` | Vitest unit and component tests | +| `pnpm typecheck` | `tsc --noEmit` | +| `pnpm copy:formplayer` | Copy existing `formulus-formplayer/build/` into `public/formplayer_dist/` | + +From `formulus-formplayer/`, `pnpm run build:copy` builds formplayer and copies assets to Formulus and ODE Desktop in one step. + +### Rust backend + +```bash +cd desktop/src-tauri +cargo test +cargo fmt +cargo clippy +``` + +## Formplayer integration + +ODE Desktop embeds the same formplayer bundle as Formulus. Production builds copy formplayer into `public/formplayer_dist/`. + +After changing formplayer or the Formulus bridge contract: + +1. Update `formulus/src/webview/FormulusInterfaceDefinition.ts` (source of truth). +2. Run `pnpm run sync-interface` in `formulus-formplayer`. +3. Rebuild and copy: `pnpm run build:copy` from `formulus-formplayer/`, or `pnpm build:formplayer` from `desktop/`. + +## OpenAPI client + +The desktop app regenerates a TypeScript Synkronus client from `synkronus/openapi/synkronus.yaml`: + +```bash +cd desktop +pnpm codegen:synk-client +``` + +CI fails if the generated client does not match the OpenAPI spec. + +## Developer mode (user-facing) + +To test a local custom app build against a profile workspace, use **Workbench developer mode**. See: + +- [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode) — user guide +- [ODE Desktop reference](/docs/reference/ode-desktop) — `bundles/dev-local/` paths and bridge behavior + +## Project layout + +| Path | Role | +|------|------| +| `desktop/src/` | React UI (pages, components, store) | +| `desktop/src-tauri/` | Rust backend (SQLite, sync, bundle apply, dev mirror) | +| `desktop/public/formplayer_dist/` | Embedded formplayer bundle | +| `desktop/public/formulus-injection.js` | Bridge injection for custom app and form preview | + +## Contributing + +- Follow [Conventional Commits](https://www.conventionalcommits.org/) +- Run `pnpm lint`, `pnpm format:check`, and `pnpm test` before pushing +- PRs touching `desktop/**` trigger the ODE Desktop GitHub Actions workflow + +## Related documentation + +- [ODE Desktop reference](/docs/reference/ode-desktop) — component overview +- [Installing ODE Desktop](/docs/getting-started/installation/installing-ode-desktop) +- [Formplayer development](/development/formplayer-development) +- [Building and testing](/development/building-testing) diff --git a/docs/development/setup.md b/docs/development/setup.md index c5c769c..2b5940e 100644 --- a/docs/development/setup.md +++ b/docs/development/setup.md @@ -142,22 +142,9 @@ pnpm run format:check ## ODE Desktop Development -### Setup - -```bash -cd packages/tokens && pnpm install && pnpm run build && cd ../.. -cd desktop -pnpm install -``` - -### Running - -```bash -pnpm run dev # Vite dev server -pnpm run build:tauri # Build formplayer assets + desktop + Tauri app -``` +See [ODE Desktop Development](/docs/development/ode-desktop-development) for setup, scripts, and Formplayer integration. -See [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode) for local custom app iteration. +For testing a local custom app build in the Workbench, see [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode). ## Synkronus Development diff --git a/docs/getting-started/architecture-overview.md b/docs/getting-started/architecture-overview.md index df9bd75..c0ada4b 100644 --- a/docs/getting-started/architecture-overview.md +++ b/docs/getting-started/architecture-overview.md @@ -8,7 +8,7 @@ ODE (Open Data Ensemble) is a comprehensive platform for mobile data collection ## Core Components -ODE consists of four main components that work together to provide a complete data collection solution: +ODE consists of these main components that work together to provide a complete data collection solution: ### Synkronus Server The backend server responsible for: @@ -39,6 +39,25 @@ Command-line utility for: - **User Administration**: Manage users and permissions - **Server Administration**: Maintenance and monitoring tasks +### ODE Desktop + +Desktop application (Tauri + React + Rust) for data stewardship and app development: + +- **Data management**: Pull, inspect, edit, import, and sync observations against Synkronus +- **Forms / app workbench**: Download app bundles, preview forms, and test custom apps +- **Developer mode**: Mirror a local custom app build without replacing the Synk-downloaded bundle +- **Same public API**: Uses Synkronus REST API — no privileged desktop channel + +Introduced in **ODE v1.1.0**. See [ODE Desktop reference](/docs/reference/ode-desktop) and [install guide](/docs/getting-started/installation/installing-ode-desktop). + +### Synkronus Portal + +Web-based administrative interface (also embedded in the Synkronus binary at `/portal`): + +- **User and bundle management** +- **Observation viewing and export** +- **Same API** as Formulus, CLI, and ODE Desktop + ## Architecture Patterns ### Embedded Portal Architecture diff --git a/docs/getting-started/installation/installing-ode-desktop.md b/docs/getting-started/installation/installing-ode-desktop.md new file mode 100644 index 0000000..975c5c8 --- /dev/null +++ b/docs/getting-started/installation/installing-ode-desktop.md @@ -0,0 +1,118 @@ +--- +sidebar_position: 3 +--- + +# Installing ODE Desktop + +Complete guide for installing **ODE Desktop** on Windows, macOS, and Linux. + +:::info ODE v1.1.0 + +ODE Desktop is part of the **ODE v1.1.0** release. Pre-built installers are published on [GitHub Releases](https://github.com/OpenDataEnsemble/ode/releases). + +::: + +## Overview + +ODE Desktop is a native desktop application (Tauri) for: + +- **Data management** — inspect, edit, import, and sync observations with Synkronus +- **Forms / app workbench** — download app bundles, preview forms, and test custom apps + +Choose the installation method that fits your role: + +| Method | Best for | +|--------|----------| +| **GitHub Release** | Data stewards and app authors who want a ready-to-run installer | +| **Build from source** | Contributors and early adopters working from the ODE monorepo | + +## System requirements + +| Platform | Requirements | +|----------|--------------| +| **Windows** | Windows 10 or 11; [WebView2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) (usually pre-installed) | +| **macOS** | Recent macOS; Xcode command-line tools for source builds | +| **Linux** | WebKitGTK and related packages per [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) | + +## Method 1: GitHub Releases (recommended) + +1. Open [OpenDataEnsemble/ode releases](https://github.com/OpenDataEnsemble/ode/releases). +2. Select the **v1.1.0** release (or the latest stable tag). +3. Download the artifact for your platform: + + | Platform | Typical artifact | + |----------|------------------| + | Windows | `.msi` installer | + | macOS | `.dmg` or `.app` bundle | + | Linux | AppImage, `.deb`, or similar | + +4. Run the installer or extract the bundle and launch **ODE Desktop**. + +:::note Installer script + +A curl-style installer script (`scripts/install-ode-desktop.sh`) exists in the monorepo as a placeholder for future one-line installs. Until it is wired to release asset URLs, use GitHub Releases directly. + +::: + +## Method 2: Build from source + +For development or when no pre-built artifact is available for your platform: + +### Prerequisites + +- **Node.js** 20+ and **pnpm** 10+ +- **Rust** toolchain ([rustup](https://rustup.rs/)) +- Platform build tools (see [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/)) + +### Build steps + +```bash +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/desktop +pnpm install +pnpm tauri build +``` + +`pnpm tauri build` runs the full pipeline: Formplayer assets are prepared, the frontend is built, and Tauri packages the native app. Installers or bundles appear under `desktop/src-tauri/target/release/bundle/`. + +### Development run + +To run with hot reload during development: + +```bash +cd ode/desktop +pnpm install +pnpm tauri dev +``` + +:::warning Use the Tauri window + +`pnpm tauri dev` opens the **Tauri desktop window**. Do not use a regular browser at `http://localhost:1420` — IPC commands such as `invoke` only work inside the Tauri shell. + +::: + +## First launch + +1. Open **ODE Desktop**. +2. Switch to **Data management** mode (if not already selected). +3. On **Profiles**, add or select a profile with your Synkronus server URL. +4. **Authenticate** with your Synkronus credentials. +5. On **Sync**, run **Pull** to download observations, or **Download & apply** an app bundle from **Workbench → Bundles**. + +## Next steps + +- [ODE Desktop user guide](/docs/using/ode-desktop/) — screen-by-screen usage +- [ODE Desktop reference](/docs/reference/ode-desktop) — architecture and workspace layout +- [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode) — test a local custom app build +- [ODE Desktop development](/docs/development/ode-desktop-development) — contributor setup + +## Troubleshooting + +| Issue | Suggestion | +|-------|------------| +| App won't start on Linux | Install WebKitGTK and dependencies from Tauri docs | +| WebView2 missing on Windows | Install the [WebView2 runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) | +| Build fails on `pnpm tauri build` | Ensure Rust and platform prerequisites are installed; run `pnpm build:formplayer` first if Formplayer assets are missing | +| Sync authentication fails | Verify server URL and credentials on **Profiles**; check server reachability on **Sync** | + +For more help, see [Getting Help](/docs/community/getting-help) or the [forum](https://forum.opendataensemble.org). diff --git a/docs/guides/ode-desktop-developer-mode.md b/docs/guides/ode-desktop-developer-mode.md index f90202e..2e66313 100644 --- a/docs/guides/ode-desktop-developer-mode.md +++ b/docs/guides/ode-desktop-developer-mode.md @@ -13,7 +13,7 @@ Observations, attachments, and sync still use the profile database. The Synk-dow ## Prerequisites -- ODE Desktop installed (see project README for build-from-source). +- ODE Desktop installed — see [Installing ODE Desktop](/docs/getting-started/installation/installing-ode-desktop). - A **profile** with a configured workspace. - A local folder whose root contains **`index.html`** (typical: your custom app `dist/` output). - Optional: download an app bundle from Synkronus on the **Bundles** page so `bundles/active/` has forms and `app.config.json` when developer mode is off. @@ -87,6 +87,10 @@ When developer mode is on, every Workbench route shows a compact status strip ** ## See also +- [Bundles (Workbench)](../using/ode-desktop/workbench-bundles) — download bundles and enable developer mode +- [Custom app (Workbench)](../using/ode-desktop/workbench-custom-app) — embedded custom app WebView +- [Form preview (Workbench)](../using/ode-desktop/workbench-form-preview) — formplayer preview +- [ODE Desktop reference](../reference/ode-desktop) — architecture and workspace layout - [Understanding app bundles](../using/app-bundles) — Synk download to `bundles/active/` - [Custom applications](../using/custom-applications) — custom app role in bundles - [Observation queries](./observation-queries) — `getObservationsByQuery` and indexes diff --git a/docs/implementer/implementer-index.md b/docs/implementer/implementer-index.md index 5413936..91d6678 100644 --- a/docs/implementer/implementer-index.md +++ b/docs/implementer/implementer-index.md @@ -61,6 +61,18 @@ Get your first form running in 30 minutes: +
+
+
+

ODE Desktop

+
+
+

Desktop app for data stewardship and app bundle development (v1.1.0+).

+ User Guide → +
+
+
+
diff --git a/docs/index.md b/docs/index.md index daf67b1..e649106 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,11 +10,11 @@ ODE is pronounced like "code", without the "C." Open Data Ensemble (ODE) is a comprehensive platform for mobile data collection and synchronization. Built for researchers, health professionals, implementers, and developers, ODE provides a robust solution for designing forms, managing data securely, and synchronizing seamlessly across devices, even in offline conditions. -:::info Pre-release Available +:::info ODE v1.1.0 -The source code for the pre-release version of ODE is now publicly available. While we're working toward the full 1.0 release, we welcome anyone willing to help with testing, development, or getting involved in the project. +**ODE v1.1.0** is available with **ODE Desktop** — a native app for data stewardship and app bundle development. [Install ODE Desktop](/docs/getting-started/installation/installing-ode-desktop) or continue with [Formulus on Android](/docs/getting-started/installation). -**Try the pre-release:** [Install Formulus on Android](/docs/getting-started/installation) +The source code for ODE is publicly available on GitHub. We welcome anyone willing to help with testing, development, or getting involved in the project. **Get involved:** Reach out to us at [hello@opendataensemble.org](mailto:hello@opendataensemble.org) - we'd love to hear from you! @@ -61,19 +61,22 @@ Here's an overview of the current members of the ensemble: Component overview * [formulus](/reference/formulus): The Android and iOS app for data collection and form interaction. +* [ode-desktop](/reference/ode-desktop): The desktop app for data stewardship and app bundle development (v1.1.0+). * [synkronus](/reference/synkronus-server): The robust server backend managing synchronization and data storage. * [synkronus-cli](/reference/synkronus-cli): Command-line interface for convenient server management and administrative tasks. ## Key Components -ODE consists of four main components that work together: +ODE consists of these main components that work together: | Component | Description | Technology | |-----------|-------------|------------| | **Formulus** | Mobile application for Android and iOS | React Native | +| **ODE Desktop** | Desktop app for data management and app workbench | Tauri (React + Rust) | | **Formplayer** | Web-based form rendering engine | React | | **Synkronus** | Backend server for data synchronization | Go | | **Synkronus CLI** | Command-line utility for administration | Go | +| **Synkronus Portal** | Web admin for users, bundles, and export | React | ## Core Capabilities diff --git a/docs/reference/components.md b/docs/reference/components.md index daaebf2..ca9da52 100644 --- a/docs/reference/components.md +++ b/docs/reference/components.md @@ -153,6 +153,39 @@ Formplayer is a React application that renders JSON Forms and communicates with - **Complete Reference**: [Formplayer Reference](/reference/formplayer) - **Development**: [Formplayer Development](/development/formplayer-development) +## ODE Desktop + +ODE Desktop is the desktop application for data stewardship and app development, introduced in **ODE v1.1.0**. + +### Overview + +ODE Desktop is a Tauri application (React + Rust) with two modes: + +- **Data management** — pull, inspect, edit, and sync observations against Synkronus +- **Forms / app workbench** — download app bundles, preview forms, and test custom apps + +It uses the same public Synkronus API as Formulus, Portal, and the CLI. + +### Key Features + +- **Profile-scoped workspaces** — SQLite, attachments, and bundle cache per server +- **Observation editing** — search, JSON editor, conflict resolution +- **Sync console** — pull, push, combined sync, index rebuild +- **Bundle workbench** — download bundles from Synkronus into `bundles/active/` +- **Developer mode** — mirror a local custom app build to `bundles/dev-local/` +- **Form preview** — formplayer with Formulus bridge parity (device APIs stubbed) + +### Installation + +- **End users**: [Installing ODE Desktop](/docs/getting-started/installation/installing-ode-desktop) +- **Developers**: [ODE Desktop Development](/docs/development/ode-desktop-development) + +### Documentation + +- **User guide**: [ODE Desktop](/docs/using/ode-desktop/) +- **Component reference**: [ODE Desktop Reference](/reference/ode-desktop) +- **Developer mode**: [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode) + ## Component Integration All components work together to provide a complete data collection solution: diff --git a/docs/reference/index.md b/docs/reference/index.md index 6a3d53b..f519e65 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -102,6 +102,17 @@ Complete technical reference for all ODE components, APIs, and specifications.
+
+
+
+

ODE Desktop

+
+
+

Desktop app for data stewardship and app bundle development.

+ View Docs → +
+
+
## Specifications diff --git a/docs/reference/ode-desktop.md b/docs/reference/ode-desktop.md new file mode 100644 index 0000000..738cf74 --- /dev/null +++ b/docs/reference/ode-desktop.md @@ -0,0 +1,119 @@ +--- +sidebar_position: 10 +--- + +# ODE Desktop Reference + +Complete technical reference for **ODE Desktop** — the Open Data Ensemble desktop application for data stewardship and app development. + +:::info Released in ODE v1.1.0 + +ODE Desktop joined the ODE ensemble in **v1.1.0**. See [Installing ODE Desktop](/docs/getting-started/installation/installing-ode-desktop) for download and setup. + +::: + +## Overview + +ODE Desktop is a **Tauri** application (React + Rust) that provides two modes in one window: + +| Mode | Purpose | +|------|---------| +| **Data management** | Pull, inspect, correct, and sync **observations**; import JSON; manage conflicts and local workspace state. | +| **Forms / app workbench** | Develop and test **app bundles**, embedded **formplayer**, and **custom apps** — aligned with Formulus and the shared WebView contract. | + +## Who it is for + +- **Field and data staff** managing observations and sync (Data management mode). +- **Form and app authors** testing bundles and custom apps against Synkronus before mobile deploy (Workbench mode). + +## Relationship to other ODE components + +ODE Desktop uses the **same public Synkronus API** as Formulus, Portal, and the CLI — there is no privileged desktop channel. + +| Component | Role relative to ODE Desktop | +|-----------|------------------------------| +| **Synkronus** | Server for sync, auth, and app bundle distribution | +| **Formulus** + **formplayer** | Runtime parity target for forms and custom apps | +| **Portal** | Browser admin for users, bundles, and export | +| **Synkronus CLI** | Scripting and automation against the same API | + +## Technology stack + +- **Shell**: Tauri 2 (Rust backend, native WebView) +- **Frontend**: React, TypeScript, Vite +- **Local storage**: SQLite per profile workspace; attachments on disk +- **Formplayer**: Bundled under `public/formplayer_dist/` (same contract as Formulus) + +## Profiles and workspace + +Each **profile** is a server-scoped unit of custody: + +- Synkronus server URL and credentials +- Dedicated **workspace folder** (SQLite database, attachments, bundle cache) +- Optional OS keyring storage for passwords + +Typical workspace layout: + +``` +/ + repository.sqlite + attachments/ + bundles/ + active/ ← Synk-downloaded bundle (app/, forms/, etc.) + dev-local/ ← Developer mode mirror (when enabled) + app/ + forms/ +``` + +Switch profiles from **Data management → Profiles**. Authentication is per profile. + +## Data management screens + +| Screen | Route | Purpose | +|--------|-------|---------| +| Profiles | `#/data/profiles` | Configure server, workspace, and authentication | +| Observations | `#/data/observations` | Search, edit, and resolve local observations | +| Import | `#/data/import` | Import JSON observation files | +| Sync | `#/data/sync` | Pull, push, and workspace maintenance | +| About | `#/data/about` | Support links and license | + +See the [ODE Desktop user guide](/docs/using/ode-desktop/) for step-by-step workflows. + +## Workbench screens + +| Screen | Route | Purpose | +|--------|-------|---------| +| Bundles | `#/workbench/bundles` | List server bundle versions; download and apply | +| Form preview | `#/workbench/form-preview` | Open forms from the bundle with init JSON | +| Custom app | `#/workbench/custom-app` | Embed the custom app WebView | + +**Developer mode** mirrors a local folder (with `index.html`) into `bundles/dev-local/` so you can iterate without overwriting `bundles/active/`. See [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode). + +## Formplayer and bridge parity + +ODE Desktop loads formplayer and custom apps with the same **Formulus bridge contract** (`FormulusInterfaceDefinition`). In Workbench preview: + +- **Observations and attachments** use the profile SQLite store and Tauri file APIs where applicable. +- **Device APIs** (camera, GPS, QR, audio, video) are **stubbed** in preview — same limitation as before developer mode. +- **Nested sub-observations** (`openFormplayer` with `subObservationMode`) open stacked preview sessions; child schemas validate independently. + +Bundle extensions (`forms/ext.json`, per-form `ext.json`) follow the same merge rules as Formulus. + +## Observation indexes + +Custom apps declare `observationIndexes` in `app.config.json`. ODE Desktop builds a local index table for fast `getObservationsByQuery` — indexes are **device-local** and never synced to Synkronus. + +After changing index declarations or bulk imports, use **Sync → Re-create index**. When developer mode is on, indexes read from the mirrored app under `bundles/dev-local/app/`. See [Observation queries](/docs/guides/observation-queries). + +## Installation + +- **End users**: [Installing ODE Desktop](/docs/getting-started/installation/installing-ode-desktop) +- **Contributors**: [ODE Desktop Development](/docs/development/ode-desktop-development) + +## Related documentation + +- [ODE Desktop user guide](/docs/using/ode-desktop/) — screen-by-screen usage +- [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode) — local custom app iteration +- [Understanding app bundles](/docs/using/app-bundles) — bundle structure and Synkronus distribution +- [Custom applications](/docs/using/custom-applications) — custom app role in bundles +- [Architecture overview](/docs/getting-started/architecture-overview) — full ODE ecosystem diff --git a/docs/using/app-bundles.md b/docs/using/app-bundles.md index 1563eda..4278e4a 100644 --- a/docs/using/app-bundles.md +++ b/docs/using/app-bundles.md @@ -169,7 +169,9 @@ For technical information about app bundle structure, format, and development, s ## ODE Desktop Workbench -On **ODE Desktop**, Synkronus bundles download into **`bundles/active/`** in the profile workspace. To iterate on a **local** custom app build without replacing that download, use [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode) (mirror to `bundles/dev-local/`). +On **ODE Desktop**, Synkronus bundles download into **`bundles/active/`** in the profile workspace. See [Bundles (Workbench)](/docs/using/ode-desktop/workbench-bundles) for download and version management. + +To iterate on a **local** custom app build without replacing that download, use [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode) (mirror to `bundles/dev-local/`). ## Related Documentation diff --git a/docs/using/custom-applications.md b/docs/using/custom-applications.md index e0aa82f..d0ba31c 100644 --- a/docs/using/custom-applications.md +++ b/docs/using/custom-applications.md @@ -109,7 +109,9 @@ Custom applications are suitable for: ## Testing locally with ODE Desktop -Use [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode) to load a local build (folder with `index.html`) in the Workbench **Custom app** page against a profile’s observations, then refresh after each build. +[Install ODE Desktop](/docs/getting-started/installation/installing-ode-desktop) and use [developer mode](/docs/guides/ode-desktop-developer-mode) to load a local build (folder with `index.html`) in the Workbench **Custom app** page against a profile's observations, then refresh after each build. + +See also [Custom app (Workbench)](/docs/using/ode-desktop/workbench-custom-app) and the [ODE Desktop user guide](/docs/using/ode-desktop/). ## Next Steps diff --git a/docs/using/ode-desktop/about.md b/docs/using/ode-desktop/about.md new file mode 100644 index 0000000..48a3ccd --- /dev/null +++ b/docs/using/ode-desktop/about.md @@ -0,0 +1,37 @@ +--- +sidebar_position: 6 +--- + +# About + +Support links, license information, and project context for ODE Desktop. + +Open **Data management → About** (`#/data/about`). + +## About ODE Desktop + +ODE Desktop is part of **Open Data Ensemble** — an offline-first ecosystem for field data collection, synchronization, and stewardship. It helps you manage observations locally and develop app bundles before deploying to Formulus in the field. + +## Links + +| Link | Description | +|------|-------------| +| [opendataensemble.org](https://opendataensemble.org) | Project website and documentation | +| [Forum](https://forum.opendataensemble.org) | Community support | +| [GitHub](https://github.com/OpenDataEnsemble) | Source code and releases | +| [License](https://github.com/OpenDataEnsemble/ode/blob/main/LICENSE) | Open-source license terms | + +## Support + +ODE Desktop is free (libre) software provided as-is under a permissive open-source license. + +For help: + +- Post on the [forum](https://forum.opendataensemble.org) +- Email [hello@opendataensemble.org](mailto:hello@opendataensemble.org) +- See [Getting Help](/docs/community/getting-help) + +## Related documentation + +- [ODE Desktop user guide](/docs/using/ode-desktop/) +- [Installing ODE Desktop](/docs/getting-started/installation/installing-ode-desktop) diff --git a/docs/using/ode-desktop/developer-mode.md b/docs/using/ode-desktop/developer-mode.md new file mode 100644 index 0000000..46d986a --- /dev/null +++ b/docs/using/ode-desktop/developer-mode.md @@ -0,0 +1,35 @@ +--- +sidebar_position: 10 +--- + +# Developer mode + +Iterate on a **local custom app build** against real profile observations without replacing the bundle downloaded from Synkronus. + +## Summary + +When developer mode is on, ODE Desktop **mirrors** your selected folder into `bundles/dev-local/` in the profile workspace. The workbench then loads: + +- **Custom app** from `bundles/dev-local/app/` +- **Form preview** from `bundles/dev-local/forms/` (when your folder includes a `forms/` directory) + +Observations, attachments, and sync still use the profile database. The Synk-downloaded bundle in `bundles/active/` is **not** overwritten. + +## Quick start + +1. Open **Workbench → [Bundles](./workbench-bundles)**. +2. Turn **Developer mode** **On**. +3. Click **Browse…** and select the folder that contains `index.html` (typically your `dist/` output). +4. After each rebuild, click **Refresh app**. + +## Full guide + +For prerequisites, folder layout, refresh behavior, limitations, and platform comparison with Formulus, see the complete guide: + +**[ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode)** + +## See also + +- [Custom app (Workbench)](./workbench-custom-app) +- [Form preview (Workbench)](./workbench-form-preview) +- [Bundles (Workbench)](./workbench-bundles) diff --git a/docs/using/ode-desktop/import.md b/docs/using/ode-desktop/import.md new file mode 100644 index 0000000..768c46d --- /dev/null +++ b/docs/using/ode-desktop/import.md @@ -0,0 +1,41 @@ +--- +sidebar_position: 4 +--- + +# Import + +Bring external **JSON observation files** into the active profile's local repository. + +Open **Data management → Import** (`#/data/import`). + +## When to use import + +Use Import when you have observation JSON files on disk (for example from an export, migration, or another tool) and want them in ODE Desktop's local SQLite store before sync or editing. + +Import targets the **active profile's** workspace database. + +## Import workflow + +1. **Stage files** — drag and drop `.json` files onto the import area, or use the file picker to select one or more files. +2. **Review the summary** — ODE Desktop shows counts, detected form types, and any parse or normalization issues per file. +3. If there are non-fatal issues, confirm whether to **import with issues** or fix files first. +4. Click **Import** to write observations into the local repository. +5. Use **Clear** to reset the staging area. + +After a successful import, refresh **Observations** and **Sync** health counts to see the new data. + +## What gets imported + +- Observation JSON payloads compatible with the local repository schema +- Attachment references may be noted in the summary; ensure attachment files exist under the workspace `attachments/` folder if needed + +## What this screen does not do + +- **Ongoing JSON editing** — use [Observations](./observations) +- **Synkronus sync** — use [Sync](./sync) after import +- **Profile switching** — change profile on [Profiles](./profiles) before importing + +## Next steps + +- [Observations](./observations) — verify imported rows +- [Sync](./sync) — push imported changes to Synkronus (if intended) diff --git a/docs/using/ode-desktop/index.md b/docs/using/ode-desktop/index.md new file mode 100644 index 0000000..5c35973 --- /dev/null +++ b/docs/using/ode-desktop/index.md @@ -0,0 +1,69 @@ +--- +sidebar_position: 1 +--- + +# ODE Desktop + +User guide for **ODE Desktop** — the Open Data Ensemble desktop application for data stewardship and app development. + +:::info New in ODE v1.1.0 + +ODE Desktop joined the ensemble in **v1.1.0**. [Install ODE Desktop](/docs/getting-started/installation/installing-ode-desktop) to get started. + +::: + +## Two modes + +ODE Desktop provides two modes in one application. Use the mode switcher in the top bar: + +| Mode | Who it's for | What you do | +|------|--------------|-------------| +| **Data management** | Field and data staff | Profiles, observations, import, sync | +| **Forms / app workbench** | Form and app authors | Bundles, form preview, custom app testing | + +Both modes share the **active profile** — the same Synkronus server, workspace, and local observation database. + +## Navigation + +### Data management + +| Screen | Purpose | +|--------|---------| +| [Profiles](./profiles) | Server connection, workspace, authentication | +| [Observations](./observations) | Search, edit, and resolve observations | +| [Import](./import) | Import JSON observation files | +| [Sync](./sync) | Pull, push, and workspace maintenance | +| [About](./about) | Support links and license | + +### Workbench + +| Screen | Purpose | +|--------|---------| +| [Bundles](./workbench-bundles) | Download app bundles from Synkronus | +| [Form preview](./workbench-form-preview) | Open and test forms from the bundle | +| [Custom app](./workbench-custom-app) | Run the custom app WebView | +| [Developer mode](./developer-mode) | Iterate on a local custom app build | + +## Typical workflows + +### Data steward + +1. [Create a profile](./profiles) with your Synkronus server URL. +2. **Authenticate** on Profiles. +3. On [Sync](./sync), run **Pull** to download observations. +4. Inspect and edit on [Observations](./observations). +5. **Push** local changes back to Synkronus. + +### App author + +1. Set up a profile and authenticate (as above). +2. On [Bundles](./workbench-bundles), **Download & apply** the active app bundle. +3. Test forms on [Form preview](./workbench-form-preview) or the full app on [Custom app](./workbench-custom-app). +4. For local iteration before publishing, enable [Developer mode](./developer-mode). + +## Related documentation + +- [ODE Desktop reference](/docs/reference/ode-desktop) — architecture and workspace layout +- [Installing ODE Desktop](/docs/getting-started/installation/installing-ode-desktop) +- [Understanding app bundles](/docs/using/app-bundles) +- [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode) — full developer mode guide diff --git a/docs/using/ode-desktop/observations.md b/docs/using/ode-desktop/observations.md new file mode 100644 index 0000000..b993080 --- /dev/null +++ b/docs/using/ode-desktop/observations.md @@ -0,0 +1,67 @@ +--- +sidebar_position: 3 +--- + +# Observations + +Inspect, edit, and resolve **observations** in the active profile's local repository. + +Open **Data management → Observations** (`#/data/observations`). + +## Search and filter + +1. Use the **search** field to filter by observation ID or form type. +2. Apply client-side **filters**: + + | Filter | Shows | + |--------|-------| + | **All** | All loaded observations | + | **Dirty** | Local changes not yet pushed | + | **Conflicts** | Observations with sync conflicts | + | **Recently modified** | Recently changed rows | + +:::note Result limit + +Search returns up to **250** observations per query. Filters apply to that loaded set only. Full-repository counts (pending push, conflicts) appear on **Sync**. + +::: + +## View and edit an observation + +1. Select an observation from the list. +2. Review metadata: ID, form type, timestamps, sync status, and dirty/conflict flags. +3. Edit the **JSON payload** in the editor. +4. Click **Save** to persist changes locally. + +Unsaved edits are marked as **dirty** until you push from **Sync**. + +## Restore backup + +If you need to undo local edits to the selected observation, use **Restore backup** to revert to the last backed-up version stored locally. + +## Create a new observation + +Click **New observation** to create a blank local observation. You will need to set the form type and data before pushing. + +## Open in Form preview + +From an observation, you can open **Form preview** in the Workbench to edit the observation through the formplayer UI instead of raw JSON. This navigates to **Workbench → Form preview** with the observation pre-loaded. + +## Resolve conflicts + +Observations with **conflict** status need attention before push: + +1. Filter by **Conflicts** to find affected rows. +2. Edit the JSON to reconcile with server expectations. +3. Save locally, then **Push** from [Sync](./sync). + +## What this screen does not do + +- **Bulk JSON import** — use [Import](./import) +- **Synkronus pull/push** — use [Sync](./sync) +- **Profile configuration** — use [Profiles](./profiles) + +## Next steps + +- [Sync](./sync) — push dirty observations to Synkronus +- [Form preview](./workbench-form-preview) — edit through the form UI diff --git a/docs/using/ode-desktop/profiles.md b/docs/using/ode-desktop/profiles.md new file mode 100644 index 0000000..47f575a --- /dev/null +++ b/docs/using/ode-desktop/profiles.md @@ -0,0 +1,79 @@ +--- +sidebar_position: 2 +--- + +# Profiles + +Configure **which Synkronus server and workspace** ODE Desktop uses. + +Open **Data management → Profiles** (`#/data/profiles`). This is the home screen when you launch ODE Desktop. + +## What profiles are for + +Each profile is an independent unit of custody: + +- Synkronus **server URL** and **credentials** +- A dedicated **workspace folder** on disk (SQLite database, attachments, bundle cache) +- Its own sync state and authentication session + +Switch profiles from the dropdown at the top of the Profiles screen. + +## Add or switch a profile + +1. Use the **profile dropdown** to select an existing profile, or click **Add profile** to create one. +2. Enter a **display name**, **server URL**, and **username**. +3. Enter a **password** (stored in the OS keyring when available). +4. Choose a **workspace folder** with **Browse…** — ODE Desktop stores the database and attachments under this path. +5. Click **Save profile**. + +:::tip Workspace layout + +Under the workspace folder, ODE Desktop creates: + +- `repository.sqlite` — local observation database +- `attachments/` — binary attachment files +- `bundles/` — downloaded and dev-mirrored app bundles + +The **Database** and **Attachments** paths are shown read-only after you pick a workspace. + +::: + +## Authenticate + +After saving server credentials: + +1. Click **Authenticate** on the Profiles screen. +2. When successful, the button shows **Authenticated** and sync operations are enabled. + +If you are not authenticated, **Sync** shows a warning with a link back to Profiles. + +:::note Password storage + +On platforms without a working secret service, profile fields still save but the password is **not persisted** on disk. Enter your password when authenticating each session, or use **Sync** after saving credentials in memory. + +::: + +## Workspace actions + +| Action | Purpose | +|--------|---------| +| **Open** | Open the workspace folder in your file manager | +| **Move workspace…** | Relocate all workspace data to a new folder and update the profile | +| **Backup workspace…** | Export the workspace to a timestamped `.zip` archive | +| **Clear saved password** | Remove the password from secure storage | + +## Delete a profile + +Click **Delete profile** to remove the profile from ODE Desktop. Local files on disk are **not** automatically deleted — you can remove the workspace folder manually if needed. + +:::warning External SQL tools + +If you open `repository.sqlite` with external tools, **quit ODE Desktop first**. Direct database edits can break sync and conflict detection. + +::: + +## Next steps + +- [Sync](./sync) — pull and push observations +- [Observations](./observations) — browse and edit local data +- [Bundles](./workbench-bundles) — download an app bundle for the workbench diff --git a/docs/using/ode-desktop/sync.md b/docs/using/ode-desktop/sync.md new file mode 100644 index 0000000..6c6a978 --- /dev/null +++ b/docs/using/ode-desktop/sync.md @@ -0,0 +1,71 @@ +--- +sidebar_position: 5 +--- + +# Sync + +Authenticate with Synkronus and **exchange data** with the server: pull remote changes, push local edits, and maintain workspace health. + +Open **Data management → Sync** (`#/data/sync`). + +## Status panel + +The Sync screen shows: + +| Field | Meaning | +|-------|---------| +| **Server** | Synkronus URL from the active profile | +| **Status** | Reachability (and server version when available) | +| **Pending push** | Count of locally dirty observations | +| **Conflicts** | Count of observations with sync conflicts | +| **Last pull / Last push** | Timestamps of recent sync operations | + +If you are not authenticated, a warning links to [Profiles](./profiles). + +## Sync actions + +| Button | Action | +|--------|--------| +| **Sync (Pull + Push)** | Pull from server, then push local changes (with push confirmation) | +| **Pull** | Download remote changes into the local repository | +| **Push** | Upload local dirty observations to Synkronus | + +Push asks for confirmation when there are pending changes. If observations reference **missing attachment files**, a dialog lets you cancel or force push without those attachments. + +While a sync job is running, you can **Pause**, **Resume**, or **Cancel** the in-flight operation. If the sync engine left a paused job, use **Resume job** or **Discard job**. + +:::tip Conflicts + +Resolve conflicts on [Observations](./observations) before pushing. The conflict count on Sync updates as you edit. + +::: + +## Danger zone + +Advanced maintenance actions appear at the bottom of the Sync screen: + +| Action | Effect | +|--------|--------| +| **Reset local data** | Remove all observations and attachment files from this device and reset sync offsets | +| **Re-create index** | Rebuild the observation index from `app.config.json` (needed after index declaration changes or bulk imports) | +| **Reset server repository and pull** | Delete all observations and attachment manifest data **on Synkronus**, then pull so this device archives its workspace and starts fresh | + +All danger-zone actions require explicit confirmation. Use them only when you understand the data loss implications. + +The panel also shows **index generation** and **last rebuild** time. + +## Observation indexes + +Custom apps declare `observationIndexes` in `app.config.json` for fast `getObservationsByQuery`. ODE Desktop maintains a local index table — it is never synced to Synkronus. + +After changing index declarations in a bundle or bulk-importing observations, click **Re-create index**. See [Observation queries](/docs/guides/observation-queries) for index design. + +## What this screen does not do + +- **Per-observation JSON editing** — use [Observations](./observations) +- **Profile and workspace setup** — use [Profiles](./profiles) + +## Next steps + +- [Observations](./observations) — review pulled or conflicting data +- [Profiles](./profiles) — update credentials if authentication fails diff --git a/docs/using/ode-desktop/workbench-bundles.md b/docs/using/ode-desktop/workbench-bundles.md new file mode 100644 index 0000000..f29998e --- /dev/null +++ b/docs/using/ode-desktop/workbench-bundles.md @@ -0,0 +1,59 @@ +--- +sidebar_position: 7 +--- + +# Bundles (Workbench) + +Download and manage **app bundles** from Synkronus into the active profile workspace. + +Open **Workbench → Bundles** (`#/workbench/bundles`). + +## Prerequisites + +1. Configure and **authenticate** a profile on [Profiles](../profiles). +2. Ensure the Synkronus server has an active app bundle published. + +## Server bundle information + +The **Server** panel shows: + +- **Active version** — the bundle version currently active on Synkronus +- **Hash** — content hash of the active bundle +- **Download & apply** — fetch the bundle ZIP and install it into `bundles/active/` +- **Versions on server** — list of all published versions and whether each is archived locally + +Click **Refresh from server** to reload the version list and manifest from Synkronus. + +If the server bundle differs from what is installed locally, a warning appears: **Server bundle differs from local workspace.** + +## Local workspace + +The **Local workspace** panel shows the bundle currently applied on this device: + +- **Version** and **hash** +- **Downloaded** timestamp + +Downloaded bundles are stored under `bundles/active/` in the profile workspace (`app/`, `forms/`, etc.). + +## Developer mode + +The **Bundles** page is also where you configure [Developer mode](../developer-mode): + +1. Turn **Developer mode** **On**. +2. **Browse…** to a folder containing `index.html` (your local custom app build). +3. Use **Refresh app** after each rebuild. + +While developer mode is on, an orange **banner** on all Workbench pages shows the folder path and a **Refresh app** shortcut. + +Developer mode mirrors your folder to `bundles/dev-local/` without overwriting `bundles/active/`. See the full guide: [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode). + +## Typical workflow + +1. **Download & apply** the server bundle. +2. Open [Form preview](../workbench-form-preview) or [Custom app](../workbench-custom-app) to test. +3. For local iteration, enable developer mode and point at your `dist/` output. + +## Related documentation + +- [Understanding app bundles](/docs/using/app-bundles) +- [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode) diff --git a/docs/using/ode-desktop/workbench-custom-app.md b/docs/using/ode-desktop/workbench-custom-app.md new file mode 100644 index 0000000..05d876b --- /dev/null +++ b/docs/using/ode-desktop/workbench-custom-app.md @@ -0,0 +1,58 @@ +--- +sidebar_position: 9 +--- + +# Custom app (Workbench) + +Run the **custom application** from the active app bundle in an embedded WebView — the same integration model as Formulus. + +Open **Workbench → Custom app** (`#/workbench/custom-app`). + +## Prerequisites + +| Mode | Requirement | +|------|-------------| +| **Normal** | Download an app bundle on [Bundles](./workbench-bundles) | +| **Developer mode** | Enable [Developer mode](./developer-mode) and mirror a folder with `index.html` | + +Authenticate on [Profiles](../profiles) so the custom app can read and write observations through the Formulus bridge. + +## Normal mode + +When developer mode is off: + +1. **Download & apply** a bundle on Bundles. +2. Open **Custom app** — ODE Desktop loads `index.html` from `bundles/active/app/`. +3. Use **Reload app** to remount the WebView after bundle updates. + +The status line shows the active bundle version and hash. + +## Developer mode + +When developer mode is on: + +- The embed loads from `bundles/dev-local/app/` (your mirrored local build). +- The downloaded bundle in `bundles/active/` remains on disk for sync and server refresh. +- After each local build, click **Refresh app** on Bundles or in the Workbench banner. + +If the folder is missing or has no `index.html`, a blocking warning appears — ODE Desktop does not silently fall back to `bundles/active/`. + +## Bridge behavior + +The custom app receives the same **Formulus JavaScript API** as on mobile (`window.formulus`). ODE Desktop handles `postMessage` bridge calls for: + +- Observations (`getObservations`, `getObservationsByQuery`, `persistObservation`, etc.) +- Opening nested form sessions (navigates to [Form preview](./workbench-form-preview)) +- Attachment URIs where applicable + +Device APIs (camera, GPS, QR, etc.) are **stubbed** in the desktop WebView. + +## Finalize flow + +When the custom app submits a form through the bridge, ODE Desktop may show a **Finalize** dialog (same concept as Formulus) before persisting the observation locally. + +## Related documentation + +- [Custom applications](/docs/using/custom-applications) +- [ODE Desktop developer mode](/docs/guides/ode-desktop-developer-mode) +- [Observation queries](/docs/guides/observation-queries) diff --git a/docs/using/ode-desktop/workbench-form-preview.md b/docs/using/ode-desktop/workbench-form-preview.md new file mode 100644 index 0000000..438656b --- /dev/null +++ b/docs/using/ode-desktop/workbench-form-preview.md @@ -0,0 +1,50 @@ +--- +sidebar_position: 8 +--- + +# Form preview (Workbench) + +Open and test **forms from the active bundle** using the embedded formplayer — the same rendering engine used inside Formulus. + +Open **Workbench → Form preview** (`#/workbench/form-preview`). + +## Prerequisites + +- An app bundle applied on [Bundles](./workbench-bundles), **or** +- [Developer mode](./developer-mode) with a mirrored `forms/` directory + +Authenticate on [Profiles](../profiles) so observation reads and writes use the profile database. + +## Select a form + +1. Choose a **form type** from the dropdown (loaded from `bundles/active/forms/` or `bundles/dev-local/forms/` when developer mode is on). +2. Optionally edit **initialization JSON** — parameters passed to the form session (for example prefill values). +3. Optionally edit **saved data JSON** — existing observation payload when simulating an edit. +4. Click **Open preview** to load the form in the embedded formplayer. + +## Edit an existing observation + +From [Observations](../observations), open an observation in Form preview. ODE Desktop navigates here with the observation ID and data pre-loaded. Saving through the form flow updates the local observation on **Finalize**. + +## Nested sub-observations + +Forms that call `openFormplayer` for sub-observations (for example repeat groups implemented as linked forms) open a **stacked** preview session. Each child session validates its own schema. See [Custom Extensions — nested sessions](/docs/guides/custom-extensions#nested-sessions-and-custom-validators). + +## Limitations + +In ODE Desktop form preview, **device APIs are stubbed**: + +- Camera, GPS, QR scanning, audio, and video do not call real hardware +- Observations and attachment URIs use the profile SQLite store and Tauri file APIs where applicable + +Behavior should match Formulus for the same bundle, except for native device features. + +## Developer mode + +When developer mode is on, form specs load from the mirrored `bundles/dev-local/forms/` tree. After editing forms locally, use **Refresh app** on [Bundles](./workbench-bundles) before re-opening preview. + +## Related documentation + +- [Formplayer reference](/docs/reference/formplayer) +- [Form design guide](/docs/guides/form-design) +- [Custom app (Workbench)](./workbench-custom-app) diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 3bfcfd3..665422d 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -14,6 +14,10 @@ const config: Config = { projectName: 'ode-docs', onBrokenLinks: 'throw', + // TOC same-page links (#overview, etc.) are validated at build time but markdown + // heading IDs are not always collected in the same pass — site-wide false positives. + // Real cross-page anchor issues are rare; onBrokenLinks still throws on bad paths. + onBrokenAnchors: 'ignore', markdown: { mermaid: false, @@ -91,6 +95,11 @@ const config: Config = { docId: 'reference/formplayer', label: 'Formplayer', }, + { + type: 'doc', + docId: 'reference/ode-desktop', + label: 'ODE Desktop', + }, ], }, { diff --git a/sidebars.ts b/sidebars.ts index 20c6785..6e0436b 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -37,6 +37,7 @@ const sidebars: SidebarsConfig = { items: [ 'getting-started/installation/installing-synkronus', 'getting-started/installation/installing-formulus', + 'getting-started/installation/installing-ode-desktop', ], }, 'getting-started/faq', @@ -104,6 +105,28 @@ const sidebars: SidebarsConfig = { 'using/app-bundles', 'using/data-management', 'using/custom-applications', + { + type: 'category', + label: 'ODE Desktop', + link: { type: 'doc', id: 'using/ode-desktop/index' }, + items: [ + 'using/ode-desktop/profiles', + 'using/ode-desktop/observations', + 'using/ode-desktop/import', + 'using/ode-desktop/sync', + 'using/ode-desktop/about', + { + type: 'category', + label: 'Workbench', + items: [ + 'using/ode-desktop/workbench-bundles', + 'using/ode-desktop/workbench-form-preview', + 'using/ode-desktop/workbench-custom-app', + 'using/ode-desktop/developer-mode', + ], + }, + ], + }, ], }, @@ -136,6 +159,7 @@ const sidebars: SidebarsConfig = { 'development/formplayer-development', 'development/synkronus-development', 'development/synkronus-portal-development', + 'development/ode-desktop-development', ], }, ], @@ -181,6 +205,7 @@ const sidebars: SidebarsConfig = { 'reference/synkronus-server', 'reference/synkronus-cli', 'reference/synkronus-portal', + 'reference/ode-desktop', ], }, { diff --git a/src/components/AnnouncementBanner/index.tsx b/src/components/AnnouncementBanner/index.tsx index 4b4abcc..9fae51a 100644 --- a/src/components/AnnouncementBanner/index.tsx +++ b/src/components/AnnouncementBanner/index.tsx @@ -8,10 +8,10 @@ export default function AnnouncementBanner(): React.ReactElement {
Birdie
- New version released! - Great news! The ODE v1.0 is now available for download! - - Install Now → + Community Days + Join us for the fist ODE Community Days @ Kampala, 16th and 17th September 2026! + + Read more on the Forum
From bfca72238a6b55990c019e95a798e3588fd04972 Mon Sep 17 00:00:00 2001 From: Emil Rossing Date: Wed, 17 Jun 2026 23:15:27 +0000 Subject: [PATCH 5/6] announce community days --- src/components/AnnouncementBanner/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AnnouncementBanner/index.tsx b/src/components/AnnouncementBanner/index.tsx index 9fae51a..53afa2e 100644 --- a/src/components/AnnouncementBanner/index.tsx +++ b/src/components/AnnouncementBanner/index.tsx @@ -10,7 +10,7 @@ export default function AnnouncementBanner(): React.ReactElement {
Community Days Join us for the fist ODE Community Days @ Kampala, 16th and 17th September 2026! - + Read more on the Forum
From 67396f439df39d028499ea7dab7ee0db98404f75 Mon Sep 17 00:00:00 2001 From: Emil Rossing Date: Wed, 17 Jun 2026 23:15:51 +0000 Subject: [PATCH 6/6] typos --- src/components/AnnouncementBanner/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AnnouncementBanner/index.tsx b/src/components/AnnouncementBanner/index.tsx index 53afa2e..98a9168 100644 --- a/src/components/AnnouncementBanner/index.tsx +++ b/src/components/AnnouncementBanner/index.tsx @@ -9,7 +9,7 @@ export default function AnnouncementBanner(): React.ReactElement { Birdie
Community Days - Join us for the fist ODE Community Days @ Kampala, 16th and 17th September 2026! + Join us for the first ODE Community Days @ Kampala, 16th and 17th September 2026! Read more on the Forum