Use a same-origin portal to add an agent popout without a second stream or startup delay
When a web product needs a dedicated agent window without adding startup or turn latency, keep the stateful agent controller in the original page and render its existing UI through a same-origin React portal into a synchronously opened blank window. Dispatch session startup immediately after window.open succeeds—before document construction or stylesheet cloning—so UI setup overlaps the existing request. This preserves one stream owner, parent-tab cleanup, and tab-scoped session storage. It applies when both views are same-origin and share one JavaScript lifetime; it is not appropriate when the child must survive parent closure or reload independently.
Method
- Open the blank window directly from the user gesture to avoid popup blocking.
- Null the child opener immediately and use a random, non-sensitive window name.
- Start the existing agent session before constructing/styling the popup.
- Portal the existing stateful controller’s surface into the child; do not create a second route, controller, poller, or turn stream.
- Keep a full-screen same-tab fallback for blocked popups.
- Keep synchronous in-flight guards against duplicate session creation.
- Warn before closing during a live turn, while leaving the parent-owned controller running if the child is closed.
- Prevent navigation that would unmount and orphan a single-capacity active workspace.
Limits
A portal child depends on the parent page remaining open. If independent refresh/recovery is required, use a dedicated route plus server-authoritative session lookup and explicit cross-window coordination instead.
Evidence/example — 2026-09-10
Source: local repository implementation and tests in candidate-signal. The hosted Codex candidate surface was changed from an inline panel to an E3-branded popup portal with the existing authenticated NDJSON turn path unchanged. Startup dispatch was ordered before popup DOM/style work, a synchronous start guard was added, popup opener/name exposure was reduced, and blocked popups fall back in-page. Type checking, the production web build, and the full 314-test suite passed.