A ruling still has to be checked
Dad's "웹키트로 가자" settled the engine. It did not settle whether the browser Dad needed could actually be built on it. So before any shell code, the plan set Phase 0: a throwaway spike that runs a gate of eleven cases on the Mac. Each case has three columns: what it is, when it passes, and what a failure would mean. The third column is the one most plans leave out, and it is what turns a test list into a gate. You decide in advance which failures change the plan.
It needed no download. The engine was already on the Mac, which is itself a small argument for WebKit.
The eleven cases and what they found
| Case | Result on 2026-10-03 |
|---|---|
| W1 1Password | Its Chrome package loads with no manifest error. As installed, its background stops at the first Chrome call WebKit leaves undefined; with stand-ins for the missing names it starts and asks for its desktop app. 1Password's desktop app refused every earlier signature and listed Voyager as a connected browser only once the installed bundle was signed with the paid team's Apple Development identity and the hardened runtime. Pairing, the pop-up and filling stayed open; the extensions track tells that story |
| W2 other extensions | The three other extensions Dad uses start, one of them only after two members WebKit defines were hidden. An assistant extension that drives pages through debugger and tabGroups fails, as expected |
| W3 Google sign-in, W4 bot checks | Pages load under a Safari user agent with no block or challenge; the sign-in itself was done by a person in the installed app, never simulated, and passed |
| W5 media | H.264/AAC, HEVC, VP9, AV1 and Opus playable; YouTube played. FairPlay available to a page, Widevine not |
| W6 trust | A microphone request reaches the host with its origin before any prompt, and a refusal is final. An HTTPS family name and plain-http loopback are secure contexts; a plain-http tailnet name is not and cannot ask at all |
| W7 identities | Cookies and stored values isolated per identity; a pop-up shares its opener's store |
| W8 input | Korean input right in pages and the address field (Dad typed it) |
| W9 sleep | interactionState restored address, scroll and back list, even from 624 bytes written to a file |
| W10 background memory | The 567-second result from the memory lesson |
| W11 Web Inspector | Not run: it attaches to an inspectable page, which the app sets, and Dad had not opened it yet |
Two habits in that table are worth copying. The results say what was exercised and what was not: the extension probe hosted no tab, pop-up or native messaging, so the evaluation says plainly that "starts" is not "works". And the cases that needed a person (a real sign-in, typing Korean, the microphone grant) are marked as needing a person, never simulated.
The exit line of Phase 0 reads: "every case a machine can judge has run, and none failed in a way that reopens the engine choice." The cases that needed a person moved into the first brick, where a real window served them better than a spike. The spike was thrown away, and nothing from it was promoted without being rewritten against the architecture.
Public API only
Architecture invariant 3 is short: the engine is the system's WebKit, through its public API. Voyager ships no engine and patches none. A private WebKit interface enters only by Dad's ruling, named in the architecture with what breaks when Apple changes it. None is used today.
The temptations are real, and the repo lists them by name: an inline inspector, text extraction, a raised memory limit. Every one would make a feature easier. Every one can break without warning on an OS update, in a browser Dad depends on daily. That is why the gaps in the previous lesson are answered in Voyager's own code (a find count from the page's visible text, a mute from its own content world) instead of reached through a private call. The parity contract's Never table holds the same line: no bundled or patched engine, and no private WebKit interface without Dad's ruling, because it breaks when Apple changes it, silently.
Presenting as Safari, coherently
Public API still leaves one decision that affects every site: what the browser says it is. WebKit's own user agent names no browser, and sites read that as an embedded web view; Google refuses sign-in to those, the same policy that counted against CEF. Voyager sets applicationNameForUserAgent, a public configuration property for the app name inside WebKit's user agent string, to the token Safari presents on the same system: Version/ plus Safari's version, then Safari/605.1.15. On the Mac it reads the version from Safari's own bundle. On iPhone and iPad, Safari's version is the system's, so it uses that. (If Safari's bundle could not be read on a Mac, the same fallback would apply there, where the system version and Safari's are not the same number.)
The Never table explains why it is all of Safari's token and not a tweak here and there: "Partial spoofing is what sites detect; one coherent identity or none." The pages, and Pippa's own panel, get the same token. A test in the real-WebKit suite, itPresentsSafarisToken, loads a page and checks what navigator.userAgent actually says.
The last line of public API in this story is one property: every page sets isInspectable = true, so Safari's Web Inspector can attach. That is Voyager's answer to Chrome DevTools: no private inline inspector, and the system's own tool through a door Apple documents.