Skip to content

feat(tmux/opencode): show pane-local OpenCode session titles with a toggle #9

Description

@itse4elhaam

Problem

I often run multiple OpenCode sessions as separate panes inside the same tmux window. The current OpenCode/tmux integration is window-oriented, so the window can only expose one name/state even when its panes contain different sessions.

Example:

Tmux window 1
├── pane 1 -> OpenCode session: Fix authentication redirect
└── pane 2 -> OpenCode session: Repair Mollie webhook reconciliation

The desired result is a small pane-border label on each pane:

┌ 1 · Fix authentication redirect ───────────────┐
│ OpenCode                                      │
└───────────────────────────────────────────────┘

┌ 2 · Repair Mollie webhook reconciliation ─────┐
│ OpenCode                                      │
└───────────────────────────────────────────────┘

Each label must follow the exact OpenCode session displayed in that pane, and pane labels must be toggleable without destroying their stored titles.

Source check before implementation

Verified against the latest relevant source/config before creating this issue:

  • Dotfiles default branch is currently master at commit 8175ac0480a98e28151541557646a677b13b7694 (chore(tmux): document status options).

  • .tmux.conf:

    • uses prefix C-a
    • uses base-index 1 and pane-base-index 1
    • loads janoamaral/tokyo-night-tmux through TPM
    • does not currently configure pane-border-format
    • currently sets the outer terminal title from window_name and session_name, not from pane-local OpenCode state
  • The current Tokyo Night tmux plugin explicitly runs:

    tmux set -g pane-border-status off

    Therefore this feature must be applied after the theme/TPM initialization, or otherwise integrated in a way that survives the theme loading order.

  • .config/opencode/opencode.json currently loads @koriit/opencode-tmux-window-status. Before implementing, inspect the exact installed package version/source and determine whether it can be extended or configured for pane-local title data. Do not blindly add a second competing window-renaming plugin.

  • .config/opencode/tui.json currently contains keybindings only and does not register a TUI plugin.

  • The current OpenCode TUI plugin specification supports:

    • file/path plugins registered through tui.json
    • api.route.current
    • session routes shaped as { name: "session", params: { sessionID } }
    • api.client
    • api.event.on(...)
  • Issue Make OpenCode tmux panes restore their exact sessions #8 already defines a pane-aware mechanism for tracking the exact active OpenCode session, including in-TUI session switches. If that implementation lands first, reuse its active-session observation rather than building a second independent source of truth.

  • Current tmux supports pane border status lines and pane-border-format. tmux 3.6 changed pane-border-format from a window option to a pane option, so implementation and tests must verify the actual installed tmux version and avoid stale scope assumptions.

Relevant source links:

Desired UX

Pane-local title

  • Every OpenCode pane displays a compact label based on the exact current session title.
  • Two OpenCode panes in the same tmux window can display two different titles simultaneously.
  • The label remains attached to the pane where that OpenCode process started, regardless of which pane is currently focused.
  • Switching sessions inside OpenCode through /sessions or the session picker updates that pane's label immediately, before another prompt has to be sent.
  • A resumed session displays its existing title promptly.
  • A new session updates when OpenCode generates or changes its title.

Toggle

  • Add one ergonomic tmux keybinding to toggle pane-border labels for the current window.
  • C-a T is a reasonable candidate, but the implementation must check the effective tmux key table after plugins load before choosing it.
  • First press hides pane labels.
  • Second press restores them.
  • Toggling visibility must not delete the pane-local OpenCode title values.
  • Reloading .tmux.conf must remain idempotent.

Fallback and cleanup

  • Non-OpenCode panes should show either a small command/path fallback or a deliberately blank/minimal label.
  • When OpenCode exits normally and the pane returns to a shell, its old OpenCode title must not remain visible as stale state.
  • Child/subagent sessions must not replace the main pane title.
  • Long titles must be truncated by tmux formatting rather than creating oversized borders.
  • Newlines, terminal control sequences, quotes, semicolons, and shell syntax in a session title must be sanitized and passed safely.

Preferred implementation shape

Use pane-local tmux user options rather than renaming the shared window:

OpenCode TUI route/current session
        ↓
obtain exact sessionID
        ↓
fetch current human-readable session title
        ↓
tmux set-option -p -t "$TMUX_PANE" @opencode_session_title "<title>"
        ↓
pane-border-format reads #{@opencode_session_title}

Likely components:

  • a small OpenCode TUI plugin registered in .config/opencode/tui.json
  • pane-local tmux user option such as @opencode_session_title
  • pane-border configuration in .tmux.conf, placed after TPM/theme loading
  • a shared toggle command or helper used by both the keybinding and tests

Implementation constraints:

  • Capture and target the originating $TMUX_PANE; never write to whichever pane happens to be active when an asynchronous event arrives.
  • Use tmux pane IDs for runtime targeting, not window names or the current active pane.
  • Do not use a global/window option for the session title.
  • Do not use the tmux window name as the pane title source.
  • Prefer supported OpenCode TUI/plugin APIs over polling the terminal or reading internal databases.
  • Avoid creating a persistent store solely for this display feature. Persistent exact-session restoration belongs to issue Make OpenCode tmux panes restore their exact sessions #8.
  • If the existing @koriit/opencode-tmux-window-status plugin is replaced or narrowed, preserve useful status behavior deliberately and remove duplicate/conflicting integration.

Test-first requirement

The implementation PR must add the failing tests before the implementation and include evidence that:

  1. the tests fail against the current base commit for the expected reason
  2. the same tests pass after implementation

Tests must use an isolated tmux socket/server, for example tmux -L opencode-pane-title-test-$$, and must never modify or terminate the developer's normal tmux server.

The feature should expose a sourceable tmux fragment or a shared helper if needed so the test exercises the same implementation used by the real keybinding. Do not copy the production toggle logic into the test and then test the duplicate.

Correct real-life tests before implementation

Test 1: two panes retain two independent titles

Setup:

  1. Start an isolated tmux server with one window and two panes.

  2. Capture the two real pane IDs using split-window -P -F '#{pane_id}' / list-panes.

  3. Invoke the production title-writer path with:

    left pane  -> Fix authentication redirect
    right pane -> Repair Mollie webhook reconciliation
    
  4. Read the pane-local values using show-options -p -v -t <pane-id> @opencode_session_title.

Expected:

left pane option  = Fix authentication redirect
right pane option = Repair Mollie webhook reconciliation

Also assert the tmux window name was not changed by the title writer.

Why this is the correct test:

  • It uses a real tmux server and real pane IDs, not a mocked tmux command.
  • A window-scoped implementation cannot satisfy both assertions simultaneously.
  • An implementation that accidentally targets the active pane causes one pane to be overwritten and fails.
  • The current repo does not have a pane-local title writer, so this test must be red before implementation.

This is the core automated test. If it does not fail for a window-level implementation, the test is incorrect.

Test 2: the effective rendered border uses the target pane's value

Setup:

  1. Use the two-pane server from Test 1.
  2. Load the production pane-border configuration in the same order as the real .tmux.conf.
  3. Confirm the final effective pane-border-status is top or bottom, not off.
  4. Render/evaluate pane-border-format separately for each target pane, using tmux format expansion such as #{E:pane-border-format}.

Expected:

render(left)  contains "Fix authentication redirect"
render(left)  does not contain "Repair Mollie webhook reconciliation"
render(right) contains "Repair Mollie webhook reconciliation"
render(right) does not contain "Fix authentication redirect"

Why this is the correct test:

Setting a custom pane option is not sufficient. This proves tmux actually resolves the border format in the context of each pane. It also catches a format that accidentally reads the active pane or a shared window option.

The current setup must fail because Tokyo Night leaves pane-border-status off and the dotfiles do not provide an overriding pane title format.

Test 3: full-config/theme ordering does not disable the feature

Setup:

  1. Start a fresh isolated tmux server using the real dotfiles configuration with required plugin paths available, or run this as a local integration test against the installed TPM setup.
  2. Reload .tmux.conf twice.
  3. Query the final effective values after Tokyo Night has loaded.

Expected:

  • pane-border-status remains enabled.
  • pane-border-format still references the pane-local OpenCode title.
  • Reloading twice does not duplicate bindings, format fragments, or shell processes.

Why this is the correct test:

The active Tokyo Night plugin explicitly disables pane-border status. A minimal test that loads only the new lines can pass while the real setup still hides every title. This test prevents that false green.

Test 4: toggle hides and restores labels without losing data

Setup:

  1. Begin with both labels visible and both pane-local title options populated.
  2. Invoke the exact production toggle command used by the keybinding.
  3. Assert pane-border-status becomes off.
  4. Assert both @opencode_session_title values are unchanged.
  5. Invoke the toggle again.
  6. Assert the original configured position (top or bottom) is restored and each pane renders its original title.

Why this is the correct test:

A test that only checks that a key is bound does not prove the toggle works. A test that manually runs different logic from the keybinding can also give a false green. The behavioral test must call the same command path as the real binding.

The current repo has no pane-label toggle, so this test must fail before implementation.

Test 5: switching sessions inside OpenCode updates only that pane

This is a live OpenCode + tmux acceptance test.

Setup:

  1. Create one tmux window with two panes in the same repository.
  2. Left pane opens OpenCode session A with title Fix port 3000 process leak.
  3. Right pane opens OpenCode session B with title Review payment webhook retries.
  4. Verify both labels are visible simultaneously.
  5. In the left OpenCode TUI, use /sessions or the session picker to switch to session C.
  6. Do not send another prompt after switching.

Expected:

left label  -> session C title
right label -> remains "Review payment webhook retries"

Why this is the correct test:

A startup-only implementation, or one that waits for the next chat message, can pass simple launch tests but fails the actual session-switch workflow. OpenCode's current TUI route exposes the displayed session ID directly, so immediate route-driven synchronization is the expected behavior.

The current window-level setup cannot display A/B simultaneously and must fail this test.

Test 6: title lifecycle and stale-title cleanup

Setup:

  1. Start a brand-new OpenCode session.
  2. Observe the initial fallback while the title is still generic.
  3. Send a first prompt and wait for OpenCode to update the session title.
  4. Confirm the pane label changes to the new title.
  5. Exit OpenCode normally back to the shell in the same pane.

Expected:

  • The generated session title appears without restarting tmux.
  • On normal OpenCode exit, the pane no longer displays the stale OpenCode title.
  • The pane shows the selected non-OpenCode fallback or no title.

Why this is the correct test:

It verifies the full lifecycle rather than only a pre-existing titled session. It catches plugins that set a title once and never update or clear it.

Test 7: hostile and long titles are safe

Use a title similar to:

Fix $(touch /tmp/opencode-pane-title-pwned); "quoted" auth redirect with a very long explanation

Also test a title containing a newline/control character through the lowest-level title-writer test API.

Expected:

  • /tmp/opencode-pane-title-pwned is not created.
  • The tmux option contains sanitized text, not executed shell syntax.
  • The rendered border is truncated to the configured maximum width.
  • No raw terminal control sequence is emitted into the pane border.

Why this is the correct test:

Session titles are data. This catches unsafe shell interpolation and format/control-character injection while exercising the real tmux writer.

Test 8: chosen keybinding is actually available after plugins load

Setup:

  1. Load the full tmux configuration and TPM plugins.
  2. Inspect the effective prefix key table with tmux list-keys -T prefix.
  3. Verify the chosen binding invokes the production toggle command.
  4. Verify existing important bindings still work, including pane splits, pane navigation, sync toggle n, and config reload r.

Expected:

  • There is exactly one binding for the chosen key.
  • It toggles the feature.
  • No existing binding is silently replaced without documentation.

Acceptance criteria

  • Each OpenCode pane stores its title in a pane-local tmux option.
  • Two OpenCode panes in one window display different session titles simultaneously.
  • The title writer always targets the originating $TMUX_PANE, not the active pane.
  • Switching through /sessions updates the label immediately without requiring another prompt.
  • Resumed and newly titled sessions update correctly.
  • Child/subagent sessions do not overwrite the main pane label.
  • Pane labels remain visible after the Tokyo Night plugin loads.
  • One documented keybinding toggles labels off and back on.
  • Toggling does not erase pane-local title state.
  • Normal OpenCode exit clears the stale title or switches to the documented fallback.
  • Long/hostile titles are sanitized, safely passed, and visually truncated.
  • Existing tmux bindings and window status behavior remain functional.
  • Tests demonstrate red on the current base and green after implementation.
  • Configuration remains idempotent when sourced repeatedly.

Deliverables

  • pane-aware OpenCode title synchronization
  • tmux pane-border rendering and toggle binding
  • automated isolated-tmux integration tests
  • documented live OpenCode acceptance-test results
  • concise comments/documentation covering:
    • chosen keybinding
    • title source and update lifecycle
    • interaction with Tokyo Night tmux
    • interaction with @koriit/opencode-tmux-window-status
    • tmux/OpenCode version assumptions

Non-goals

  • Do not build a general tmux dashboard.
  • Do not replace tmux-resurrect behavior; exact restoration is covered by issue Make OpenCode tmux panes restore their exact sessions #8.
  • Do not persist human-readable titles as the source of truth for restoring sessions.
  • Do not show subagent titles as the main pane label.
  • Do not redesign the full Tokyo Night status bar.
  • Do not rely on window names to distinguish panes.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions