@condorinst/mobile-bridge 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +142 -34
- package/CHANGELOG.md +198 -0
- package/README.md +30 -465
- package/contract/bridge-contract.json +34 -22
- package/dist/cli.cjs +639 -43
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +635 -39
- package/dist/cli.js.map +1 -1
- package/dist/dev-harness.global.js +388 -55
- package/dist/dev.cjs +389 -55
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.d.cts +239 -9
- package/dist/dev.d.ts +239 -9
- package/dist/dev.js +388 -55
- package/dist/dev.js.map +1 -1
- package/dist/index.cjs +30 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +154 -26
- package/dist/index.d.ts +154 -26
- package/dist/index.js +29 -8
- package/dist/index.js.map +1 -1
- package/docs/agent-guide.md +9 -265
- package/docs/bridge-contract.md +313 -26
- package/docs/client-guide.md +34 -398
- package/docs/device-testing.md +286 -22
- package/docs/pilot-onboarding.md +41 -111
- package/docs/shell-integration.md +292 -0
- package/docs/troubleshooting.md +19 -11
- package/examples/minimal-home/README.md +2 -2
- package/examples/minimal-home/package.json +1 -1
- package/examples/minimal-home/src/main.ts +23 -3
- package/llms-full.txt +825 -881
- package/llms.txt +13 -15
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -2,12 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
You are writing the **Home screen** of a Condor Canvas app. It is a single web
|
|
4
4
|
bundle that renders inside a sandboxed iframe in a native mobile app. The app
|
|
5
|
-
owns login, settings
|
|
6
|
-
the person sees
|
|
5
|
+
owns login, its settings screen, Bluetooth and background collection; you own
|
|
6
|
+
everything the person sees after login, including the way to those settings and
|
|
7
|
+
the way out of the account.
|
|
7
8
|
|
|
8
|
-
This file is the
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
This file is the procedure: read it before writing code, top to bottom. Each
|
|
10
|
+
subject has one owner, so look things up there instead of guessing:
|
|
11
|
+
|
|
12
|
+
| Subject | Owner |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| What each key, command, ack and error means | `docs/bridge-contract.md` (and `contract/bridge-contract.json`) |
|
|
15
|
+
| Payload shapes, field by field | `dist/index.d.ts` — your editor shows them |
|
|
16
|
+
| Harness, scripted tests, `validate`, publishing, the phone | `docs/device-testing.md` |
|
|
17
|
+
| A symptom you do not understand | `docs/troubleshooting.md` |
|
|
18
|
+
| Sizes, rates and timeouts | `docs/limits.md` |
|
|
19
|
+
| A working client to copy | `examples/minimal-home/` |
|
|
20
|
+
| All of it in one file | `llms-full.txt` |
|
|
21
|
+
|
|
22
|
+
Everything here is enforced, either by a gate in the app or by the browser
|
|
23
|
+
runtime on a real device, where mistakes are expensive to diagnose.
|
|
11
24
|
|
|
12
25
|
## The one thing that makes this different
|
|
13
26
|
|
|
@@ -19,11 +32,34 @@ So never write code whose correctness you infer from the absence of an error.
|
|
|
19
32
|
Drive the UI from the events that arrive, give every wait a deadline, and
|
|
20
33
|
always have a screen for "it did not come".
|
|
21
34
|
|
|
35
|
+
## Your bundle is the whole screen
|
|
36
|
+
|
|
37
|
+
The app draws **nothing** around your page except the status bar strip (its
|
|
38
|
+
color is yours: `setStatusBarColor`) and the system safe areas, which it already
|
|
39
|
+
keeps clear — do not pad for them again. There is no native title, no back
|
|
40
|
+
arrow, no menu. So your bundle must provide:
|
|
41
|
+
|
|
42
|
+
- **a header and navigation** between your screens, with the Android back button
|
|
43
|
+
wired (`reportNavigation` / `onBack`): back goes one screen up, and only on
|
|
44
|
+
your home does it leave;
|
|
45
|
+
- **a way to log out** — `send('logout')` is the **only** way out of the account;
|
|
46
|
+
the app has no logout button of its own;
|
|
47
|
+
- **a way to the app settings** — `openSettings()` opens the Canvas settings
|
|
48
|
+
screen (language, notifications, Bluetooth, refresh the device list, date
|
|
49
|
+
format);
|
|
50
|
+
- **a way to refresh** the device list (`requestUserDevices({ getFromServer: true })`)
|
|
51
|
+
and to grant missing permissions (`requestPermission`).
|
|
52
|
+
|
|
53
|
+
A bundle without these traps the person inside it.
|
|
54
|
+
|
|
22
55
|
## Hard rules
|
|
23
56
|
|
|
24
57
|
1. **No storage.** `localStorage`, `sessionStorage`, `indexedDB` and cookies all
|
|
25
|
-
fail — the iframe has a `null` origin
|
|
26
|
-
|
|
58
|
+
fail — the iframe has a `null` origin — and the bridge has no storage
|
|
59
|
+
command: there is no channel to keep anything. Keep state in memory and
|
|
60
|
+
accept that it is lost whenever the app reloads the page (an app restart,
|
|
61
|
+
for one). Re-request what you need at startup; if the person typed
|
|
62
|
+
something, say on screen that it is not kept.
|
|
27
63
|
2. **No network.** `connect-src 'self'` on a null origin blocks every `fetch`,
|
|
28
64
|
`XMLHttpRequest` and `WebSocket`. All data comes through the bridge.
|
|
29
65
|
3. **No `eval`, no `new Function`.** The CSP has no `'unsafe-eval'`. Configure
|
|
@@ -42,6 +78,16 @@ always have a screen for "it did not come".
|
|
|
42
78
|
branch on it. See the capability matrix in `docs/bridge-contract.md`.
|
|
43
79
|
9. **Never ask once and wait.** Use `BridgeClient.requestUserDevices()` for the
|
|
44
80
|
device list; a single `send('userDevice')` can be lost (see below).
|
|
81
|
+
10. **No `crypto.randomUUID`.** It exists only in a secure context, and the
|
|
82
|
+
app's iframe is probably not one — it works in the harness on `localhost` and
|
|
83
|
+
may be `undefined` on the phone. `fetchSensorLog` creates its `requestId`;
|
|
84
|
+
use `createRequestId()` or `crypto.getRandomValues` for anything else.
|
|
85
|
+
11. **Update in place; don't rebuild the screen on every event.** Events arrive
|
|
86
|
+
on their own (`systemStatus`, telemetry, a reading per second), and
|
|
87
|
+
replacing the node under the user's finger loses the tap. Rebuild a view
|
|
88
|
+
when the user navigates; on an event, change the text and attributes that
|
|
89
|
+
moved. The example rebuilds with `innerHTML`, which is fine for a screen
|
|
90
|
+
with no live readings, not for one that updates every second.
|
|
45
91
|
|
|
46
92
|
## What you cannot reach
|
|
47
93
|
|
|
@@ -50,19 +96,56 @@ always have a screen for "it did not come".
|
|
|
50
96
|
camera, microphone and geolocation (denied unconditionally) · the `dfu` key and
|
|
51
97
|
command (denied by policy).
|
|
52
98
|
|
|
99
|
+
**Nothing leaves the screen — by design, for security.** The iframe is
|
|
100
|
+
`sandbox="allow-scripts"` and nothing more, and the bridge has no share,
|
|
101
|
+
open-URL, copy or save command. Do not look for a way around it; if a client
|
|
102
|
+
needs one of these, it is a request to Condor:
|
|
103
|
+
|
|
104
|
+
| You want to | On the phone |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| Download a file (`<a download>`, a `blob:`/`data:` URL) | blocked: no `allow-downloads`, and the app registers no download handler |
|
|
107
|
+
| Open a link (`target=_blank`, `window.open`, `_top`) | blocked: no `allow-popups`, no `allow-top-navigation`. A plain `<a href="https://…">` navigates **your iframe** away and the user loses your screen — don't render external links as links |
|
|
108
|
+
| `navigator.share` | treat as unavailable: no `allow="web-share"` |
|
|
109
|
+
| `navigator.clipboard.writeText` | unavailable: no `allow="clipboard-write"`, so the permissions policy rejects it with `NotAllowedError` (measured in the harness, which has the same iframe) |
|
|
110
|
+
| `document.execCommand('copy')` on a selected `<textarea>`, inside a tap handler | works in the harness (desktop Chrome); **not measured on a device** |
|
|
111
|
+
|
|
112
|
+
To hand data to the user, show it: a read-only `<textarea>` with the text
|
|
113
|
+
(CSV, a code) that the user can select and copy, plus an `execCommand('copy')`
|
|
114
|
+
button that says when it failed. Say in your README that export is copy-only.
|
|
115
|
+
`img-src` is `'self' data:` (no `blob:`): draw a chart on a `<canvas>` or use
|
|
116
|
+
a `data:` URL, never `URL.createObjectURL`.
|
|
117
|
+
|
|
53
118
|
## The golden path
|
|
54
119
|
|
|
120
|
+
Start from the example — it already has this boot sequence and a single-file
|
|
121
|
+
build. Copy it, then install, so its `package.json` is the one you keep:
|
|
122
|
+
|
|
55
123
|
```bash
|
|
56
|
-
|
|
124
|
+
mkdir my-home && cd my-home
|
|
125
|
+
npm pack @condorinst/mobile-bridge@^0.23.0 && tar -xzf condorinst-mobile-bridge-*.tgz
|
|
126
|
+
cp -r package/examples/minimal-home/. . && rm -rf package condorinst-mobile-bridge-*.tgz
|
|
127
|
+
npm install
|
|
57
128
|
```
|
|
58
129
|
|
|
130
|
+
In a project you already have: `npm install @condorinst/mobile-bridge@^0.23.0`
|
|
131
|
+
(the caret gives you patches only while the package is below 1.0.0) and copy
|
|
132
|
+
`build.mjs` from `node_modules/@condorinst/mobile-bridge/examples/minimal-home/`.
|
|
133
|
+
|
|
134
|
+
**Render first.** Paint the whole screen with no data — empty list, unknown
|
|
135
|
+
status, default language — then fill it as events arrive. Events are emitted
|
|
136
|
+
before your bundle exists, so there is always a window with nothing.
|
|
137
|
+
|
|
59
138
|
```ts
|
|
60
139
|
import {
|
|
61
140
|
BridgeClient, BRIDGE_KEYS, versionAtLeast, type UserDevicePayload,
|
|
62
141
|
} from '@condorinst/mobile-bridge';
|
|
63
142
|
|
|
64
|
-
// 1. Subscribe before you ask for anything. Roster changes arrive here too
|
|
65
|
-
|
|
143
|
+
// 1. Subscribe before you ask for anything. Roster changes arrive here too —
|
|
144
|
+
// and, from app 0.18.0, failures: `error` set + empty arrays. Only
|
|
145
|
+
// `NO_DEVICES` means "no devices"; on any other error keep what is on screen.
|
|
146
|
+
BridgeClient.on(BRIDGE_KEYS.userDevice, (devices: UserDevicePayload) => {
|
|
147
|
+
if (devices.error === undefined || devices.error === 'NO_DEVICES') renderDevices(devices);
|
|
148
|
+
});
|
|
66
149
|
BridgeClient.on(BRIDGE_KEYS.systemStatus, (s) => renderEnvironment(s)); // bluetooth, internet, permissions
|
|
67
150
|
|
|
68
151
|
// 2. Handshake. nativeVersion is the ONLY capability signal; ack.accepted is
|
|
@@ -72,11 +155,13 @@ const canAddressById = versionAtLeast(ack.nativeVersion, '0.17.0'); // null →
|
|
|
72
155
|
|
|
73
156
|
// 3. Ask for the devices with the helper. It resends until they arrive.
|
|
74
157
|
const result = await BridgeClient.requestUserDevices();
|
|
75
|
-
if (result.status === '
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
|
|
158
|
+
if (result.status === 'no-devices') {
|
|
159
|
+
renderEmpty('No device is linked to your account yet.'); // server confirmed (app 0.18.0+)
|
|
160
|
+
} else if (result.status === 'error' || result.status === 'timeout') {
|
|
161
|
+
// error: NETWORK | SESSION | UNKNOWN (app 0.18.0+). timeout: no reply in 30 s,
|
|
162
|
+
// the only failure an older app can produce. Always offer "Try again", which
|
|
163
|
+
// calls requestUserDevices({ getFromServer: true }).
|
|
164
|
+
renderEmpty('Could not load your devices.');
|
|
80
165
|
} else {
|
|
81
166
|
// 4. Subscribe to the connection state of each device (`add` pairs nothing).
|
|
82
167
|
for (const d of result.devices.devicesConfig) {
|
|
@@ -88,9 +173,15 @@ if (result.status === 'timeout') {
|
|
|
88
173
|
|
|
89
174
|
// 5. History: one page per call; it resolves { success: false, error } on
|
|
90
175
|
// failure and only rejects on timeout.
|
|
91
|
-
const page = await BridgeClient.fetchSensorLog({
|
|
176
|
+
const page = await BridgeClient.fetchSensorLog({ serialNo }) // requestId is created for you;
|
|
92
177
|
```
|
|
93
178
|
|
|
179
|
+
`on` returns an unsubscribe function: call it when a view unmounts, or handlers
|
|
180
|
+
pile up. **Permissions:** when `systemStatus.missingEssentialPermissions` is not
|
|
181
|
+
empty, background collection is off — show a blocking card whose button calls
|
|
182
|
+
`BridgeClient.requestPermission(name)`. There is no result event: the app
|
|
183
|
+
re-emits `systemStatus`, and the card goes when the array empties.
|
|
184
|
+
|
|
94
185
|
**Why the helper.** Device data comes from a background service that the app
|
|
95
186
|
starts *after* your bundle is already running. Anything you send to it before it
|
|
96
187
|
is up is lost without a reply — while the handshake, `systemStatus` and
|
|
@@ -111,7 +202,17 @@ with `toISOString()` or a `'Z'` suffix.
|
|
|
111
202
|
|
|
112
203
|
Three commands open a confirmation dialog owned by Condor: `syncTime`,
|
|
113
204
|
`deleteMemory` and `logout`. If the person declines, you get nothing — the ack
|
|
114
|
-
never arrives. Do not block your UI on them
|
|
205
|
+
never arrives. Do not block your UI on them, and do not add a second
|
|
206
|
+
confirmation of your own: explain the consequence next to the button instead.
|
|
207
|
+
`logout` never answers — the app unloads your page.
|
|
208
|
+
|
|
209
|
+
**An ActLumus is disconnected most of the time** — it connects for a short sync
|
|
210
|
+
every few minutes. `realTime`, `syncTime` and `deleteMemory` work anyway: the ack
|
|
211
|
+
means "recorded", and the app carries it out at the device's next session
|
|
212
|
+
(`deleteMemory` only after that sync uploaded the data). Never disable them for
|
|
213
|
+
a disconnected device. `deviceConnection` `add` reports only *changes*: start
|
|
214
|
+
each device as "connection unknown". Details: `docs/bridge-contract.md`,
|
|
215
|
+
"What an ack means".
|
|
115
216
|
|
|
116
217
|
## Develop in the harness, not in a bare browser tab
|
|
117
218
|
|
|
@@ -126,9 +227,9 @@ once outside the shell. Rebuild and reload; the harness re-reads the file.
|
|
|
126
227
|
|
|
127
228
|
| Scenario | What it reproduces |
|
|
128
229
|
| --- | --- |
|
|
129
|
-
| `default` | two devices, realistic latency |
|
|
230
|
+
| `default` | two devices, realistic latency; the first starts a BLE session after 3 s, the second stays between sessions |
|
|
130
231
|
| `cold-start` | the background service takes 4 s; earlier `ping`/`userDevice` are lost |
|
|
131
|
-
| `no-devices` | `userDevice` never answers; `hasDevices: false` |
|
|
232
|
+
| `no-devices` | `userDevice` answers `error: 'NO_DEVICES'` (`--native-version 0.17.0`: never answers); `hasDevices: false` |
|
|
132
233
|
| `shared-serial` | an ActLumus and a Plus share serial 7047 |
|
|
133
234
|
| `offline` | no internet; `sensorLog` fails |
|
|
134
235
|
|
|
@@ -137,6 +238,20 @@ answered and dropped. The **◀ OS back** button at the top presses the OS back
|
|
|
137
238
|
button. `--native-version null` (or `0.15.0`, …) makes the handshake report that
|
|
138
239
|
version, to test your capability branches.
|
|
139
240
|
|
|
241
|
+
The harness does not change state by itself after load: drive internet loss,
|
|
242
|
+
disconnects, readings and the confirmation dialog from a script through
|
|
243
|
+
`window.condorDev` — what it simulates, the scripted-test recipe and your own
|
|
244
|
+
scenario files are in `docs/device-testing.md`.
|
|
245
|
+
|
|
246
|
+
## Publishing — ask the human, never run it yourself
|
|
247
|
+
|
|
248
|
+
`npx condor-mobile-bridge canvas publish` puts the bundle live at once for every
|
|
249
|
+
user of a company, and asks for the person's password. **Do not run it.** When
|
|
250
|
+
the bundle is ready, tell the human the exact command — for example
|
|
251
|
+
`npx condor-mobile-bridge canvas publish dist/index.html --version 1.0.1` — and
|
|
252
|
+
recommend publishing to their **test company** first. See
|
|
253
|
+
`docs/device-testing.md`.
|
|
254
|
+
|
|
140
255
|
## The validation loop — run this before you call the work done
|
|
141
256
|
|
|
142
257
|
```bash
|
|
@@ -160,20 +275,13 @@ check, so a clean run is necessary, not sufficient.
|
|
|
160
275
|
- [ ] Every capability beyond the baseline is guarded by a `nativeVersion` check
|
|
161
276
|
(`versionAtLeast`), and the page still renders with
|
|
162
277
|
`--native-version null`.
|
|
163
|
-
- [ ] Nothing waits forever on a reply that may never come
|
|
278
|
+
- [ ] Nothing waits forever on a reply that may never come — the three
|
|
279
|
+
confirmable commands may never ack, because the person can decline.
|
|
280
|
+
- [ ] The person can navigate back, reach the app settings and log out from
|
|
281
|
+
your screens ("Your bundle is the whole screen").
|
|
282
|
+
- [ ] `serialNo` is a number everywhere; commands carry `deviceId` too when
|
|
283
|
+
`nativeVersion` ≥ 0.17.0.
|
|
284
|
+
- [ ] Unexpected errors go to `BridgeClient.send('jsError', …)`, your only
|
|
285
|
+
channel out.
|
|
164
286
|
|
|
165
287
|
To put the bundle on a phone, read `docs/device-testing.md`.
|
|
166
|
-
|
|
167
|
-
## Where to read more
|
|
168
|
-
|
|
169
|
-
| Question | File |
|
|
170
|
-
| --- | --- |
|
|
171
|
-
| Every key, command, error shape and capability | `docs/bridge-contract.md` |
|
|
172
|
-
| The same, as JSON you can parse | `contract/bridge-contract.json` |
|
|
173
|
-
| Step-by-step build of a Home | `docs/agent-guide.md` |
|
|
174
|
-
| "My command did nothing" / "the device list never came" | `docs/troubleshooting.md` |
|
|
175
|
-
| Getting the bundle onto a device, and seeing its errors | `docs/device-testing.md` |
|
|
176
|
-
| Every hard limit, with the code that enforces it | `docs/limits.md` |
|
|
177
|
-
| Payload shapes, authoritative | `dist/index.d.ts` (your editor shows them) |
|
|
178
|
-
| A working client you can copy | `examples/minimal-home/` |
|
|
179
|
-
| All of the above in one file | `llms-full.txt` |
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,204 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.23.0] — 2026-10-21
|
|
11
|
+
|
|
12
|
+
For Canvas apps with `nativeVersion` **0.18.0**, which fix the silent
|
|
13
|
+
`userDevice` on the native side.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `UserDevicePayload.error` (`UserDeviceErrorCode`: `NO_DEVICES`, `NETWORK`,
|
|
18
|
+
`SESSION`, `UNKNOWN`): a failed lookup now arrives as empty arrays plus
|
|
19
|
+
`error` instead of silence.
|
|
20
|
+
- `SystemStatusPayload.hasDevicesKnown`: whether `hasDevices` is the server's
|
|
21
|
+
answer.
|
|
22
|
+
- `BridgeClient.requestUserDevices()` resolves `{ status: 'no-devices' }` and
|
|
23
|
+
`{ status: 'error', code }`; the result type is exported as
|
|
24
|
+
`UserDevicesResult`. On older apps it behaves as in 0.21.0 (`ok` or
|
|
25
|
+
`timeout`), and `timeout.hasDevices` is `null` when the app said
|
|
26
|
+
`hasDevicesKnown: false`.
|
|
27
|
+
- Contract capabilities `userDeviceAlwaysReplies`, `hasDevicesKnown` and
|
|
28
|
+
`userDevicePayloadAllowlist` (`minNativeVersion` 0.18.0).
|
|
29
|
+
- `docs/device-testing.md`: loading the bundle from your machine on a
|
|
30
|
+
development build (`WL_BUNDLE_DEV_URL` + `adb reverse`).
|
|
31
|
+
- Harness: `window.condorDev` on the `dev` page (`setStatus`, `setConnected`,
|
|
32
|
+
`emit`, `setConfirmations`, `pressBack`, `log`, `ready`) and `?confirm=accept|decline`,
|
|
33
|
+
for scripted tests; `DevHarness.setStatus`, `setConnected` and
|
|
34
|
+
`setConfirmations`; `lightSensorsReading()` fixture. Recipe in
|
|
35
|
+
`docs/device-testing.md`, "Scripted tests".
|
|
36
|
+
- `dev --latency <ms>`, `dev --no-panel`, `dev --scenario <file.json>` (a scenario
|
|
37
|
+
that extends a named one) and the URL parameters `latency`, `panel`;
|
|
38
|
+
`condorDev.ready` waits for the handshake; `log` entries carry the complete
|
|
39
|
+
`args` / `payload`.
|
|
40
|
+
- `createRequestId()`; `fetchSensorLog` creates the `requestId` when it is left
|
|
41
|
+
out (`SensorLogFetchQuery`).
|
|
42
|
+
- `validate` warns on `crypto.randomUUID` (`crypto-randomuuid`): it exists only in
|
|
43
|
+
a secure context, and the app's iframe is probably not one. The harness removes
|
|
44
|
+
it from the iframe, which on `localhost` would otherwise hide the problem.
|
|
45
|
+
- Docs: "Your bundle is the whole screen" in `AGENTS.md` (navigation, logout,
|
|
46
|
+
settings and refresh are the bundle's job); the permission names; what
|
|
47
|
+
`openSettings` opens; `syncTime` uses the phone's local time; `deviceNotification`
|
|
48
|
+
vs `deviceSyncErrorById` vs `syncErrors`; `memoryDataUsage` vs
|
|
49
|
+
`memorySystemUsage`; real-time cadence; `sensorLog` timeouts and pacing; the
|
|
50
|
+
`validate` rules (a drift test keeps the list complete).
|
|
51
|
+
- Docs: what an ack of `realTime`, `syncTime` and `deleteMemory` means ("recorded",
|
|
52
|
+
carried out at the device's next session; `deleteMemory` only after the upload),
|
|
53
|
+
`deviceConnection` reports only changes, `logout` has no reply, no second
|
|
54
|
+
confirmation dialog, `SensorLogItem.device_id` = `devicesConfig[].deviceId`.
|
|
55
|
+
|
|
56
|
+
### Documentation
|
|
57
|
+
|
|
58
|
+
- **One owner per subject.** `AGENTS.md` is the procedure for agents and people
|
|
59
|
+
alike, with a table of which document owns what; `docs/agent-guide.md` (a
|
|
60
|
+
second copy of the procedure that kept drifting) and `docs/client-guide.md`
|
|
61
|
+
are now short pointers. The `language` section moved to
|
|
62
|
+
`docs/bridge-contract.md`; what `build` does moved to `docs/device-testing.md`;
|
|
63
|
+
`docs/pilot-onboarding.md` is the process only. The README is the npm landing
|
|
64
|
+
page; the shell-side material (`BridgeShell`, legacy `Bridge`, mock mode, dev
|
|
65
|
+
panel) moved to `docs/shell-integration.md`, outside `llms-full.txt`.
|
|
66
|
+
`llms-full.txt` shrank by about a third.
|
|
67
|
+
- A drift test checks every relative link in the docs.
|
|
68
|
+
|
|
69
|
+
### Added (round 4)
|
|
70
|
+
|
|
71
|
+
- `SensorLogItem.off_wrist` and `body_part` documented (types, contract, docs):
|
|
72
|
+
`off_wrist` is the raw wear flag — `1` = worn, `0` = on the dock — and
|
|
73
|
+
`body_part` is the configured placement, not a detection. A recipe for time
|
|
74
|
+
worn per day; `endTime` is exclusive.
|
|
75
|
+
- Harness history is configurable per scenario (`history`: `days`, `dockHours`,
|
|
76
|
+
`gapHours`, `gapDates`; `sensorLogFailOnRequest`). The default history now has
|
|
77
|
+
a dock hour (07:00, `off_wrist: 0`) and `body_part` follows each device's
|
|
78
|
+
`deviceBodyPart` — the Plus fixture was set to the necklace but logged
|
|
79
|
+
`wrist`. Pages stay full across gaps.
|
|
80
|
+
- `docs/device-testing.md`: what the default history contains, and the harness
|
|
81
|
+
behaviours a test trips on (language from the browser, declined confirmation,
|
|
82
|
+
the 2 s `null` handshake, the 500-entry log).
|
|
83
|
+
|
|
84
|
+
### Added (round 5)
|
|
85
|
+
|
|
86
|
+
- Sync progress documented ("Sync progress: `deviceNotification`" in
|
|
87
|
+
`docs/bridge-contract.md`): the steps of a session in order, the codes, the
|
|
88
|
+
only success marker (`pipeline_done_notification`), what a failed sync looks
|
|
89
|
+
like, and that `message` is an English log line — the JSDoc said it was
|
|
90
|
+
localized. `isConnect` on sync steps is `!isError`, not the connection.
|
|
91
|
+
- The harness emits that feed on every simulated session (`setConnected`),
|
|
92
|
+
with a pending `syncTime`/`deleteMemory` as its step; `sessionFailAt` makes
|
|
93
|
+
syncs fail at a stage, and without internet they fail at `send_data_log`.
|
|
94
|
+
- `AGENTS.md`: what the iframe cannot do (download, open a link, share,
|
|
95
|
+
`navigator.clipboard` — measured as blocked in the harness), with a copy-only
|
|
96
|
+
export recipe; rule 11, update in place instead of rebuilding on every event.
|
|
97
|
+
- `docs/bridge-contract.md`: a report "day" is the date part of `log_time`.
|
|
98
|
+
|
|
99
|
+
### Added (round 8)
|
|
100
|
+
|
|
101
|
+
- `syncErrors[].messageCode` is the failing stage's `…_error` code (the
|
|
102
|
+
`deviceNotification` vocabulary); the harness fixture used `SYNC_FAILED`,
|
|
103
|
+
which the app never sends — now `send_data_log_error`.
|
|
104
|
+
- History vs live readings: they overlap in time, not in data; prefer the
|
|
105
|
+
history for any span it covers.
|
|
106
|
+
- `AGENTS.md` rule 1: no persistence channel at all, and what to tell the user.
|
|
107
|
+
- A pending `syncTime` never reports `set_sync_time_not_needed_notification`.
|
|
108
|
+
|
|
109
|
+
### Added (round 7)
|
|
110
|
+
|
|
111
|
+
- Session lifetime documented: the app disconnects a device as soon as its sync
|
|
112
|
+
ends, unless `realTime` is on for it — then the session stays open until
|
|
113
|
+
`realTime` goes off or the link drops (docs, contract).
|
|
114
|
+
- Every sync stage emits its `…_notification` code when it **starts**, then
|
|
115
|
+
`…_not_needed_notification` if it had nothing to do; a pending `syncTime` /
|
|
116
|
+
`deleteMemory` counts as done at `pipeline_done_notification`, and a failed
|
|
117
|
+
sync keeps it pending.
|
|
118
|
+
- Harness: simulated sessions emit each stage's start code (and "not needed"),
|
|
119
|
+
and end by themselves after the sync unless `realTime` is on; switching
|
|
120
|
+
`realTime` off ends a session it kept open.
|
|
121
|
+
|
|
122
|
+
### Added (round 6)
|
|
123
|
+
|
|
124
|
+
- `docs/bridge-contract.md`, sync progress: which `*_error` end a sync and the
|
|
125
|
+
two that don't (`config_write_not_persisted`, `save_device_telemetry_error`);
|
|
126
|
+
the session end is the only event with `isConnect: false` and
|
|
127
|
+
`isError: false`, and it is not muted — no need to read `message`.
|
|
128
|
+
- `deviceLightSensors` `data.time` documented: the phone's receive time, epoch
|
|
129
|
+
ms UTC (types and docs).
|
|
130
|
+
- No order is guaranteed between events at start (`systemStatus` vs the
|
|
131
|
+
`userDevice` answer).
|
|
132
|
+
- `deviceSyncErrorById` can be cleared seconds after a failed sync (the failure
|
|
133
|
+
saves telemetry too); the durable record is `devicesTelemetry[].syncErrors`.
|
|
134
|
+
- Harness: a failed simulated sync sets `deviceSyncErrorById` /
|
|
135
|
+
`deviceSyncError` and emits `systemStatus`; the next good sync clears it.
|
|
136
|
+
|
|
137
|
+
### Fixed (harness)
|
|
138
|
+
|
|
139
|
+
- The `systemStatus` and `language` re-emitted after the handshake were
|
|
140
|
+
snapshots taken before it, so a `setStatus` made in between was undone.
|
|
141
|
+
- `deviceConnection`, `deviceNotification` and `deviceLightSensors` carry
|
|
142
|
+
`deviceId` only when the `serialNo` is shared, as in the app (it was on every
|
|
143
|
+
event from 0.17.0). A repeated `setConnected` with the same state emits
|
|
144
|
+
nothing.
|
|
145
|
+
- `docs/device-testing.md`: `?scenario=` in the URL replaces a `--scenario`
|
|
146
|
+
file, and the puppeteer recipe no longer passes it; `history.days` goes to 30.
|
|
147
|
+
|
|
148
|
+
- Only the `default` scenario starts a BLE session by itself (after 3 s), as the
|
|
149
|
+
docs said; every scenario did, which made tests of `shared-serial` flaky.
|
|
150
|
+
`Scenario.firstSessionAfterMs` sets it (`null` in a scenario file turns it off).
|
|
151
|
+
|
|
152
|
+
### Fixed
|
|
153
|
+
|
|
154
|
+
- `examples/minimal-home`, `AGENTS.md`, `README.md`, the agent guide and pilot
|
|
155
|
+
onboarding treated a `userDevice` carrying `error` (0.18.0) as an empty roster —
|
|
156
|
+
"no devices" on a network error. Only `NO_DEVICES` empties the list now.
|
|
157
|
+
- `dev --help` and `docs/bridge-contract.md` said the harness reports 0.17.0; a
|
|
158
|
+
drift test now ties every mention to the contract.
|
|
159
|
+
- `docs/client-guide.md` pointed at `examples/client_demo` as if it shipped in
|
|
160
|
+
the package.
|
|
161
|
+
|
|
162
|
+
### Changed
|
|
163
|
+
|
|
164
|
+
- The harness reports `nativeVersion` 0.18.0 by default: `cold-start` answers
|
|
165
|
+
the lost request once the service is up, `no-devices` answers with
|
|
166
|
+
`error: 'NO_DEVICES'`, and `systemStatus` carries `hasDevicesKnown`.
|
|
167
|
+
`--native-version 0.17.0` reproduces the old silence.
|
|
168
|
+
- Harness behaves like the app on connections: `deviceConnection` `add` reports
|
|
169
|
+
only changes (no immediate `isConnect: true`); the first device of `default`
|
|
170
|
+
starts a BLE session after 3 s; `realTime` streams `deviceLightSensors` once
|
|
171
|
+
per second while the device is connected; without internet `sensorLog` fails,
|
|
172
|
+
and `offline` recovers after `setStatus({ internet: true })`. `lightSensorsReading`
|
|
173
|
+
is exported from `/dev`.
|
|
174
|
+
- **Type change:** `SensorLogQueryPayload.bodyPart` is `'wrist' | 'necklace'`.
|
|
175
|
+
- **Type change:** `BridgeClient.send` is typed per command (`CommandArgs`,
|
|
176
|
+
`CommandName`, `DeviceAddress`): a string `serialNo`, a missing argument or an
|
|
177
|
+
unknown command no longer compiles. Each of them was already dropped in
|
|
178
|
+
silence by the app.
|
|
179
|
+
- **Type change:** `requestUserDevices()` gained two statuses. Code that read
|
|
180
|
+
`result.hasDevices` after checking only `status !== 'ok'` must now narrow to
|
|
181
|
+
`status === 'timeout'` (TypeScript flags it).
|
|
182
|
+
- The contract's `versions.nativeVersion` is 0.18.0; the two `userDevice`
|
|
183
|
+
silent-drop paths are marked `untilNativeVersion: 0.18.0`.
|
|
184
|
+
|
|
185
|
+
## [0.22.0] — 2026-10-20
|
|
186
|
+
|
|
187
|
+
### Added
|
|
188
|
+
|
|
189
|
+
- **`condor-mobile-bridge canvas publish | list | revoke`** — publish the bundle
|
|
190
|
+
of your company in the Condor Canvas app from the command line, through the
|
|
191
|
+
same endpoints and login as the Condor web admin. The bundle is validated
|
|
192
|
+
locally first; the command shows company, version, size and hash and asks
|
|
193
|
+
before sending; the password is read only from a prompt without echo (there
|
|
194
|
+
is no flag or variable for it) and never stored; the two-factor code is asked
|
|
195
|
+
when the company uses it. Production API by default; `--env dev` for Condor
|
|
196
|
+
staff, `--base-url` to override. Company and user ids may come from
|
|
197
|
+
`CONDOR_CANVAS_COMPANY` / `CONDOR_CANVAS_USER`.
|
|
198
|
+
- `docs/device-testing.md`: publishing from the command line, and the test
|
|
199
|
+
company workflow.
|
|
200
|
+
|
|
201
|
+
### Changed
|
|
202
|
+
|
|
203
|
+
- `docs/device-testing.md` no longer sends clients to a development
|
|
204
|
+
environment: clients work in production, on a test company.
|
|
205
|
+
- `AGENTS.md`: an agent must never run `canvas publish`; it hands the command to
|
|
206
|
+
the human.
|
|
207
|
+
|
|
10
208
|
## [0.21.0] — 2026-10-20
|
|
11
209
|
|
|
12
210
|
A client built from the 0.20.0 docs worked in the browser and stalled on the
|