Light means not running
Dad keeps a lot of tabs. A browser that keeps every one of them running pays for pages nobody is looking at, and a browser that closes them to save memory throws away the thing he kept them for. Voyager's architecture settles it in one invariant: light means not running, never removing. A tab out of view lets go of its web view once the sleep policy fires, keeps its place in the strip, and comes back where Dad left it.
So a sleeping tab is not a closed tab with a bookmark. It is the same BrowserTab, same id, same identity, same title and icon, with page set to nil and one extra thing held on the side: the page's place, as bytes.
The bytes
Since macOS 12 and iOS 15, WKWebView has a public interactionState property: an opaque value that captures where a page is. Voyager reads it into a PageState, which is two fields, the engine's name and the bytes. The parent never looks inside. It stores them and hands them back.
Before any policy was written, the engine gate checked that this actually works (case W9, 2026-10-03). A new web view given the old one's interactionState came back to the same address and scroll position, with its back list. The state for one page was 624 bytes. Written to a file, read back and given to a third web view, it restored the same place again. That last step is what lets a session file carry a tab's place across a relaunch, which is the next lesson.
The engine name on the bytes is a small piece of humility. If the bytes were written by another engine, the waking tab ignores them and loads the address instead. Voyager has one engine today, but the bytes mean something only to the engine that wrote them, so the label travels with them.
sleep() and wake()
Putting a tab to sleep is short: capture the place, clear what only a live page can claim (loading, progress, sound), then discard the page, which closes the web view and bumps contentRevision so nothing mistakes an old read for a current one.
Waking is the careful half:
- Native pages (Home, Pippa herself) are never woken; they have no web page to get back.
- An extension's own page (
chrome-extension://) is rebuilt through the extension's configuration and never from saved bytes, and never in a private window. - Everything else gets a fresh page made in the tab's own identity, restoring the bytes when the engine wrote them.
- The new page inherits the tab's mute and the zoom Dad chose for that site, so a woken tab is not suddenly loud or small.
- With no usable bytes, the page simply loads the address.
Two small rules keep the place honest. A sleeping tab that is sent somewhere new drops its old place, because that is no longer where it should wake. And a hard reload (⇧⌘R) on a sleeping tab drops the place before it wakes, since asking for the page without its caches means starting fresh.
Measure the engine, then write the policy
How long should a tab wait before it sleeps? The answer came from measuring WebKit, not from guessing. WebKit ends a web content process whose memory passes a threshold, and the threshold depends on where the page is. Case W10 measured it on a Mac on 2026-10-03:
| The page | Where its web view was | What happened |
|---|---|---|
| 6 GB | out of the window | ended after 567 s (alive at minute 8, gone at minute 9) |
| 3 GB | out of the window | lived the full 14 minutes |
| 6 GB | in the window, under another view | lived the full 13 minutes |
So a page out of the window keeps the visible limit for about eight minutes and then falls under a lower one, and the limits turn on whether the web view is in a window, not on whether anyone can see it. That gave the design two levers. A heavy page that Dad is still using can stay in the window under the tab in front and keep the visible limit; that is how Pippa herself is kept, in lesson 4. Every other page is better asleep than killed, because sleep keeps the place and a kill keeps nothing.
The idle policy
Settings has tabSleepMinutes: 60 by default, offered as never, 15, 30, 60 or 120 minutes. Once a minute a timer asks every window to sleep its idle tabs, on the Mac and on the phone alike. A tab sleeps only if all of these hold: it is not in front; it has been behind for at least that long (the backgroundSince stamp from lesson 1); it holds a page, so Home and Pippa herself are never touched; it is not loading; it is not an extension's page; it is an http or https page; it is in the outside world, so a family app, which may be in the middle of a voice turn or an edit, never sleeps; and the engine does not call it busy.
Busy is the engine's call. WebKit's child says a page is busy when the camera or microphone is in use, when media is playing, or when the field Dad last typed into still holds text. That last check reads only whether the field is empty, never what it says.