# Private device notes — local v0.23

This checkpoint adds deliberately retained, private **text notes** for an already authorised owner or assigned worker. It is suitable for further local testing with invented records. Signed-in browser journeys and real phones have not been verified for this change. It is not live approval or full PWA/Site Mode parity.

## Use the local feature

1. Open **Field work**, choose an active permitted job, and select **Unlock this job’s device notes** while connected.
2. Enter a text note and work date. Select **Keep on this device — do not send**. Wait until the note appears as **Device only · never sent**. Until then it is an unfinished editor, not a retained note.
3. Reconnect when convenient, unlock the same job using the same account, and review its retained text. Select **Check / retry this note** deliberately. Reconnecting alone never submits anything.
4. Wait for **Server confirmed**, then review the saved field log. Only then use **Remove confirmed device copy** if wanted. Removing one copy never clears other notes.

The unlock lasts at most two minutes and ends when the tab becomes hidden. During a brief connection failure an already unlocked page can retain text; it cannot open private notes from a cold offline start. Returning to the tab or reopening the browser requires a connection and current job access. Periodic access checks can lock the view sooner.

| Display | Meaning and next action |
|---|---|
| Editor only | Keep the page open. Select Keep and wait for confirmation of device retention. |
| Device only · never sent | The browser confirmed the encrypted write. The server has not confirmed a log. |
| Sending · confirmation pending | The operation identity and request are already retained. Wait; reopening uses the same identity. |
| Outcome uncertain | A response was lost. Check / retry first checks the server receipt, then sends only if needed. |
| Needs correction | The text and identity remain. Review / correct while connected; keeping the correction blocks delayed older versions. |
| Server confirmed | One server log and its creation history exist. A repeated delivery returns that same receipt. |

Validation failures and changed-job conflicts retain the original text. A correction retains both versions before asking the server to block an older delayed send. If that reply is lost, Check / retry finishes retaining the correction; a separate deliberate retry sends it. If an earlier version was already committed, the display identifies that revision and keeps the proposed correction. Select Inspect exact saved log to compare its current text and version. Apply proposed text to saved log uses the ordinary permissions, correction reason, version check and history, and creates no new log. Alternatively discard only the proposed correction. A pending or uncertain correction must be resolved before its text is discarded.

If an editor is still open after a failed correction attempt, its text is also displayed in the retained candidate below. Discarding **that unfinished editor** enables Check / retry without removing the retained candidate. Storage/quota failures leave the editor open and do not report success. A locked unfinished editor has an encrypted, memory-only copy and continues to block app updates and warn before leaving; it does not survive closing the page. Return with the same access to retain it or deliberately discard it.

## Privacy and retention limits

- IndexedDB stores an opaque device ID and encrypted per-note envelopes: random operation ID, opaque scope, revision counter, nonce, ciphertext and a short processing claim. Version 2 also keeps opaque closed-operation receipts (operation ID, scope and outcome only), atomically written when a device entry is removed. It stores no session, password, account/customer/job record, photo, text label, replay URL or encryption key. The service worker does not receive these notes or cache their API responses.
- AES-GCM uses a fresh random 96-bit nonce and authenticates the envelope identity/scope/counter. The server derives separate scope and encryption values using domain-separated HMAC-SHA256 over the dataset, business, actor, membership/version, exact job/customer and device, keyed by the existing server-held salted scrypt password representation. Imported browser keys are non-extractable and held in memory only. This local choice avoids a new backup secret; it is not a production key-management endorsement.
- Reopening requires the server to recheck current session, membership, assignment, active resource, active customer and active/non-archived job before returning a key. Each delivery, correction and receipt lookup repeats those checks under registry/tenant transaction locks. New deliveries also require the reviewed job version. Confirmed receipts may be read after a harmless job-version change; they do not create another log.
- Sign-out/business-switch events, hidden tabs, access-check failures and lease expiry clear displayed device text and key references. Late responses cannot acknowledge or reveal notes after that boundary. This is application-level protection, not a claim of forensic erasure of JavaScript memory.
- **Password changes, membership changes, customer transfers, lost access, deleted jobs or restored datasets can leave old ciphertext permanently locked.** It is kept rather than silently reset. No recovery/export of an old decryption key is provided. Confirm important notes with the server before changing access or passwords.
- Browser storage can be denied, full, evicted or deleted. Private browsing and another browser/device/origin do not share this store. Retention is not guaranteed against browser deletion, device loss or power failure. There is no remote offline wipe. A disconnected already authorised page may retain access until its local lease ends; a compromised unlocked page or machine is outside this protection.

The Web Crypto API supplies the encryption primitive, and browser storage has explicit quota and eviction limits. See the [W3C Web Crypto specification](https://www.w3.org/TR/webcrypto/), [HMAC specification](https://www.rfc-editor.org/rfc/rfc2104), [IndexedDB transactions](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB) and [storage limits](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria). Independent security review, production key lifecycle and device policy remain open.

## Scope and evidence

The original `useOfflineQueue.js` stored a shared plaintext array of arbitrary endpoint/method/body requests, replayed on reconnect with current authentication and replaced the entire array after processing. `SiteModeTab.jsx` used it for material logs (204–241), text notes (245–274) and team size (315–334). This version maps **text notes only** to eight typed routes and a per-entry encrypted store. Material/photo uploads, timer/team changes or reconstructed time, invoices/payments, portal replies and generic replay remain **OPEN**, not agreed exclusions from full parity.

The note state machine uses one permanent operation UUID, per-entry transaction claims and compare-and-swap acknowledgement. A server correction advances a durable revision fence before a revised delivery. Concurrent tabs, new enqueues, partial acknowledgements and duplicate deliveries cannot replace the whole queue. Only explicit actions send; no online/reconnect/service-worker handler submits notes. Saving, encrypting, sending and unfinished editors participate in the v0.21 update guard; safely retained idle notes survive an update and require a new unlock.

See [original source map](device-note-source-map.json), [current lifecycle checks](device-note-lifecycle-checks.json), [lifecycle guide](DEVICE-NOTE-LIFECYCLE.md), [historical v0.22 checks](device-note-milestone-checks.json), `tests/test_device_notes.py`, `frontend/src/device-notes.test.js` and `device-note-evidence/`. Actual Chrome tests used the exact storage driver on a separate anonymous fixture origin, including real competing tabs and reload. They did not sign in, seed a browser session or exercise the private product UI. Authenticated end-to-end owner/staff, revocation/expiry UI, mobile/standalone, interrupted OS/background behaviour and accessibility remain unverified.

No database schema or recovery-format change was made. Existing `operations` rows hold typed creation, revision, cancellation and correction receipts; existing field logs/history hold confirmed notes. Recovery remains **format 12 / metadata 0.20.0**. The drill verifies confirmed logs, history and receipts survive backup/restore and a second backup, while new credentials/dataset identity cannot unlock old device notes. Device-only entries are outside server backup. Old packages, schemas, evidence and the user's running v0.19 review remain preserved.
