feat: Dynamic multi-terminal UI with split-pane support#6
Conversation
shreyas-lyzr
left a comment
There was a problem hiding this comment.
Large feature PR — multi-terminal UI with split-pane support. The concept is solid and addresses a real gap for power users. A few things need attention before this is merge-ready:
Blocking — dependency regression
The package-lock.json downgrades monaco-editor from ^0.55.1 to ^0.53.0 and bumps vite from ^5.2.0 to ^8.0.1. The vite major bump is especially risky — vite 8 introduced breaking config changes. If this PR was developed on a different base branch, please rebase onto current main and re-lock. The lockfile changes will likely conflict once rebased.
New clawcontainer dependency
clawcontainer was added without explanation in the PR body. What does it provide and is it a trusted/internal package? A note on this would help reviewers.
Split-pane state on page reload
The new tab/pane state appears to live entirely in JS memory. If the user reloads, all open terminals and their layout disappear. Is that the intended behavior, or should open sessions persist in localStorage?
Accessibility
The new tab buttons use emoji characters (⚙, +, ⊞) as their visible label with no aria-label. Screen readers will get meaningless characters. Add aria-label attributes to the interactive buttons.
Terminal lifecycle
When a tab is closed, is the associated WebContainer process explicitly terminated? If not, processes will leak silently in the background.
The overall direction is good. Addressing the dependency regression and clarifying the clawcontainer addition are the minimum requirements before merge.
shreyas-lyzr
left a comment
There was a problem hiding this comment.
Large, well-structured PR with a clear purpose. The multi-terminal tab system is a meaningful UX improvement and the code is generally solid. A few issues need attention before merge.
Blocking: onData listener accumulation in spawnShell
In container.ts, spawnShell registers terminal.onData(...) without disposing the previous one. Because TerminalManager.onData in this PR does dispose-before-register (via onDataDisposable), each new shell spawned to the same terminal will silently detach the previous shell's listener and attach the new one. This means switching between shells that share a TerminalManager instance will break input routing for all but the last-spawned shell. If each dynamic tab gets its own TerminalManager instance, this is fine — but that invariant is not enforced or documented, and the Map<string, TerminalManager> in UIManager suggests it might not always hold.
Blocking: window.addEventListener('resize') leak in spawnShell
Each call to spawnShell adds a new resize event listener on window with no corresponding removal. Spawning N shells registers N listeners, all of which survive even after the shell is closed. After a few sessions this adds up. Store the listener and remove it when the shell is killed, or use the ResizeObserver pattern already used in TerminalManager.
Concern: clawcontainer added as a production dependency
package.json adds clawcontainer: ^1.1.0 to dependencies. The diff does not show any direct import of this package in the source files being changed. If it is only used internally by spawnShell or if it is an internal package, that needs to be documented. If it is unused, remove it to avoid unnecessary bundle bloat.
Concern: vite 5 → 8 major bump without documented rationale
The package.json bump from vite ^5.2.0 to vite ^8.0.1 is a two-major-version jump. Vite 8 requires Node >=20.19.0 || >=22.12.0 and switches from esbuild to rolldown. This changes the minimum runtime requirement for contributors and may affect the WebContainer boot environment. This should be explicitly called out in the PR description and ideally confirmed against the CI node version.
Minor: monaco-editor downgraded from ^0.55.1 to ^0.53.0
This is a regression — going backwards on a dependency is unusual and may re-introduce bugs that were fixed between those versions. The reason is not mentioned in the PR description. Please justify or revert.
Minor: CSS split-pane specificity
The rule #terminal-panes.split-active .term-pane.active { display: flex; } and #terminal-panes.split-active #terminal-pane-split { display: flex !important; } mixes a specificity-won selector with !important. Prefer consistent specificity throughout; the !important here is a code smell that will make future layout overrides harder.
Summary
Adds a fully dynamic multi-terminal interface to the ClawLess terminal panel. Users can now open multiple independent shell sessions, rename them inline, close them individually, and view them in a split-pane layout alongside the Agent terminal — all without any page reload.
Motivation
The original UI had a single fixed terminal pane tied to the agent. Power users working with ClawLess often need a separate shell for running commands while the agent is active. This PR addresses that gap with a scalable, tab-based terminal system.
File Changes
shellProcess2/shellWriter2fields with aMap<string, WebContainerProcess>and a generic spawnShell(id, terminal) method supporting unlimited concurrent shells_terminal2andstartShell2calls⚙ Agenttab ++button +⊞split button; cleaner pane structure+button, inline rename viacontenteditable, scrollable overflow tab bar, split-pane layoutHow to Test
Environment: Windows 11, Node.js v20, Chrome 123
npm install && npm run devhttp://localhost:5173in ChromeTab creation:
+→ "Terminal 1" tab appears; a livejshshell launches inside the WebContainer+again → "Terminal 2" opens with another independent shellRename:
Close:
×button appears×→ tab and its shell pane are removed; view switches back to AgentSplit view:
⊞→ terminal panel splits: active shell on right, Agent on left⊞again → returns to single-pane viewNo regressions:
npm run buildpasses with zero TypeScript errorsChecklist
npm run buildpasses