Skip to content

A class's own keys replace its ancestors'; a read-only TextArea lets app shortcuts through - #242

Merged
trissim merged 3 commits into
mainfrom
fix/most-derived-binding-wins
Nov 6, 2026
Merged

trissim merged 3 commits into
mainfrom
fix/most-derived-binding-wins

Conversation

@trissim

@trissim trissim commented Nov 6, 2026

Copy link
Copy Markdown

Summary: 2 commits, 2 files, +39/−6. Written by claude-b-ui. Carried here by claude-a-toad because b-ui's cloud session is attached only to toad and can't push to textual.

1. The most-derived class's word on a key wins (dom.py). BindingsMap.project used to put every merged BINDINGS entry ahead of the method declarations, whichever class wrote them. So ScrollableContainer's end → scroll_end beat TextArea's own @MethodBinding.declare("end"): End scrolled the view instead of moving to the line end. The same thing broke Home, the arrows and PageUp/PageDown in TextArea, Input and DataTable, plus Vim hjkl. class_bindings now walks the MRO once per mode, and each class's own keys, from BINDINGS or from declared methods, replace its ancestors'. Each class's BINDINGS are parsed once at creation (_own_bindings), and the walk reads that attribute directly.
Across every widget Toad loads, 136 key resolutions change, all with one shape: an inherited scroll_* binding stops sitting ahead of the widget's own cursor action.

2. A read-only TextArea lets the application's shortcuts through (_text_area.py:769). allows_application_commands used to ask only the editing mode, so Toad's read-only ACP log viewer swallowed ctrl+w.

Evidence (b-ui's runs):

  • Textual's binding, keymap, navigation-spellings and text_area tests: 347 passed, 3 failed. The 3 are test_editor_state ×3, which also fail on 09c5aec without this change.
  • test_cursor_page_down/up[True] were red before this change and pass now.
  • Toad's test_acp_log_view: 4/4 passed. On main's source it's red, with End stuck at column 0 and ctrl+w closing nothing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd

…S or declared on a method

BindingsMap.project put every merged BINDINGS entry before the method declarations, whichever class wrote them. ScrollView inherits ScrollableContainer, so its end -> scroll_end sat ahead of TextArea's own @MethodBinding.declare("end"): End scrolled a scrollable TextArea instead of moving to the line end, and the same held for Home, arrows and PageUp/PageDown in TextArea, Input and DataTable, and Vim hjkl in them.

class_bindings walks the MRO once per mode: each class projects its own BINDINGS with its own declared methods, and those keys replace its ancestors'. _binding_defaults appends the key families after and skips method declarations the walk already placed; declared_bindings still lists them for callers reading fallbacks and invocations. Each class's BINDINGS are parsed once at creation (_own_bindings), so a class whose creation failed adds nothing.

Across every widget Toad loads, 136 key resolutions change, all one shape: an inherited scroll_* binding stops preceding the widget's own cursor action. test_cursor_page_down/up[True] now pass; test_editor_state x3 fail with or without this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSrQKHwQJSkZZaLyQha
TextArea.allows_application_commands asked only its editing mode, which answers False for any mode that accepts text. A read-only area takes no typed text (takes_typed_text already says so), but it still swallowed the application's priority shortcuts: ctrl+w in Toad's read-only ACP log viewer deleted nothing and closed nothing. read_only now answers first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSrQKHwQJSkZZaLyQha
Copilot AI balanced review requested due to automatic review settings November 6, 2026 04:42

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. Try requesting a review at a lower effort level.

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 Nov 6, 2026

Copy link
Copy Markdown
Author

Evidence from the host, with no verdict: I committed this PR (git am of b-ui's patches), so I don't approve it.

Same test selection at head 4a01edc and base 09c5aec (test_binding_inheritance, test_binding, test_dynamic_bindings, test_keymap, test_navigation_spellings, tests/text_area), run with -p no:xdist:

passed failed
base 09c5aec 346 5
head 4a01edc 348 3
  • Fixed by this PR: test_selection_bindings::test_cursor_page_down[True] and test_cursor_page_up[True].
  • Red at both ends, unchanged: test_editor_state ×3.
  • New failures: none.

It needs a reviewer who neither wrote nor carried it.

class_bindings and _merge_bindings each wrote the same reversed-MRO walk over DOMNode bases, restarting at any base that does not inherit bindings. The command palette's check reads _merged_bindings and key dispatch reads class_bindings, so a change to the inherit rule in one of them would have split what the palette sees from what keys do. _binding_bases owns the walk; both derive from it, and _merge_bindings reads each base's _own_bindings instead of parsing BINDINGS again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSrQKHwQJSkZZaLyQha
@trissim
trissim merged commit c345080 into main Nov 6, 2026
3 of 24 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.

3 participants