Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Pop out a project or integration window

Popping a project, an integration panel, or a marketplace plugin panel out of the main Omniscio window into its own desktop window: what qualifies, where the entry point lives, how the window follows your theme and remembers its position, and how a popped-out plugin differs from a popped-out project.

What it is

A whole project, an integration (KMS-style work surface), or a marketplace plugin panel can be popped out of the main Omniscio window into its own desktop window. This is the project-level sibling of the detached session window (which pops out a single conversation).

  • Pop out a project → you get a self-contained workspace for that one project: its session list on the left, the open conversation on the right. You can switch between that project's sessions and start new ones — all inside the popped-out window, while the main window stays on whatever you left it.

  • Pop out an integration → you get that integration's panel (Tasks, Recipes, Skills, Tools, Stats, …) in its own window. A split-surface integration — one with its own list pane, like Team Chat (channel/DM list + conversation), Supermail, or the Marketplace — pops out as the full two-pane window, list and panel together, exactly as it looks docked in the main app. The Google work surfaces — Calendar, Drive, and Sheets — pop out too, each as its own full panel.

  • Pop out a marketplace plugin → you get that plugin's panel in its own window, with no work required from the plugin author. Any plugin that declares a panel (ui.entryPoint) qualifies automatically, including ones published long before this existed. A plugin that ships only an overlay and no panel (virtual-pets) correctly does not offer it — there is nothing to detach.

It is one mechanism, not two: in Omniscio's data model an integration is a "virtual project" (a projects row whose folderPath is a __sentinel__), so a single window keyed by a project id covers both — a real project renders the workspace, a virtual project renders the integration's panel.

Key behaviors:

  • "Open in new window" appears in a project's (or integration's) sidebar right-click menu. It is gated by eligibility: a real project always qualifies; a virtual project qualifies only if it renders a real panel and does not already manage its own dedicated window. This includes the Google Calendar / Drive / Sheets panels. The two-pane messaging surfaces — Gmail and text messages (SMS) — stay out for now: a standalone window for them needs its own list-plus-reader layout, which is a separate follow-up.
  • KMS and Scratchpads use their own dedicated windows — they ship their own standalone windows (with their own toolbar pop-out buttons), so they are deliberately kept off the generic mechanism. Their sidebar right-click Open in new window still works — it just opens that dedicated window instead of a second generic one, so you never end up with two windows for the same surface.
  • Live — the popped-out window stays in sync with the main app: new messages, status changes, and integration updates appear in real time.
  • Signed in, and loading its own data — a popped-out window is a second window of the same app, and it signs in for itself, so a surface that needs to know who you are works the moment the window opens. The same rule covers the background loading the main window normally does: a panel that reads a live list the main window keeps (Team Chat's channels, for example) loads its own copy inside the popped-out window — a popped-out Team Chat shows your channels and reopens the conversation you were last in, exactly as it does docked. What a pop-out deliberately does not start is the main window's notification and inbox services, so you never get a second copy of a desktop alert from the window you popped out.
  • Theme-following — the window adopts your light/dark + visual theme and updates when you change it.
  • Themed title bar (opt-in) — by default a popped-out window keeps the native OS title bar. An integration can instead opt its window into Omniscio's frameless "work-surface" chrome — a slim draggable title strip + themed OS caption buttons (minimize/maximize/close that follow your theme), the same look as the KMS and Scratchpad windows. Tasks is the first to use it; real projects and every other integration stay native-framed.
  • Window-position memory (per project) — Omniscio remembers where you put each project's window and how big you made it; re-opening the same project restores the last x/y/width/height, plus whether you left it maximized or in fullscreen. First open defaults to 1100×760.
  • Ctrl+W closes it (Cmd+W on a Mac) — exactly like its close button, and it works the same in every pop-out window: the Vault (KMS), Scratchpad, Writer, Support Chat, Job Monitor, project docs and a single SuperMail email. If a dialog is open inside the window, Ctrl+W closes the dialog first; the next Ctrl+W closes the window. It still closes while you are typing — a reply you were composing in SuperMail is kept as a draft, and the Vault, Scratchpad and Writer save your last typing when the window closes. A popped-out session window is the one exception: there Ctrl+W asks Close session window? first, because closing it can also stop the session.
  • Sandboxed and isolated — Chromium OS sandbox, contextIsolation: true, nodeIntegration: false; the same preload bridge the main window uses is the only renderer→main surface. Exception: a webview-hosting surface (the Browser integration, and every marketplace plugin panel — a plugin panel is a <webview>) gets webviewTag: true + host sandbox: false, the same posture as the main window; the guest itself is still force-hardened on attach.
  • The main window is untouched — for a project or an integration. A project shown in its own window is not removed from the main window; they are independent views (no placeholder, no "reattach"). Two mounts of a React panel are two views of one store, which is harmless.
  • A PLUGIN behaves like a session instead, and must. A plugin panel is a <webview>: a separate JS realm with its own timers, network calls and storage handle, so a second copy is not a second view — it is a second running instance on the same origin and partition, and the host delivers every plugin event to BOTH. So while a plugin is popped out the main window drops its copy and shows the same "Open in another window" placeholder a detached session shows, with Bring window to front and Reattach here. Clicking the plugin in the sidebar takes you to that placeholder rather than to the window, which is what makes Reattach reachable at all.
  • A popped-out sidebar group launcher opens its members in their own windows. The catch-all sidebar groups — Productivity, Automation, Insights — show a roll-up "launcher" of their member apps (Tasks, Scratchpads, KMS, …). Pop that launcher out to a second monitor and click a member tile, and it opens that app in its own window (reusing the same "Open in new window" opener), because a popped-out window is pinned to one surface and can't navigate in place. In the main window the same tile switches the view inline, exactly as before.

Where to find it

Pop out is always available on desktop, no setting or feature flag. It lives in the right-click (context) menu of the project/integration row in the projects sidebar: Open in new window.

How it behaves

How to use it

  1. Right-click a project (or integration) row in the projects sidebar of the main Omniscio window.
  2. Pick Open in new window. A new window opens: for a project, its workspace (session list + conversation); for an integration, its panel.
  3. Switch sessions / start new ones inside a project window using its session list — exactly like the main window, scoped to that one project.
  4. Move and resize the window wherever you want; Omniscio remembers the position and size per project, so next time you open this project it reopens in the same spot.
  5. Open it again while it's already open — Omniscio just brings the existing window to the front instead of spawning a duplicate.
  6. Close the window from its title bar — or press Ctrl+W — when you're done (the project always remains available in the main window).
  7. For a plugin, bring it back by clicking the plugin in the sidebar and pressing Reattach here on the placeholder. That closes the plugin's window and re-mounts the panel inline. (Closing the window from its title bar does exactly the same thing — the close is the reattach.)

How it works

The Open in new window entry lives in the project row's context menu (ProjectListItem.tsx); its target is resolved by resolveProjectPopOut (pop-out-eligibility.ts). For a generic-eligible row — a real project, or a virtual project that renders a panel and is not one of the dedicated-window integrations (KMS / Scratchpads), i.e. canOpenProjectInWindow returns true — it opens the shared project-window. "Renders a panel" has two sources: the integration's UI manifest has a panelComponent, OR its channel-adapter sentinel is listed in the main-safe POPPABLE_CHANNEL_PANELS table (poppable-channel-panels.ts) — the second source, which covers Calendar / Drive / Sheets (their channel-adapter kind forbids them a UI-registry virtualProjectPath). It calls the WINDOW_OPEN IPC with { projectId }. For the two dedicated-window integrations — KMS and Scratchpads — resolveProjectPopOut instead returns a kms / scratchpads target and the item calls that surface's OWN opener (KMS_WINDOW_OPEN / SCRATCHPAD_WINDOW_OPEN), so the menu offers pop-out for them without routing through — or duplicating — the generic window. The dispatch is locked by pop-out-eligibility.test.ts.

The handler (project-window-handlers.ts) delegates to the manager project-window.ts. openProjectWindow(projectId) is idempotent — if a window already exists for that project it focuses it and returns { state: 'already-open' }; otherwise it resolves the kind (resolveProjectWindowKind: real project vs integration, deriving the integration's push allowlist from its manifest pushChannels), creates a BrowserWindow (sandbox: true, contextIsolation: true, nodeIntegration: false, the shared preload) and loads the renderer with a ?window=<projectId> query param.

The renderer's main.tsx branches on that param at boot (the same single-divergence trick the detached session window uses with ?detached=) and mounts DetachedWindowApp (DetachedWindowApp.tsx) instead of the full App. DetachedWindowApp adopts the theme (useWindowThemeBootstrap), resolves the project, and forks:

  • Real project → DetachedProjectWorkspace (DetachedProjectWorkspace.tsx): the shared SessionHostSidebar + useProjectSessionHost (the contract-blessed reuse path — never hand-rolled session rows) on the left, the selected session's SessionPanel on the right.
  • Virtual project (integration) → DetachedIntegrationPanel (DetachedIntegrationPanel.tsx): looks the integration's panelComponent up in the UI registry (getByVirtualPath) and mounts it. Channel-adapter panels (Calendar / Drive / Sheets) are the exception — they have no UI-registry panelComponent, so when getByVirtualPath misses, it falls back to a small sentinel→panel map (CHANNEL_ADAPTER_POPOUT_PANELS, reusing the Dashboard's lazy panel) — the renderer half of the POPPABLE_CHANNEL_PANELS table that also drives eligibility and push-routing, the two halves locked by poppable-channel-panel-parity.test.ts. It renders panel-only only when the integration has no sidebarComponent; whenever one exists it re-hosts that Pane-2 nav beside the panel — including the split-surface integrations that ALSO set panelOwnsLayout (Team Chat, Supermail, Marketplace, Marketplace-review). That panelOwnsLayout flag is a Dashboard-Pane-2 concern (it suppresses the empty session-list fallback) and must never gate the pop-out sidebar — gating on it dropped Team Chat's channel/DM list from the popped-out window until it was fixed. The re-hosted nav sits in a fixed-width column, except a sidebar that owns its width (sidebarWidthFixed — Supermail), which is hosted bare the way the main window hosts it: Supermail's folder nav renders nothing while hidden (its default), so a fixed column around it showed an empty strip down the left of the window. Locked by pop-out-window-contract.md a-split-surface-brings-its-sidebar.

Frameless themed chrome (opt-in). An integration whose manifest sets standaloneWindowChrome: true (tasks-v2.ts today) gets the frameless "work-surface" chrome instead of the native frame. openProjectWindow builds the BrowserWindow with the shared standaloneWindowChromeOptions() (hidden native title bar, no native caption-button overlay) plus a dark-first backgroundColor (no white flash on open), and DetachedWindowApp draws the shared StandaloneWindowTitleBar strip above the panel with custom HTML caption buttons (WindowControls) at the trailing edge (which gets a flex-1 min-h-0 wrapper so it fills only the remaining height, never overflowing by the strip's height). Both halves read the same flag — main via resolveProjectWindowKind → kind.standaloneWindowChrome, the renderer via integrationWantsStandaloneChrome (integration-registry.ts) — so the OS frame and the in-renderer strip can never disagree. Locked by pop-out-window-contract.md frameless-chrome-is-opted-in-once and window-controls-contract.md.

Group-launcher tiles in a pop-out. A sidebar parent-group roll-up (ParentGroupRollup.tsx) — the Productivity / Automation / Insights launcher — renders in BOTH the main window and a pop-out. Its tiles activate the member inline in the main window (activateVirtualProject); but a pop-out's panel is pinned to the window's ?window= project and can't navigate, so in a pop-out (isProjectPopoutWindow, platform.ts) a tile instead opens the member in its own window via the shared openProjectPopOut (pop-out-eligibility.ts) — the SAME generic / KMS / Scratchpads opener the sidebar "Open in new window" item uses (so the two never drift), with an inline fallback if the row isn't hydrated. Without it the tile was a dead no-op in a pop-out. Locked by parent-group-rollup-popout.test.tsx.

Agent Alerts and Approvals in a pop-out. A popped-out Alerts window re-hosts the list and the reading pane, but in the main window a live alert is drawn by DesktopMainOverlay — which a pop-out never mounts — so clicking one used to highlight the row and show nothing. The reading pane (AlertsVirtualProject.tsx) now asks isMainAppShell() and, outside the main window, draws the live viewer itself; the Approvals hub (ApprovalsVirtualProject.tsx), which had the same dead click, draws the approval pane in place of its list the same way. Two follow-on dead ends are fixed at their root: a session link (navigate-session.ts) from a pop-out pinned to an integration whose panel shows no sessions — every integration except Tasks, the one manifest with embeddedSessionProjectPath — now opens in the main window, the same hand-off the single-session pop-out already used (a real-project pop-out still navigates its own workspace); and on desktop the HTML preview (InboxHtmlPreview.tsx) shows its fallback outside the main window, because a pop-out without hostsWebview has no <webview> support and the frame would spin forever. Both lists also stay live. A pop-out never runs the App shell, which is what starts the app-wide push syncs, so the Alerts list (AlertsSidebar.tsx) starts the alert sync and the snooze cache itself outside the main window, and the Approvals hub turns on the approvals reload (useCliPendingPushReload.ts) there; the Approvals manifest declares CLI_PENDING_CHANGED, because the pop-out filter forwards only the channels a manifest declares. An alert archived elsewhere while it is open on the Agent Alerts hub — in the main window too — is now reconciled instead of spinning on "Loading alert". A jump out now goes to the main window, which comes forward: from a popped-out window, the two navigation funnels (activateVirtualProject, navigateToSettingsProject) send the main window a link — Settings to the exact page, any other hub to that hub — and change nothing locally; before, they switched the pop-out's own store, which did nothing on screen and wrote the main window's restore anchor. Inside a popped-out alert, Open channel lands on the exact Team Chat channel (a reply notice opens its channel rather than the thread), the setup assistant's new session opens in the main window, and Start session plus the actions that need the main app (Review updates, Resume them now, Finish setup, the app tour, Restart) or pick a view inside a hub (View your records, View spend dashboard, Open this job, Review emails) open the alert there, where they work (main-window-jump.ts, alert-action-popout.ts). Locked by AlertsVirtualProject.test.tsx, ApprovalsVirtualProject.test.tsx, AlertsSidebar.test.tsx, alert-reload-reconcile.test.ts, navigate-session.test.ts and InboxHtmlPreview.fullscreen.test.tsx.

Push routing is one new 'project-window' role in the central window-registry.ts, filtered by detached-push-filter.ts shouldForward. The global chrome allowlist (theme / network / focus / update) reaches every window. Beyond that the entry's shape decides routing: an integration window forwards exactly its manifest-derived panelPushChannels; a real-project window forwards session-scoped channels whose session belongs to that project — the originating session's project id is resolved once per push by resolveSessionProjectIdForPush in index.ts and handed to the (otherwise pure, DB-free) filter.

Per-project position memory reuses the same detached_window_positions SQLite table the single-session pop-out uses, under the sentinel key __window_<projectId>__ (helpers in queries-detached-window-positions.ts). On every open the manager restores the saved bounds — including the maximized / fullscreen state, re-applied via the shared window-geometry-persist helpers; move/resize and entering/leaving fullscreen trigger a 500 ms debounced save, with a final synchronous save on close.

Ctrl+W. A pop-out never runs App.tsx, whose shortcut dispatcher is where the main window's Ctrl+W lives, and Windows installs no application menu — so without help, Ctrl+W reached nothing in a pop-out. Every pop-out root therefore mounts the shared useCloseWindowShortcut hook, which closes the window through IPC.WINDOW_CLOSE (the same path as the title-bar X) and steps aside for an open dialog, for an inner command that already consumed the key, and for a held key. The key match is the one layout-aware isCloseChord (the typed letter decides on a Latin layout, so AZERTY's Ctrl+Z is never a close). Locked by pop-out-window-contract.md ctrl-w-closes-the-pop-out and the guard popout-ctrl-w-close-coverage.test.ts.

Invariants are locked by pop-out-window-contract.md and its unit tests.

Related

detached-session-window.md is the single-session sibling — it pops out one conversation rather than a whole project. tray-and-window.md covers the main window, the tray, and the global hotkeys that sit around these windows, and start-a-new-session.md explains how sessions are spawned, which is what a popped-out project window lists.

Last verified 2026-09-29