Skip to content

Own derived editor bindings and reconcile footer groups in place - #210

Merged
trissim merged 6 commits into
mainfrom
fix/keymap-footer
Oct 30, 2026
Merged

trissim merged 6 commits into
mainfrom
fix/keymap-footer

Conversation

@trissim

@trissim trissim commented Oct 29, 2026 •

Copy link
Copy Markdown

P966: retain Footer controls and stop rebuilding nominal bindings

7 changed files, +161/-111 against integrated main. Footer recompose calls: 2 -> 0; Footer readiness flag: 1 -> 0; persistent ordinary-node binding copies: 1 per accessed node -> 0; shared mutable cached binding tables: 1 per class/mode -> 0. Textual #211 is merged on the integrated main at 40873a8. This is source delivery, not installed/application acceptance.

1. Preserve current external editor ownership

Main replaced the native Vim implementation with external editing capabilities. The merge resolves _editing.py and _text_area.py to main exactly. The obsolete EditingMode.command_map, TextArea.binding_map command-map overlay and editor_command_bindings experiment are gone, not resurrected alongside the external engine. The earlier six-mode construction observations covered historical source and are not evidence for this head.

Before: TextArea repeatedly constructed modal command maps; an intermediate checkpoint moved those maps onto native mode values. After main's external-editor replacement: there is no native modal command family here to cache. Existing external editing, document, selection, history and async engine lifecycle behavior is preserved unchanged from main.

2. Cache frozen declarations; give callers their own mutable projection

Before: DOMNode cached one mutable BindingsMap per class/mode and returned it directly. Screen copied it before remapping, but actual Toad consumers at widget_actions.py:112 and widgets/prompt_popup.py:50 remap a direct binding_map result. One widget could therefore change the keys seen by every instance of its class.

After: DOMNode._binding_defaults uses the existing functools class/mode cache, but its value is an immutable tuple of (key, tuple[Binding, ...]) pairs; Binding is frozen. Public binding_defaults/binding_map return a fresh dictionary and fresh lists over those frozen values. No cached mutable map escapes. Callers retain the existing mutating apply_keymap contract without a second remapping API, compatibility alias, per-widget cache or revision table. Map allocation remains per lookup; mode projection and binding-value construction are cached. No frame-time improvement is claimed without measurement.

App alone keeps its existing runtime mutation layer for bind()/palette additions. The redundant empty-runtime-map branch is deleted; BindingsMap.merge supplies the same result. DOMNode's persistent per-instance declaration copy is deleted. Screen consumes keys into a fresh projection before applying user keymaps; filtering/remapping never rewrites declarations.

3. Native scopes expose their own keymap metadata

P917's catalog called an absent editor_command_bindings API and appended Menu as a second hand-written scope beside DOMNode.contexts(). The shared frozen KeymapClaim declaration moves from Toad into textual.binding with its unchanged fields and from_bindings conversion. DOMNode.keymap_claims(mode) derives native claims from binding_defaults. Menu can override that native scope hook with command declaration metadata without constructing uncaptured command instances, importing Menu inside the catalog, or adding a registry/dispatcher.

Toad consumer/Menu migration is owned by toad-keymap on existing Toad Textualize#1338; it must delete its KeymapClaim copy and absent-API call. The native hook is implemented here, not a compatibility alias. Pairing that consumer with this native head remains an integration obligation.

4. Footer containers and keys survive projection changes

Before: flat/grouped projections had incompatible DOM shapes. Group/palette changes selected full recomposition and remounted FooterKey controls.

After: KeyGroup owns one stable keys-and-label shape. Group slots update in place; existing action keys move across groups through Widget.reparent. Only removed actions and excess group slots retire. Descriptions, compact style, palette and mode label derive from FooterProjection. Mode-only changes skip key reconciliation. An empty projection replaces the nullable snapshot/readiness flag. Both recompose paths, the old structural compatibility switch, parent iterator/assert and per-parent destruction of migrating keys are deleted. Main's mode-label behavior remains.

Source evidence and limits

  • Before editing: NRA SourceModule/ModuleSyntaxIndex enumerated 567 production modules across this Textual tree and the retained paired Toad source: zero parse omissions. Conflicted files were explicitly analyzed at origin/main, not passed off as valid working source. Reflection/external subclass resolution remains OPEN.
  • Retained source map: /home/ts/.cache/agent-scratch/queue-toad-fix/keymap-footer/current-ast-owner-map.jsonl. Whole production/test consumer search: projection-consumers.txt and family-consumers-after-main.txt in that directory.
  • _editing.py and _text_area.py have zero diff from integrated main. No navigation keys from Finish native key declarations through shared navigation #211 are rebound here.
  • Existing test consumers migrate from removed private per-node tables to public binding_map/binding_defaults. No new fixture or synthetic app.
  • No author pytest, suite, provider, GUI, build or installation was run. Root owns combined qualification and the actual prompt -> conversation -> prompt Footer journey, preserved-key identity/mount observation, remap isolation, and total UI timing.
  • Historical baseline _binding_chain: standard ~0.6 ms; vim-normal ~0.85 ms. Those are not after measurements. Current after timing, Footer mount counts, visible behavior and frame target are UNMEASURED.

Assumed: declared class bindings remain class-lifetime metadata, as required by the existing class/mode cache; external mutation of class declarations after creation is not supported by this change. No claim that installed Toad already uses this head.

one widget remaps its keyboard
the class cache offers a group discount
cached tuples keep their hands off everyone else's keys

Copilot AI balanced review requested due to automatic review settings October 29, 2026 21: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 merged commit e9f681e into main Oct 30, 2026
1 check was pending
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