Skip to content

[codex] fix v1 split pane startup sizing#3416

Merged
Kitenite merged 2 commits into
mainfrom
fix-split-pane-sizing
Apr 13, 2026
Merged

[codex] fix v1 split pane startup sizing#3416
Kitenite merged 2 commits into
mainfrom
fix-split-pane-sizing

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Apr 13, 2026

Summary

Fixes a v1 terminal race where opening a split pane and launching an initial command at the same time could start the terminal session at fallback dimensions, causing TUIs to initialize with the wrong size.

Root Cause

The helper-side launch path could create or attach the daemon session before the newly-created split pane had mounted and finished its first real attach. In that case the backend could start from fallback sizing, and the initial command would run before the mounted terminal had established the correct dimensions.

What Changed

  • Added a streamReady waiter to the v1 terminal cache so callers can wait for the mounted terminal attach to complete.
  • Updated launchCommandInPane to optionally wait for the mounted session instead of issuing a helper-side attach first.
  • Wired the v1 split-pane preset launch paths to use that mounted-session readiness path before writing the initial command.
  • Propagated initial attach failures into the readiness waiter so it does not hang on attach errors.

Impact

  • Split-pane terminal launches in v1 now wait for the pane's real fitted session before sending the startup command.
  • TUIs opened via preset-driven split panes should start with the correct size instead of briefly booting at fallback dimensions.

Validation

  • bunx biome check apps/desktop/src/renderer/lib/terminal/launch-command.ts apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/v1-terminal-cache.ts apps/desktop/src/renderer/stores/tabs/useTabsWithPresets.ts
  • bun test apps/desktop/src/renderer/lib/terminal/launch-command.test.ts

Notes

I also checked the v2 terminal path for inspiration. V2 gates initial commands on shell readiness, but the v1 bug here was specifically about pane sizing, so the correct fix was to wait on v1's mounted-session readiness instead of copying the v2 shell-ready flow directly.


Summary by cubic

Fixes a race in v1 split panes where the initial command ran before the pane mounted, causing fallback terminal sizes. Launches now wait for the mounted, fitted session before sending startup commands; background tabs still use the helper-side attach.

  • Bug Fixes
    • Added a session-readiness tracker (waitForTerminalSessionReady with mark/clear/reject) and integrated it into the v1 terminal lifecycle.
    • Added a waitForMountedSession option to launchCommandInPane; preset and split-pane flows use it only when panes mount in the active tab.
    • Propagate attach errors and pane closes to reject waiters and prevent hangs; clear readiness on re-attach.

Written for commit b28c69b. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved terminal command execution reliability by ensuring sessions are properly ready before launching commands
    • Enhanced handling of multi-pane and preset command launches to wait for terminal initialization
    • Fixed potential issues with command execution timing in newly created or split terminal panes

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 13, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR introduces a terminal session readiness system that allows callers to wait for a terminal pane's session to be fully mounted before executing commands. A new session-readiness module tracks pane readiness states, with integration points including command launching (new waitForMountedSession option), terminal lifecycle management (clearing/marking/rejecting readiness states), preset command launching (forwarding the option), and terminal cleanup (rejecting readiness on kill).

Changes

Cohort / File(s) Summary
Session readiness core
apps/desktop/src/renderer/lib/terminal/session-readiness.ts
New module implementing in-memory readiness tracking per pane. Exports functions to clear, mark, reject, and wait for terminal session readiness.
Command launch integration
apps/desktop/src/renderer/lib/terminal/launch-command.ts, apps/desktop/src/renderer/lib/terminal/launch-command.test.ts
Added optional waitForMountedSession flag to LaunchCommandInPaneOptions. When enabled, branches to wait for session readiness before writing command. Includes test cases for successful and rejected readiness scenarios.
Lifecycle management
apps/desktop/src/renderer/lib/terminal/useTerminalLifecycle.ts, apps/desktop/src/renderer/stores/tabs/utils/terminal-cleanup.ts
Integrated readiness signaling: clears readiness on attach start, marks readiness on successful attach, rejects readiness on errors, and rejects readiness when killing a terminal pane.
Preset command launching
apps/desktop/src/renderer/stores/tabs/useTabsWithPresets.ts
Extended launchPresetCommand and launchPresetCommands to accept and forward optional { waitForMountedSession?: boolean } flag. Updated multi-pane and split-pane launch paths to pass { waitForMountedSession: true }.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant LaunchCommand as launchCommandInPane
    participant SessionReady as waitForTerminalSessionReady
    participant Lifecycle as useTerminalLifecycle
    participant WriteCmd as writeCommandInPane

    Caller->>LaunchCommand: launchCommandInPane({..., waitForMountedSession: true})
    LaunchCommand->>SessionReady: waitForTerminalSessionReady(paneId)
    activate SessionReady
    note over SessionReady: Waits for signal (returns promise)
    
    Lifecycle->>Lifecycle: ensureTerminalAttached + attach flow
    Lifecycle->>SessionReady: markTerminalSessionReady(paneId)
    deactivate SessionReady
    
    SessionReady-->>LaunchCommand: Promise resolves
    LaunchCommand->>WriteCmd: writeCommandInPane(paneId, command)
    WriteCmd-->>LaunchCommand: Command written
    LaunchCommand-->>Caller: Complete

    alt On Error
        Lifecycle->>SessionReady: rejectTerminalSessionReady(paneId, error)
        SessionReady-->>LaunchCommand: Promise rejects
        LaunchCommand-->>Caller: Error propagates
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A readiness awaits, a pane must be prepared,
Before the command springs to life, with proper care.
Through lifecycle's dance, the session finds its way,
Mounted and ready, commands execute their play! 🎯✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% 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 title '[codex] fix v1 split pane startup sizing' is specific and directly reflects the main change: fixing a race condition in v1 terminal split-pane startup that caused incorrect sizing.
Description check ✅ Passed The description is comprehensive and well-structured, covering root cause, changes, impact, and validation. All required template sections are present with substantive content.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-split-pane-sizing

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.

@Kitenite Kitenite marked this pull request as ready for review April 13, 2026 15:56
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 13, 2026

Greptile Summary

This PR fixes a v1 terminal race condition where a split-pane's initial command could be written before the new pane had finished mounting and fitting its xterm instance, causing TUIs to initialise at fallback dimensions. The fix introduces a streamReadyWaiters promise queue in the v1 terminal cache so that launchCommandInPane can await the mounted, fitted session before writing the startup command, rather than issuing its own helper-side attach upfront.

Changes at a glance:

  • v1-terminal-cache.ts — adds waitForStreamReady / failStreamReady exports and a streamReadyWaiters side-table; setStreamReady and dispose now drain the waiter queue.
  • launch-command.ts — adds a waitForMountedSession fast-path that skips createOrAttach and awaits stream readiness before writing.
  • useTerminalLifecycle.ts — propagates TERMINAL_SESSION_KILLED and general attach errors into failStreamReady so waiters are not left hanging on failure.
  • useTabsWithPresets.ts — all split-pane and multi-pane preset launch paths now pass { waitForMountedSession: true }.

Issues found:

  • waitForStreamReady has no timeout; the isTerminalAttachCanceledMessage early-return branch in useTerminalLifecycle does not call failStreamReady, so in a pathological cancel-loop the waiter could be left unresolved (the command would silently never run). dispose is the safety net, but an explicit timeout would be more robust.
  • The waitForMountedSession: true code path silently ignores createOrAttach, tabId, workspaceId, and cwd — worth a JSDoc note so future callers know these are no-ops on that branch.
  • No unit tests cover the new waitForMountedSession path in launch-command.test.ts.

Confidence Score: 4/5

Safe to merge — the race condition fix is correct and all main failure paths are covered; remaining issues are non-blocking P2s.

The core fix is sound: the waiter queue is properly initialised, resolved on success, rejected on the two attach-error paths and on dispose. The three P2 findings (no timeout, undocumented ignored params, missing tests) do not affect correctness in normal operation. The only realistic risk is the pathological cancel-loop scenario with no timeout, but since all launched commands are fire-and-forget (void + .catch), the worst outcome is a silently dropped command rather than a crash or hang visible to the user.

v1-terminal-cache.ts (timeout concern) and launch-command.test.ts (new path untested)

Important Files Changed

Filename Overview
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/v1-terminal-cache.ts Adds streamReadyWaiters map, waitForStreamReady/failStreamReady exports, and wires them into setStreamReady/dispose; no timeout on waiters is a minor concern.
apps/desktop/src/renderer/lib/terminal/launch-command.ts Adds waitForMountedSession fast-path that awaits stream readiness before writing; ignores createOrAttach/tabId/workspaceId/cwd on that path without documentation.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts Propagates attach errors (TERMINAL_SESSION_KILLED and general failure) into failStreamReady to unblock waiters; small, focused addition with correct placement in existing error handlers.
apps/desktop/src/renderer/stores/tabs/useTabsWithPresets.ts Threads waitForMountedSession:true through all split-pane and multi-pane preset launch paths; launchFirstPresetInFocusedPane is internal-only so no external callers are missed.

Sequence Diagram

sequenceDiagram
    participant UTP as useTabsWithPresets
    participant LCP as launchCommandInPane
    participant Cache as v1-terminal-cache
    participant TLC as useTerminalLifecycle

    UTP->>UTP: storeSplitPane*(tabId, ...)
    UTP->>LCP: launchCommandInPane({waitForMountedSession:true})
    LCP->>Cache: waitForStreamReady(paneId)
    Note over Cache: Returns pending Promise,<br/>adds waiter to streamReadyWaiters

    par React mounts new pane
        TLC->>TLC: createOrAttach.mutateAsync(...)
        alt attach success
            TLC->>Cache: setStreamReady(paneId)
            Cache->>Cache: resolveStreamReadyWaiters(paneId)
            Cache-->>LCP: Promise resolved
            LCP->>LCP: writeCommandInPane(paneId, command)
        else attach error (killed / general)
            TLC->>Cache: failStreamReady(paneId, error)
            Cache->>Cache: rejectStreamReadyWaiters(paneId)
            Cache-->>LCP: Promise rejected
            LCP-->>UTP: .catch → log error
        else pane disposed
            TLC->>Cache: dispose(paneId)
            Cache->>Cache: rejectStreamReadyWaiters(paneId)
            Cache-->>LCP: Promise rejected
        end
    end
Loading

Comments Outside Diff (1)

  1. apps/desktop/src/renderer/lib/terminal/launch-command.test.ts, line 1-10 (link)

    P2 Missing test coverage for the waitForMountedSession path

    The new waitForMountedSession: true branch in launchCommandInPane — which skips createOrAttach entirely and awaits waitForStreamReady instead — has no test coverage. This is the core behavioural change introduced by the PR, and the happy path ("stream already ready → write command") and the failure path ("stream rejects → error propagated") would both be straightforward to test by mocking the v1-terminal-cache import.

    For example:

    it("writes the command immediately when the stream is already ready (waitForMountedSession)", async () => {
        // mock v1-terminal-cache so waitForStreamReady resolves immediately
        ...
        const createOrAttach = mock(async () => ({}));
        const write = mock(async () => ({}));
    
        await launchCommandInPane({
            paneId: "pane-1", tabId: "tab-1", workspaceId: "ws-1",
            command: "echo hello", createOrAttach, write,
            waitForMountedSession: true,
        });
    
        expect(createOrAttach).not.toHaveBeenCalled();
        expect(write).toHaveBeenCalledWith({ paneId: "pane-1", data: "echo hello\n", throwOnError: true });
    });

Reviews (1): Last reviewed commit: "fix v1 split pane startup sizing" | Re-trigger Greptile

Comment thread apps/desktop/src/renderer/lib/terminal/launch-command.ts
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 4 files

Copy link
Copy Markdown
Contributor

@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.

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/ContentView/TabsContent/Terminal/v1-terminal-cache.ts (1)

346-363: ⚠️ Potential issue | 🟠 Major

Reject stream-ready waiters even when the cache entry does not exist.

Line 348 returns before waiter rejection, so promises created by waitForStreamReady (when called before getOrCreate) can hang forever if the pane is disposed before mounting.

💡 Proposed fix
 export function dispose(paneId: string): void {
+	rejectStreamReadyWaiters(
+		paneId,
+		new Error("Terminal disposed before reaching stream-ready state"),
+	);
+
 	const entry = cache.get(paneId);
 	if (!entry) return;
@@
 	entry.cleanupCreation();
 	entry.xterm.dispose();
 	cache.delete(paneId);
-	rejectStreamReadyWaiters(
-		paneId,
-		new Error("Terminal disposed before reaching stream-ready state"),
-	);
 }
🤖 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/ContentView/TabsContent/Terminal/v1-terminal-cache.ts`
around lines 346 - 363, The dispose function currently returns immediately if
cache.get(paneId) is falsy which prevents calling rejectStreamReadyWaiters and
can leave promises from waitForStreamReady hanging; change dispose (function
dispose) so it still calls rejectStreamReadyWaiters(paneId, new Error(...)) when
no cache entry exists (i.e., move or duplicate the rejectStreamReadyWaiters call
before the early return or invoke it in the !entry branch) while preserving the
existing behavior when entry exists (disconnect resizeObserver, unsubscribe
subscription, call cleanupCreation, dispose xterm, delete cache) and keeping the
DEBUG_TERMINAL log.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/v1-terminal-cache.ts`:
- Around line 346-363: The dispose function currently returns immediately if
cache.get(paneId) is falsy which prevents calling rejectStreamReadyWaiters and
can leave promises from waitForStreamReady hanging; change dispose (function
dispose) so it still calls rejectStreamReadyWaiters(paneId, new Error(...)) when
no cache entry exists (i.e., move or duplicate the rejectStreamReadyWaiters call
before the early return or invoke it in the !entry branch) while preserving the
existing behavior when entry exists (disconnect resizeObserver, unsubscribe
subscription, call cleanupCreation, dispose xterm, delete cache) and keeping the
DEBUG_TERMINAL log.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dbc4189e-37f7-4c08-9839-35386ed829aa

📥 Commits

Reviewing files that changed from the base of the PR and between 102633c and a4f9dba.

📒 Files selected for processing (4)
  • apps/desktop/src/renderer/lib/terminal/launch-command.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/v1-terminal-cache.ts
  • apps/desktop/src/renderer/stores/tabs/useTabsWithPresets.ts

@Kitenite Kitenite merged commit 31fcf19 into main Apr 13, 2026
6 of 7 checks passed
@Kitenite Kitenite deleted the fix-split-pane-sizing branch April 13, 2026 16:32
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

MocA-Love pushed a commit to MocA-Love/superset that referenced this pull request Apr 14, 2026
* fix v1 split pane startup sizing

* Handle background
MocA-Love pushed a commit to MocA-Love/superset that referenced this pull request Apr 14, 2026
* fix v1 split pane startup sizing

* Handle background
MocA-Love added a commit to MocA-Love/superset that referenced this pull request Apr 14, 2026
…ssion-readiness

[PR4/5] fix(upstream): v1 split pane 起動時のサイジング修正(ターミナル Session Readiness 機構)(superset-sh#3416)
MocA-Love pushed a commit to MocA-Love/superset that referenced this pull request Apr 14, 2026
All 9 upstream commits have been individually cherry-picked via PR#159~#163:

| Upstream | Our PR | Description |
|---|---|---|
| d656b7e (superset-sh#3415) | #159 (PR#1) | terminal clipboard handling |
| 31fcf19 (superset-sh#3416) | #162 (PR#4) | v1 split pane startup sizing fix |
| 039edf2 (superset-sh#3403) | #161 (PR#3) | Cmd+Alt+Arrow spatial pane focus |
| b18a00c (superset-sh#3421) | #159 (PR#1) | v2 right sidebar toggle reactive |
| 3dd1de2 (superset-sh#3420) | #161 (PR#3) | v2 diff viewer + tab title resolution |
| b42a114 (superset-sh#3418) | #159 (PR#1) | CodeMirror hotkey enablement |
| c925f4d (superset-sh#3422) | #160 (PR#2) | unbound defaults + restore prev/next tab/workspace |
| bb12c09 (superset-sh#3419) | #163 (PR#5) | version bump 1.5.3 |
| 47efa73 (superset-sh#3432) | #159 (PR#1) | pending/update-required error selectable |

Fork-specific features preserved:
- auto-updater (IS_FORK, GitHub Releases API)
- QuitMode/cleanupMainWindowResources lifecycle
- GitHubSyncService, SpreadsheetViewer
- BROWSER_RELOAD / BROWSER_HARD_RELOAD / SEARCH_IN_FILES hotkeys
- HotkeyCategory "Browser"
- v1 deep-link navigation (useSearch/WorkspaceSearchParams)
- v1 tRPC-based PREV/NEXT_WORKSPACE handlers
- v1 CLOSE_TERMINAL/CLOSE_TAB hotkey handlers
- v2 extra state (rightSidebarOpenViewWidth, showPresetsBar)
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