Two things the UI couldn't do: rearrange the tab strip, and watch the
browser while working somewhere else.
**Drag to reorder.** `moveTab`/`moveActiveTab` on the store, HTML5 drag on
the strip with a marker showing where the drop lands, `Ctrl+Shift+←/→` for
the same thing without a mouse. Reordering deliberately does not select
what it moves, so a drag aimed at a background tab doesn't yank the main
area away from a terminal mid-run. A tab being renamed is not draggable —
a draggable ancestor swallows the mouse-drag that selects text in its
input.
**Pop the browser view out.** `browser_view/popout.rs` opens the view's
existing token-bearing loopback URL as a second OS window, with a
"Keep on top" toggle so it can float above the app. Window-only: the
viewer, the proxy and the container are untouched, so popping out and
back interrupts nothing.
Three things it rests on:
- No capability lists that window, so it has no IPC surface — right for a
page served out of a container, and it must stay that way.
- The app CSP is irrelevant to it: `frame-src` constrains what the app's
document may *embed*, and this is a top-level document. The port range
and the token gate are what actually protect it, unchanged.
- The window is owned by the session, so the supervisor's teardown closes
it. A window onto a viewer that no longer exists is worse than none.
The pane drops its iframe while popped out — two viewers can both *drive*
the browser, and two cursors on one page is not a feature.
`lib.rs`'s `on_window_event` is now guarded on `label() == "main"`. It
fires for every window and its body stops every container and exits, so
without the guard closing a pop-out would quit the app.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>