Skip to content

feat(desktop): ペインのポップアウト(別ウィンドウ分離)機能#11

Merged
MocA-Love merged 7 commits intomainfrom
feat/desktop-tab-tearoff
Mar 29, 2026
Merged

feat(desktop): ペインのポップアウト(別ウィンドウ分離)機能#11
MocA-Love merged 7 commits intomainfrom
feat/desktop-tab-tearoff

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

@MocA-Love MocA-Love commented Mar 29, 2026

Summary

  • 各ペインツールバーにPop outボタンを追加。クリックすると対象ペインが独立した新ウィンドウとして開く
  • 複数ペインのタブでは対象ペインのみを抽出してポップアウト
  • ポップアウトウィンドウを閉じるとタブがメインウィンドウに自動返却
  • ターミナルセッションはkillせずにdetach(セッション維持)

Architecture

  • preload同期注入方式: タブデータをZustandストア初期化前にipcRenderer.sendSyncで取得し、persist middlewareとの競合を完全排除
  • WindowManager: 複数BrowserWindowの管理、IPC同期ハンドラ、タブ返却のブロードキャスト
  • tearoffPaneIds: ペインの「ティアオフ中」フラグでターミナルcleanupのkillをスキップ

UI

  • ポップアウトウィンドウではTopBar・サイドバー・RightSidebar(Changes/Files)・プリセットボタン群を非表示
  • タブバー右の空きスペースでウィンドウドラッグ可能
  • ポップアウトウィンドウ内ではPop outボタン非表示(無限増殖防止)

Test plan

  • ターミナルペインのポップアウト → 新ウィンドウでセッション維持
  • ブラウザペインのポップアウト → 同じURLがロード
  • チャットペインのポップアウト → 同じセッション継続
  • 複数ペインのタブで1ペインだけポップアウト → 元タブに残りのペインが残る
  • ポップアウトウィンドウで新タブ追加 → close時に全タブがメインに返却
  • ポップアウトウィンドウのドラッグ移動
  • macOSトラフィックライトの表示位置

Summary by CodeRabbit

  • New Features
    • Added "Pop out to new window" functionality, allowing users to detach tabs and panes into separate, independent windows.
    • Tearoff windows display content with optimized layouts, removing redundant interface elements for focused work.

各ペインツールバーのSplit Paneボタン左にPop outボタンを追加。
クリックするとそのペインだけが独立した新ウィンドウとして開く。
ウィンドウを閉じるとタブがメインウィンドウに自動返却される。

- WindowManager: 複数BrowserWindowの管理、IPC同期ハンドラ
- preloadで同期的にタブデータを注入(Zustand persistとの競合回避)
- tearoffPaneIds: ターミナルセッションをkillせずにペインを分離
- 複数ペインのタブでは対象ペインのみ抽出してポップアウト
- TopBar・左サイドバー・右サイドバー(Changes/Files)を非表示
- プリセットボタン群(+Code等)を非表示、SetRunは維持
- ウィンドウドラッグ用バーをタブバー上部に追加
- ポップアウトウィンドウ内ではPop outボタンを非表示(無限増殖防止)
太いドラッグバーを削除し、タブバーの+ボタン右の空きスペースで
ウィンドウを掴めるように変更。トラフィックライト用の左パディングも調整。
- 全タブを1回のIPCメッセージでまとめて送信(個別送信による重複を防止)
- 受信側で既存タブIDとの重複チェックを追加
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 29, 2026

Warning

Rate limit exceeded

@MocA-Love has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 50 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 2 minutes and 50 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ce27ad8c-0067-4d9a-9188-25981b409e20

📥 Commits

Reviewing files that changed from the base of the PR and between b38002f and 042d2ce.

📒 Files selected for processing (4)
  • README.md
  • apps/desktop/src/main/lib/window-manager/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/components/PaneToolbarActions/PaneToolbarActions.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
📝 Walkthrough

Walkthrough

Implements tab tearoff functionality enabling users to detach tabs and panes into separate Electron windows. Adds a WindowManager class for multi-window lifecycle tracking, TRPC endpoint for tearoff creation, preload-level data retrieval, renderer-side hooks for tearoff initialization, store actions for tab/pane hydration, and conditional UI updates to handle tearoff window state.

Changes

Cohort / File(s) Summary
Main Process Architecture
apps/desktop/src/lib/trpc/routers/index.ts, apps/desktop/src/lib/trpc/routers/tab-tearoff.ts, apps/desktop/src/main/lib/window-manager/index.ts, apps/desktop/src/main/windows/main.ts
Introduces WindowManager for tracking multiple BrowserWindow instances and managing tearoff lifecycle (IPC handlers, pending data staging with 30s expiry, window creation/registration). Adds TRPC tabTearoff router with create endpoint validating tearoff input and delegating to WindowManager. Integrates WindowManager into main window setup, registering the main window and wiring IPC handlers.
Type System & Preload Integration
apps/desktop/src/lib/window-loader.ts, apps/desktop/src/preload/index.ts
Expands WindowId type to include "tearoff". Preload script now parses --tearoff-window-id argument, synchronously fetches corresponding tearoff data via IPC, and exposes tearoffWindowId and tearoffData on window.App.
Renderer State & Hooks
apps/desktop/src/renderer/hooks/useTearoffInit/index.ts, apps/desktop/src/renderer/hooks/useTearoffInit/useTearoffInit.ts, apps/desktop/src/renderer/lib/trpc-storage.ts, apps/desktop/src/renderer/stores/tabs/store.ts, apps/desktop/src/renderer/stores/tabs/types.ts, apps/desktop/src/renderer/screens/main/components/.../pane-guards.ts
Introduces tearoff detection helpers, lifecycle hooks for workspace navigation and IPC cleanup, and store hydration from preload-injected data. Skips storage persistence/hydration in tearoff windows. Adds detachTabForTearoff, hydrateFromTearoff, and hydrateReturnedTab store actions. Tracks tearoff pane IDs to prevent terminal cleanup during detach.
Layout & Sidebar Components
apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/index.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ContentHeader/ContentHeader.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceLayout/WorkspaceLayout.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/PresetsBar/PresetsBar.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
Conditionally suppresses sidebar, top bar, presets dropdown, and sidebar controls in tearoff windows. Adds drag region in tearoff window tab strip. DashboardLayout initializes tearoff hooks and gates rendering based on tearoff state.
Pane Components & Tearoff UI
apps/desktop/src/renderer/screens/main/components/.../BrowserPane/BrowserPane.tsx, apps/desktop/src/renderer/screens/main/components/.../ChatPane/ChatPane.tsx, apps/desktop/src/renderer/screens/main/components/.../DevToolsPane/DevToolsPane.tsx, apps/desktop/src/renderer/screens/main/components/.../FileViewerPane/FileViewerPane.tsx, apps/desktop/src/renderer/screens/main/components/.../TabPane.tsx, apps/desktop/src/renderer/screens/main/components/.../BasePaneWindow/BasePaneWindow.tsx, apps/desktop/src/renderer/screens/main/components/.../PaneToolbarActions/PaneToolbarActions.tsx, apps/desktop/src/renderer/screens/main/components/.../FileViewerToolbar/FileViewerToolbar.tsx, apps/desktop/src/renderer/screens/main/components/.../TabView/index.tsx
Adds optional onPopOut callback to all pane component props and integrates with toolbar actions. BasePaneWindow wires handlers; PaneToolbarActions renders conditional "Pop out" button with tooltip. TabView implements handlePopOut logic: either detaches entire tab or moves selected pane to new tab, then triggers TRPC tearoff creation with collected tab/pane data.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Renderer as Renderer<br/>(Main Window)
    participant IPC as Electron IPC
    participant Main as Main Process
    participant WindowMgr as WindowManager
    participant NewWindow as New Tearoff<br/>Window
    participant Preload as Preload Script
    participant NewRenderer as Renderer<br/>(Tearoff Window)

    User->>Renderer: Click "Pop out" button on pane
    activate Renderer
    Renderer->>Renderer: handlePopOut: Prepare tab/panes data
    Renderer->>IPC: electronTrpc.tabTearoff.create(tab, panes, workspaceId, screenX, screenY)
    deactivate Renderer

    activate Main
    IPC->>Main: TRPC mutation received
    Main->>WindowMgr: setPendingTearoffData(windowId, {tab, panes, workspaceId})
    activate WindowMgr
    WindowMgr->>WindowMgr: Store data with 30s expiry
    deactivate WindowMgr
    
    Main->>WindowMgr: createTearoffWindow({windowId, screenX, screenY})
    activate WindowMgr
    WindowMgr->>NewWindow: Create BrowserWindow<br/>(frameless, hidden, partition)
    WindowMgr->>NewWindow: Pass --tearoff-window-id arg
    deactivate WindowMgr
    
    NewWindow->>NewWindow: did-finish-load
    WindowMgr->>NewWindow: Show window
    deactivate Main

    activate NewWindow
    NewWindow->>Preload: Load preload script
    activate Preload
    Preload->>Preload: Parse --tearoff-window-id from argv
    Preload->>IPC: ipcRenderer.sendSync("get-tearoff-data", windowId)
    IPC->>Main: Sync IPC call
    activate Main
    Main->>WindowMgr: Retrieve pending data
    Main->>IPC: Return {tab, panes, workspaceId}
    deactivate Main
    Preload->>Preload: Expose tearoffWindowId & tearoffData<br/>on window.App
    deactivate Preload
    
    NewWindow->>NewRenderer: Initialize with tearoff data
    activate NewRenderer
    NewRenderer->>NewRenderer: useTearoffInit(): Hydrate store<br/>from window.App.tearoffData
    NewRenderer->>NewRenderer: Render isolated tab/workspace
    deactivate NewRenderer
    
    User->>NewRenderer: Work on pane content
    
    User->>NewWindow: Close tearoff window
    NewRenderer->>NewRenderer: beforeunload: Package tabs & panes
    NewRenderer->>IPC: ipcRenderer.send("tearoff-return-tabs", {tab, panes})
    deactivate NewWindow
    
    activate Main
    IPC->>Main: IPC handler: tearoff-return-tabs
    Main->>IPC: webContents.send to main window:<br/>"tearoff-tab-returned"
    deactivate Main
    
    activate Renderer
    Renderer->>Renderer: useReturnedTabListener():<br/>hydrateReturnedTab(tab, panes)
    Renderer->>Renderer: Merge tab & panes into store
    deactivate Renderer
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Hoppity-hop, tabs take flight!
Panes pop out to windows bright,
Tearoff magic, clean and tight,
WindowManager keeps windows right! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main feature: pane pop-out (tear-off into separate window) functionality.
Description check ✅ Passed The PR description is comprehensive and complete, including summary, architecture, UI changes, and test plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/desktop-tab-tearoff

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@MocA-Love MocA-Love self-assigned this Mar 29, 2026
メインウィンドウとの意味的な衝突を防止。
@MocA-Love MocA-Love marked this pull request as ready for review March 29, 2026 04:19
@MocA-Love
Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 29, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceLayout/WorkspaceLayout.tsx (1)

37-53: ⚠️ Potential issue | 🟠 Major

Tearoff windows can still render ChangesContent full-width.

The new guard only suppresses the RightSidebar panel. When currentMode === SidebarMode.Changes, tearoff windows still take the isExpanded branch and never render ContentView, so the popped-out pane can disappear behind the changes screen.

Possible fix
 export function WorkspaceLayout({
 	workspaceId,
 	defaultExternalApp,
 	onOpenInApp,
 	onOpenQuickOpen,
 }: WorkspaceLayoutProps) {
 	useBrowserLifecycle();
+	const isTearoff = isTearoffWindow();
 	const isSidebarOpen = useSidebarStore((s) => s.isSidebarOpen);
 	const sidebarWidth = useSidebarStore((s) => s.sidebarWidth);
 	const setSidebarWidth = useSidebarStore((s) => s.setSidebarWidth);
 	const isResizing = useSidebarStore((s) => s.isResizing);
 	const setIsResizing = useSidebarStore((s) => s.setIsResizing);
 	const currentMode = useSidebarStore((s) => s.currentMode);
 
-	const isExpanded = currentMode === SidebarMode.Changes;
+	const isExpanded = !isTearoff && currentMode === SidebarMode.Changes;
 ...
-			{!isTearoffWindow() && isSidebarOpen && (
+			{!isTearoff && isSidebarOpen && (
 				<ResizablePanel
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceLayout/WorkspaceLayout.tsx`
around lines 37 - 53, The tearoff window still shows ChangesContent because
isExpanded only checks currentMode; change the expanded logic in WorkspaceLayout
so tearoff windows never take the Changes branch: update the isExpanded
computation to include the tearoff check (e.g., isExpanded = currentMode ===
SidebarMode.Changes && !isTearoffWindow()), so when isTearoffWindow() is true
the component renders ContentView instead of ChangesContent (this keeps the
existing RightSidebar guard intact).
🧹 Nitpick comments (1)
apps/desktop/src/main/windows/main.ts (1)

18-18: Use the main/* alias for this import.

This file already uses tsconfig aliases for main-side modules, so the new ../ hop adds a second import style for the same layer.

♻️ Proposed fix
-import { windowManager } from "../lib/window-manager";
+import { windowManager } from "main/lib/window-manager";
As per coding guidelines, "Use aliases as defined in `tsconfig.json` when possible".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/windows/main.ts` at line 18, The import uses a relative
path instead of the tsconfig alias; replace the relative import of windowManager
(import { windowManager } from "../lib/window-manager") with the main/* alias
form (e.g., import { windowManager } from "main/lib/window-manager") so all
main-side modules use the same alias-style imports.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/lib/trpc/routers/tab-tearoff.ts`:
- Line 18: The windowId currently uses Date.now() which can collide; replace
creation of windowId (the `const windowId = ...` in the tearoff creation flow)
with a collision-resistant UUID (e.g., crypto.randomUUID() or a nanoid) and use
that same generated id when populating `pendingTearoffData` and adding the entry
in `WindowManager.windows` so keys cannot collide across rapid tearoffs.

In `@apps/desktop/src/main/lib/window-manager/index.ts`:
- Around line 35-58: The ad-hoc IPC handlers in registerIpcHandlers (the
ipcMain.on listeners for "get-tearoff-data", "tearoff-return-tabs" and the
"tearoff-tab-returned" send) must be replaced with the repo's tRPC layer: remove
those ipcMain handlers and instead implement corresponding tRPC procedures
(e.g., tearoff.getTearoffData query that reads pendingTearoffData, and
tearoff.returnTabs mutation that accepts Array<{tab, panes}> and forwards to the
main window via the same getMain().webContents.send logic or via a tRPC
subscription/event); update the preload/renderer code to call the new trpc query
during preload for synchronous data needs (or adapt to an initial trpc query at
startup) and to call the trpc mutation for returning tabs; keep the same data
shape and ensure pendingTearoffData is accessed from the new trpc resolver.
- Around line 40-43: The current ipcMain.on("get-tearoff-data") handler deletes
pendingTearoffData immediately when the preload reads it, so crashes/reloads
before renderer bootstrap lose the only copy; change it to return the data
without deleting and instead delete only when the new window explicitly ACKs
bootstrap. Concretely: update the "get-tearoff-data" listener to read and return
this.pendingTearoffData.get(windowId) but not call delete, add a new ipcMain
listener (e.g., "tearoff-bootstrap-ack" or similar) that accepts the windowId
and removes this.pendingTearoffData.delete(windowId) when the renderer signals
it has hydrated, and optionally add a TTL/cleanup routine to purge stale entries
from pendingTearoffData to avoid unbounded growth.

In `@apps/desktop/src/preload/index.ts`:
- Around line 23-26: Replace the untyped tearoffData any with a proper interface
and expose it on window.App: define a shared type (e.g. TearoffPayload) in a
common types/schema module, import that type into the preload file, change the
declaration const tearoffData: any to const tearoffData: TearoffPayload | null,
cast/validate the ipcRenderer.sendSync("get-tearoff-data", tearoffWindowId)
result to that type (or run a small runtime guard using the shared schema), and
then assign it to window.App.tearoffData typed as TearoffPayload | null so the
renderer sees a typed payload; update references to tearoffData and
tearoffWindowId accordingly.

In `@apps/desktop/src/renderer/hooks/useTearoffInit/useTearoffInit.ts`:
- Around line 39-48: The payload built in tabsWithPanes only returns { tab,
panes } and therefore loses which tab was active; update the construction to
include the active-tab information by referencing the current active tab
identifier in state (e.g., state.activeTabId or similar) and either add an
isActive flag to each entry or include activeTabId at the top-level of the
tearoff payload so the main window can restore the correct active tab; change
the tabsWithPanes mapping (where state.tabs and state.panes are used) to include
that active marker or include state.activeTabId in the message sent.

In `@apps/desktop/src/renderer/lib/trpc-storage.ts`:
- Around line 5-8: The module-level boolean _isTearoffWindow is currently
causing createTrpcStorageAdapter to skip hydration/writes for every persisted
adapter; change the logic so tearoff-only bypass is scoped to the tabs adapter
(trpcTabsStorage) or made opt-in per adapter: remove the global early-return
based on _isTearoffWindow inside createTrpcStorageAdapter and instead either (A)
add an options parameter like { disableInTearoff?: boolean } to
createTrpcStorageAdapter and only skip IO when that option is true and
_isTearoffWindow is true, or (B) keep a special-case that only performs the
tearoff bypass when the adapterId equals "trpcTabsStorage" (or the exported
identifier for the tabs adapter). Update any call sites that construct the tabs
adapter to pass the new option if you choose the opt-in approach and remove the
module-level behavior so theme, hotkeys, ringtone, etc., are no longer affected.

In `@apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx`:
- Around line 108-110: The two hooks useTearoffInit() and
useReturnedTabListener() currently use ipcRenderer directly and must be
refactored to use the electronTrpc client from renderer/lib/electron-trpc
following the same pattern as useWorkspaceShortcuts and
useBrowserNewWindowHandler: replace direct ipcRenderer calls inside
useTearoffInit and useReturnedTabListener with calls to
electronTrpc.procedure/mutation for one-off actions and
electronTrpc.subscription (implemented via observable<T>((emit) => { ... })) for
event streams; move the tearoff event emission logic out of
src/main/lib/window-manager/index.ts into corresponding tRPC
procedures/subscriptions on the main side and wire the renderer hooks to
subscribe/consume those procedures, keeping isTearoffWindow() as needed for
local checks and ensuring you unregister subscriptions in the hooks cleanup.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ContentHeader/ContentHeader.tsx`:
- Around line 18-24: The current header applies paddingLeft: 88 whenever
isTearoff is true; change it to only apply that inset on macOS by combining the
tearoff check with a macOS/platform check (e.g., process.platform === 'darwin'
or an existing isMac helper) in the ContentHeader component where isTearoff is
used; update the style prop so it sets { paddingLeft: 88 } only when isTearoff
&& isMac, otherwise undefined.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/components/PaneToolbarActions/PaneToolbarActions.tsx`:
- Around line 37-50: The pop-out icon-only button (the button with
onClick={onPopOut} that renders <LuArrowUpRight> inside
Tooltip/TooltipTrigger/TooltipContent) lacks an accessible name; add an
accessible label by giving that button an aria-label (e.g., aria-label="Pop out
to new window") or an aria-labelledby pointing to a visually hidden element
containing the same text so screen readers can announce it while keeping the
visual tooltip unchanged.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx`:
- Around line 58-112: handlePopOut currently calls tearoffMutation.mutate()
fire-and-forget and immediately detaches the tab, which can leave local state
inconsistent on failure; change to use await tearoffMutation.mutateAsync inside
a try/catch in handlePopOut, only call state.detachTabForTearoff(tab.id) and
updated.detachTabForTearoff(newTabId) after mutateAsync resolves, and on error
rollback the local split created by movePaneToNewTab (implement a small helper
like revertMovePaneToNewTab(newTabId, paneId) or restore the original panes/tabs
in useTabsStore) and surface/log the error.

In `@apps/desktop/src/renderer/stores/tabs/store.ts`:
- Around line 2139-2148: The current fixed 5s timeout that clears tearoffPaneIds
(the setTimeout loop deleting paneId for each paneId) is fragile; replace the
timer-based clearing with an explicit lifecycle acknowledgement: stop using the
setTimeout block and instead clear the pane IDs when the tearoff window emits a
ready/ACK event (e.g., an IPC message like "tearoff-ready" or a lifecycle
callback from the new window) or when a reattach/close event is received; update
the code around tearoffPaneIds, paneIds and the timeout-removal logic so
deletion happens only on that explicit ACK (and also ensure you delete on
failure/abort paths) rather than after a fixed delay.
- Around line 191-213: The seeded tearoff state from _tearoffData is being
overwritten by the persisted rehydrate merge; update the persist call inside
useTabsStore (the create<TabsStore>() → devtools(persist(...)) block) to pass a
custom merge function that, when _tearoffData is present, preserves the seeded
keys (tabs, panes, activeTabIds, focusedPaneIds, tabHistoryStacks) from the
currentState instead of letting savedState overwrite them; implement the merge
to shallow-merge savedState into currentState but if _tearoffData is truthy copy
currentState.tabs, currentState.panes, currentState.activeTabIds,
currentState.focusedPaneIds and currentState.tabHistoryStacks into the merge
result so the tearoff bootstrap survives rehydration.

---

Outside diff comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceLayout/WorkspaceLayout.tsx`:
- Around line 37-53: The tearoff window still shows ChangesContent because
isExpanded only checks currentMode; change the expanded logic in WorkspaceLayout
so tearoff windows never take the Changes branch: update the isExpanded
computation to include the tearoff check (e.g., isExpanded = currentMode ===
SidebarMode.Changes && !isTearoffWindow()), so when isTearoffWindow() is true
the component renders ContentView instead of ChangesContent (this keeps the
existing RightSidebar guard intact).

---

Nitpick comments:
In `@apps/desktop/src/main/windows/main.ts`:
- Line 18: The import uses a relative path instead of the tsconfig alias;
replace the relative import of windowManager (import { windowManager } from
"../lib/window-manager") with the main/* alias form (e.g., import {
windowManager } from "main/lib/window-manager") so all main-side modules use the
same alias-style imports.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b525fefc-e23c-4d6c-b36a-438cf85c1669

📥 Commits

Reviewing files that changed from the base of the PR and between 9fed59e and b38002f.

📒 Files selected for processing (27)
  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/lib/trpc/routers/tab-tearoff.ts
  • apps/desktop/src/lib/window-loader.ts
  • apps/desktop/src/main/lib/window-manager/index.ts
  • apps/desktop/src/main/windows/main.ts
  • apps/desktop/src/preload/index.ts
  • apps/desktop/src/renderer/hooks/useTearoffInit/index.ts
  • apps/desktop/src/renderer/hooks/useTearoffInit/useTearoffInit.ts
  • apps/desktop/src/renderer/lib/trpc-storage.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ContentHeader/ContentHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/DevToolsPane/DevToolsPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerToolbar/FileViewerToolbar.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/TabPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/components/BasePaneWindow/BasePaneWindow.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/components/PaneToolbarActions/PaneToolbarActions.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/pane-guards.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/PresetsBar/PresetsBar.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceLayout/WorkspaceLayout.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/stores/tabs/types.ts

Comment thread apps/desktop/src/lib/trpc/routers/tab-tearoff.ts
Comment thread apps/desktop/src/main/lib/window-manager/index.ts
Comment thread apps/desktop/src/main/lib/window-manager/index.ts
Comment thread apps/desktop/src/main/lib/window-manager/index.ts
Comment thread apps/desktop/src/preload/index.ts
Comment thread apps/desktop/src/renderer/stores/tabs/store.ts
Comment thread apps/desktop/src/renderer/stores/tabs/store.ts
- mainWindow不在時のタブ返却消失にログ警告を追加
- handlePopOutでmutate成功後にdetachするよう修正(タブ消失防止)
- Pop outボタンにaria-labelを追加(a11y)
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