Repository navigation
Draw toasts above live web pages instead of freezing the page under them - #419
Conversation
A toast in the bottom right (a workspace deleted, a worktree removed, Zen Mode) parked every web pane it covered: the page was swapped for a still poster, so it could not scroll, and the park handed the keyboard to the client and never gave it back. The Electron shell now draws the toast stack itself, in a see-through WebContentsView (the "corner layer") placed over the stack's box above every page view. The client still lays the stack out, at opacity 0, but no longer registers its box, so nothing parks. A click on the layer acts on the card under it, and focus on the layer goes straight back to its previous owner. Both directions are executeJavaScript long-polls: no preload, no daemon protocol change. Where the layer is not drawing (a browser, a phone, an older shell, a failed layer) the old in-page stack and poster still apply. Plugin notifications are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMT4Nhd3hzhXa2EEaHi4u7
The battery's plugin-browser-features and plugin-terminal-features went red on "the renderer threw nothing": TypeError reading 'next' of undefined. A call the frame loop sent while the client window reloaded ran in the new document before its bridge was installed, and threw there. A call into a document without the global now answers a sentinel instead of throwing, and the controller turns that into the rejection its loops already handle (the stack goes back to the page until the next load). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMT4Nhd3hzhXa2EEaHi4u7
Battery results
Run 1 (7524b74) caught a real bug: Run 2 (8419e92): typecheck, root tests (11,550), shell tests (1,084), bundles, full audit and packaged smoke (75/75) passed. Scenario lane: 62 of 68 green. The six reds:
The last two fail after 🤖 Generated with Claude Code |
Problem
A toast in the bottom right (a workspace deleted, "Removed worktree", Zen Mode) took a web pane away while it was up. You could not scroll the page, and the keyboard left it.
The toast stack was drawn in the client's document, which sits under a web pane's native page view. So it registered its box (
useOverlayPresence) and every page it overlapped was parked: swapped for a still poster, which a wheel cannot scroll. The park also handed the keyboard to the client (releaseKeyboardIfHeld) and nothing gave it back when the toast went.Change
The Electron shell now draws the toast stack itself, in a see-through
WebContentsView(the corner layer) placed over the stack's box, above every page view. The page under a toast stays live, scrolls, and keeps the keyboard.client/src/chrome/corner-layer.ts,ToastStack.tsx): the stack moved out ofApp.tsxintoToastStack.tsx. Inside the shell (?shellWindow=) the page exposesglobalThis.__kelpiCorner. While the shell is attached, the page still lays the stack out (it is the measurement) but at opacity 0, marks itdata-drawn-by="shell", and does not register its box, so nothing parks. If the shell stops taking frames for 1.5 s (UNTAKEN_FRAME_MS) the page detaches it and draws the stack itself again.client/overlay.html,client/src/overlay/CornerOverlay.tsx): a new Vite entry that renders the sameToastCardson a transparent document and reports clicks back.shell/src/corner-layer.ts,corner-layer-view.ts): a pure controller (probe, frame loop, click loop, placement with a 12 px margin, up to 3 overlay restarts, then it gives the stack back to the page) and its Electron view (transparent background, own partition, no background throttling). Both directions areexecuteJavaScriptlong-polls, asquit-prompt.tsalready does: no preload and no daemon protocol change.webHost.keyboardOwner()/restoreKeyboard(), newly exposed).onViewAttachedand the layer re-raises itself.docs/shell-ui.md§4.2 gains a "Toast-over-a-web-page requirement" paragraph.Reviewer notes
BundledPromptsNotifications) share the corner but were not moved: they are still drawn in the page and still park. The toast layer draws above them where the two overlap.web-pane-poster-under-worktree-toast(Show a web page's still frame under a toast it arrives beneath, instead of a gray pane #358) is renamed toweb-pane-live-under-toastand rewritten: it used to assert the poster under the toast, and now asserts the page stays live, the card is drawn by the layer, and a click on the layer's card dismisses the toast. It keeps Show a web page's still frame under a toast it arrives beneath, instead of a gray pane #358's "arrive at the pane while the toast is up" round.cornerCallScript). A call sent while the client window reloads runs in the new document, sometimes before its bridge is installed; it answers a sentinel instead of throwing an uncaught error in the page, and the controller hands the stack back to the page until the next load. The first battery run caught this (plugin-browser-features,plugin-terminal-features); fixed in 8419e92.Test plan
client/src/chrome/corner-layer.test.tsx,client/src/overlay/CornerOverlay.test.tsx,shell/src/corner-layer.test.ts).node scripts/scenario.mjs --window offscreen web-pane-live-under-toast: 25/25.web-pane-middle-click-and-modal-poster,plugin-backend-prompts,cmd-click-codex-links,terminal-copy-paste-chords,recover-interface-keeps-web-panes,web-pane-comes-back-after-hide,workspace-agent-delete.plugin-interaction-presentersfails only in its cleanup step, the pre-existing plugin-interaction-presenters fails its cleanup on main: Settings does not open to restore the bundled placements #396.node scripts/verify.mjs --since origin/mainfull battery on 8419e92: typecheck, root tests (11,550), shell tests (1,084), bundles, full audit and packaged smoke (75/75) passed. Scenario lane 62/68; every red is also red onmainor a known issue (details in the comment below).🤖 Generated with Claude Code
https://claude.ai/code/session_01DMT4Nhd3hzhXa2EEaHi4u7