also looking at this
docs(android): thirteen more deferrals, so the remaining 65 reads honestly
#180
1 file
+60
-2
| @@ -207,8 +207,9 @@ const deliberate_deferrals = [_]Deferral{ | ||
| 207 | 207 | // answer from Zig for. Moving a refusal across changes which language |
| 208 | 208 | // holds the string and nothing a page can observe, so it would lower the |
| 209 | 209 | // 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. | |
| 212 | 213 | // |
| 213 | 214 | // Each is also honest as it stands, which is the other half of the reason. |
| 214 | 215 | // Unlike `setBadge`, a page calling these is told plainly that the feature |
| @@ -237,6 +238,63 @@ const deliberate_deferrals = [_]Deferral{ | ||
| 237 | 238 | .{ .action = "stopAR", .reason = "resolves {stopped:true} for a session startAR always refuses to open" }, |
| 238 | 239 | .{ .action = "getARPlanes", .reason = "resolves [], which a page cannot tell from AR running with no planes found" }, |
| 239 | 240 | .{ .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" }, | |
| 240 | 298 | }; |
| 241 | 299 | |
| 242 | 300 | test "every recorded deferral is real, and still a deferral" { |