Skip to content
C.W.K.
Lesson 03 of 05 · published

A Superset With Named Gaps

~15 min · parity, requirements, evidence, gaps

Level 0New Tab
0 XP0/44 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete

A word that is easy to say

"Superset of Chrome, Safari and Comet" is the bar Dad set, and it is the kind of claim that becomes false quietly. Voyager runs on WebKit, and WebKit is not Chrome's superset by itself. Some Chrome features do not exist there at all. The architecture says so in its second section, before any design: where WebKit lacks something Chrome has, the gap is named in the parity contract and then closed in the shell, bridged natively, or accepted by Dad. It is never hidden.

The parity contract is the document that does the naming. It is short, and almost every part of it exists to stop "superset" from being claimed past what is true.

One oracle, two instruments

Comet is the oracle, because it is the browser Dad lives in. Chrome and Safari are secondary oracles. None of them is a runtime dependency. Against that oracle the contract uses two instruments, and the split between them is the design:

  • The P0 table is what must already work before Dad is asked to live in Voyager. It was drawn from evidence on his own Mac, not from a feature list: the extensions in his Comet profile, the native messaging hosts registered there, a screenshot he shared, and his own words in the talk.
  • The departure log is everything after that. Rows enter the later tiers only from the log.

The contract compresses the second instrument into one sentence: a feature nobody left for is not work. That is what stops a superset goal from turning into an endless reimplementation of Chrome. The bar is everything Dad needs, and the log is how Voyager learns what that is.

What a P0 row looks like

Every P0 row has three columns: an area, a case, and the evidence that proves it. The areas cover credentials, extensions, sign-in that survives a restart, media (sound, full screen, Picture in Picture, an H.264/AAC file that plays and downloads), input (Korean composition in every field, Chrome's shortcuts on every surface a tab can show), the basics (download, upload, find, zoom remembered per site, print to PDF, pop-ups, HTTP authentication, certificate errors), the family half, Pippa, identities, the system (a link from another app opens in Voyager) and lightness.

The evidence column is the useful part. "Korean input works" is a claim. "Typed by hand, including composition at a line end" is a test someone can run. The contract's evidence rules tighten it further:

  • A case passes from /Applications/Voyager.app, never from a debug build.
  • A run that skipped a case is not a pass; the skip is named.
  • A case that passed on one macOS version is run again after a macOS update, because the engine changes with the system.

That last rule follows directly from the engine choice. When your engine ships with the operating system, an OS update is an engine update you did not schedule.

One row shows what the contract does when a P0 case fails. The first row is 1Password filling a login through its desktop app, and the contract writes the fallback next to it: if 1Password cannot connect from a browser of ours, Dad rules what is enough, and the row is rewritten to what he rules. The honest state of that row is the subject of a later track.

The gaps, each with a reason and an answer

The contract's engine-bound table is the core of this lesson. Each row is a thing WebKit does not do, the reason, and Voyager's answer. Read it together with the engine evaluation's list of what WebKit lacks, and all three answers from the architecture appear in practice:

AnswerExample
Closed in the shellWebKit has no public tab mute, so Voyager's own script mutes the page's media elements in every frame. WebKit's find answers only whether a match exists, so the "3 of 17" count is computed from the page's visible text.
Bridged nativelyWeb MIDI is absent in WebKit, and cwkBonfire uses it. A native bridge for that one family page is one option; the ruling is still open.
Accepted by DadOn iPhone and iPad, a family web app's service worker does not run without Apple's web-browser entitlement. Dad accepted that, because with no network the household servers are unreachable anyway.

The table ends with the sentence that gives the whole document its point: these are named here so that "superset" is never claimed past them. And each one that starts producing departures is a reason to reopen the reserved option in the engine evaluation: Voyager's features attached to an existing Chromium browser, reusing the same Swift core. A named gap carries its own escape hatch.

The phone is held to a different oracle

The iPhone and iPad app is measured against Safari on the phone, not against Chrome on the Mac, and its gaps get their own table: no service workers for family apps, no passkeys, not the default browser, no extension host, no downloads list. The entitlement that would close the first three was dropped on 2026-10-26, because Apple grants it only after a public TestFlight link and external Beta App Review, and no family app goes there:

아니, 근데 이거 다 디폴트 브라우져 자격 얻는데 필요한 깐깐한 심사 아니야? 디폴트로 안 쓰면 그만이잖아? 이걸 외부 베타 테스트 심사까지 받는다고? 그건 하면 안 돼.

"Wait, isn't all this the strict review you need to qualify as a default browser? If we don't use it as the default, that's the end of it, right? Going all the way through external beta review for this? We shouldn't do that."

That is a gap accepted with its price written down, which is exactly what the contract asks for.

Never, with the reason

The last table lists what Voyager will not build: a bundled or patched engine, a private WebKit interface without Dad's ruling, a second Pippa, a hand-kept list of family apps, and a workaround that hides Voyager's identity from a site in pieces. Each row carries its reason, and a row moves only by Dad's ruling with the departure that forced the question cited beside it. A "never" without a reason is a prejudice. A "never" with one is a decision the next session can check.

Gaps live in code as well as in documents. The address resolver's header says Phase 2 puts a family app's name in front of both (the launch catalog), and its resolve(_:) knows schemes, hosts and the search template, nothing else. The gap is visible to anyone who opens the file.

Code

cwkVoyager, docs/VOYAGER-PARITY-CONTRACT.md: the engine-bound gaps and the Never table, each row with its reason (verbatim, lines 113-139)·text
## Engine-bound: what WebKit does not do

| Gap | Why | Voyager's answer |
|---|---|---|
| The three assistant extensions (Claude, ChatGPT, Kimi) | They drive Chrome's own tabs through Chrome-only APIs | Pippa is Voyager's assistant; testing those assistants happens in the browser they were made for |
| WebUSB, Web Serial, Web Bluetooth, WebHID | WebKit declines them | Named when a departure needs one |
| Web MIDI | Absent in WebKit; cwkBonfire's MIDI input uses it | A native bridge for that family page, or Bonfire's MIDI stays in another browser (open ruling) |
| Google Docs offline | Chrome and Edge only | None |
| The Chrome Web Store as a store | It installs into Chrome | Extensions are installed into Voyager's host from their packages |
| Chrome DevTools | Chrome's | Web Inspector |

These are named here so that "superset" is never claimed past them. Each one
that produces departures is a reason to reopen the reserved Chromium option
in `ENGINE-EVALUATION.md` §5, by Dad's ruling.

## Never, with the reason

| Not built | Because |
|---|---|
| A bundled or patched engine | The engine is the system's WebKit by invariant |
| A private WebKit interface without Dad's ruling | It breaks when Apple changes it, silently |
| A second Pippa, a model key, a conversation store | cwkPippa owns Pippa |
| A hand-kept list of family apps | Firelink's catalog owns it |
| A workaround that hides Voyager's identity from a site in pieces | Partial spoofing is what sites detect; one coherent identity or none |

A "Never" row moves only with Dad's ruling, and the departure that forced the
question is cited beside it.
cwkVoyager, Sources/VoyagerCore/AddressResolver.swift: the resolver as it is today, schemes, hosts and a search, with no family name yet (verbatim, lines 44-62)·swift
    /// Schemes typed in full are taken as written.
    private static let writtenSchemes: Set<String> = ["http", "https", "file", "about", "data", "mailto", "tel", "chrome-extension", "voyager"]

    public func resolve(_ text: String) -> AddressIntent? {
        let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty else { return nil }

        if let colon = trimmed.firstIndex(of: ":") {
            let scheme = trimmed[..<colon].lowercased()
            if Self.writtenSchemes.contains(scheme), let url = URL(string: trimmed) {
                return .open(url)
            }
        }

        if !trimmed.contains(where: \.isWhitespace), let url = Self.addressURL(trimmed) {
            return .open(url)
        }
        return search.url(for: trimmed).map { .search($0, query: trimmed) }
    }

External links

Exercise

Take a product you are building that promises to replace something people already use. Write its parity contract in three tables. P0: the cases that must pass before you ask anyone to switch, each with its evidence written as a test someone else could run. Gaps: what your platform cannot do, each with a reason and one of four answers (closed in the shell, bridged natively, accepted, named when real use needs it). Never: what you refuse to build, each with its reason. Then name the instrument that will add rows after launch.
Hint
Draw P0 from evidence about the real user (what they have installed, what they said, what they do daily), not from the incumbent's feature list. If an evidence cell says "works", rewrite it until it says how you would know.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.