A session is a moment from the first second
Before the shutter, look at what opening the live camera already did. The view's .task calls lane.start, and that creates a moment of kind live on the engine: the phone samples the place with a two-second limit, takes the current time as the capture truth, mints the moment's ULID itself, and sends it with a client request id prefixed live-. Only when the engine has that moment does the status change to Point at text. Texts the scanner reported in the meantime were waiting, because flush refuses to run without a moment id, and the first flush sends them.
This moment is created straight against the engine, not through the phone's outbox, and it has to be. Every live answer comes from the engine's corpus or a brain behind it, so with no engine there is nothing for the lane to do. The status says so in words: Live needs the engine, followed by the reason. A session that closes before any text was seen is still kept, and the day's list on both the phone and the web marks it No text seen. It asked me nothing, and the record says so.
The shutter
At the bottom of the live camera sits a white 68-point circle with an ember ring. Its accessibility label is the whole design in six words: Keep this as a photo moment. Pressing it asks the scanner for a photo of what it sees.
There is a small ownership problem in that sentence. The button is SwiftUI. The scanner is a UIKit DataScannerViewController, created inside the representable's makeUIViewController, and SwiftUI never holds it. Spark bridges the two with a tiny class, ScannerHandle: the representable stores the scanner in the handle's weak property when it creates it, and the button calls shutter.capture(), which awaits the scanner's own capturePhoto(). If the scanner is gone or the capture throws, the handle returns nil and nothing happens. A weak reference means the handle can never keep a dismissed camera alive.
One line, shared with the camera
The photo goes back to RootView through the onPhoto closure. Read the third code block: the live camera's cover and the ordinary camera's cover end in the same line, keep { await PreparedPhoto.prepare(camera: image) }. The live camera closes, and from that line on, the frame is exactly what a camera still would be.
- The original is the image as JPEG at full resolution, and a 2048-pixel copy is made for me.
- Vision locates the lines on that copy, numbered in reading order.
- The place is sampled at the same time, and the situation chip Dad had chosen comes along. So does a pending crumb link, if one was waiting.
- The moment goes into the outbox on the phone before anything crosses the network, and it opens at once.
A still from the scanner carries no metadata of its own, the same as a camera still. AppModel.capture fills the moment's capture time and place into both the original and my copy, so the photo keeps its truth even if it waits on the phone for hours before it is sent (Track 9).
Two moments, on purpose
After the shutter there are two moments, not one. The live moment holds every text the camera saw that got an answer, each a short live-line. The new photo moment holds the frame, and its practical turn is my full reading of the picture: every line in context, explanations when Dad taps one, roots when they are real (Track 3). No field links the two. Each carries its own place sample and time, so they sit side by side in the day's list.
The handoff exists because the lanes see different things. The live lane gets words the scanner transcribed and answers in seconds with labels as short as the signs. The photo path gives me the image itself, so I can correct what Vision misread and explain what the sign means here. In the architecture's words, the frame becomes a photo moment and continues as practical photo translation. The shutter is how Dad says this one is worth the longer look.
The two paths also fail differently. The live session needs the engine. The photo does not: it is safe on the phone the moment the shutter's work returns, and it reaches the engine whenever the link allows.
Track 3 follows that photo from here: the copy I see, the numbered lines, and the spark block I answer in. Track 9 covers the outbox that keeps it safe until it is sent. If the general iOS mechanics interest you, Swift Quest has camera capture and UIKit-in-SwiftUI; its app is also named Spark, but it is an invented app.