Skip to content

Finish central key declarations across Toad consumers - #1338

Merged
trissim merged 23 commits into
mainfrom
work/toad-keymap--central-declarations
Oct 31, 2026
Merged

trissim merged 23 commits into
mainfrom
work/toad-keymap--central-declarations

Conversation

@trissim

@trissim trissim commented Oct 29, 2026 •

Copy link
Copy Markdown

Untangles Toad's key bindings: 23 commits, 32 files, +540/-337. Block menus stop being strings, up/down come from one native cursor family, and every menu key shows up in the keymap and can be remapped.

Block menus

Before: MenuItem("Copy thinking", "block.copy", "y"), a label, an action string and a key. Menu.on_key matched keys by hand, and run_action resolved the string at click time. block.copy had no target at all, so "Copy thinking" did nothing.

After: MenuCommand is a declared family in menus.py. Each command is a class that owns its description and key and captures what it acts on (CollapseCommand(block), OpenSenderCommand(link)). Menu.keymap_claims reports the keys to the keymap, MenuOption.refresh_key draws the live key, and MenuConversation declares what a command may ask of its conversation.

Cursor keys

Before: toad.widget_actions.CursorAction, MovesCursor, SelectionUpAction/SelectionDownAction, and TargetTreeMember.cursor_up forwarding to its tree.

After: textual.navigation.MovesCursor and its CursorCommand family. Controls implement action_cursor_up/down. Tree rows no longer forward: the key reaches TargetTree through the binding chain. GridSelect has one move_row in place of two copied walks.

Other bindings

WorkspaceScreen escape moves from check_action string dispatch to a ModeBinding with available=. WelcomeScreen's n moves to a ModeBinding. KeymapCatalog reads KeymapClaims.

Deleted

MenuItem, Menu.on_key, present_navigation_binding, Action.declarations' hand walk, the local cursor families, ProjectDirectoryTree's ctrl+c binding (it named an action the tree doesn't have), Conversation.action_focus_block, ToolCall.action_collapse/expand, OpensMessageNames, LineHistory.message_row, MessageRow.destination, BlockCursor.export_render's None (now export_to_svg, a no-op on the base with an override on the cursor that draws a block). Guard ceiling for string-found action_* methods: 46 to 36.

Bug the review caught

944056540 deleted j/k on the permissions screen while claiming to derive them from the shared family, which only holds up and down. All tests stayed green because none pressed j. Restored, with a test that presses them from the question.

Evidence

Open

  • Action.declarations in widget_actions.py is a forwarder that exists only to narrow a type. The owner fix is KeyDeclaration.declarations at textual/keys.py:137.
  • WorkspaceFrame.back_to_bottom_window still answers with None, which back_to_bottom_available and action_back_to_bottom both test.
  • Not yet seen in a real Toad.

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings October 29, 2026 22:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@trissim

trissim commented Oct 30, 2026

Copy link
Copy Markdown
Author

Rescue of review-p917 (P975): the unit stopped mid-rebase of this branch onto c59b376 with uncommitted src edits, now saved as claude-nra/rescue-review-p917 @ ccc2115 (4 rebased commits + the edits). The edits move the Menu claims onto Menu.keymap_claims and make KeymapCatalog call owner.keymap_claims(mode). They import textual.binding.KeymapClaim and DOMNode.keymap_claims, which no OpenHCSDev/textual commit declares yet, so the branch is not buildable or reviewed. The unresolved pyproject/uv.lock rebase conflict is in commander scratch (rescue/review-p917). Whoever picks this up next starts from that branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XbFMSpSd8zQxyozgEV2Re7

trissim and others added 10 commits October 30, 2026 17:40
…r expansion

ExpansionCommand restated the overlap check ExclusiveKeys already owns
(widget_actions.py:177), so it now inherits the mixin and the copied issubclass
goes. test_block_menu_and_copy imported the deleted MenuItem and failed at
collection; its extra block now declares a keyed command.

tests/textual/test_block_menu_and_copy.py: 2 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mports

menus.py imported Conversation, Menu, ToolCall and LineLink only under
TYPE_CHECKING. Conversation and Menu now inherit MenuConversation and MenuOwner,
which state what a command may ask of them. LineLink is a real import.
CollapseCommand and ExpandCommand move to BlockContent, which owns the methods
they call. The session and navigation check, and the link route through the
transcript, move onto Conversation behind accepts_menu_owner and open_line_link.

Delete present_navigation_binding: no parent defines it and nothing calls it.
Action.declarations narrows the native registry's type to the family asking.
MenuOption inherits ToadNode to see the application's settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
settings.ui.key_mode is already a class. type() of it handed menu_keys the
metaclass, so the row asked a metaclass for its binding mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ation

ConversationSessionBinding already owns is_current_session and reads the
transcript. Navigation and the two menu answers move there, so Conversation
shrinks instead of growing by two forwarding methods.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
action_cursor_up and action_cursor_down were one walk written twice. move_row
owns it, matching the layout's size and the reactive highlight once, where
those foreign absent values arrive. The back-to-bottom check matches the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
trissim and others added 4 commits October 30, 2026 19:01
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MenuConversation and Conversation no longer spell block operations action_*:
nothing reaches them by name. MovesCursor's methods are a contract its key family
calls, so the keymap guard derives that exemption from Textual's class and its
ceiling falls from 46 to 36. The message-link guard now asks the declared commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9440565 deleted the j and k review keys on the permissions screen as
"derived from the shared key family", but that family only holds up and
down. Restore the declarations and press them in a test.

Delete Conversation.action_focus_block, ToolCall.action_collapse and
action_expand, OpensMessageNames with LineHistory.message_row and the
destination property: only menu action strings reached them, and commands
now capture their operation. Delete CommsRow's CursorCommand scope and
Menu and Question's signal unsubscribes, which Textual does on close.

BlockCursor.export_to_svg replaces export_render's None: a no-op on the
base, an override on the one cursor that draws a block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@trissim
trissim enabled auto-merge October 31, 2026 00:14
@trissim
trissim merged commit 193b779 into main Oct 31, 2026
2 of 4 checks passed
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