Code Raven · Plan · 19 September 2026 · Complete · corrected for TestFlight · runtime unverified

Mobile Setup Guide + Remote Meetings Readiness

The phone has no reader-facing document anywhere, and remote meetings depend on a four-link chain where a break in any link looks identical from the phone. One standalone guide, plus the three places the apps failed without saying why.

17Tasks
4Phases
5Files touched
6Guide steps
The gist

Code Raven Mobile is documented only in Mobile/README.md, which is written for whoever builds it, and the fourteen-week guide series stops at Ventures without mentioning the phone. This writes the missing setup guide as a standalone page in docs/guides/, and closes the three places where the Mac and the phone report a broken remote meeting without saying which link broke.

The problem

Getting a meeting onto the phone requires four things in order: the iOS app installed from TestFlight, signed by the team that owns iCloud.com.lochlustra.Code-Raven; iCloud working, because Documents/remote/pairing.json in the shared container is how the phone learns the server URL and bearer token; Tailscale signed into the same account on both devices; and Publish to Tailnet on the Mac.

Two prerequisites exist in the code and in no prose. HTTPS certificates for the tailnet surface only after a twenty-second hang, inside a timeout string in RemoteTailscale.publish(). MagicDNS on the phone is never mentioned at all, and the Mac reads its own name out of status --json precisely because Tailscale DNS is off here, so the Mac can publish happily while the phone cannot resolve anything.

On the phone, connectionFailed accepted an error parameter and never read it. DNS failure, connection refused, TLS failure and connect timeout all produced one sentence: Your Mac is unreachable. Is it awake, with Tailscale signed in on both devices? — the wrong instruction for three of the four.

Approach

A standalone guide, outside the weekly series

Setup is a one-time gate that has to be readable before whichever week teaches the phone, and the resume tally in start-here.html counts sets. A page absent from statics.WEEKS gets no data-key from _KEYS, so it lists in the index without entering the count. The reference card already works this way.

TestFlight, so Xcode stays out of the reader’s path

Step 1 first sent the reader through Xcode, xcodegen and the Signing & Capabilities pane. That is the development machine’s path and never a second user’s: the Mac app already arrives as a Developer ID DMG through the Sparkle appcast (scripts/deploy-beta.sh, Loch Lustra LLC X7BPXRX9NA) carrying the container entitlement, and the phone app arrives through TestFlight signed by the same team. Neither half asks him to open Xcode, and step 1 fell from 25 minutes to 10.

Reuse the existing state machines instead of adding one

RemoteTailscale.PublishState already distinguishes every Mac-side failure worth naming, and ClaudeSetupService with ClaudeSetupView is the established pattern for one instruction per state. The Settings block gained a fix sentence under the Tailnet row rather than a new service.

A cause payload on .unreachable, not a longer sentence

The phone's State.unreachable(String) carried a pre-rendered sentence, so the card could not branch on cause. It now carries MeetingConnection.Unreachable, whose ten cases each supply a message and an optional card title. A DNS failure reads This phone cannot look up your Mac and points at MagicDNS; a refused upgrade reads The Mac refused this phone and points at the token.

Decisions made for you

Judgment call

Full walkthrough machinery, not a static page

The guide uses the step shell with its localStorage key and copy-for-the-call plumbing, because setup is a checklist someone works through alone and reports back on. Reversible by moving it to statics.py.

Judgment call

A refused upgrade re-reads the pairing record on retry

The fix for a rejected phone happens on the Mac and arrives as a new pairing record. Retrying the same token would never see it until the app next came to the foreground, so that one case passes rereadingPairing: true. Every other cause keeps the existing backoff untouched.

Judgment call

No new FlowMapKind

The page lands in docs/guides/ and surfaces in the existing Guides segment, on the Mac and through MobileDocumentMirror on the phone.

The work

Phase 1 — The setup guide

6 tasksEverything here is generated. The spec is the source; the HTML is a build product.
  • Write docs/guides/_generator/spec_mobile.py with the mobile-setup.html spec: filename, title, meta, lede, h1, banner, key, reportTitle, steps, reflect
  • Cover the four links in order as steps: install the app, confirm iCloud sync, install and sign into Tailscale on both devices, publish and connect
  • Name the HTTPS-certificates admin-console step explicitly, as its own step rather than a troubleshooting note
  • Put MagicDNS, the otherConfig Serve collision, and the stale-pairing-record case in tr troubleshooting blocks
  • Add a ROWS entry for the guide in statics.py, keeping it out of WEEKS so the weekly tally does not move
  • Build the page and confirm the run prints voice:clean and no OVER-BUDGET! on any of the three scanner tags

Phase 2 — Mac-side readiness

4 tasksThe state machine already existed; only the guidance was missing.
  • Give each RemoteTailscale.PublishState case a fix sentence in MobileSyncSettingsSection, rendered under the Tailnet row
  • Make the cliMissing state carry a download link to the Tailscale Mac app
  • Name the HTTPS-certificates prerequisite in the Remote Meetings prose instead of only in the publish timeout string
  • Add a pairing-record row that says when the phone last had a readable record, so an iCloud break is distinguishable from a Tailscale break

Phase 3 — Phone-side failure distinction

3 tasksconnectionFailed took an error it never read.
  • Extend the phone's meeting connection error type so a missing pairing record, an unresolvable host, a refused connection and a rejected upgrade are four different states
  • Write the user-visible sentence for each state on the Meetings tab, each naming the thing to go and fix
  • Keep the existing retry behaviour unchanged for transient failures, and stop retrying where the fix is manual

Phase 4 — Verification

4 tasksCompile-level only. No part of this has run against a real phone.
  • Build the Mac target and verify the product with scripts/verify-build-product.sh
  • Run xcodegen generate and build the iOS target
  • Run python3 lintpages.py from the generator directory and confirm the new page is clean
  • Render docs/plans/mobile-setup-guide.html from the plan template

Risks & edge cases

Risk

Nothing here has run end to end

Remote meetings have never been exercised against a real phone. Both targets compile and the guide describes a path no one has walked, so every claim about what the reader sees comes from source.

Edge case

The guide names UI that Tailscale owns

Steps 3 and 4 describe the admin console's DNS page. A redesign there makes the guide wrong with no signal.

Edge case

Two copies of the same failure list

Step 5's troubleshooting mirrors publishFix and step 6's mirrors MeetingConnection.Unreachable. Adding a case to either without editing the spec leaves the guide quietly short.

Risk

TestFlight builds expire after ninety days

The expiry arrives as an app that stops launching, and the fix is an upload from Sarah rather than anything he can do. Roughly four uploads across a one-year assignment. Neither app warns that a build is close to expiring, and nothing reminds her.

Risk

A second user configures his own tailnet

Every account-level instruction assumes the reader owns the tailnet being configured, which holds for a second user with his own Mac and phone and fails if anyone tries to join an existing one.

Out of scope

Final review

The strongest part is that the guide and the two code changes were derived from the same reading of the chain, so the troubleshooting text in steps 5 and 6 matches what the apps now actually say. The ordering is forced by the code rather than chosen: iCloud carries the pairing file, so it has to work before Tailscale means anything, and that ordering is what makes the guide worth following.

The weakest part is verification. Nothing has been run against a phone, and a setup guide is exactly the kind of document where a single wrong label wastes an evening. Step 1 was also wrong on its first pass, which is the useful warning here: it described the development machine’s install path because that is what the repository documents, and the real one was never written down anywhere.

If pushed to adjust one thing, I would fold step 4 into step 3 and keep the certificate setting as a caveat, since both steps happen in the same admin console within the same minute. Reflection question m3 exists to settle that with evidence rather than taste.

78/100

High confidence in the shape: the dependency order is read out of the source, the two code changes reuse patterns already in the app, and the guide passes the voice build clean. Held back from higher by the absence of any runtime verification, and by a ninety-day TestFlight expiry that nothing in either app warns about.