# Changelog ## 0.3.0 (2026-07-19) No breaking changes: no method name, event name, payload shape, or rejection code changed. Two behavior-observable notes: (1) the diagnostic test message on `messageReceived` no longer fires in Release builds (it was documented as filter-me noise; gate it back on via `setFileLoggingEnabled(true)`); (2) payloads containing `null`/`undefined`/non-finite values that previously failed with a native 'unsupported type' rejection now succeed with those values stripped by `plistSafe`. ### Fixed - Message-queue drain no longer loses messages: sends that fail during a batch drain (e.g. the watch drops reachability mid-batch) are now re-queued instead of silently dropped; the drain stops rather than hot-looping against an unreachable watch. - The bare native `sendMessage` path now actually enqueues the message before rejecting `QUEUED`, matching the `sendMessageWithReply` path (previously it rejected `QUEUED` without enqueuing, dropping the message). - `getFileTransfers()` now returns a **stable** id for a transfer it's already tracking, instead of minting a fresh UUID and registering a duplicate KVO progress observer on every call. - `checkWatchConnectivityStatus()`'s diagnostic test message (`messageReceived` with `source: 'test'`) no longer fires unconditionally — it's now gated on file logging being enabled (off by default in Release builds), so production listeners no longer see it. - All shared native module state (message queue, reply handlers, file transfers, logging flag) is now serialized on private dispatch queues, removing data races under concurrent watch traffic. No API-visible change. ### Added - `messageReceived` event bodies now carry a `source: 'message' | 'userInfo' | 'applicationContext' | 'test'` field identifying which WatchConnectivity channel the event came from. - Apple's `sessionReachabilityDidChange` delegate callback is now implemented — a pure reachability flip (no accompanying watch-state change) now fires `watchStateUpdated` promptly and triggers a queue drain, instead of requiring a poll of `getReachability`. - Reply handlers (both for queued phone→watch messages and for unanswered watch→phone messages) now expire after a 30-second TTL, firing a default reply (`{"error": "reply timeout", "acknowledged": false}`) instead of leaking indefinitely. - New `setFileLoggingEnabled(enabled): Promise` method to toggle the `Documents/watch_connectivity.log` file write at runtime (console logging is unaffected). The JS wrapper resolves `false` on older native builds that don't implement it yet, so it's safe to call unconditionally. - `plistSafe` sanitization is now applied automatically on every outbound send path (`sendMessage`, `updateApplicationContext`, `transferUserInfo`, `transferCurrentComplicationUserInfo`, `replyToMessage`, and `sendAuthTokensToWatch` via `transferUserInfo`) — `undefined`/`null` and non-finite numbers are stripped recursively (including as array elements), `Date` instances are preserved, and dropped array entries are filtered out densely. `plistSafe` remains exported for callers who want to pre-sanitize a payload themselves. - New `GetReachabilityResult` type (`activating?`, `pingSucceeded?`, `fromTimeout?`) formalizing `getReachability()`'s return shape. - `WatchState` gained optional `lastStateChange` (epoch seconds of the last delegate state change) and `queuedMessages` (current in-memory queue length) fields; `sessionState` now includes `'Unknown'` in its union. ### Changed - Native file logging (`Documents/watch_connectivity.log`) now defaults to OFF in Release builds and ON in Debug builds (was: unconditionally on with no size cap). Logged entries no longer include payload VALUES — only keys and the `type` field are written. The log file is capped at 512KB and all file I/O is now asynchronous. ## 0.2.3 and earlier - See git history.