ReviewOS

also looking at this

craft-native/craft

docs(android): thirteen more deferrals, so the remaining 65 reads honestly

#180
Merged glennmichael123 wants to merge docs/android-deferrals into main
1 file +60 -2
packages/zig/test/android_conformance_test.zigmodified+60-2
Changes to packages/zig/test/android_conformance_test.zig
@@ -207,8 +207,9 @@ const deliberate_deferrals = [_]Deferral{
207207 // answer from Zig for. Moving a refusal across changes which language
208208 // holds the string and nothing a page can observe, so it would lower the
209209 // count without serving anything. That is worth recording explicitly,
210 // because the incentive runs the other way: eleven rows here are eleven
211 // easy decrements left on the table.
210 // because the incentive runs the other way. Seventeen rows in this table
211 // are a refusal or a fabricated success — every one of them an easy
212 // decrement, and every one of them a way of making this number lie.
212213 //
213214 // Each is also honest as it stands, which is the other half of the reason.
214215 // Unlike `setBadge`, a page calling these is told plainly that the feature
@@ -237,6 +238,63 @@ const deliberate_deferrals = [_]Deferral{
237238 .{ .action = "stopAR", .reason = "resolves {stopped:true} for a session startAR always refuses to open" },
238239 .{ .action = "getARPlanes", .reason = "resolves [], which a page cannot tell from AR running with no planes found" },
239240 .{ .action = "closePDF", .reason = "resolves true and does nothing; the Kotlin says PDFs open in an external viewer" },
241
242 // ---- The background-task quartet, which schedules nothing ---------
243 //
244 // All four resolve success and do no work. `registerBackgroundTask`'s
245 // comment says "just track them" and nothing is tracked — no field, no
246 // map. `scheduleBackgroundTask` says outright that it "is a placeholder
247 // that shows the API structure", and the WorkManager dependency it names
248 // is not in the generated build.gradle.kts. The cancels then report
249 // success for work that was never enqueued.
250 //
251 // The same reason as `setBadge`: porting a fabricated success spreads the
252 // fabrication into a second language. Tracked in #178.
253 .{ .action = "registerBackgroundTask", .reason = "resolves {registered:true} and tracks nothing; the comment says otherwise" },
254 .{ .action = "scheduleBackgroundTask", .reason = "resolves {scheduled:true} and enqueues nothing; the Kotlin calls itself a placeholder" },
255 .{ .action = "cancelBackgroundTask", .reason = "resolves {cancelled:true} for work that was never scheduled" },
256 .{ .action = "cancelAllBackgroundTasks", .reason = "the same, for all of it" },
257
258 // ---- Two more refusals, one of which does look first --------------
259 //
260 // `scanNFC` reads the adapter before deciding *which* refusal to send —
261 // "NFC not available" when there is no adapter or it is off, and "NFC
262 // requires activity integration" otherwise. Both outcomes are refusals, so
263 // migrating it would move two strings and a null check.
264 //
265 // `isWatchReachable` returns false unconditionally, with a comment saying
266 // a CapabilityClient is what would answer it.
267 .{ .action = "scanNFC", .reason = "both branches reject; foreground dispatch was never wired up" },
268 .{ .action = "isWatchReachable", .reason = "returns false unconditionally; no CapabilityClient is consulted" },
269
270 // ---- State the natives cannot reach -------------------------------
271 //
272 // `watchPosition` and `clearWatch` share `locationCallback`,
273 // `fusedLocationClient` and `watchId`, all instance fields of the
274 // `CraftBridge` that `CraftNative` has no reference to — the wall the
275 // flashlight pair sits behind. They also have a bug of their own: a second
276 // watch overwrites the first's callback and orphans it, and `clearWatch`
277 // never reads its argument. Tracked in #179.
278 .{ .action = "watchPosition", .reason = "stores its callback in a CraftBridge field the natives cannot reach" },
279 .{ .action = "clearWatch", .reason = "reads the same field; migrating one half leaves the other stale" },
280
281 // The auth-persistence trio keeps its expiry in a plain `authSessionExpiry`
282 // field — which is also why the feature does not survive the process it
283 // exists to outlive. Zig would have to keep a second copy, and the two
284 // would disagree the moment either language answered a call. Once the
285 // expiry lives in a store both can read, all three become portable.
286 // Tracked in #162.
287 .{ .action = "setAuthPersistence", .reason = "writes authSessionExpiry, an in-memory CraftBridge field" },
288 .{ .action = "checkAuthPersistence", .reason = "reads the same field; a second copy in Zig would diverge immediately" },
289 .{ .action = "clearAuthPersistence", .reason = "zeroes the same field" },
290
291 // `registerVoiceAction` writes a preferences row that nothing reads, and
292 // both reply to `_craftVoiceResolve`, a global the injected bridge never
293 // assigns — so the reply is discarded by its own guard. Neither method is
294 // in craft.d.ts either. The port was written and deleted; migrating it
295 // would move the silence. Tracked in #169.
296 .{ .action = "registerVoiceAction", .reason = "writes a row nothing reads and replies to a global nothing assigns" },
297 .{ .action = "removeVoiceAction", .reason = "the same, for removing it" },
240298};
241299
242300test "every recorded deferral is real, and still a deferral" {