ReviewOS

also looking at this

craft-native/craft

test(bridge): the other direction — what window.craft promises

#141
Merged chris wants to merge test/page-surface-conformance into main
2 files +220 -0
10 days ago

Closes the direction ios_conformance_test.zig was explicitly never checking. Reports #140.

The check that existed, and the half it left out

every action the page can call is one the spec handles — The ota* check. Five methods in the injected JavaScript posted actions the switch never handled…

That is JS → dispatcher. The reverse — a declared method with no implementation — has never been checked, and it is where the surface has drifted furthest.

craft.d.ts declares window.craft as CraftBridge, an interface of 78 direct methods:

  • 33 are not on the object iOS injects
  • 17 are on neither platform's

They type-check and throw TypeError. And 16 of them work on Android, so the same source compiles and runs on one platform and not the other:

const tag = await window.craft.scanNFC()   // fine on Android, TypeError on iOS

What this does not claim

The actions work. Both bridges expose a generic escape hatch — craft._invoke(action, payload) on iOS (CraftApp.swift:2543), craft.invoke('namespace.action', params) in craft-bridge.js — and the dispatcher arms behind these names are implemented, several of them now in Zig.

So the gap is a missing named method and a declaration promising one, not a missing capability. Worth stating plainly, because "33 unreachable actions" was my first reading and it was wrong — I went looking for the hatch before filing, and it exists.

Already found once, by hand

bridge_mobile_motion.zig documents exactly this for a single action:

Worth knowing while reading any of that: window.craft.startMotionUpdates does not exist in the injected iOS JavaScript. … That is a second pre-existing bug

One module noticed, wrote it into its own header, and nothing else could see it. Same shape as craftShortcut in #127 and the twelve gate claims in #132. It is thirty-three, not one.

Ratchets, not a table of reasons

A table row would have to carry a reason, and there is no honest reason to write for most of these — they are a backlog nobody has worked through, not decisions. ios_conformance_test.zig already records why demanding one for that state "would only invite an invented reason". The failure message names every offender, so the count loses nothing:

craft.scanNFC() is declared, exists on Android, TypeError on iOS
craft.getContacts() is declared and exists on neither bridge
...
33 declared methods are absent from iOS (allowed 33), 17 from both (allowed 17).
  A method on `CraftBridge` is a promise that `window.craft.<name>` is callable.
  Either implement it on the bridge or stop declaring it.

One judgment call

Nesting depth is ignored when looking for a method. craft.d.ts declares these as direct methods, so a namespaced implementation (craft.contacts.getAll) is still a mismatch with the type — but it's a different mismatch from nothing existing at all, and treating the second as "missing" while quietly passing the first would hide the worse case behind the milder one.

Verified

mutationresult
SDK declares a method on neither bridgeDeclaredMethodMissingFromBridge
ratchet lowered 33 → 32fails — the count really is 33
the window.craft = { scan brokenboth non-vacuity floors, and cleanly (orelse return, not .?, so a broken scan names itself instead of aborting the runner)

Its own gate rather than folded into the iOS one — it embeds three files and is about window.craft, not about iOS. Joins test, test:ios and test:android, since both platforms owe this contract.

zig build test:ios 21/21 + surface gate · test:android 9/9 steps.

2 changed files on the files tab, with 0 review threads.

Sign in to comment on this pull request.