Skip to content
C.W.K.
Stream
Lesson 01 of 07 · published

The Watch Is a Five-Second Surface

~14 min · on-the-wrist, watchos, design, capture, status

Level 0Bundle Opener
0 XP0/81 lessons0/17 achievements
0/100 XP to next level100 XP to go0% complete
"Put the watch in. It may not look like much, but for someone who wears one, it is really convenient."

What a Wrist Is For

A watch app is not a small phone app. It is the thing you can use while your other hand holds a bag, in the five seconds before a thought is gone. So the family's watch apps have one door: raise the wrist, tap the complication or press the Action Button, speak, and the capture is safe. No lists to browse, no settings to read, no keyboard, because typing on a wrist defeats the reason the app exists. The journal app's first watch view used a SwiftUI text field labelled "Speak", and on a real watch it opened the keyboard: a control whose label promised speech while the screen asked for typing. The dictation lesson in this track explains why SwiftUI cannot ask for the microphone there.

The Phone Is the Watch's Engine

The family's engines live on a private network the watch is not part of, and never will be. The App Group that ties the phone app to its extensions does not cross a pairing either: it is shared between processes on one device. So the watch does not talk to the engine at all. It hands its captures to the phone, the phone treats them like any other capture, and the phone's outbox takes them home. Every design choice in this track follows from that one hop: the watch keeps its own queue, the handoff is treated like a network send that can fail, and the capture carries the time it was spoken, not the time it arrived, because a phone that is off can take hours to receive it.

Surfaces Trusted at a Glance Must Not Overclaim

People read a watch in a glance and believe it. That makes stale or overconfident text worse here than anywhere else. The journal's watch view said "N waiting for the phone" when its queue was not empty and "Handed to the phone" otherwise, and on a fresh install, before a word had been spoken, it announced a handover that never happened. There are three facts, not two: something is waiting, something was delivered, and nothing has happened yet. The same reasoning keeps counts off complications, which refresh on the watch's schedule rather than when you capture, and it is why the phone's answer to the watch's ring matters: "waiting for the phone" and "the phone has it" should come from what the phone actually said.

What Stays on the Phone

Deciding what the watch does not do is most of the design. It holds no engine address and no PIN, because it never talks to the engine. It shows no history to browse and no settings to change; those belong to the phone, where there is room to read. It keeps no copy of the phone's data, because nothing on the wrist needs it. What remains is small enough to trust: a queue of words not yet handed over, a status line, and one way in. That smallness is also why the watch app ships inside the same TestFlight build as the phone app and costs almost nothing to maintain.

Code

One capture, from the wrist to the engine and back·text
  WRIST (not on the private network, never will be)
    tap the complication, or press the Action Button
      -> dictation opens (one door)
      -> the words commit to the watch's own queue            <- the capture is safe here
      -> transferUserInfo (the record) + sendMessage (the ring)

  PHONE (the watch's engine)
    one door for both deliveries, a ledger that drops the echo
      -> the phone's capture store, idempotent on the capture id
      -> the outbox drains to the engine at home when it can

  BACK TO THE WRIST
    the transfer's didFinish        -> the watch lets go of the entry
    the ring's reply                 -> "the phone has it", before didFinish arrives
A status line built from facts the watch holds, with nothing-happened-yet as its own state·swift
import Foundation

/// What the wrist may say, from facts it actually holds.
struct WristStatus: Equatable, Sendable {
    var waiting: Int            // every entry still in the watch's own queue (no didFinish yet)
    var handedOver: Int         // the phone answered the ring: it has these, the transfer is still settling
    var capturedThisSession: Bool
    var phoneReachable: Bool

    /// Three different facts, never two: something waits, something was delivered, nothing happened yet.
    var line: String {
        let stillOnWrist = waiting - handedOver
        if stillOnWrist > 0 {
            return phoneReachable ? "\(stillOnWrist) sending to the phone" : "\(stillOnWrist) waiting for the phone"
        }
        if capturedThisSession { return "The phone has it" }
        return "Nothing waiting"
    }
}

External links

Exercise

Write down Spark's one wrist door in a single sentence, then list every other screen or control you were tempted to add to the watch app and the reason each one stays on the phone. Add WristStatus to the watch target and write tests for four moments: a fresh install, two captures with the phone out of reach, three captures the phone has answered for, and a mix. Read each line aloud and change any that claims more than the watch knows.
Hint
If a line would still be true when the phone is off and nothing has been captured, it is probably overclaiming in one of the other cases. capturedThisSession resets on launch on purpose: yesterday's success says nothing about this minute.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.