Skip to content

feat(terminal): tmux shell navigator with presets and mobile support#4

Closed
dguerizec wants to merge 17 commits intotwidi:mainfrom
dguerizec:feature/tmux-navigator
Closed

feat(terminal): tmux shell navigator with presets and mobile support#4
dguerizec wants to merge 17 commits intotwidi:mainfrom
dguerizec:feature/tmux-navigator

Conversation

@dguerizec
Copy link
Contributor

Summary

  • Add a full tmux shell navigator: create, switch, and monitor named tmux windows from the UI
  • Support preconfigured shell presets via .twicc-tmux.json at the project root (documented in docs/terminal-presets.md)
  • Mobile-optimized toolbar: dropdown for window switching, copy mode toggle, Ctrl+C/Z buttons
  • Touch scroll support with context-aware behavior (arrow keys for less/vim, SGR mouse for shell scrollback)
  • Desktop mouse wheel fix: custom tmux WheelUpPane/WheelDownPane bindings for proper scroll in all contexts
  • Background monitor detects window close/rename and alternate screen changes in real-time

Test plan

  • Open terminal tab → shell navigator appears with preset list
  • Create a preset shell → window opens, command runs, icon turns green
  • Switch between windows via tabs (desktop) or dropdown (mobile)
  • Close a tmux window externally → disappears from tabs/dropdown within 2s
  • Mobile: swipe to scroll in shell and in less/vim
  • Desktop: mouse wheel scrolls correctly in both shell and less
  • Copy mode toggle: enable → select text → auto-reset to scroll mode

🤖 Generated with Claude Code

@twidi
Copy link
Owner

twidi commented Mar 4, 2026

Thanks for this contribution! The tmux navigator with presets and mobile support is a solid feature. After going through the overall approach, here's some feedback:

1. Documentation: preset path resolution is misleading

The docs (terminal-presets.md) say that relative cwd paths are resolved "against the project directory" and that the config file lives "at the root of your project directory". But in practice, load_tmux_presets(cwd) receives the cwd from get_session_info(), which follows a priority chain: session.git_directory → project.directory → project.git_root → ~.

This means that if a session has a specific git_directory (e.g. a submodule or worktree), the .twicc-tmux.json file will be looked up there, not at the project root. The docs should reflect this reality.

2. Consider adding global presets + a UI editor

Currently presets are per-project only (one .twicc-tmux.json per project directory). If a user wants common presets across all projects (e.g. a generic "shell" or "htop"), they have to duplicate the file everywhere.

A possible improvement:

  • Global presets in ~/.twicc/tmux-presets.json, merged with per-project presets at runtime (project overrides global when same name).
  • A dialog to edit presets from the UI, following the existing ProjectEditDialog.vue pattern — a form per preset with name/command/cwd fields and a global vs project scope toggle. This would make the feature accessible to users who aren't comfortable editing JSON files by hand. The dialog could be triggered from a button in the TmuxNavigator.

The file is already re-read on every refresh, so changes from a UI editor would take effect immediately.

3. Consider splitting this PR into smaller, focused PRs

This PR bundles three fairly independent concerns:

A — Tmux window management: creating/switching/monitoring windows, the TmuxNavigator component, presets, the tab bar and mobile dropdown. This is the core feature.

B — Scroll and touch interaction: desktop mouse wheel fix (WheelUpPane/WheelDownPane bindings), mobile touch scroll with context-aware behavior (shell vs alternate screen), copy mode toggle, alternate_on detection. This benefits all terminal users regardless of multi-window usage.

C — Mobile action buttons: Ctrl+C and Ctrl+Z buttons. These are hardcoded right now, but this could be designed as a generic configurable shortcuts toolbar from the start — letting users define their own buttons (label + escape sequence) via settings. This would be more useful and more extensible than two fixed buttons.

Splitting would make each PR easier to review, and would allow merging B (scroll fixes) and C (action buttons) independently since they provide value even without multi-window support.


This review reflects a discussion between @twidi and Claude Code on the overall approach — not a detailed code review.

@twidi
Copy link
Owner

twidi commented Mar 8, 2026

Please consider splitting this PR into smaller, focused PRs as explained above

dguerizec added 17 commits March 9, 2026 21:37
Add list, create, select, and rename window operations for tmux
sessions, following existing patterns (timeout, error handling,
socket naming).
Add WebSocket message handlers for list_windows, create_window, and
select_window operations, enabling the frontend to manage tmux windows.
Rename the default tmux window to "main" on connection, and update the
protocol documentation to reflect the new control messages and response
types.
wa-tab-show doesn't fire when re-clicking an already-active tab.
Pass -c cwd to tmux new-window so new shells open in the project's
working directory instead of tmux's session-level default.
Add support for a .twicc-tmux.json config file in the project root
that defines reusable shell presets with name, optional cwd, and
optional command. Presets appear in the TmuxNavigator and can be
launched with one click.

Backend: load_tmux_presets() reads the config, tmux_send_keys() runs
the optional command after window creation. Both list_windows and
create_window handlers are extended to support presets.

Frontend: TmuxNavigator shows available presets (not yet running)
with a play icon. useTerminal.createWindow() accepts either a string
or a preset object {name, cwd, command}.
Desktop: horizontal tab bar with active state highlighting.
Mobile: wa-select dropdown + Ctrl+C/Z buttons always accessible.
Auto-focus terminal after switching windows on both platforms.
Fetch window list on connect so tabs/dropdown appear immediately.
Poll tmux window list to detect external changes (shell exit, etc.).
Mobile toolbar gets a Copy toggle button (off by default):
- Off (scroll mode): swipe scrolls the terminal with natural direction
- On (copy mode): swipe selects text, auto-resets after copy

Scroll adapts to context via tmux pane state detection:
- Shell prompt: SGR mouse wheel → tmux copy-mode scrollback
- Alternate screen (less, vim): arrow keys → app handles scroll
- Raw shell (no tmux): xterm.js viewport scrollLines()

Backend polls tmux #{alternate_on} every 2s alongside window list,
sends state to frontend so scroll method switches automatically.
Also enables tmux mouse mode for proper wheel event handling.
…h icon

Presets now always stay in their section (even when running) with a green
icon indicator. Clicking a running preset selects it instead of creating
a duplicate. Preset shells are prefixed with a circle-play icon in both
the desktop tab bar and mobile dropdown.
…icc-tmux.json

Replace flat preset list with grouped sources (project dir, git root, CWD walk-up).
Each source resolves its own .twicc-tmux.json and appears as a labeled section in
the navigator. Context is refreshed from DB on each list_windows/create_window to
reflect CWD changes. Presets without explicit cwd now default to their config directory.
…aste

In tmux mode, SGR mouse tracking causes xterm.js to send all mouse events
as escape sequences to tmux instead of handling them locally. This breaks
text selection and paste on desktop.

A capture-phase mousedown listener on the terminal container blocks all
mouse buttons from reaching xterm.js. Left-button gets preventDefault for
custom drag selection via terminal.select(). Middle/right-click only get
stopPropagation so browser default actions (X11 paste, context menu) flow
through to xterm.js's native paste handler.

Also removes ClipboardAddon so Ctrl+V reaches the shell (quoted-insert)
instead of being intercepted for clipboard paste.
@dguerizec
Copy link
Contributor Author

replaced by:
#5
#6
#7 (which contains #6 )

@dguerizec
Copy link
Contributor Author

replaced by:
#5
#6
#7 (which contains #6 )

@dguerizec dguerizec closed this Mar 11, 2026
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.

2 participants