Skip to content

Converge keyboard dispatch — ~2,000 lines, and the TUI half's 19 'mirrors mod.rs:NNNN' pointers are all stale #734

Description

@JDonaghy

Summary

Keyboard dispatch is duplicated across the backends, ~2,000 production lines, and the TUI
half documents itself as a transcription of the GTK half:

Backend Entry point Lines
GTK handle_key_press (src/gtk/mod.rs:2884) 371
GTK dispatch_gtk_panel_accelerator (:398) 80
TUI handle_key_pressed (src/tui_main/shell_app.rs:3526) 584
TUI handle_sidebar_focused_key (:3027) 467
TUI dispatch_panel_accelerator_sizeless (:2811) 120

The evidence is written into the code

src/tui_main/ carries 19 mirrors mod.rs:NNNN comments, twelve of them on
handle_key_pressed's own precedence rungs:

shell_app.rs:3544  // ── Modal dialog intercepts ALL keys (mirrors mod.rs:1629-:1651) ────
shell_app.rs:3565  // ── Folder picker modal (mirrors mod.rs:1653-:1708) ─────────────────
shell_app.rs:3625  // ── Activity bar (toolbar) focused (mirrors mod.rs:1805-:1854) ──────
shell_app.rs:3677  // ── Sidebar focused (mirrors mod.rs:1886-:2415) ─────────────────────
shell_app.rs:3708  // ── Ctrl+L: force a full screen redraw (mirrors mod.rs:2429-:2435) ──
shell_app.rs:3723  // ── Terminal (PTY) key routing (#351, mirrors mod.rs:2439-:2513) ────
shell_app.rs:3802  // ── Alt-modifier block (mirrors mod.rs:2526-:2601) ──────────────────
shell_app.rs:3908  // ── Shift+F5 → stop, Shift+F11 → stepout (mirrors mod.rs:2662-:2679) ─

Every one of those line references is now wrong. The #540 Relm4→ShellApp cutover
renumbered mod.rs and nothing updated them:

cited what is actually there today
mod.rs:1629 debug_sidebar_lh: Rc::new(Cell::new(20.0)), — a field initialiser
mod.rs:1653 char_width_cell: Rc::new(Cell::new(9.0)), — a field initialiser
mod.rs:2439 | Msg::OpenWorkspaceDialog — a match arm in dispatch
mod.rs:2703 engine.calculate_group_window_rects(...) — window layout

So the only record of what these two handlers are supposed to have in common is a set
of comments that no longer resolve. A divergence introduced today is invisible: there is
no test, no type, and now not even a working pointer that relates the two.

What is already shared

The actions are engine-side and correct — Engine::handle_key (core/engine/keys.rs:9)
plus 15 focused handlers: handle_picker_key, handle_quickfix_key,
handle_terminal_key, handle_ext_panel_key, handle_ext_panel_input_key,
handle_editor_hover_key, handle_settings_key, handle_ai_panel_key,
handle_search_input_key, handle_context_menu_key, handle_spell_suggestion_key,
handle_sc_key, handle_sc_commit_input_key, handle_explorer_rename_key,
handle_explorer_new_entry_key.

What is duplicated is the precedence ladder that decides which one to call — dialog
before folder picker before activity bar before sidebar before terminal before the
Alt block before the editor. That ladder is pure state inspection over Engine. It has
no backend-specific content, which is exactly why the TUI side could be written by
transcribing the GTK side in the first place.

Proposed shape

One shared render::route_key(&Engine, KeyInput) -> KeyRoute, mirroring the
route_mouse_event proposal in #733 — an enum naming the resolved consumer plus its
payload. Each backend converts its native key event into KeyInput, calls the router,
matches the KeyRoute, and calls the engine method. The ladder is then stated once and
both backends inherit changes to it.

Check quadraui first. MenuSystem::handle already owns the menu-bar intercept for
both backends and is the pattern to follow; if quadraui's ShellApp/AppShell seam can
own more of this ladder, that is where it belongs and this becomes an adoption issue
rather than a render.rs build. Resolve before writing code (CLAUDE.md
Platform-Neutrality Rule).

Do not simply update the 19 stale comments. Re-pointing them buys another six months
of the same drift; they exist because there was no shared artefact to point at.

Staging

Slice by rung, top of the ladder down, each slice deleting both implementations in the
same PR:

  1. Modal intercepts — dialog, folder picker, context menu, completion, spell suggestion
  2. Focus owners — activity bar, sidebar (handle_sidebar_focused_key is the 467-line
    one; expect this slice to be the largest)
  3. Terminal / PTY routing (Terminal: shared key dispatch — engine decides copy vs SIGINT vs paste #351)
  4. The Alt-modifier block + vscode-mode divergence
  5. Clipboard-paste pre-load and Ctrl+Shift+Vcoordinate with Ctrl+V paste silently does nothing on GTK — quadraui swallows the key, vimcode has no ClipboardPaste handler #593, which touches
    the same rung on the GTK side
  6. Panel accelerators (dispatch_gtk_panel_acceleratordispatch_panel_accelerator_sizeless)
  7. Delete the two entry points' remains and the 19 mirrors comments

Acceptance criteria

Per slice:

  • The rung is implemented once, in shared code, called from both backends.
  • Both bespoke arms are deleted in the same PR — a migration that leaves both paths
    is not done (GOALS.md).
  • Black-box coverage on both backendsTuiDriver via driver_with_shell(TuiShellApp, …)
    and GtkDriver (src/gtk/testing.rs). Assert on rendered output after the keypress,
    never on state being populated (CLAUDE.md rule 1).
  • The PR states the new tests were verified RED against unfixed develop (rule 2).
  • Combined production line count of the five entry points strictly decreases; PR states
    before/after.
  • cargo build && cargo test && cargo clippy -- -D warnings && cargo fmt --check EXIT=0.

Final slice:

  • grep -rn 'mirrors mod\.rs' src/tui_main/ returns nothing.

Sequencing

After #732 (Msg bus) — GTK's key rungs currently terminate in Msg variants, so
untangling those first means this issue migrates a direct call rather than a bus hop.
Runs alongside or after #733, which does the same job for mouse (and before #735, which composes what both produce); the two share the
route_* shape, so whichever lands first sets the pattern and the second should
follow it rather than invent a second one.

Files

  • src/render.rs
  • src/gtk/mod.rs
  • src/tui_main/shell_app.rs
  • src/gtk/testing.rs

Milestone

#7 Platform-Neutral


⚠️ Line numbers in this body drift — locate by symbol

Every line number above was measured on develop @ 4872ab9 (2026-09-01). They are
already wrong.
#727 landed src/gtk/mod.rs +435/−44 within hours and moved
enum Msg 1096→1131, fn dispatch 1799→1896, sync_scrollbar 2657→2754. Seven more
issues sit ahead of this one in the chain, each deleting or moving thousands of lines.

Treat the numbers as provenance — proof the claim was verified once — never as
coordinates. Locate by these anchors instead:

grep -nE "fn (handle_key_press|dispatch_gtk_panel_accelerator)" src/gtk/mod.rs
grep -nE "fn (handle_key_pressed|handle_sidebar_focused_key|dispatch_panel_accelerator_sizeless)" src/tui_main/shell_app.rs
grep -rn "mirrors mod.rs" src/tui_main/          # the 19 stale pointers; must be 0 at the end

This is the same defect #734 exists to fix: src/tui_main/ carries 19
mirrors mod.rs:NNNN comments whose targets all drifted after #540, leaving the only
record of a cross-backend contract pointing at unrelated code. Do not re-point the
numbers when they rot — the anchors are the durable form.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    coordTracked by coord-tui pipelinestatus:readyRefined and ready to enter the work pipelinetier:largeMulti-module / algorithm-heavy work. Routes to opus via models.labels.uiUI/rendering

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions