"iOS buys OS capability, not network reach."
One Writer, Many Windows
Every family app with a phone or Mac client has the same shape. The engine runs on one Mac at home and owns the record: its database, its logs, its rules. The native apps are thin clients. They capture, display and decide, and they talk to the engine over the tailnet, the private network that already joins the family's Macs, phones and iPads. There is no public route to an engine, no Tailscale Funnel, and no second copy of the database on a device. No tailnet means no app, and that is accepted rather than engineered around.
So what does a native client add, if not reach? The family wrote the list down so nobody re-derives it per app: reliable push, a share extension, App Intents and the Action Button, declared background modes, the camera as a capture surface, storage the system will not evict, background audio, widgets and Live Activities, haptics, and a native control for anything a web button must not be able to answer. None of it changes the trust boundary or which machine writes the record.
How a Client Reaches the Engine
An engine binds 127.0.0.1 on its port and never listens on a network interface itself. Tailscale Serve publishes it to the tailnet as twins: plain HTTP on the same port number, and HTTPS on the adjacent port with a certificate for the Mac's tailnet name. On the engine's own Mac a client can use loopback; everywhere else it dials the tailnet name and one of the two twins. The next lessons cover what each twin costs a client (App Transport Security) and what it costs a Swift engine (how it binds).
Because the twins sit on adjacent ports, an address without a written port is a wrong door dialled silently. The shared endpoint type is therefore strict: a scheme of http or https, a host, a port from 1 to 65535 that the user actually typed, and nothing else but a trailing slash. A pasted path, a query or credentials are refused with a reason instead of being dropped. Every question about the address (is it loopback, which keychain account holds its PIN) is answered from the parsed host, because one app once answered "is this loopback?" from the raw text while another method used the parsed value, and a local address demanded a PIN.
The Engine Is Older Than the App
A phone updates through TestFlight on Apple's schedule; the engine updates when someone restarts it. A new build of a travel journal parked a capture with "Method Not Allowed". The request was fine. The engine at home had been running for days without the new route, and because it serves its web client from / through a static-files mount that accepts only GET and HEAD, an unknown POST fell through to it and came back 405 instead of 404. Restarting the engine fixed it. The fix that lasted was in the client: it now says that the engine is probably older than the app, names the restart command, and treats the capture as retryable rather than refused. When a client and an engine change together, expect this skew every time.