# Business rules — formulys-mobile-app

Evidence date: 2026-10-14. Source revision: `main` at `8fef7bc`.

No business-rules document exists in the source repository. Rules below are taken from the routed screens. Where the code implies a rule that nobody wrote down, the rule is marked **Inferred**. Form.io may enforce further rules when a form is saved. Those rules are not in this git tree. Silicon Ark `pestelite_html` records field constraints from a Form.io fetch (for example email and phone components on `clientregister`). That fetch is not repeated here, and this app does not enforce those constraints in JavaScript.

## Access

### BR-001 — A stored token is the only gate

**Implemented.** If AsyncStorage `token` is missing, routes other than login and sign-up are replaced with `/login` (`app/_layout.js`).

The token is not decoded, not expired locally, and not sent to Form.io on launch. Any non-empty stored string satisfies the gate. **Inferred** from the truthiness check: a corrupt or expired JWT still counts as logged in until a later API call fails.

Login and sign-up skip the gate. A person who already has a token can still open those screens.

### BR-002 — No role rules in this app

**Implemented** as an absence. No screen reads a role, `isActive`, or a user id. Drawer items are the same for every token.

`pestelite_html` hides navigation unless `pesteliteusers` role is `admin`. This app does not. Whether Pest Elite still wants that split on mobile is **Unknown / Requires Confirmation**. Do not treat the HTML rule as a rule of this app.

### BR-003 — Logout is local

**Implemented.** Logout deletes `token` and opens `/login`. It does not POST a logout to Form.io. The server session, if Form.io keeps one, is unchanged by this client.

## Identity

### BR-004 — Login body shape

**Implemented.** Login posts `{ data: { email, password } }` to `.../user/login/submission` with `x-jwt-token` set to `""`. The only value kept is the response header `x-jwt-token`.

### BR-005 — Registration body shape

**Implemented.** Register posts the same `{ data: { email, password } }` shape to `.../user/register/submission`.

### BR-006 — No client-side identity validation

**Implemented.** Email and password can be empty strings. There is no format check, length check, or match confirmation. Password inputs use `secureTextEntry`.

**Inferred:** any stricter rule is Form.io's, and it was not visible in this repo.

### BR-007 — One error sentence

**Implemented.** Login failures always show "Could not login, please try again". Register failures that reach the outer catch show "Could not register, please try again". The register HTTP `.catch` logs and swallows the axios error, so the user-facing sentence is raised by the following property read when the response is missing.

### BR-008 — Login and the HTML client do not use the same path

**Implemented** in this repo: `user/login` and `user/register`.

**Discrepancy:** `pestelite_html` authenticates with `pestelitelogin`. These are different Form.io paths. Whether both accept the same email is **Unknown / Requires Confirmation**. This document does not choose one as the business rule.

## Registers

### BR-009 — Three collections, fixed paths

**Implemented.**

| UI label | Form.io path | Create key (`formId`) |
|---|---|---|
| Site Inspections / All Sites / home "Site Forms" | `digimonkv1` | `digimonk` (the WebView URL still uses `digimonkv1`) |
| Clients / All Clients | `clientregister` | `register-client` |
| Pesticides / All Pesticides | `pesticidesregister` | `pesticidesregister` |

The unused component `components/forms/digimonk/index.js` points at `digimonk` without `v1`. That URL is not used by the routed create screen. **Inferred:** `digimonkv1` is the path the current UI intends. Why the form is named Digimonk is **Unknown**.

### BR-010 — Lists require a token and then show the raw collection

**Implemented.** Each list returns early when `token` is missing. The GET has no query string. There is no client sort, filter, search, or page size.

**Unknown / Requires Confirmation:** Form.io's default page size. This client does not set `limit`.

### BR-011 — Site card content

**Implemented.** A site card must render:

- `data.clientName`
- `data.selectClient.data.accountNumber` prefixed with `- `
- `Contract Number: ` plus `data.contractNumber`
- `Technician: ` plus `data.techName`
- `Next Visit: ` plus the date part of `data.nextVisit`, or `TBD` when `nextVisit` is nullish

**Inferred** failure rule: if `selectClient` or `selectClient.data` is missing, rendering throws. The screen has no error boundary.

### BR-012 — Client card content

**Implemented.** A client card must render `clientName`, `accountNumber`, `contractNumber`, the date part of `contractExpiry`, `siteContact`, `telephone`, and `email`, with the labels in `app/dashboard/clients/index.js`.

**Inferred** failure rule: `contractExpiry.split` assumes a string. A missing expiry throws and can blank the list.

### BR-013 — Pesticide card content

**Implemented.** A pesticide card renders `data.pesticide` and `data.type` only.

Dosage is not a rule of this client. The HTML client's dosage line is a different implementation.

### BR-014 — Create, view, and edit are delegated

**Implemented.** The native app does not post a submission body for sites, clients, or pesticides. It opens FormView:

- create: `https://pro.formview.io/#/rzkiqpzlxxtfqjl/{path}?header=0&reset=1`
- detail: `.../{path}/submission/{id}?header=0&reset=1`
- edit: `.../{path}/submission/{id}/edit?header=0&reset=1`

`header=0` and `reset=1` are always present. What those query flags do inside FormView is **Unknown / Requires Confirmation**.

The WebView is created without the stored JWT. **Inferred:** if FormView requires that JWT, create and edit fail closed inside the WebView even though the native list succeeded. That was not tested.

### BR-015 — No delete

**Implemented** as an absence. No control and no HTTP DELETE. The 2024-01-30 commit message says "CRU", which matches.

### BR-016 — No refresh after the WebView

**Implemented.** No `onNavigationStateChange`, message handler, or focus reload is attached to the create, detail, or edit WebViews. The lists reload when their `segments` change, which includes leaving and coming back only if that navigation updates `segments`. Whether returning from a WebView always refetches was not runtime-tested.

### BR-017 — Detail chrome stripping is only on sites and pesticides

**Implemented.** Site detail and pesticide detail inject a script that deletes DOM nodes added under `app-root` when the node is an element without class `main`. Client detail does not. The `setTimeout` call does not delay `observe` (the observe call runs immediately).

**Inferred** intent: hide FormView chrome and keep the node that has class `main`. Whether `main` still exists on the live FormView page is **Unknown**.

### BR-018 — List failure looks like an empty register

**Implemented.** A failed GET logs to the console, stops the spinner, and does not set an error message. The person cannot tell an empty register from a failed request.

### BR-019 — Card colours alternate

**Implemented.** Index even uses `#354785`, index odd uses `#bbbbbb`, text `#fff`. The order is the array order from Form.io, not a sort.

## Navigation labels

**Implemented.** These strings disagree with each other and are all in the tip:

| Surface | Sites label | Clients label | Pesticides label |
|---|---|---|---|
| Home button | Site Forms | Clients | (no button) |
| Drawer | Site Inspections | Clients | Pesticides |
| Stack title | All Sites | All Clients | All Pesticides |
| Detail title | Site Details | Client Details | Pesticide Details |

Which label is the business term is **Unknown / Requires Confirmation**.

## Platform rules visible in the native project

### BR-020 — Android identity

**Implemented.** `applicationId` and namespace are `com.siliconoverdrive.pestelite`. `versionName` is `1.0.0`. `versionCode` is `1`. Launcher label is `pestelite-mobile`.

### BR-021 — Release uses the debug keystore

**Implemented.** `android/app/build.gradle` sets the release `signingConfig` to `signingConfigs.debug`. The file comment says a production keystore is still required. The debug keystore file is in git. This is a build rule, not a product rule about who may use the app.

### BR-022 — OTA updates are off

**Implemented.** `android/app/src/main/AndroidManifest.xml` sets `expo.modules.updates.ENABLED` to `false`, while `CHECK_ON_LAUNCH` is `ALWAYS` and `LAUNCH_WAIT_MS` is `0`. The enable flag is the one that turns updates off.

### BR-023 — Cleartext traffic is debug-only

**Implemented.** `android/app/src/debug/AndroidManifest.xml` sets `usesCleartextTraffic` to true. The main manifest does not. Routed URLs are `https`.

## Rules that are not established

- Who may register.
- Password policy.
- Whether a technician may only see their own sites (`techName` is displayed, not filtered).
- Contract expiry meaning, visit scheduling, and pesticide dosage. The fields are displayed or omitted. No calculation is in this repo.
- Data retention, backup, and offline use. Submissions are not stored on device. Only the token is.
- Whether the `%ProgramData%` file in the tree is intentional. It is not referenced by the app.
