Skip to content

Codex pane UI breaks on splits and re-renders (duplicated content, layout corruption) #93

@justinchoo93

Description

@justinchoo93

Bug

Codex pane UI breaks in multiple ways when the layout changes — pane splits, re-renders, or any event that triggers a layout recalculation. This appears to be a broader manifestation of the resize issue in #92.

Symptoms

  1. Duplicate content rendering — After splitting a Codex tab, the Codex pane renders its content twice (stacked vertically), as if the terminal was duplicated within the same pane
  2. Content cutoff — Codex pane content is clipped or doesn't reflow to the new pane dimensions
  3. General layout corruption — Any re-render of the interface can leave the Codex UI in a broken visual state

Only affects Codex — Claude Code and shell terminals handle splits/re-renders cleanly.

Steps to Reproduce

  1. Open a Codex session in a tab
  2. Split the tab (add a new terminal pane)
  3. Observe the Codex pane — content may appear duplicated or cut off

Also reproducible by triggering any re-render while a Codex pane is visible.

Root Cause Analysis

The codebase already has a comment documenting part of this at TerminalView.tsx:1154-1160:

Do NOT send terminal.resize here. At this point fit() hasn't run yet, so term.cols/rows are xterm defaults (80×24). Sending a resize with wrong dimensions causes the PTY to resize, triggering a TUI redraw at the wrong size (e.g., Codex renders 24 rows in a 40-row viewport, putting the text input at the top of the pane).

The issue is a resize/reflow event sequencing problem during pane splits that manifests most severely in Codex due to its stricter TUI rendering requirements:

  1. Split triggers tree restructuresplitPane() transforms leaf(codex)split(leaf(codex), leaf(new)), causing both PaneContainer children to re-render
  2. Multiple ResizeObserver fires race — Both pane containers' ResizeObservers fire simultaneously as flex layout recalculates dimensions. If fit() hasn't completed before the resize WS message fires, the PTY gets the wrong dimensions
  3. Codex TUI sensitivity — Codex has a full interactive TUI that absolutely requires accurate terminal dimensions at startup/resize. Shell terminals recover gracefully from dimension mismatches; Codex does not — it renders its UI components based on exact terminal size, so wrong dimensions produce duplicated/corrupted output
  4. React reconciliation during split — When the pane tree restructures, old TerminalView cleanup and new TerminalView initialization overlap. ResizeObserver callbacks from the old instance can fire with stale refs, potentially routing resize events to the wrong terminal

Key code paths

  • panesSlice.ts:313-362splitPane reducer: { ...targetPane } shallow copy creates new split children, triggering full re-render of both sides
  • PaneContainer.tsx:387-403 — Split rendering: both children get percentage-based flex sizing, both fire ResizeObserver
  • TerminalView.tsx:992-996 — ResizeObserver watches containerRef, calls requestTerminalLayout({ fit: true, resize: true })
  • TerminalView.tsx:551-587flushScheduledLayout: fit() → then resize WS message. If fit() doesn't run (hidden, disposed, or raced), resize sends wrong 80×24 defaults

Relationship to #92

Issue #92 describes content cutoff specifically on pane splits. This issue broadens the scope — the root cause affects all re-render scenarios, not just the initial split. They should be solved together.

Proposed Fix Direction

The fix likely involves:

  • Debouncing/sequencing resize events during split transitions — Ensure only one resize fires after both panes have settled into their final dimensions
  • Guarding against stale ResizeObserver callbacks — Verify terminal instance identity before sending resize
  • Post-split stabilization pass — After a split completes, schedule a single authoritative fit+resize for all visible panes (similar to how the visibility effect already does this for hidden→visible transitions)

Acceptance Criteria

  • Splitting a Codex pane horizontally renders both panes correctly — no duplicated content, no cutoff
  • Splitting a Codex pane vertically renders both panes correctly
  • Splitting from a Codex pane (adding a new terminal to the right) leaves the Codex pane intact
  • Splitting from a terminal pane into a Codex pane renders the Codex pane at correct dimensions
  • Resizing the divider between panes causes Codex to reflow correctly (no corruption)
  • Browser window resize with a Codex pane in a split layout renders correctly
  • Switching tabs away from and back to a split layout with Codex renders correctly
  • Zooming/unzooming a Codex pane in a split layout renders correctly
  • Claude Code and shell terminals continue to work correctly in all split scenarios (no regression)
  • The fix handles the edge case where Codex is still initializing when a split occurs

Environment

  • Freshell v0.5.0 (production build, localhost:3344)
  • macOS, Chrome

Screenshots

(See screenshots in conversation — showing duplicate Codex content after split, and layout corruption with content cutoff)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions