diff --git a/docs/development/installing-formulus-dev.md b/docs/development/installing-formulus-dev.md
index 0d751b2..330f0df 100644
--- a/docs/development/installing-formulus-dev.md
+++ b/docs/development/installing-formulus-dev.md
@@ -160,18 +160,20 @@ adb install app-debug.apk
```bash
-# Download and install in one command
-curl -L https://github.com/OpenDataEnsemble/ode/releases/download/v1.1.1/formulus.apk -o /tmp/formulus.apk
-adb install /tmp/formulus.apk
+# Browse the release and download the arm64-v8a APK for most phones:
+# https://github.com/OpenDataEnsemble/ode/releases/tag/v1.2.1
+# Asset names look like: formulus-v1.2.1-25-arm64-v8a-release-YYYYMMDD.apk
+adb install /path/to/formulus-v1.2.1-*-arm64-v8a-release-*.apk
```
```powershell
-# Download and install
-Invoke-WebRequest -Uri "https://github.com/OpenDataEnsemble/ode/releases/download/v1.1.1/formulus.apk" -OutFile "$env:TEMP\formulus.apk"
-adb install "$env:TEMP\formulus.apk"
+# Browse the release and download the arm64-v8a APK for most phones:
+# https://github.com/OpenDataEnsemble/ode/releases/tag/v1.2.1
+# Asset names look like: formulus-v1.2.1-25-arm64-v8a-release-YYYYMMDD.apk
+adb install "C:\path\to\formulus-v1.2.1-*-arm64-v8a-release-*.apk"
```
diff --git a/docs/getting-started/installation/installing-formulus.md b/docs/getting-started/installation/installing-formulus.md
index 5253bb9..435ec61 100644
--- a/docs/getting-started/installation/installing-formulus.md
+++ b/docs/getting-started/installation/installing-formulus.md
@@ -86,7 +86,7 @@ You have two options to install Obtainium:

-**Stable release:** Install **v1.1.1** (or the latest [GitHub release](https://github.com/OpenDataEnsemble/ode/releases)). Pre-release toggles are only needed for alpha/beta testing.
+**Stable release:** Install **v1.2.1** (or the latest [GitHub release](https://github.com/OpenDataEnsemble/ode/releases)). Pre-release toggles are only needed for alpha/beta testing.
#### Step 3: Install Formulus
@@ -97,7 +97,7 @@ You have two options to install Obtainium:
- App name: **ode**
- Developer: **OpenDataEnsemble**
- Package: `org.opendataensemble.formulus`
- - Latest version: **v1.1.1** (or current [release](https://github.com/OpenDataEnsemble/ode/releases))
+ - Latest version: **v1.2.1** (or current [release](https://github.com/OpenDataEnsemble/ode/releases))
- Status: **Not installed**
5. **Tap the "Install" button** at the bottom of the screen
6. **Confirm installation** when prompted:
diff --git a/docs/guides/building-custom-apps-v2.md b/docs/guides/building-custom-apps-v2.md
index e43588a..cb9740f 100644
--- a/docs/guides/building-custom-apps-v2.md
+++ b/docs/guides/building-custom-apps-v2.md
@@ -560,22 +560,25 @@ Upload to Synkronus:
## How it works: Data injection
-When you call:
+Prefer `defaultData` (keys must match schema root properties):
```javascript
await api.openFormplayer(
'pull_shot',
- { bean: beanName }, // Inject this value
+ { defaultData: { bean: beanName } },
{}
)
```
+Legacy flat keys on the params object (other than reserved bridge keys) are still accepted when `defaultData` is omitted.
+
The Formulus API:
1. Opens the `pull_shot` form
-2. Pre-fills the `bean` field with the provided value
-3. Marks it read-only (from ui.json)
-4. User fills in the rest (yield, time, rating)
-5. When submitted, the observation includes the injected value
+2. Prefills matching schema fields from `defaultData`
+3. User fills in the rest (yield, time, rating)
+4. When submitted, the observation includes the injected value
+
+**Important — visibility vs injection:** Formplayer **clears** a field when its Control is hidden by a `SHOW`/`HIDE` rule. Injected stamps must live in the **schema** (and `defaultData`) and must **not** be bound to a Control that starts hidden. To show the value read-only, use SwipeLayout `headerFields` or a separate computed / `lbl_*` display field—not a hidden Control on the real property. See [Form design — Conditional Logic](./form-design.md#conditional-logic-in-ode-forms).
---
diff --git a/docs/guides/form-design.md b/docs/guides/form-design.md
index 5a32a71..4e77bd2 100644
--- a/docs/guides/form-design.md
+++ b/docs/guides/form-design.md
@@ -891,6 +891,21 @@ Rules are defined in the UI schema using the `rule` property on Control elements
}
```
+:::warning Clear-on-hide deletes the field value
+When Formplayer hides a **Control** (`SHOW` until the condition is true, or `HIDE` when the condition is true), it **clears that field’s value** from the observation (ODK-style `relevant` behaviour). This is intentional for user-entered answers that should not stay when the question is no longer relevant.
+
+**Do not** put `SHOW`/`HIDE` on fields you need to keep—especially values injected by a custom app via `defaultData` (cluster IDs, device role, AF codes, enrollment mode, etc.).
+
+**Recommended pattern for injected / stamp fields:**
+
+1. Declare the field in `schema.json` so it can be stored and validated.
+2. Pass it in `openFormplayer` / `FormInitData` as `params.defaultData`.
+3. **Do not** add a UI `Control` for that field (or never attach a visibility rule to it).
+4. If the enumerator must **see** the value, use a separate display-only / computed proxy (for example `lbl_region`, `lbl_af`) or list the stamp in SwipeLayout `headerFields`—not a hidden Control on the real field.
+
+Using a Control solely to “hide” an injected field will wipe the inject on open and can cause required-field failures or silent data loss.
+:::
+
### Scope Resolution Rules
**Critical Rule**: Rule condition scopes **must exist in the schema at all times**, even when the field is hidden. The scope is evaluated against the current form data.
diff --git a/docs/reference/formplayer-contract.md b/docs/reference/formplayer-contract.md
index 92e9f28..d5bdaa6 100644
--- a/docs/reference/formplayer-contract.md
+++ b/docs/reference/formplayer-contract.md
@@ -738,6 +738,8 @@ Use `type: object` with `format: select_file`. The Formplayer stores **basename-
| `ENABLE` | Element is disabled until condition is true |
| `DISABLE` | Element is enabled until condition is true |
+> **Clear-on-hide:** For `SHOW` and `HIDE`, when a Control becomes not visible Formplayer **deletes that field’s value** from form data (see `useClearOnHide`). Use visibility rules only for answers that should reset when irrelevant. Keep injected / stamp fields in the schema and `defaultData` **without** a hidden Control—display via `headerFields` or a separate computed/`lbl_*` field if needed. Details: [Form design — Conditional Logic](../guides/form-design.md#conditional-logic-in-ode-forms).
+
### Condition Schema
**Supported Condition Types:**