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
10 changes: 4 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ Do not assume custom app authors have local checkouts of **ODE** or internal exa
| [synkronus-portal](synkronus-portal/) | Web administration | React, TypeScript, Vite | [synkronus-portal/AGENTS.md](synkronus-portal/AGENTS.md) |
| [packages/tokens](packages/tokens/) | Design tokens (`@ode/tokens`) | Style Dictionary | [packages/tokens/AGENTS.md](packages/tokens/AGENTS.md) |
| [packages/components](packages/components/) | Shared UI (`@ode/components`) | React | [packages/components/AGENTS.md](packages/components/AGENTS.md) |
| [desktop](desktop/) | Data management + Forms / app workbench (Tauri) | React, Rust | [desktop/AGENTS.md](desktop/AGENTS.md) |

---

## Cross-cutting contracts

- **Formulus ↔ WebView (custom apps + formplayer):** [`formulus/src/webview/FormulusInterfaceDefinition.ts`](formulus/src/webview/FormulusInterfaceDefinition.ts) is the **source of truth** for the injected JavaScript API. Formplayer copies a synced TypeScript snapshot via `npm run sync-interface` in `formulus-formplayer` (see [formulus-formplayer/AGENTS.md](formulus-formplayer/AGENTS.md)).
- **ODE Desktop workbench developer mode:** local custom app mirror under `bundles/dev-local/` (profile-scoped); see [desktop/AGENTS.md](desktop/AGENTS.md) and [developer mode guide](https://opendataensemble.org/docs/guides/ode-desktop-developer-mode).
- **Built-in attachment fields:** `photo`, `audio`, `video`, and generic file (`select_file`) persist attachment **basenames** (and metadata) in observation JSON while binaries live under Formulus **`attachments/`** storage and sync via the attachment pipeline—see published docs ([form specifications](https://opendataensemble.org/docs/reference/form-specifications), [form design guide](https://opendataensemble.org/docs/guides/form-design)) and [`FormulusInterfaceDefinition.ts`](formulus/src/webview/FormulusInterfaceDefinition.ts).
- **Shared UI tokens:** Install **tokens** before **components** / **formplayer** where the docs require it (see package READMEs and formplayer AGENTS).

Expand All @@ -75,13 +77,9 @@ Do not assume custom app authors have local checkouts of **ODE** or internal exa

---

## Planned (not shipped here)
## Roadmap

Do **not** implement or assume APIs for these as if they were in-repo unless issues/specs say otherwise:

- **ODE Desktop** — Tauri app: **Data management** and **Forms / app workbench** in one shell; see [ROADMAP.md](ROADMAP.md).

See [product roadmap context](https://opendataensemble.org/docs/) and organization roadmaps on [GitHub](https://github.com/OpenDataEnsemble).
ODE Desktop ships in [`desktop/`](desktop/) (see [desktop/AGENTS.md](desktop/AGENTS.md)). Broader product direction: [ROADMAP.md](ROADMAP.md) and [opendataensemble.org](https://opendataensemble.org/docs/).

---

Expand Down
98 changes: 98 additions & 0 deletions desktop/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# ODE Desktop — AI and developer guide

**ODE Desktop** (`desktop/`) is the Tauri + React + Rust app for **Data management** (observations, sync, import) and the **Forms / app workbench** (bundles, form preview, custom app embed). User-facing overview: [desktop/README.md](README.md).

Published docs: [ODE Desktop developer mode](https://opendataensemble.org/docs/guides/ode-desktop-developer-mode) (local custom app iteration).

---

## Layout

| Area | Path | Notes |
| ----------------- | -------------------------------------------------------------- | --------------------------------------------------------- |
| Frontend | `src/` | React, Zustand (`useCustodianStore`), workbench pages |
| Backend | `src-tauri/src/lib.rs` | Workspace, bundles, SQLite, sync, dev mirror |
| Formplayer assets | `public/formplayer_dist/` | Copy from `formulus-formplayer` (`pnpm build:formplayer`) |
| Bridge | `public/formulus-injection.js`, `src/lib/formPreviewBridge.ts` | Same contract as Formulus WebView |

**Profiles** are server-scoped settings in Tauri config: workspace path, Synk credentials, and workbench options. Switching profile switches workspace + DB.

---

## Workbench developer mode

Lets authors iterate on a **local build** of a custom app (e.g. `dist/`) against a profile’s real observations **without** overwriting the Synk-downloaded bundle in `bundles/active/`.

### Profile fields

| Field | Type | Purpose |
| ------------------------ | ---------------- | ----------------------------------------------- |
| `customAppDeveloperMode` | `boolean` | When true, workbench app + forms use dev mirror |
| `customAppLocalFolder` | `string \| null` | Absolute path to folder containing `index.html` |

Persisted per profile via `upsertProfileRemote` / Rust `ServerProfile`.

### Workspace paths

| Mode | Custom app | Forms |
| ---- | --------------------------------- | -------------------------------------------------------------- |
| Off | `bundles/active/app/` | `bundles/active/forms/` |
| On | `bundles/dev-local/app/` (mirror) | `bundles/dev-local/forms/` (mirror if `<folder>/forms` exists) |

Synk downloads and **Refresh from server** on the Bundles page only touch `bundles/active/`. The source folder on disk is **never** modified.

### UI

- **Configure:** Workbench → **Bundles** → `DeveloperModePanel` (`variant="full"`): Off/On toggle, folder picker, Browse, Refresh app.
- **Banner:** When on, `DeveloperModePanel` (`variant="banner"`) in `App.tsx` `Shell` on all `/workbench/*` routes, **above** activity/sync banners (path + Refresh app).
- **Consumers:** `WorkbenchCustomAppPage` (embed only), `FormPreviewPage`, `formPreviewBridge` (`getCustomAppUri`, `getFormSpecsUri` via Rust dev-aware roots).

### Mirror command

`refresh_custom_app_dev_mirror` (TS: `tauriClient.refreshCustomAppDevMirror()`):

1. Validates `index.html` at source root.
2. Copies source tree → `bundles/dev-local/app/`.
3. If `source/forms/` exists, copies → `bundles/dev-local/forms/`.

On success, Zustand bumps `devMirrorGeneration` so embeds and form lists reload.

### Key TypeScript

- `src/hooks/useDeveloperMode.ts` — profile read/write, refresh, generation counter from store.
- `src/components/DeveloperModePanel.tsx` — full vs banner UI; auto-mirror `useEffect` only on `variant="full"`.
- `src/lib/bundleLayout.ts` — `bundleSegment()`, `bundleFormsRel()`.
- `src/store/useCustodianStore.ts` — `devMirrorGeneration`, `devBusy`, `devError`, `refreshDevMirror`.

### Key Rust

- `profile_developer_mode`, `bundle_segment`, `bundle_form_roots_for_ctx`
- Dev-aware: `list_active_bundle_forms`, `read_bundle_form_spec`, `get_active_bundle_forms_file_base_url`, `scan_bundle_custom_question_types`, `bundle_app_config_path`
- Tests: `validate_custom_app_dev_source_requires_index_html`, `mirror_custom_app_dev_folder_copies_tree`

### Errors

Developer mode on with missing/invalid folder → blocking error in UI; no silent fallback to `bundles/active/` for custom app load.

---

## Bridge and bundles

- **Contract source of truth:** [`formulus/src/webview/FormulusInterfaceDefinition.ts`](../formulus/src/webview/FormulusInterfaceDefinition.ts).
- **Form preview:** `formPreviewBridge.ts` handles injection `postMessage` types; device APIs stubbed; observations/attachments use Tauri.
- **Extensions:** `bundleExtensionLoader.ts` merges `forms/ext.json` like Formulus `ExtensionService`; pass `developerMode` for path prefix.

---

## Commands

```bash
cd desktop
pnpm dev # Vite
pnpm tauri dev # Full app
pnpm test # Vitest
pnpm typecheck
cd src-tauri && cargo test
```

Conventional Commits; see root [AGENTS.md](../AGENTS.md) and [.github/CICD.md](../.github/CICD.md).
13 changes: 13 additions & 0 deletions desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,22 @@ Override at generation time:

CI regenerates the client and **fails** if the repo does not match (`ode-desktop` workflow).

## Developer mode (Workbench)

For **custom app authors** testing locally before publishing a bundle:

1. Workbench → **Bundles** → turn **Developer mode** **On** and pick a folder that contains **`index.html`** (e.g. your `dist/` output).
2. Optional: add **`forms/`** next to `index.html` with the usual `{formType}/schema.json` + `ui.json` layout for **Form preview**.
3. Use **Refresh app** after each build (also available from the orange Workbench banner while mode is on).

Mirrored files live under **`bundles/dev-local/app/`** and **`bundles/dev-local/forms/`** in the active profile workspace. Synk downloads stay in **`bundles/active/`** — use **Refresh from server** on Bundles to update those.

User guide: [ODE Desktop developer mode](https://opendataensemble.org/docs/guides/ode-desktop-developer-mode). Agent reference: [AGENTS.md](AGENTS.md).

## Architecture pointers

- **Bridge contract**: [`formulus/src/webview/FormulusInterfaceDefinition.ts`](../formulus/src/webview/FormulusInterfaceDefinition.ts) — source of truth for `formulusAPI` / postMessage. After changes, run **`sync-interface`** in `formulus-formplayer` and mirror behavior in the desktop WebView host.
- **Dev mirror paths**: `bundles/dev-local/app/`, `bundles/dev-local/forms/` when developer mode is on; `bundles/active/` otherwise (see [AGENTS.md](AGENTS.md)).
- **Form preview host** (Workbench → Form preview): `public/formulus-injection.js` + iframe shim; parent handles `postMessage` in **`src/lib/formPreviewBridge.ts`** (explicit matrix per `FormulusInjectionScript` request `type`; device APIs including camera, audio, and video are stubbed in preview; observations + URIs use Tauri where applicable). Nested **sub-observation** flows (`openFormplayer` + `options.subObservationMode`) open a stacked Form preview iframe and resolve the parent promise with `FormCompletionResult` without persisting the child as a top-level observation.
- **Bundle extensions**: merge rules for `forms/ext.json` and `forms/{form}/ext.json` follow Formulus `ExtensionService`; see `src/lib/bundleResolution.ts`.
- **Embedded formplayer**: production build copied into `public/formplayer_dist/`; load in a WebView with the same **`FormInitData`** expectations as mobile (see `src/lib/formplayerHost.ts` for placeholder types).
Expand Down
Loading
Loading