bridge_mobile_locrecording.zig's module comment argues at length that the five
lifecycle recording actions cannot move to Zig. Two of its three blockers have
since been removed by other work, and nothing re-checked the argument. This is
the same failure mode as #110 and #114 — a reason recorded in prose, outliving
the thing it described.
The five actions are startLocationRecording, pauseLocationRecording,
resumeLocationRecording, stopLocationRecording, getLocationRecordingState.
They are rows in the deliberate_deferrals table in
packages/zig/test/ios_conformance_test.zig.
Blocker 1 — "a flag with no mirror" — expired
packages/zig/src/bridge_mobile_locrecording.zig:167 says startLocationRecording
is behind config.enableGeolocation, "a flag with no mirror anywhere in
packages/zig/src", and concludes that serving it would mean writing a location
track in an app that disabled geolocation.
packages/zig/src/ios_config.zig now reads craft.config.json — the same bytes
Swift decodes — and exposes exactly that flag at ios_config.zig:141
(.geolocation => "enableGeolocation"). Its own header states it closes this
gap, and names this very note as one of the two occurrences it closes
(ios_config.zig:5-7). It also singles out startLocationRecording as one of
only five gated Swift cases that answer CAPABILITY_DISABLED properly rather
than hanging (ios_config.zig:47), so the gated reply shape is already settled.
Blocker 2 — "an event channel that does not exist" — expired, wrong enum
bridge_mobile_locrecording.zig:190 says the recorder's live samples go out as
craftLocationUpdate, that capabilities.Channel has no such member, and that
.location_update is "craft:location:update" — "which no iOS page listens
for".
That is true of capabilities.zig:165, but it is not the enum the iOS event
path uses. ios_events.Event.location_update is "craftLocationUpdate"
(ios_events.zig:65, pinned by a test at ios_events.zig:259), and
bridge_mobile_location.zig:1249 already emits on it for the shipped
watchPosition. The channel the paragraph calls missing is in use one module
away.
Blocker 3 — the summary's memory-vs-file divergence — real, and portable
bridge_mobile_locrecording.zig:155-164 is still correct: Swift answers
getLocationRecordingState from memory, and
restoreLocationRecordingState's guard state["active"] as? Bool == true else
{ return } leaves id/startedAt nil after a relaunch that follows a stop,
while the state file still holds the stopped recording's values.
This is a rule, not a wall — a Zig reader that applies the same guard reaches the same answer. It has to be ported deliberately rather than discovered.
The deferral-table reason — mechanically true, but not an ObjC wall
The table's reason is "Coordinator.init re-adopts recordings at launch; Zig
cannot stop what Swift restored". The mechanism is real:
restoreLocationRecordingState() is called from init(config:)
(packages/ios/templates/CraftApp.swift:460), and SwiftUI runs
makeCoordinator() before makeUIView, where CraftZigRuntime.attach reaches
craft_ios_set_webview (CraftApp.swift:345). Swift restores first, every
launch.
But that is a statement about where Swift chooses to restore, not about
CoreLocation. craft_ios_set_webview only stores a pointer
(packages/zig/src/ios_dispatch.zig:474); there is no launch hook yet. An
adoption export that Swift consults in place of its own restore would settle it,
with the no-Zig fallback being exactly today's behaviour.
Why this is worth doing rather than just re-documenting
bridge_mobile_location.zig:106-116 records a live defect that only this
migration closes: while a recording runs, Swift's manager and Zig's manager both
feed craftLocationUpdate, so a page that records and watches gets roughly
two events per fix. Its own words — "it disappears when the recording actions
migrate".
The faithful shape is one manager with three consumers, which is what Swift
already is: a flag on the shared delegate plus two files. didUpdateLocations
(bridge_mobile_location.zig:1210) currently drops a fix when neither a
one-shot nor a watch is waiting, so a recording has to become a consumer there
rather than a second CLLocationManager.