You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The GTK live render path (src/gtk/mod.rs::render_content) reads only 11 of the ~25 populated fields on render::ScreenLayout. 13 populated fields are silently dropped — the engine computes the state, TUI paints it, GTK paints nothing.
Every one of these is a user-visible feature that works on TUI and is invisible on GTK. There is no error, no warning, and no compile-time signal.
picker was the 14th. It was fixed in #587 (bce7a76) after burning roughly five agent sessions, because the symptom ("Ctrl+Shift+P does nothing") points at input, while the actual fault is paint. This issue is the rest of that blast radius.
That dead_code allow muted the exact warning that would have caught this. draw_editor owned the entire "8. Popups and modals" block, so everything it painted became invisible on GTK the day the migration landed.
Partial ports have happened piecemeal and reactively, each time someone noticed a specific feature was broken:
❌ dead draw.rs:3868 (GTK has no PANEL_AI arm — mod.rs:8207 "not yet migrated")
find_replace was confirmed empirically the same way the palette was: trigger "Edit: Find & Replace" from the command palette, and the state opens while nothing paints —
KEYDBG: OVERLAY STATE OPEN: ["find_replace"]
The others are established statically: their only paint sites are in draw.rs, which has no live callers.
Related breakage from the same cause
1. GTK scroll dispatch runs against a permanently-empty surface list.engine.scroll_surfaces is written only from dead code (draw.rs:85, :644, :709). GTK's dispatch_scroll (mod.rs:1838) and dispatch_click (mod.rs:3577) therefore hit-test against nothing. TUI registers 4 surfaces per frame (render_impl.rs:135 clear, then :443, :717, :781, panels.rs:96).
2. Hit-test caches with no live writer. GTK event handlers read caches that only the dead path ever filled:
cache field
sole writer
live readers
completion_layoutmod.rs:715
draw.rs:361 (dead)
click/key handlers
tab_switcher_popup_rectmod.rs:723
draw.rs:920 (dead)
modal-stack registration
editor_hover_link_rectsmod.rs:726
draw.rs:421 (dead)
mod.rs:3154
editor_hover_scrollbarmod.rs:730
draw.rs:422 (dead)
drag handler
editor_hover_popup_rectmod.rs:712
none
mod.rs:3155, :3376
panel_hover_popup_rectmod.rs:709
none
hover-dismiss logic
3. Modal z-order is exactly inverted between the backends. For the four modals both paint:
overlay
GTK
TUI
menu dropdown
1st (lowest)
4th (highest)
dialog
2nd
3rd
context menu
3rd
2nd
picker
4th (highest)
1st (lowest)
Both files carry comments claiming they match the other backend (mod.rs:8268 "matching TUI"; render_impl.rs:1024 "rendered last so it draws on top"). The comments are the only coordination mechanism and are already wrong. (#587 drew the picker to match TUI, so the picker is currently the only overlay whose z-order agrees.)
4. tab_bar_h loses the terminal_maximized rule.mod.rs:7835 recomputes it via render::tab_bar_height_px instead of using el.tab_bar_h from the compute_editor_layout call nine lines later, dropping the terminal_maximized → 0.0 rule (render.rs:12932) that TUI honours (render_impl.rs:299).
Proposed approach
Porting the 13 one at a time repeats the reactive pattern that produced #546/#547/#587 and leaves the next gap undiscovered until a user hits it. Suggest instead:
Delete src/gtk/draw.rs::draw_editor (and everything only it reaches) once its surfaces are ported. Leaving dead code behind the dead_code allow is what made this invisible for so long. If it can't be deleted yet, remove the dead_code allow so the compiler surfaces it.
Drive both backends from one shared overlay list — a compose_overlays(screen) -> Vec<OverlaySpec> in src/render.rs defining which overlays exist and in what z-order, with each backend supplying only its units struct (the PickerGeometry::compute + PickerSizing pattern, which is the one place this is already done right). A surface then cannot be dropped from one backend, and z-order cannot diverge.
Add a guard test that asserts every Option-bearing screen.* field is consumed by both render paths, so the next added overlay can't silently skip GTK.
Step 2 depends on upstream work — see quadraui#455 (ModalStack drives hit-testing but not paint, so "registered but invisible" is undetectable) and quadraui#456 (two APIs paint the same primitive: Backend::draw_* vs Surface + ScreenLayout; TUI pushes zero Surfaces, GTK builds 12 one-element ScreenLayouts).
Note also quadraui#454 — ShellAdapter owns its AppShell as pub(crate) with no accessor, forcing vimcode into the shadow engine.app_shell (src/core/engine/mod.rs:3393). That is the root cause of the other half of #587's symptom: Ctrl+B, Ctrl+T, Alt+E, Alt+F route through sender.send(Msg::…) → sync_sidebar_widgets(), which drives dead Relm4-era GTK widgets instead of the rendered shell. Those accelerators are still dead on GTK and are not covered by this issue.
Verification
No GTK acceptance harness exists yet (quadraui#301 GtkDriver is unbuilt), so each ported overlay needs live smoke on a machine with a display until that lands. The #587 loop that worked: launch the GTK binary on X11, inject real keystrokes with xdotool key --clearmodifiers (XTEST, not --window, which GTK4 ignores), screenshot with import -window, and instrument the paint path with eprintln!.
Found while root-causing #587 in a human-attended session on 2026-07-19.
Staged into sub-issues (2026-08-26) — this is now a tracker
Thirteen fields in one issue is not dispatchable; #587 burned roughly five agent sessions on one of them. Split into four, mirroring how #448 and #595 were staged:
scroll surfaces + hit-test caches, then delete src/gtk/draw.rs
—
A/B/C each port from draw.rs; D deletes it and so must come last.
A/B/C are serialized, not parallel. All three declare the same three files
(src/gtk/mod.rs, src/render.rs, src/gtk/testing.rs), so running them concurrently is a
merge conflict by construction. They are chained A → B → C → D in the drive queue.
Deadness re-confirmed on origin/develop 2026-08-26: of the 29 exported pub(super) fns in src/gtk/draw.rs (3,733 lines), zero have a live caller — the single apparent hit on draw_editor is a comment, not a call.
Note for all four: the TUI line numbers in the table above are pre-#634. The TUI cutover to run_with_shell moved those paint sites into src/tui_main/shell_app.rs. Locate current TUI
reference sites by symbol, not by these line numbers.
Summary
The GTK live render path (
src/gtk/mod.rs::render_content) reads only 11 of the ~25 populated fields onrender::ScreenLayout. 13 populated fields are silently dropped — the engine computes the state, TUI paints it, GTK paints nothing.Every one of these is a user-visible feature that works on TUI and is invisible on GTK. There is no error, no warning, and no compile-time signal.
pickerwas the 14th. It was fixed in #587 (bce7a76) after burning roughly five agent sessions, because the symptom ("Ctrl+Shift+P does nothing") points at input, while the actual fault is paint. This issue is the rest of that blast radius.Root cause (established in #587)
src/gtk/draw.rs::draw_editorhas zero callers since the #540 Relm4→ShellApp migration, and is annotated:That
dead_codeallow muted the exact warning that would have caught this.draw_editorowned the entire "8. Popups and modals" block, so everything it painted became invisible on GTK the day the migration landed.Partial ports have happened piecemeal and reactively, each time someone noticed a specific feature was broken:
dialog+context_menupicker/ command paletteThe 13 still-dropped fields
screen.*fieldcompletionquadraui_tui::draw_completions,render_impl.rs:534draw.rs:1332onlyhoverrender_impl.rs:566draw.rs:1406onlyeditor_hoverpanels.rs:1032render_editor_hover_popupdraw.rs:1552onlydiff_peekrender_impl.rs:619draw.rs:1697onlysignature_helprender_impl.rs:647draw.rs:1792onlyquickfixpanels.rs:1543draw.rs:2389onlybottom_tabs(terminal / debug-output panel)panels.rs:1592draw.rs:2283,:591/595/612debug_toolbarrender_impl.rs:809draw.rs:2448separated_status_linerender_impl.rs:667tab_switcherrender_impl.rs:945draw.rs:1896find_replacequadraui_tui::draw_find_replace,render_impl.rs:936draw.rs:1833tab_tooltiprender_impl.rs:478panel_hoverrender_impl.rs:874draw.rs:3484ai_panelpanels.rs:66render_ai_sidebardraw.rs:3868(GTK has noPANEL_AIarm —mod.rs:8207"not yet migrated")find_replacewas confirmed empirically the same way the palette was: trigger "Edit: Find & Replace" from the command palette, and the state opens while nothing paints —The others are established statically: their only paint sites are in
draw.rs, which has no live callers.Related breakage from the same cause
1. GTK scroll dispatch runs against a permanently-empty surface list.
engine.scroll_surfacesis written only from dead code (draw.rs:85,:644,:709). GTK'sdispatch_scroll(mod.rs:1838) anddispatch_click(mod.rs:3577) therefore hit-test against nothing. TUI registers 4 surfaces per frame (render_impl.rs:135clear, then:443,:717,:781,panels.rs:96).2. Hit-test caches with no live writer. GTK event handlers read caches that only the dead path ever filled:
completion_layoutmod.rs:715draw.rs:361(dead)tab_switcher_popup_rectmod.rs:723draw.rs:920(dead)editor_hover_link_rectsmod.rs:726draw.rs:421(dead)mod.rs:3154editor_hover_scrollbarmod.rs:730draw.rs:422(dead)editor_hover_popup_rectmod.rs:712mod.rs:3155,:3376panel_hover_popup_rectmod.rs:7093. Modal z-order is exactly inverted between the backends. For the four modals both paint:
Both files carry comments claiming they match the other backend (
mod.rs:8268"matching TUI";render_impl.rs:1024"rendered last so it draws on top"). The comments are the only coordination mechanism and are already wrong. (#587 drew the picker to match TUI, so the picker is currently the only overlay whose z-order agrees.)4.
tab_bar_hloses theterminal_maximizedrule.mod.rs:7835recomputes it viarender::tab_bar_height_pxinstead of usingel.tab_bar_hfrom thecompute_editor_layoutcall nine lines later, dropping theterminal_maximized → 0.0rule (render.rs:12932) that TUI honours (render_impl.rs:299).Proposed approach
Porting the 13 one at a time repeats the reactive pattern that produced #546/#547/#587 and leaves the next gap undiscovered until a user hits it. Suggest instead:
src/gtk/draw.rs::draw_editor(and everything only it reaches) once its surfaces are ported. Leaving dead code behind thedead_codeallow is what made this invisible for so long. If it can't be deleted yet, remove thedead_codeallow so the compiler surfaces it.compose_overlays(screen) -> Vec<OverlaySpec>insrc/render.rsdefining which overlays exist and in what z-order, with each backend supplying only its units struct (thePickerGeometry::compute+PickerSizingpattern, which is the one place this is already done right). A surface then cannot be dropped from one backend, and z-order cannot diverge.Option-bearingscreen.*field is consumed by both render paths, so the next added overlay can't silently skip GTK.Step 2 depends on upstream work — see quadraui#455 (
ModalStackdrives hit-testing but not paint, so "registered but invisible" is undetectable) and quadraui#456 (two APIs paint the same primitive:Backend::draw_*vsSurface+ScreenLayout; TUI pushes zero Surfaces, GTK builds 12 one-elementScreenLayouts).Note also quadraui#454 —
ShellAdapterowns itsAppShellaspub(crate)with no accessor, forcing vimcode into the shadowengine.app_shell(src/core/engine/mod.rs:3393). That is the root cause of the other half of #587's symptom:Ctrl+B,Ctrl+T,Alt+E,Alt+Froute throughsender.send(Msg::…)→sync_sidebar_widgets(), which drives dead Relm4-era GTK widgets instead of the rendered shell. Those accelerators are still dead on GTK and are not covered by this issue.Verification
No GTK acceptance harness exists yet (quadraui#301
GtkDriveris unbuilt), so each ported overlay needs live smoke on a machine with a display until that lands. The #587 loop that worked: launch the GTK binary on X11, inject real keystrokes withxdotool key --clearmodifiers(XTEST, not--window, which GTK4 ignores), screenshot withimport -window, and instrument the paint path witheprintln!.Found while root-causing #587 in a human-attended session on 2026-07-19.
Staged into sub-issues (2026-08-26) — this is now a tracker
Thirteen fields in one issue is not dispatchable; #587 burned roughly five agent sessions on
one of them. Split into four, mirroring how #448 and #595 were staged:
completion,hover,editor_hover,diff_peek,signature_helpquickfix,bottom_tabs,debug_toolbar,panel_hover,ai_panelfind_replace,tab_switcher,separated_status_line,tab_tooltipsrc/gtk/draw.rsA/B/C each port from
draw.rs; D deletes it and so must come last.A/B/C are serialized, not parallel. All three declare the same three files
(
src/gtk/mod.rs,src/render.rs,src/gtk/testing.rs), so running them concurrently is amerge conflict by construction. They are chained
A → B → C → Din the drive queue.Deadness re-confirmed on
origin/develop2026-08-26: of the 29 exportedpub(super) fns insrc/gtk/draw.rs(3,733 lines), zero have a live caller — the single apparent hit ondraw_editoris a comment, not a call.Note for all four: the TUI line numbers in the table above are pre-#634. The TUI cutover to
run_with_shellmoved those paint sites intosrc/tui_main/shell_app.rs. Locate current TUIreference sites by symbol, not by these line numbers.
Work order
Sub-issues