Skip to content

feat(floating-workspace): render the floating panel through the shared workspace surface - #22302

Merged
brennanb2025 merged 73 commits into
mainfrom
brennanb2025/floating-unify-impl
Oct 6, 2026
Merged

brennanb2025 merged 73 commits into
mainfrom
brennanb2025/floating-unify-impl

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 86 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​3633 $\color{#cf222e}{\Huge{\mathbf{−}}}$​725 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2908
Prod 163 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2617 $\color{#cf222e}{\Huge{\mathbf{−}}}$​2156 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​461

ELI5

With Chat UI as the default view, picking Codex or Claude from the "+" menu in the floating workspace opened the old chat drawn over a terminal, while the same "+" in a normal workspace opened the real chat.

The floating workspace had nowhere a real chat could live: no folder Orca could start the agent in and no place to keep the chat between restarts. This PR gives it one, so both "+" menus now open the same chat.

What Changed

Before: in the floating panel, "+" → Codex or Claude opened a terminal tab running the agent's command-line tool, shown as chat over the terminal. The panel also showed one tab group at a time, so moving a tab into a split could make it disappear.

After: in the floating panel, "+" → Codex or Claude opens the same chat a normal workspace gets: start, send, stop, close, and pick up where it left off after Orca restarts. The panel shows split groups side by side, using the same tab groups as a normal workspace.

Where a floating chat runs: the floating workspace's folder is a setting the user can change. A chat starts in the folder set at launch and records it on its saved session. After a restart it resumes in that same folder, even if the setting has changed since.

If that folder is gone: the chat says "The folder this chat ran in no longer exists. Restore it to continue this chat." It does not quietly start somewhere else.

Mechanism: the host now resolves the floating workspace like a folder workspace whose path is the floating-folder setting. The rule that sent every floating launch to a terminal is removed, so the "+" menu and agent.launch (CLI, phone) take the same route as everywhere else.

Mechanism, saved state: a floating chat's saved session gains an optional launchDirectory. The host resolves the floating folder when it creates the chat and writes it in the same step that creates the saved session, so the folder can't be lost to a separate write failing. Worktree and folder chats don't store it; their workspace already names a fixed folder. A /clear continues in the same folder. Floating chats saved before this field existed get it filled in on their next launch.

Mechanism, renderer: the floating panel renders through the shared tab-group surface instead of its own single-group renderer. Tab selection belongs to the workspace that owns the tab, so the panel never moves the main window's selection.

Also changes in regular workspaces

This comes from regular workspaces and the floating panel now closing tabs through one shared path:

  • "Close All Editor Tabs" from a tab's menu now keeps pinned tabs, like the keyboard shortcut and Close Others already did. On main it asked "Close pinned tab?" and closed them.

Scope note: this is broader than the original launcher PR (#21390). It also replaces the floating panel's separate tab renderer with the shared one (the reason splits and chat panes work there) and deletes the old floating-only tab code. It is about 230 files against current main, many of them tests.

Why

The terminal fallback came from a rule that said the floating workspace "can host a terminal and nothing else", because it had no folder or saved place a chat could belong to. Giving it a real launch scope removes the reason for the rule, rather than adding a special case beside it.

Keeping the launch folder on the session answers "where did this chat run?" after the floating setting moves. Re-reading the setting on each resume would have silently moved a chat to a different folder.

Rendering the panel through the shared tab surface removes a second tab renderer that had drifted (no splits, no chat panes) instead of teaching it each missing feature.

Differences from the common pattern:

  • Intended: in the common pattern a chat's folder comes from a durable workspace or project record whose path never changes after creation. Orca's floating workspace is a single, settings-driven scratch surface whose folder can change, so the folder is stored on the chat's own saved session instead. This keeps one floating workspace identity (chats, status and history are keyed by it) instead of minting a new workspace each time the setting moves.
  • Matches: a missing folder is a refusal, not a substitution, which is also how the common pattern treats a workspace folder that has disappeared.

Linked Issue

Follow-up to merged #21390. No separate issue.

Visual Proof

Hidden dev builds of main (before) and this branch (after), isolated profile and home, stand-in Codex and Claude. Floating panel, "+" → Codex, then "+" → Claude:

Before (main) After (this PR)
A-floating-codex B-floating-codex-reply
A-floating-claude B-floating-claude-reply

On main both open a "Terminal" tab with chat drawn over it; on this branch they open "Codex Chat" and "Claude Chat" and reply.

Stop on a running Codex turn, and Claude resumed in the same conversation after the app relaunched:

B-stop
B-after-restart-claude

A floating chat keeps its folder: after the floating folder setting moved to another folder and Orca restarted, the chat resumed the same conversation in its original folder (a new chat started in the new folder). If that original folder is deleted, the chat refuses instead of running elsewhere:

C4-after-move-resumed
C4-folder-gone

Split groups side by side in the floating panel (a terminal and the Codex chat), and a normal workspace's "+" → Codex unchanged:

B-floating-split
B-normal-codex

Testing

  • Merged current main (1,091 commits); every conflict was resolved by hand, keeping main's changes and this PR's intent.

  • Typecheck (node, web, cli) passes; oxlint and the changed-code quality gate (including React Doctor) report zero findings.

  • 1,472 named test files (every changed test, plus tests that import a changed module): all pass except tests that timed out while the machine was overloaded. Those pass when rerun on their own, apart from the cross-version suites, which could not unpack old releases within their time limit locally; CI runs them.

  • New and ported tests cover: the floating workspace resolving to its folder, floating agent.launch taking the structured route, the launch folder being pinned once and reused on resume, the missing-folder refusal and its wording, /clear carrying the folder, the status feed publishing it, and the renderer using it only for local chats.

  • Live macOS QA, main vs this branch, in hidden dev builds with an isolated home and profile and stand-in Codex and Claude (never the real tools):

    • On main, floating "+" → Codex or Claude opened a terminal tab.
    • On this branch both opened structured chats running in the floating folder, and both replied.
    • Stop interrupted a running Codex turn.
    • After relaunching the same profile, both chats came back with their history and resumed the same conversation (same Codex thread and Claude session) in the same folder.
    • Moving the chat into a split showed two groups side by side; closing the Claude chat stopped its process.
    • A normal workspace's "+" → Codex opened a structured chat on both builds.
  • After the review fixes, two more QA passes on the final behavior:

    • closing an unsaved floating file asks once (Cmd+W and the tab's X);
    • closing one of two split views of the same file keeps the other;
    • floating "Close All Editor Tabs" asks only about the unsaved file and keeps pinned tabs and chats;
    • a chat that finishes behind another tab shows an unread marker that clears when selected;
    • after the floating folder setting moved and Orca restarted, the old chat resumed the same conversation in its original folder, with its turns in order and finished, while a new chat used the new folder;
    • with the original folder deleted, sending shows "The folder this chat ran in no longer exists. Restore it to continue this chat." and nothing starts anywhere else.
  • What QA did not show: Orca's own in-app restart can't run in a dev build (it stops the dev page server), so resume was checked by quitting and relaunching. Real Codex and Claude accounts were not used.

  • The real ~/.codex and ~/.claude settings files were hashed before, during and after QA and did not change.

  • SSH: not exercised. No container runtime was running on this laptop or on the remote test Mac, and Docker Desktop was not started.

  • Linux, Windows and mobile were not exercised by hand. The floating workspace is local-only. On Windows, a floating folder on a WSL path gets "could not start" rather than a terminal, which is what a folder workspace on a WSL path does on main today, so it is not a regression.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

Review

Two review rounds ran on this branch; what they found, what was fixed and what was deferred is in the PR comment below.

Agent skill upstream boundary

  • Not applicable.

Notes

  • Compatibility: both new fields are optional. Older builds keep launchDirectory on a saved session and ignore it. Older clients ignore it on the status feed, and show the generic "could not start" wording for the new missing-folder reason. No new message type is sent between client and host.
  • SSH / paired server: the floating workspace always runs on this machine. A paired client never applies a folder published by a remote host.
  • Security: the floating folder is resolved on the host from the trusted floating-folder setting; the renderer never supplies a launch path.

Author X handle: @BrennanKB5

Checklist

  • This PR is small and focused (intentionally broader, as noted above)
  • I explained what changed and why
  • Before/after screenshots attached
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered
  • PR CI green

…-chat' into brennanb2025/floating-unify-impl
… workspace kind

The host half removed the 'floating-workspace' blocker; this pin still asserted it and went red
on the merge. Floating now routes like any workspace, so with capabilities un-negotiated the
resolver answers 'runtime-capability-unknown'.
…ow without a worktree record

The renderer had no Worktree row for the floating id: markdown creation, tab-bar path entry,
native-chat file links, attachments and model discovery all resolve a workspace through
getKnownWorktreeById and silently fail for it. The host already answers with a synthetic row
minted from the resolved floating directory; this gives the renderer the same single seam.

- The repo slice stores the host-resolved floating directory (floatingWorkspacePath); the panel's
  existing cwd resolution now writes it there instead of panel-local state, so there is one copy.
- findKnownWorktreeById mints the shared floatingWorkspaceToWorktree row from that path, local
  host only, cached while the directory is unchanged — mirroring the folder-workspace branch it
  sits beside.
- No consumer changes needed: every row-dependent path already goes through this resolver.
Shared editor/browser activation always wrote global selection (activeFileId,
activeBrowserTabId, activeTabType). The floating panel is an overlay above the active
workspace, and its active tab must never become global selection — the invariant
documented in agent-auto-ack-targets.ts. Group-scoped activation is a prerequisite for
rendering the floating workspace through the shared tab-group surface.

- setActiveFile and setActiveBrowserTab take an optional target workspace, mirroring
  setActiveTabType: global selection moves only when the target IS the globally active
  workspace; per-workspace state always updates. Omitted keeps legacy behavior.
- The shared tab-group activation/creation commands pass their workspace, which is a
  no-op for the main surface (hidden surfaces are inert, so commands only fire on the
  active workspace) and confines floating activation to floating state.
- Terminal setActiveTab was already owner-scoped; now pinned by test.
… from main-window chrome

Preparation for hosting the floating workspace on the shared surface, whose shell owns its own
titlebar tab strip and cannot mount the main chrome:

- WorkspaceTabDragLayer: the one dnd-kit scope for a workspace's strips and tree, extracted from
  TabGroupSplitLayout as a render-prop layer so a host can put shell-owned chrome (a titlebar
  strip) inside the same drag scope as the tree.
- TabGroupSplitNodeTree: the chrome-free recursive group tree, with an explicit tabStrip
  composition point on TabGroupPanel ('attached' keeps today's per-group strip; 'external' hosts
  render no per-group strip at all — a hidden one would register duplicate drag sortables).
- WorkspacePaneOverlayLayers: the retained pane hosts extracted from WorktreeSplitSurface so any
  tree host mounts the same pane lifecycle stack.
- TerminalPaneOverlayLayer gains ownsNativeChatToggleShortcut, following the existing rule that
  only one live workspace registers the chord.

TabGroupSplitLayout and WorktreeSplitSurface recompose these parts; the main surface's DOM is
unchanged.
…mically

An empty floating workspace has no tab-group layout at all (the slice starts empty and
hydration skips tab-less workspaces), while the shared group tree requires one. This model
resolves the two together: the panel shows its empty state exactly until the first tab exists,
and mounts the tree only with a layout plus a live focused group (stored focus, else the group
with an active tab, else the first group — matching the shell chrome's focused-group policy).
…d workspace surface

The panel previously projected exactly one tab group and hand-rendered its panes, so any tab
that left that group — 'Move Tab to Split' creates a second group — stayed alive in state but
vanished from the panel. The body now mounts the shared group tree and retained pane overlay
stack for the floating workspace id, so every group renders, splits work, and structured chat
finally has a floating surface.

- One drag scope: WorkspaceTabDragLayer wraps the titlebar strip AND the body tree, so exactly
  one DndContext owns floating tab drag; strip drops reorder, body-edge drops split. The
  floating-only FloatingWorkspaceTabDragContext is deleted.
- The titlebar keeps the panel's single TabBar, now fed by the shared per-group workspace model
  of the focused group (tabStrip='external' keeps per-group strips out of the shell), with
  workspace-scoped activation commands so global selection never moves.
- Panes render through WorkspacePaneOverlayLayers: terminals gate on the resolved cwd and a
  settled viewport exactly as before; browser guests ride the shared retention layer; agent
  session tabs appear in the strip and render through the structured overlay.
- The empty state renders exactly until the first resolvable tab exists (stale unified entries
  still show it), per the atomic layout/emptiness model.
- Deleted: the single-group projection, the hand-written pane maps, FloatingBrowserSlot, and
  the floating pane-handle registry.

Known behavior deltas, both narrowing floating onto main-workspace semantics: Cmd/Ctrl+W with
panel (not terminal) focus closes the active tab rather than one pane of a multi-pane tab, and
a pane-owned close of the last tab no longer arms the panel focus reclaim (strip and shortcut
closes still do).
The retained overlay stack moved into WorkspacePaneOverlayLayers and the floating chrome model
became a guest-paint consumer; the structured-pane layering pin and the browser-guest retention
census both name files, so they follow the seam.
@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

We couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting @coderabbitai full review.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: a83d6b73-403a-485c-8e55-9eddb7998265

📥 Commits

Reviewing files that changed from the base of the PR and between ffd0494 and 12ec8c7.

📒 Files selected for processing (29)
  • docs/readme/README.es.md
  • docs/readme/README.fr.md
  • docs/readme/README.ja.md
  • docs/readme/README.ko.md
  • docs/readme/README.pt.md
  • docs/readme/README.zh-CN.md
  • src/main/agent-launch/agent-launch-mode.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-status-feed.ts
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.empty-state.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.focus.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.shortcuts.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tab-lifecycle.test.tsx
  • src/renderer/src/components/floating-terminal/floating-workspace-surface-model.test.ts
  • src/renderer/src/components/floating-terminal/floating-workspace-surface-model.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-create-actions.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-panel-focus-reclaim.ts
  • src/renderer/src/components/floating-terminal/use-floating-workspace-chrome-model.ts
  • src/renderer/src/components/native-chat/StructuredAgentSessionStatusBridge.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/lib/agent-launch-route-input.test.ts
  • src/renderer/src/store/slices/browser/browser-page-focus-actions.ts
  • src/renderer/src/store/slices/browser/browser-slice-contract.ts
  • src/renderer/src/store/slices/editor/types/editor-files-slice.ts
  • src/shared/agent-session-wire.ts
💤 Files with no reviewable changes (9)
  • src/main/agent-launch/agent-launch-mode.ts
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.shortcuts.test.tsx
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/store/slices/browser/browser-page-focus-actions.ts
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/i18n/locales/ja.json

Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

Floating workspaces can use structured sessions and resolve to local directories. Session records can retain launch directories, which structured launches and TUI handoffs use. The renderer stores floating workspace paths and panel state. It renders floating tabs through shared split-tree, drag, and overlay components. Workspace-scoped activation, attention, tab closing, and native-chat directory resolution also account for the floating panel.

Priority: ➖ Normal

Merge Risk: 🟡 Moderate · up to 12ec8

“Close All Files” can unexpectedly close structured chat sessions in the floating panel. Correct that behavior before merging; the split-pane test also leaves a gap in protection against a pane-rendering regression.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 12ec8

The design pins chats to their original folder and refuses to resume when that folder is unavailable, which limits accidental folder switching. It also shares the folder path with session-status consumers. The access rules for those consumers and the validation of a path at initial session creation remain unconfirmed.

Retained concerns

  • Medium · security · inferred: The status feed now distributes a persisted host filesystem path to its existing subscribers. That is a new information flow; the available evidence does not establish whether every subscriber is authorized to receive the path.
Security review details

Security Blast Radius

  • inferred — The demonstrated new exposure is a local session directory in existing status subscriptions and local renderer directory context, not a demonstrated grant of execution ownership or a deployment-level privilege change.

Trust Boundaries and Controls

  • observed — The attach admission shown checks provider consistency and the operation fingerprint, but workspacePath is not among the fingerprint fields; its initial caller provenance and authorization were not established in the inspected path.

Resilience and Maintainability Implications

  • observed — Once recorded, the directory is not replaced by a later reservation, and a missing pinned directory blocks resume instead of silently redirecting work.

Hardening Proposals

  • proposed — Establish and enforce which status subscribers may receive a host path, and verify that only a host-authorized directory can supply the initial create-time pin.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 84 functions across 102 files. (6 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: rendering the floating panel through the shared workspace surface.
Description check ✅ Passed The description is detailed and mostly complete. It explains the user-facing change, mechanism, rationale, visual proof, testing, platform limitations, compatibility, and security. It identifies the l…
Full details: Docstring Coverage

Explanation

Docstring coverage is 19.05% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 84 functions across 102 files. (6 skipped: 6 unsupported.)

✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 671921f1-4d2b-408c-89c0-057b76e63f10

📥 Commits

Reviewing files that changed from the base of the PR and between a8786c0 and fcf18c5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (69)
  • src/main/agent-launch/agent-launch-executor.test.ts
  • src/main/agent-launch/agent-launch-mode.ts
  • src/main/runtime/orca-runtime-persist-headless-terminal-title.ts
  • src/main/runtime/orca-runtime-resolve-browser-network-execution-host-for-worktree.ts
  • src/main/runtime/orca-runtime-resolve-recovered-structured-tui-transcript.ts
  • src/main/runtime/rpc/methods/agent-launch-floating-workspace.test.ts
  • src/renderer/src/components/TerminalWorktreeSplitSurface.tsx
  • src/renderer/src/components/WorkspacePaneOverlayLayers.tsx
  • src/renderer/src/components/activity/ActivityPrototypePage.tsx
  • src/renderer/src/components/activity/activity-thread-actions.ts
  • src/renderer/src/components/activity/use-agent-pane-threads.ts
  • src/renderer/src/components/browser-pane/host-guest/browser-guest-retention-site-census.test.ts
  • src/renderer/src/components/floating-terminal/FloatingBrowserSlot.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingBrowserSlot.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.bounds.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.empty-state.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.focus.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.markdown-editor.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.shortcuts.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tab-lifecycle.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanelSurface.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingWorkspaceTabDragContext.tsx
  • src/renderer/src/components/floating-terminal/floating-terminal-panel-render-probe.ts
  • src/renderer/src/components/floating-terminal/floating-terminal-panel-test-fixtures.ts
  • src/renderer/src/components/floating-terminal/floating-terminal-panel-test-harness.ts
  • src/renderer/src/components/floating-terminal/floating-terminal-panel-test-module-mocks.ts
  • src/renderer/src/components/floating-terminal/floating-workspace-surface-model.test.ts
  • src/renderer/src/components/floating-terminal/floating-workspace-surface-model.ts
  • src/renderer/src/components/floating-terminal/terminal-pane-handle-registry.test.ts
  • src/renderer/src/components/floating-terminal/terminal-pane-handle-registry.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-close-actions.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-create-actions.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-focus-lifecycle.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-initial-focus-effects.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-panel-controller.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-panel-focus-reclaim.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-panel-geometry.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-panel-items.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-panel-local-state.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-panel-maximize.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-panel-shortcuts.ts
  • src/renderer/src/components/floating-terminal/use-floating-terminal-panel-store-state.ts
  • src/renderer/src/components/floating-terminal/use-floating-workspace-chrome-model.ts
  • src/renderer/src/components/native-chat/native-chat-stop-layering.test.ts
  • src/renderer/src/components/sidebar/SidebarAgentsList.tsx
  • src/renderer/src/components/tab-group/TabGroupPanel.tsx
  • src/renderer/src/components/tab-group/TabGroupSplitLayout.test.ts
  • src/renderer/src/components/tab-group/TabGroupSplitLayout.tsx
  • src/renderer/src/components/tab-group/TabGroupSplitNodeTree.tsx
  • src/renderer/src/components/tab-group/WorkspaceTabDragLayer.tsx
  • src/renderer/src/components/tab-group/useTabGroupActivationCommands.ts
  • src/renderer/src/components/tab-group/useTabGroupCreationCommands.ts
  • src/renderer/src/components/tab-group/useTabGroupWorkspaceModel.focus.test.ts
  • src/renderer/src/components/terminal-pane/TerminalPaneOverlayLayer.tsx
  • src/renderer/src/lib/agent-launch-route-input.test.ts
  • src/renderer/src/lib/agent-launch-routing.test.ts
  • src/renderer/src/store/repos/repo-state.ts
  • src/renderer/src/store/slices/browser/browser-slice-contract.ts
  • src/renderer/src/store/slices/browser/browser-tab-focus-actions.ts
  • src/renderer/src/store/slices/editor/actions/open-file-mutations.ts
  • src/renderer/src/store/slices/editor/types/editor-files-slice.ts
  • src/renderer/src/store/slices/floating-workspace-row.test.ts
  • src/renderer/src/store/slices/repos.ts
  • src/renderer/src/store/slices/workspace-scoped-activation.test.ts
  • src/renderer/src/store/slices/worktrees/listing/detected-worktree-meta.ts
  • src/shared/floating-workspace-worktree.ts
  • src/shared/structured-native-chat-launch-route.test.ts
  • src/shared/structured-native-chat-launch-route.ts
💤 Files with no reviewable changes (9)
  • src/renderer/src/components/floating-terminal/terminal-pane-handle-registry.test.ts
  • src/renderer/src/components/floating-terminal/terminal-pane-handle-registry.ts
  • src/renderer/src/components/floating-terminal/FloatingBrowserSlot.tsx
  • src/renderer/src/components/floating-terminal/FloatingBrowserSlot.test.tsx
  • src/renderer/src/components/floating-terminal/use-floating-terminal-panel-local-state.ts
  • src/renderer/src/components/floating-terminal/FloatingWorkspaceTabDragContext.tsx
  • src/renderer/src/components/floating-terminal/use-floating-terminal-panel-items.ts
  • src/main/agent-launch/agent-launch-mode.ts
  • src/shared/structured-native-chat-launch-route.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 2 remain after this review.

Comment on lines +55 to +57
} else if (item.contentType === 'agent-session') {
// Mirrors the shared workspace close: a structured chat closes by its unified tab.
closeUnifiedTab(item.id)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '1,180p' src/renderer/src/components/floating-terminal/use-floating-terminal-close-actions.ts
rg -n "closeFloatingItemConfirmed|closeFloatingItems|onClose" src/renderer/src/components/floating-terminal/FloatingTerminalPanelSurface.tsx src/renderer/src/components/floating-terminal/use-floating-terminal-panel-shortcuts.ts

Repository: stablyai/orca

Length of output: 9904


🏁 Script executed:

#!/bin/bash
printf '%s\n' '--- FloatingTerminalPanelSurface focused section ---'
sed -n '130,205p' src/renderer/src/components/floating-terminal/FloatingTerminalPanelSurface.tsx
printf '%s\n' '--- shortcut close paths ---'
sed -n '1,80p' src/renderer/src/components/floating-terminal/use-floating-terminal-panel-shortcuts.ts
sed -n '130,165p' src/renderer/src/components/floating-terminal/use-floating-terminal-panel-shortcuts.ts
printf '%s\n' '--- action declarations/usages ---'
rg -n -C 4 "closeFile\s*[:=]|closeUnifiedTab\s*[:=]|closeUnifiedTab\(|closeFile\(" src/renderer/src/store src/renderer/src/components src/renderer/src/lib
printf '%s\n' '--- agent-session tab model/type references ---'
rg -n -C 3 "agent-session|agentSessionItems" src/renderer/src/components/floating-terminal src/renderer/src/store

Repository: stablyai/orca

Length of output: 42377


🏁 Script executed:

#!/bin/bash
printf '%s\n' '--- closeFile implementation ---'
fd -i 'close-file-action.ts' src/renderer/src
close_file="$(fd -i 'close-file-action.ts' src/renderer/src | head -n 1)"
if [ -n "$close_file" ]; then
  cat -n "$close_file"
fi
printf '%s\n' '--- closeUnifiedTab implementation ---'
cat -n src/renderer/src/store/slices/tabs/tabs-close-actions.ts | sed -n '1,125p'

Repository: stablyai/orca

Length of output: 18725


Add the agent-session branch to closeFloatingItemConfirmed.

The tab-strip close action uses closeFloatingItemConfirmed. Its fallback calls closeFile(item.entityId), which does not close an agent-session unified tab. The tab remains open.

The alternate close actions use closeFloatingItems, so their agent-session handling remains correct. The panel close shortcut also uses the confirmed path.

🐛 Suggested fix in the confirmed-close chain
           if (item.contentType === 'browser') {
             destroyWorkspaceWebviews(latest.browserPagesByWorkspace, item.entityId)
             closeBrowserTab(item.entityId)
-          } else if (item.contentType === 'simulator') {
+          } else if (item.contentType === 'simulator' || item.contentType === 'agent-session') {
             closeUnifiedTab(item.id)
           } else {

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Floating panel visual proof

Both builds launched hidden (ORCA_BACKGROUND_LAUNCH=1), same window (1728×999) and the same default panel bounds (920×560 at 784,355; maximized 1704×927 at 12,36).

  • After: this branch, devRepoRoot /Users/brennanbenson/orca/workspaces/orca/floating-unify-qa
  • Before: origin/main at a8786c040d, separate checkout /tmp/orca-pr22302-main

Pixel compare is of the floating panel. The tab strip (top 36px, 72px on the browser shot) matched on every paired shot. The pane body did not, except the empty state.

1. Empty state — identical

Panel pixels: 0 of 515,200 differed.

Before:

before empty

After:

after empty

The full window differed by 169 pixels, all in the status-bar memory readout (919.9 MB vs 901.2 MB).

2. One terminal — body does not match

Before shows the shell prompt (brennanbenson ~ $) on the terminal theme background. The pane fills the panel (522px tall, screen 904×512).

After shows a black body and no prompt. The shared pane’s absolute inset-0 container is 0px tall, and the xterm screen stays at the default 640×384.

Before:

before one terminal

After:

after one terminal

3. Several tabs — tab strip matches, body does not

Three terminals (Terminal 1–3). Tab strip pixels match. The body is the same failure as scenario 2 (~93% of panel pixels differ).

Before:

before several

After:

after several

4. Maximized — same bounds, body does not match

Both panels maximized to the same rect. Tab strip matches. Before still shows the prompt. After stays black, and the pane measures 0×0.

Before:

before maximized

After:

after maximized

5. Markdown editor — chrome tab matches, editor is clipped

Both opened untitled.md. On main the path, formatting toolbar, and “Write markdown…” placeholder are visible. On this branch the tab is in the strip and the editor is in the DOM, but every layout ancestor under absolute inset-0 is 0px tall (overflow: auto clips it), so the body is black.

Before:

before editor

After:

after editor

6. Browser — toolbar matches, empty state does not

Both show the New Tab, about:blank address bar, and browser toolbar (those 72px match pixel for pixel). Main also shows the globe and “Type a URL above to start browsing.” This branch leaves that area black.

Before:

before browser

After:

after browser

7. Move Tab to Split → Right — both panes do not render

Right-clicked Terminal 2, then Move Tab to Split → Right. The store on both sides ended with two groups (the moved terminal alone, and the other four tabs).

On main, Terminal 2 disappears from the strip. The original group stays (Terminal 1, Terminal 3, untitled.md, New Tab) in a single pane.

On this branch the strip shows only Terminal 2. The original group’s tabs are not on screen, and there is no second pane. Under the tab is a crushed browser toolbar (about:), then black. The Codex Chat tab in the later shot was added by scenario 8, after this capture.

Before:

before split

After (includes the later Codex Chat tab):

after split

8. Structured chat — session tab created, surface not visible

Flags set on this branch only: experimentalNativeChat, openAgentTabsInChatByDefault, experimentalStructuredNativeChat, defaultTuiAgent: codex. Titlebar button “Open Codex in floating workspace” was clicked.

The new tab is Codex Chat with content type agent-session (id prefix structured-agent-session-codex_). There is a [data-native-chat-root] and no “Show terminal” / “Show chat view” toggle. The root’s text is the Codex empty state plus “Chat connection could not be confirmed.” There is no composer textarea inside the root. The root’s box is 456×0, so none of that is on screen.

Console warning:

StructuredAgentSessionCreateUnknownOutcomeError: floatingWorkspaceToWorktree is not defined

That comes from hostSupportsCreate in src/renderer/src/lib/launch-structured-agent-session.ts. floatingWorkspaceToResolvedWorktree in src/main/runtime/orca-runtime-resolve-browser-network-execution-host-for-worktree.ts calls floatingWorkspaceToWorktree without importing it (@ts-nocheck on that file).

This launch happened after the split, so the panel was already in the broken split layout. The 0-height pane was already true for the terminal and the editor before the split.

after structured chat

9. Floating launch does not move the main window’s active tab

Before the titlebar click, the main window’s active tab was d23e9904-caa1-4556-86ca-fa2ab50288ef (Terminal 1 on the demo repo). After the click it was the same id, and activeWorktreeId was unchanged.

Console

No console.error on either build. Warnings on this branch: a tooltip controlled/uncontrolled notice, an xterm “task queue exceeded allotted deadline by 22ms”, “Skipped persist worktree activity timestamp” for global-floating-terminal, and the structured-launch ReferenceError above. ws-transport failed to bind port 6769 on both processes and picked another port.

Not claimed

The floating panel is not identical to main once a tab is open. Only the empty state matched pixel for pixel.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — a couple of things worth a look before merge.

Reviewed changes

  • Host resolution for the floating workspace: resolveRuntimeFileTarget gains a floating branch that mints a synthetic worktree from the configured directory, floatingWorkspaceToWorktree/isFloatingWorkspaceSelector are added, and the 'floating-workspace' structured blocker is deleted so floating routes by capability.
  • Shared surface in the floating panel: the panel body now renders the shared group tree, the extracted WorkspacePaneOverlayLayers, and one WorkspaceTabDragLayer; the private one-group projection, floating browser slot, floating drag context and pane-handle registry are deleted.
  • Workspace-scoped activation: setActiveFile/setActiveBrowserTab take an optional owning-workspace id so floating activation never moves global selection (setActiveTabType already had one).
  • Synthetic renderer row: a new floatingWorkspacePath store field plus a one-slot cache lets getKnownWorktreeById answer for the floating sentinel, local-only.

ℹ️ The floating session's TUI resume still resolves $HOME, not the configured directory

The new branch makes a structured session's location the configured floating directory, but the structured-to-TUI handoff resolves the same workspace through the terminal scope, which still hardcodes homedir() for the floating sentinel — and then trusts that path for the agent. So a resumed floating session runs its agent in $HOME while its record says the configured directory. Before this PR floating could not hold a structured session, so this path was unreachable for it; it is now reachable.

Technical details
# Floating session location and resumed TUI cwd disagree

## Affected sites
- `src/main/runtime/orca-runtime-persist-headless-terminal-title.ts:204-212` — new floating branch resolves the configured directory for `resolveRuntimeFileTarget`.
- `src/main/runtime/orca-runtime-resolve-browser-network-execution-host-for-worktree.ts:117-131` — unchanged `resolveTerminalWorkspaceLaunchTarget` floating arm returns `path: homedir()`.
- `src/main/runtime/orca-runtime-get-agent-session-execution-namespace.ts:108` — `ensureAgentSession` resolves the workspace through `resolveTerminalWorkspaceLaunchScope(request.worktree)`, then `createTerminal(\`id:${workspace.id}\`, …)` with no `cwd`, so it falls back to that scope path.
- `src/main/runtime/orca-runtime-get-agent-session-execution-namespace.ts:169` — `markWorkspaceTrustedForAgent(agent, connectionId, workspace.path)` trusts the resolved (`$HOME`) path.

## Required outcome
- A floating structured session's resumed TUI should start in the same directory the session is filed under, and only that directory should be marked trusted for the agent.
- Either give `resolveTerminalWorkspaceLaunchTarget`'s floating arm the same configured-directory resolution as the new file-target branch, or route the handoff through the file-target resolver.

## Open questions for the human
- Is the terminal-scope `homedir()` arm deliberately left alone as a follow-up, or is the intended fix to unify both resolvers on one floating-directory source?

ℹ️ Nitpicks

  • src/shared/workspace-launch-kind.ts doc still says the floating terminal "can host a PTY and nothing else"; src/main/agent-launch/agent-launch-mode.ts still carries the "The floating workspace has nowhere to keep a session" comment above resolveStructuredNativeChatSupport. Both are now false.
  • workspaceKind is still accepted by resolveStructuredNativeChatSupport (src/shared/structured-native-chat-launch-route.ts:48) and passed by callers, but the function no longer reads it — the field is now dead input.
  • Deleting FloatingBrowserSlot.test.tsx removed the only assertions that a floating browser tab registers its overlay slot viewport. The shared BrowserPaneOverlayLayer owns that now, but nothing pins it for the floating worktree.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

…so panes own their height

The tree's nodes size themselves as flex items, and the floating shell mounted
them in a block container, so every group body — and every pane anchored to
one via anchor-size() — measured 0px tall. Wrap the tree and overlay layers in
the same 'absolute inset-0 flex' frame the worktree split surface uses, record
the host contract on the tree, pin the frame in the panel's element-tree test,
and add a real-renderer e2e spec that measures group-body and pane boxes for
the single-group and split layouts (jsdom computes no layout, which is how the
0px regression passed 50k tests).
…ate path calls

floatingWorkspaceToResolvedWorktree called floatingWorkspaceToWorktree without
importing it. The module is @ts-nocheck (mechanically split runtime), so tsc
never saw the missing import and it only failed at call time — the structured
createSupport probe for the floating workspace threw a ReferenceError. Add the
import and a test that runs the real method, which fails on the missing import
where the typechecker cannot.
…ebook navigation

navigateBrowserPageToUrl looked the workspace up in allWorktrees(), which has
no row for folder or floating workspaces, so their notebook navigations ran
with an undefined workspace path. Use getKnownWorktreeById, the same resolver
every other consumer was moved to.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Rendered proof after the fixes (head 7a13982709)

Same rig as the failing pass above: this branch's dev build over CDP (window.api.app.getIdentity().devRepoRoot = this worktree), fresh profile, hidden launch, default panel bounds 920×560 at (784,355).

The three defects from the earlier proof comment are fixed on this head:

  1. Zero-height panes — the floating shell now mounts the shared group tree + overlay layers inside the same absolute inset-0 flex frame the main worktree surface uses. The tree's nodes size themselves as flex items; a block parent collapsed everything to 0px.
  2. floatingWorkspaceToWorktree is not defined — the import now exists in the @ts-nocheck runtime module; a new test executes the real method (it reproduced the exact ReferenceError before the fix).
  3. navigate-browser-page-url.ts now resolves floating/folder workspaces via getKnownWorktreeById (the previously disclosed gap).

1. One terminal — prompt paints, pane fills

Group body 918×522, xterm screen 904×512 — the same rects the main build measured in the earlier pass.

floatqa-1-one-terminal.png

2. Browser tab — body no longer black

Toolbar plus the globe/"Type a URL above to start browsing" empty state.

floatqa-2-browser-tab.png

3. Markdown editor — path bar, toolbar, placeholder all visible

floatqa-3-markdown.png

4. Maximized — same rect as main (1704×927 at 12,36), prompt visible

Body 1702×889, xterm screen 1688×880.

floatqa-4-maximized.png

5. Move Tab to Split → Right (real context menu) — both panes render

Store: two groups (4 tabs / 1 tab), layout split. Two group bodies at 456×522 each; the moved Terminal 2 paints its prompt on the right, the original group's active tab (the browser) renders on the left. Clicking the left pane switches the titlebar strip to Terminal 1, Terminal 3, untitled.md, New Tab — every tab reachable. On main, the moved tab is unreachable; here nothing is lost.

floatqa-5-split.png

6. Structured chat — live session, usable composer

Flags set through the real settings path (updateSettingsOrThrow, so the host store sees them — setting them via renderer setState alone makes the host gate correctly refuse; the one "launch failed" warning earlier in this run's console was that rig artifact). Codex Chat tab opens in the panel; [data-native-chat-root] is 456×522 (was 456×0); the provider child really started — its event stream renders in the pane (the red text is the user account's own codex_apps MCP server failing with a 401, environment noise unrelated to this branch); the composer accepted typed input (pressSequentially → read back "composer input works").

floatqa-7-structured-chat.png

Console

No ReferenceError anywhere in the run. Remaining entries are the same pre-existing warnings the earlier pass saw on both builds (tooltip controlled/uncontrolled, xterm task-queue deadline, "Skipped persist worktree activity timestamp"), plus one Invalid guestInstanceId error thrown while destroying a never-navigated webview during a Close-Tabs-To-The-Right — that close path's code predates this branch.

Regression tests (both ablated against the broken code)

  • tests/e2e/floating-workspace-pane-height.spec.ts measures real bounding boxes in the packaged renderer; against the pre-fix surface it fails at single group body height — the exact shipped regression. Passing while panes are 0px is no longer possible for this surface.
  • The panel's element-tree suite pins that the tree and overlays mount inside the flex surface frame (vitest-level, fails on the pre-fix code).
  • agent-launch-floating-workspace.test.ts executes the real synthetic-row minting; before the import fix it fails with the exact ReferenceError — @ts-nocheck keeps tsc blind there, so only execution can catch it.

Not claimed

  • No new pixel-diff against main was taken for this pass; the geometry (rects above) matches the earlier pass's main measurements, and the titlebar/tab-strip DOM is unchanged and matched pixel-perfect in that pass.
  • Linux/Windows/SSH not exercised (floating workspace is local-only by construction; renderer changes are shared code).
  • The codex session's MCP 401 is an account-level issue on this machine; a full chat turn was not exercised.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/e2e/floating-workspace-pane-height.spec.ts (1)

134-141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover both split-pane overlays.

After the split, the test checks only group-body rectangles. It does not check that both [data-emulator-pane] overlays are visible and sized. A regression that mounts an overlay only for the active group can pass these assertions while the other split is blank. Assert two visible emulator panes and check both bounding boxes.

Proposed test addition
   for (const body of await splitBodies.all()) {
     const box = await body.boundingBox()
     expect(box, 'each split group body must have a box').not.toBeNull()
     expect(box!.height, 'split group body height').toBeGreaterThan(MIN_PANE_EDGE_PX)
     expect(box!.width, 'split group body width').toBeGreaterThan(MIN_PANE_EDGE_PX)
   }
+
+  const splitPanes = openPanel.locator('[data-emulator-pane]:visible')
+  await expect(splitPanes).toHaveCount(2)
+  for (const pane of await splitPanes.all()) {
+    const box = await pane.boundingBox()
+    expect(box, 'each split emulator pane must have a box').not.toBeNull()
+    expect(box!.height, 'split emulator pane height').toBeGreaterThan(MIN_PANE_EDGE_PX)
+    expect(box!.width, 'split emulator pane width').toBeGreaterThan(MIN_PANE_EDGE_PX)
+  }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 47b5f2b3-ca78-425a-b62a-dc57ced50c84

📥 Commits

Reviewing files that changed from the base of the PR and between fcf18c5 and 7a13982.

📒 Files selected for processing (7)
  • src/main/runtime/orca-runtime-resolve-browser-network-execution-host-for-worktree.ts
  • src/main/runtime/rpc/methods/agent-launch-floating-workspace.test.ts
  • src/renderer/src/components/browser-pane/navigate/navigate-browser-page-url.ts
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tab-lifecycle.test.tsx
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanelSurface.tsx
  • src/renderer/src/components/tab-group/TabGroupSplitNodeTree.tsx
  • tests/e2e/floating-workspace-pane-height.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/runtime/orca-runtime-resolve-browser-network-execution-host-for-worktree.ts
  • src/renderer/src/components/tab-group/TabGroupSplitNodeTree.tsx

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in the incremental delta — the three fix commits land cleanly.

Reviewed changes

  • Panes no longer collapse to 0px: the floating panel's body now mounts TabGroupSplitNodeTree and WorkspacePaneOverlayLayers inside the same absolute inset-0 flex surface frame the main worktree surface uses, with a unit pin on the class contract and a new real-renderer e2e spec that measures actual bounding boxes for single/split groups and the anchored pane.
  • Missing import restored in the @ts-nocheck resolver mixin: floatingWorkspaceToWorktree is imported again, and a new test executes the real floatingWorkspaceToResolvedWorktree, which is the only kind of check that can catch a missing import in that file.
  • Notebook navigation resolves floating and folder workspaces: navigate-browser-page-url.ts now uses getKnownWorktreeById instead of allWorktrees(), so those workspaces get the correct path instead of undefined.
  • Cleanup: the orphaned FloatingBrowserSlot.test.tsx is deleted and a stray pnpm-lock.yaml rewrite is reverted.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — two small suggestions inline.

Reviewed changes

  • Let the shared terminal create path own floating-tab activation: createFloatingTerminalTab no longer passes activate: false or calls activateTab, and the floating create tests now assert activateTab is not called.
  • Re-check focus reclaim when the intent arms: the focus-reclaim hook subscribes to the reclaim intent via useSyncExternalStore and gates consumption on the armed snapshot, so a close that lands after the empty panel rendered can still reclaim the keyboard.
  • Keep the split surface mounted with an empty focused group: the surface model now returns empty only when no unified tab has a backing entity, hasVisibleFloatingTabs derives from the surface, and activeClosableTab checks the visible tab order.
  • Added surface-model, empty-state, and focus-reclaim test cases covering those paths.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment on lines +36 to +54
const terminalIds = new Set(
(state.tabsByWorktree[FLOATING_TERMINAL_WORKTREE_ID] ?? []).map((tab) => tab.id)
)
const browserIds = new Set(
(state.browserTabsByWorktree[FLOATING_TERMINAL_WORKTREE_ID] ?? []).map((tab) => tab.id)
)
const fileIds = new Set(state.openFiles.map((file) => file.id))
const hasVisibleTab = tabs.some((tab) => {
if (tab.contentType === 'terminal') {
return terminalIds.has(tab.entityId)
}
if (tab.contentType === 'browser') {
return browserIds.has(tab.entityId)
}
if (tab.contentType === 'agent-session' || tab.contentType === 'simulator') {
return true
}
return fileIds.has(tab.entityId)
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasVisibleTab restates the per-tab visibility rule already in selectFloatingVisibleTabCount (src/renderer/src/store/floating-workspace-panel-selector.ts:40), and the two now diverge: the selector counts an editor file only when file.worktreeId === FLOATING_TERMINAL_WORKTREE_ID, while fileIds here spans every worktree. That selector also caches its scan, so reusing it (or extracting one shared predicate) keeps the surface and empty-state decisions from drifting apart.

expect(panelElement.focus).toHaveBeenCalledWith({ preventScroll: true })
})

it('reclaims focus when intent arms after the empty panel has rendered', async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test does not pin the subscription the fix relies on: the harness mocks useSyncExternalStore to just call getSnapshot() and ignores the subscribe callback (floating-terminal-panel-test-module-mocks.ts:17), while runEffects re-runs every registered effect — so the manual renderPanel(true) at line 516 is what makes it pass, and it would still pass if subscribeFloatingPanelReclaimIntent were dropped. Assert the subscription is registered (spy on it) or drive the re-render from the listener so removing the wiring turns this red.

Resolve 71 conflicts against main 8127d94 (1091 commits). Main removed
the terminal handoff and made hold a no-op, so the PR's handoff launch-folder
plumbing and hold-refusal UI are dropped; the pinned launch folder now rides
the /clear founding identity instead of a replacement attach. Unify main's
and the PR's revealFloatingWorkspacePanel into one store-backed helper.
…nch directory

Port what the merge left on main's newer shapes:
- The floating chat's "folder is gone" refusal becomes a typed reason,
  launchFolderMissing, worded like every other start refusal (copy, desktop
  words, five translations). Older clients drop the unknown reason and show
  the code's generic words.
- The PR's launch-folder tests run on main's journal-database record store;
  the attach-level case is a row in the pre-spawn refusal table.
- Tests main added since the PR's base learn the PR's close-queue settling,
  workspace-scoped notebook lookup and tab-stamped chat host.
- Move the store's visible-tab-index reads into the tab table, so the record
  store stays under the line cap with the pin writer in it.
- Drop a duplicate Worktree import the merge left in a @ts-nocheck file.

Rename the persisted field, the status summary field and the renderer slice
from workspacePath to launchDirectory: it records the folder a session first
launched in, not its workspace's path, and it has not shipped yet.
@brennanb2025
brennanb2025 marked this pull request as draft October 6, 2026 03:56
The floating panel now closes through the shared unsaved-changes prompt, so
its own dialog's six strings had no caller and failed the runtime-catalog
check.
Attach now reads params.location to decide whether a new record is a floating founder, so the partial fixture needs the location real attach params always carry.
The cast tripped the changed-code type-assertion gate.
… it was created in

The model picker read the floating setting's current folder, so a chat
pinned to another folder could show that folder's project default.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Problem. With Chat UI as the default view, Codex or Claude from the floating workspace's "+" menu opened the old chat drawn over a terminal, because the floating workspace had its own tab renderer and no folder or saved place a real chat could belong to.

What changes for you. The floating panel now uses the same tab groups and panes as a regular workspace. "+" → Codex or Claude opens a real chat there: send, stop, close, and pick up where you left off after a restart. Each floating chat keeps the folder it started in. Changing the floating folder setting later does not move it, and if that folder is deleted the chat says so instead of running somewhere else.

Also changes in regular workspaces. "Close All Editor Tabs" from a tab's menu now keeps pinned tabs, like the shortcut and Close Others.

Fixed during review.

  • A floating terminal that finished behind an editor or browser tab lost its unread marker.
  • Floating "Close All Editor Tabs" also closed chats.
  • Closing one split view of a file removed it from the other view.
  • A new floating chat could lose its folder if a separate save failed. The folder is now saved in the same step that creates the chat.
  • Bulk editor close prompted for, or removed, pinned tabs (a regression from an earlier fix in this loop).
  • The model picker read the floating setting's current folder instead of the chat's own folder.
  • Several test fixtures were updated for the shared close path and the new folder field.

Deferred (P3, no current user impact). A client that sends its own location when creating a floating chat gets a raw error instead of a typed refusal if the folder can't be resolved. No current client does this.

Verified.

  • Two review rounds with fresh reviewers, covering the host launch path and the renderer close and unread paths. No P0 or P1.
  • Scoped typechecks, the changed-code quality gate, and tests for each fix that fail without it.
  • Four live passes in hidden dev builds of main and this branch with stand-in Codex and Claude:
    • the floating "+" opens a terminal tab on main and a chat on this branch;
    • send, stop, splits and close all work;
    • after a restart the chat resumes the same conversation in its original folder, even after the floating folder setting moved;
    • a deleted folder is refused;
    • closing unsaved floating files asks once;
    • unread markers show behind another tab and clear on select;
    • a regular workspace's "+" is unchanged.
  • The real ~/.codex and ~/.claude settings files were unchanged throughout.

Not verified.

  • Real Codex and Claude accounts, and a packaged-app restart (dev builds were relaunched instead).
  • SSH: no container runtime was running on the test machines.
  • Windows, WSL and Linux.
  • Mobile UI.
  • Measured performance.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 6, 2026 12:12
…-unify-impl

# Conflicts:
#	src/main/claude/claude-structured-launch-resolution.ts
@brennanb2025
brennanb2025 merged commit defd85a into main Oct 6, 2026
65 of 67 checks passed
brennanb2025 added a commit that referenced this pull request Oct 6, 2026
…agent-tree-job-object

Main (#22302) gave the Codex launch resolver a launch-directory pin, changed
its test store fakes and added a test passing the creation-time seam this
branch removed. Kept main's store shape and new test without that seam, and
kept this branch's Windows test that resolves a launch without creation times.
brennanb2025 added a commit that referenced this pull request Oct 6, 2026
…t-launch-instant-tab

Conflicts resolved against #24934's rebuild (82544cb), which this branch was built on, so each
keeps main's squashed #24934 plus this branch's change. agent-launch-floating-workspace.test.ts takes
main's #22302 routing (a structured preference opens a Codex chat) with this branch's host-derived
viewMode on the terminal branch. The record-store slot test gets the resolveLaunchArgs dep #25721 made
required, the same line as main's fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant