Skip to content

Fix priority binding order in Footer on key collision (#4639)#6655

Open
gianaae-ux wants to merge 1 commit into
Textualize:mainfrom
gianaae-ux:fix/footer-binding-priority
Open

Fix priority binding order in Footer on key collision (#4639)#6655
gianaae-ux wants to merge 1 commit into
Textualize:mainfrom
gianaae-ux:fix/footer-binding-priority

Conversation

@gianaae-ux

Copy link
Copy Markdown

Link to issue or discussion

Fixes #4639 (original report with screenshots: #4637)

Summary
When an app-level priority binding wins a key collision against a focused widget's binding, the Footer displays it out of order. This restores the app's declared binding order for that case with a one-line fix.

Problem
Priority bindings don't respect their declared order in the Footer when they win a key collision against a focused widget's binding. See #4639 for the bug report; the original report in #4637 (whose screenshots show the problem) is what motivated the suggestion to use priority=True in the first place.

Root cause
Screen.active_bindings (src/textual/screen.py) merges bindings from the focused widget, screen, and app into a single insertion-ordered dict. On a key collision where an app priority binding beats a non-priority one, it reassigns the key:

if existing_key_and_binding := bindings_map.get(key):
# This key has already been bound
# Replace priority bindings
if (
binding.priority
and not existing_key_and_binding.binding.priority
):
bindings_map[key] = ActiveBinding(
namespace, binding, enabled, binding.tooltip
)
In Python, assigning to an existing dict key updates its value but keeps the key at its original insertion position — reassignment never moves a key. So the winning priority binding inherits the position of the losing binding (the focused widget's, which was inserted earlier during the merge) instead of the position implied by the app's declared order. Since the Footer renders keys in bindings_map iteration order, the priority binding shows up in the wrong slot.

Fix
Delete the key before re-inserting it, so the winning binding lands at its correct (later) position in iteration order:

del bindings_map[key]
bindings_map[key] = ActiveBinding(
namespace, binding, enabled, binding.tooltip
)
The whole fix is the single added line del bindings_map[key].

Testing
Added tests/test_binding_priority_collision.py, which reproduces the actual collision: a focused widget and an app-level priority=True binding competing for the same key (f). The app declares its priority bindings in the order q, j, f, n, and the test asserts the winning f binding ends up after j in active_bindings (keys.index("f") > keys.index("j")). I confirmed the test fails against the pre-fix code and passes with the fix — not just that it passes now. I also ran the full suite (poetry run pytest tests/ -n 16 --dist=loadgroup) with no regressions.

Screenshots:

bug reproduction — priority binding shown out of order in the Footer
image

fixed behavior — priority binding shown in declared order
image

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.

priority binding order is not respected in the Footer

1 participant