One bit the phone needs at every turn
When a walk's turn comes, the phone has to answer one question before it posts a lock-screen notification: is the watch app already tapping this turn itself? If its navigation session runs, it is, and a mirrored notification on top would tap Dad twice for one corner. Track 5 walked through that rule (TurnAlertPolicy.shouldAlert) and the watch's mirror of it. This lesson is about the input it depends on: how the phone comes to know the watch's session is live, and why the first answer was wrong.
The watch is the only one who knows, so the watch has to say it. While its session runs it says so every watchingEvery = 30 seconds; when the session ends it says so once, at once. The phone believes a "live" for watchingTrust = 75 seconds: two missed beats and a margin. A watch whose app was swiped away never gets to say "ended", so the phone simply stops believing it, and the notifications come back.
First version: a heartbeat in a durable queue
The first wrist sent that heartbeat the same way it sent Dad's taps, with link.capture(.watching(true)). Lesson 1 described that lane: the watch writes each entry to its own outbox, hands it to transferUserInfo, rings the phone if it can, and lets go only when the transfer reports it landed. For a tap, that is exactly right. For a heartbeat it is a slow leak.
On the bench, a watch simulator with no phone near, the controls page read 4 items waiting for the phone after three minutes and 10 after six (coop learning #734). Nothing can land while the phone is away, so nothing is let go, and every beat adds one. On a real walk with the phone out of reach that is two a minute. Then the phone comes back, the link's flush re-offers the whole pile, and every stale "I'm live" costs a transfer and a ring. The phone app gets woken repeatedly to be told, in order, a pile of things that stopped being true minutes ago.
Nothing was broken in the lane. The heartbeat was a fact riding a lane built for acts.
The fix: the watch's own application context
The application context has two ends. The phone's updateApplicationContext fills the watch's receivedApplicationContext, and the watch's own call fills the phone's. Pathfinder used only the downward half for the glance. Commit fc2558d used the upward half for presence: the watch seals WristCommand(action: .watching(live)) and publishes it as its application context. Latest wins, so while the phone is away there is one slot, overwritten every 30 seconds, never a queue. Nothing rings, so a phone asleep in a pocket is never woken for it. After the change, the bench outbox held at its old count through two minutes of a running session.
The phone does not listen for it either. WatchBridge.watchLive reads receivedApplicationContext at the moment a turn asks, opens it as a WristCommand, and hands it to TurnAlertPolicy.watchLive(presence:now:). That is the kit's rule from lesson 1 ("read, not hooked") working in the other direction: updateApplicationContext is a method, not a delegate callback, so the kit's proxy stays the session's only delegate on both devices.
Reading a fact: its own clock, its own kind
Two details make the read trustworthy.
- The fact carries its age. Liveness is judged by the presence's own
issuedAt, not by when the phone happened to read it. A context the phone reads ten minutes later says exactly how old it is. A lane that keeps only the latest word makes that word the watch's only word, so its timestamp is the whole truth. - Only a presence counts. The phone's context slot is opened with
WristEnvelope.open(WristCommand.self, from:), which checks the envelope's kind first. Anything else in the slot reads as no word at all. The hosted test seals a glance, hands it towatchLive, and asserts false: "only a presence counts".
Two more lines of the watch's code are worth reading for what they leave out. announcePresence does nothing until the session has activated, and does not retry; a word lost that way is said again on the next beat. And the phone's command door still refuses a .watching that arrives through the capture lane. An old watch build, or a mistake, cannot turn a presence back into a command.