The heaviest page Dad keeps is me
Voyager has a tab that is not a web page: voyager://pippa, which the code calls Pippa herself. That tab holds no engine page. It draws my whole WebUI, cwkPippa's own front end, in a web view of its own, at the connection Settings name. Asking for me brings my tab to the front, or opens it and pins it there. A private window has no Pippa. My view opens the first time my tab comes to the front, not at launch. What I am lives in Pippa Quest; the narrow panel beside other pages is track 7. This lesson is about keeping me alive inside the browser.
My conversation is the heaviest page Dad keeps, and until 2026-10-22 Voyager treated it like any other tab behind. When another tab came to the front, my web view left the window. WebKit then saw my page as hidden. My own document.hidden checks paused, including the timer that recovers a reply stream, and after about eight minutes the lower background memory limit applied to me, the one that ended a 6 GB page at 567 seconds in lesson 2. If my page process ended, the panel said so and stayed down until Dad pressed something.
Under the page in front, never out of the window
The fix applies the measured fact directly. W10 showed that a web view left in the window, under another view, keeps the visible page's limit. So show(_:) keeps my surface at the bottom of the page container while I am open. The page Dad switches to is added on top of me and taken away again; I never leave. Three small rules make that safe:
- A tab with no page to draw shows
EmptyPageView, a plain view in the window's background colour. Without it, I would show through the gap. - When my tab closes, I close with it. Staying in the window is for an open tab, not a hidden one.
- The beep cwkPippa plays when an answer finishes fires only when
document.hasFocus()is false. Focus stays false under another tab, so counting as visible does not silence that beep.
The price is also named in the engine evaluation: a page kept in the window keeps running timers and drawing as a visible page does, and what that costs was not measured. For one page, the one Dad talks to all day, the trade was worth making.
A second commit the same day closed a gap the first one left. If I was reloaded while another tab was in front (after a network retry or a process end), my new web view did not enter the window until my tab came back, so the reload ran as a hidden page after all. Now every redraw hands my surface its current page whenever I sit under the front tab.
When I cannot be reached, or my page ends
Two failures used to wait for Dad. Now neither does:
| What happened | What Voyager does now |
|---|---|
| My page could not reach cwkPippa | tries again after 2, 4, 8, 16 and 32 seconds, then every minute; at once when the Mac's network path comes back; a successful load resets the wait |
| My page's process ended | reloads where it was, once a minute at most |
| It ended again within that minute | stays down and says my page stopped twice within a minute |
The Mac watches its network with NWPathMonitor, so joining Wi-Fi or Tailscale reconnecting is a reason to try me again right away. The phone app has no such monitor yet, so there an unreachable Pippa is retried only on the timer.
Any page whose process ends
Ordinary tabs got the same treatment. When WebKit ends a page's process, the tab marks itself ended and clears what only a live page can claim: loading, progress, sound. Then it depends on where the tab is. In front, it loads again at once and a toast says the page stopped, from a crash or the system's memory limit for one page, and was loaded again. Behind, it waits and loads when Dad chooses it, so a tab he is not looking at costs nothing until he wants it. Either way, once a minute is the ceiling. A page that ends again inside that minute stays ended, with a bar that offers Reload, because a page that crashes while loading would otherwise loop forever. Before this, every ended tab waited behind that bar for a click.
Keys and commands on a page that is not a page
Two smaller fixes came from the same fact, that I am a native page wrapped around a web view. Switching back to my tab used to make the frame around my web view the first responder. The frame takes no keys, so every key Dad typed beeped while the caret still blinked in my composer. Now focus goes to the view that takes the keys, which for me is the web view inside the frame. And ⌘R once did nothing on me, because the menu treated native pages as blank. Dad left for Comet over it. Now ⌘R reloads my page on the conversation he is reading, and on Home it reads the family catalog again.