Skip to content

fix(tui): make composer tabs and footer hints clickable - #57307

Open
AdityaZxxx wants to merge 2 commits into
anomalyco:devfrom
AdityaZxxx:composer-clickable-tabs
Open

AdityaZxxx wants to merge 2 commits into
anomalyco:devfrom
AdityaZxxx:composer-clickable-tabs

Conversation

@AdityaZxxx

@AdityaZxxx AdityaZxxx commented Oct 20, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #51903
Closes #56002

Type of change

  • Bug fix

What does this PR do?

The composer's tab row and hint row were plain <text> with no mouse handlers, so Subagents / Shell / Terminals and the footer hints could only be reached from the keyboard. The list rows and the esc label in the same panel were already clickable, so the panel was inconsistent with itself.

The tab switch was also the only composer action that was not a named keymap command. It was registered as an inline { bind: "left" } / { bind: "right" }, so it was not rebindable, not dispatchable and not visible to plugins. composer.subagent.toggle-activity had the mirror problem: it had an id, but the id was missing from TuiKeybind.Definitions, so config.keybinds.get(id) was always empty and only the inline bind: "ctrl+a" could ever bind it. Because Definitions also drives the config schema, adding that id to cli.json was silently dropped instead of rejected.

Three ids now resolve through Definitions, so the defaults live in one place, the user can override them, and the footer hint renders the configured key:

 config/keybind.ts
   Definitions
+  "composer.tab.previous": keybind("left", "Previous composer tab"),
+  "composer.tab.next": keybind("right", "Next composer tab"),
+  "composer.subagent.toggle-activity": keybind("ctrl+a", "Toggle active subagents"),
 composer/index.tsx
   Keymap.createLayer(() => ({
-    { bind: "left",  title: "Previous tab", group: "Composer", run: () => switchTab(-1) },
-    { bind: "right", title: "Next tab",     group: "Composer", run: () => switchTab(1) },
+    { id: "composer.tab.previous", title: "Previous tab", group: "Composer", run: () => switchTab(-1) },
+    { id: "composer.tab.next",     title: "Next tab",     group: "Composer", run: () => switchTab(1) },
   }))

The chrome becomes clickable, and the mouse path runs the same command the keyboard path runs:

 composer/index.tsx
   <text>{t.label}</text>
+  onMouseUp={() => ctx.select(t.id)}
   <text>{hint.label} {hint.shortcut}</text>
+  onMouseUp={() => keymap.dispatch(hint.command)}

ComposerHint carries the command id and ComposerContext gains select(id), so each action has one implementation rather than a mouse copy of it.

composer.subagent.toggle-activity keeps ctrl+a as its default, and composer.tab.previous / composer.tab.next keep left / right. The tabs hint now renders the configured bindings instead of a hardcoded ←/→.

How did you verify your code works?

From packages/tui:

bun test --timeout 30000 test/cli/tui/composer-keymap.test.tsx test/cli/tui/composer-mouse.test.tsx
→ 15 pass, 0 fail (56 expect() calls)

New file packages/tui/test/cli/tui/composer-mouse.test.tsx covers:

  • clicking a tab label activates that tab
  • clicking the show active / show inactive hint toggles the subagent activity filter
  • clicking the tabs hint switches to the next tab
  • clicking the kill hint removes the selected shell
  • composer.subagent.toggle-activity rebinds to a custom key, the footer hint shows that key, and the key still toggles the filter
  • composer.tab.next rebinds to a custom key, the arrow key stops switching tabs, and the tabs hint shows the configured binding
  • a "none"-bound toggle still runs from its hint — dispatchCommand resolves active layers rather than bindings, matching the existing prompt/footer.tsx precedent

Full package suite, typecheck and lint:

bun test --timeout 30000                              → 1541 pass, 2 skip, 0 fail (182 files)
bun typecheck                                         → clean
bunx oxlint packages/tui/src/routes/session/composer/ packages/tui/src/config/keybind.ts packages/tui/test/cli/tui/
                                                      → 0 warnings, 0 errors
bunx prettier --check <touched files>                 → All matched files use Prettier code style
bun run check (repo root: lint + turbo typecheck)     → exit 0, 34/34 tasks successful

The composer fixture was extracted from composer-keymap.test.tsx into composer-fixture.tsx so both files share it; that file's existing tests are unchanged.

Screenshots / recordings

The composer panel rendered by the test fixture, showing the tab row and the hint row this PR makes clickable:

┃
┃  Subagents  Shell                                                                            esc
┃
┃  Build: First                                                                           Running
┃  Build: Second                                                                          Running
┃
┃
┃
┃
┃
┃  interrupt ctrl+d  show inactive ctrl+a  tabs ←/→
┃

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #56153

Public API: adds config keybinds composer.tab.previous, composer.tab.next, composer.subagent.toggle-activity (backwards compatible, defaults unchanged)

The composer panel's tab row and hint row were plain <text> with no mouse
handlers, so the tabs and hints could only be reached from the keyboard even
though the list rows and the esc label in the same panel were clickable.

The tab switch was also the only composer action that was not a named keymap
command, so it was not rebindable or dispatchable. composer.subagent.toggle-activity
had the mirror problem: its id was missing from TuiKeybind.Definitions, so it
could never be overridden through the config.

Register the three ids in Definitions, dispatch them from the mouse handlers,
and let the footer hints render the configured bindings.
@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good. I checked it in the TUI from source with real mouse events, on the base and on this branch. On the base, clicking the show inactive hint, the Shell tab label and the tabs hint does nothing. With this PR they toggle the filter, open the Shell tab and move to the next tab. The composer keymap and mouse tests pass (15/15), and packages/tui typechecks.

Public API: adds three config keybind keys, composer.tab.previous (default left), composer.tab.next (right) and composer.subagent.toggle-activity (ctrl+a). This is backwards compatible: the defaults match the old inline bindings, and before this change a config entry for composer.subagent.toggle-activity was silently ignored.

One small gap: the keybinds docs table doesn't list the new keys yet (see inline).

Related: #56153

Before

After

"prompt.history.previous": keybind("up", "Previous history item"),
"prompt.history.next": keybind("down", "Next history item"),

"composer.tab.previous": keybind("left", "Previous composer tab"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These three ids are now user-configurable keys, but the composer table in services/www/src/docs/content/cli/keybinds.mdx (around line 330) still only lists the composer.subagent.* and composer.shell.* entries. Could you add composer.tab.previous / composer.tab.next / composer.subagent.toggle-activity there so people can find them? #56002 asks for exactly this setting.

The composer table only listed the subagent and shell entries. Add the tab
and toggle ids introduced for anomalyco#51903 and anomalyco#56002, plus the terminal entries
that landed with persistent session terminals, so the documented set matches
TuiKeybind.Definitions.

This branch has not been deployed

No deployments
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.

[FEATURE]: Allow rebinding the subagent active/inactive toggle TUI: composer tabs and footer hints are not clickable

1 participant