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 2026-09-04 audit sized ~8,000 production lines across vimcode's two backends as "the same apply/marshalling logic written twice against different geometry types", and concluded it needed a new unit-neutral type upstream (filed as quadraui#781). Re-examining the actual fields shows that conclusion was wrong. The unit-neutral type already exists, is already adopted for most surfaces, and both backends already share a router over it. What remains is an unfinished migration.
The evidence
quadraui::Rect is f32-based (quadraui/src/event.rs:153-158) and every hit_test in the crate takes (x: f32, y: f32) — there is no unit parameter anywhere in quadraui's layout API. A TUI passes cell coordinates as f32; GTK passes pixels as f32; the same code runs.
Where vimcode already adopted it, the two backends are identical:
Cache field
App (GTK)
TuiShellApp
dialog_layout
quadraui::DialogLayout
quadraui::DialogLayout
completion_layout
quadraui::CompletionsLayout
quadraui::CompletionsLayout
context_menu_layout
quadraui::ContextMenuLayout
quadraui::ContextMenuLayout
Both clear them the same way each frame, and both feed them into the same shared router — GTK builds the struct at src/app.rs:3506, the TUI builds it at src/tui_main/mouse.rs:439. That is the pattern working today, at scale.
The divergence is confined to the fields that were never migrated:
Cache field
App (GTK)
TuiShellApp
tab_switcher_popup_rect
Option<(f64,f64,f64,f64)> — app.rs:503
Option<quadraui::Rect> — shell_app.rs:448
editor_hover_popup_rect
Option<(f64,f64,f64,f64)> — app.rs:492
Option<(u16,u16,u16,u16)> — shell_app.rs:438
panel_hover_popup_rect / hover_popup_rect
Option<(f64,f64,f64,f64)> — app.rs:489
Option<(u16,u16,u16,u16)> — shell_app.rs:437
picker_popup_rect
Option<(f64,f64,f64,f64)> — app.rs:544
—
panel_hover_link_rects
Vec<(f64,f64,f64,f64,String,bool)> — app.rs:486
HoverLinkRects — shell_app.rs:436
editor_hover_link_rects
Vec<(f64,f64,f64,f64,String)> — app.rs:569
HoverLinkRects — shell_app.rs:439
tab_switcher_popup_rect is the proof: the TUI already migrated it to quadraui::Rect and GTK did not. The direction works; it was half-finished.
The primitives for the hold-outs already exist and are unused:
RichTextPopupLayout — hit_test(x, y) at primitives/rich_text_popup.rs:262, plus char_at(x, y, col_width) at :305
TooltipLayout — hit_test(x, y, id) at primitives/tooltip.rs:261
vimcode hand-rolls both, in two incompatible tuple shapes, against primitives that ship hit-testing.
Why the appliers diverge
They diverge because the caches diverge. A (f64,f64,f64,f64) on one side and a (u16,u16,u16,u16) on the other cannot share a hit-test, so each backend writes its own — and once each has its own, the surrounding apply logic follows. Where the cache is a shared *Layout, the router is shared and the apply is one call.
Fix
Migrate every raw-tuple paint cache above to quadraui::Rect, or to the primitive's own *Layout where one exists (RichTextPopupLayout for the two hover popups and both link-rect vectors; TooltipLayout for tab tooltips).
Finish tab_switcher_popup_rect on the GTK side to match the TUI's already-migrated field.
Delete the per-backend hit-test arithmetic these fields exist to feed, and route through the primitive's hit_test / char_at.
Per feedback_cache_paint_layout: cache what paint returned; never re-derive geometry in a click handler.
Then re-measure, before assuming more work exists
The 8,000-line figure was a residual estimate — what was left after the other buckets were accounted for — not a census. Re-run the duplication measurement after this lands. The honest expectation is that a substantial part of it collapses once the caches agree, and whatever remains will be specific and nameable (most likely "primitive X returns a layout with no hit_test"), which is a small upstream issue rather than an architecture.
Do not size this work from the 8,000 figure, and do not promise a reduction from it in the PR.
Acceptance
No raw (f64,f64,f64,f64) or (u16,u16,u16,u16) paint-cache field remains in either App or TuiShellApp.
Black-box tests, both backends: hover popup click and link activation, tab tooltip, tab-switcher popup dismissal, picker popup outside-click.
The finding this replaces
The 2026-09-04 audit sized ~8,000 production lines across vimcode's two backends as "the same apply/marshalling logic written twice against different geometry types", and concluded it needed a new unit-neutral type upstream (filed as quadraui#781). Re-examining the actual fields shows that conclusion was wrong. The unit-neutral type already exists, is already adopted for most surfaces, and both backends already share a router over it. What remains is an unfinished migration.
The evidence
quadraui::Rectisf32-based (quadraui/src/event.rs:153-158) and everyhit_testin the crate takes(x: f32, y: f32)— there is no unit parameter anywhere in quadraui's layout API. A TUI passes cell coordinates asf32; GTK passes pixels asf32; the same code runs.Where vimcode already adopted it, the two backends are identical:
App(GTK)TuiShellAppdialog_layoutquadraui::DialogLayoutquadraui::DialogLayoutcompletion_layoutquadraui::CompletionsLayoutquadraui::CompletionsLayoutcontext_menu_layoutquadraui::ContextMenuLayoutquadraui::ContextMenuLayoutBoth clear them the same way each frame, and both feed them into the same shared router — GTK builds the struct at
src/app.rs:3506, the TUI builds it atsrc/tui_main/mouse.rs:439. That is the pattern working today, at scale.The divergence is confined to the fields that were never migrated:
App(GTK)TuiShellApptab_switcher_popup_rectOption<(f64,f64,f64,f64)>—app.rs:503Option<quadraui::Rect>—shell_app.rs:448editor_hover_popup_rectOption<(f64,f64,f64,f64)>—app.rs:492Option<(u16,u16,u16,u16)>—shell_app.rs:438panel_hover_popup_rect/hover_popup_rectOption<(f64,f64,f64,f64)>—app.rs:489Option<(u16,u16,u16,u16)>—shell_app.rs:437picker_popup_rectOption<(f64,f64,f64,f64)>—app.rs:544panel_hover_link_rectsVec<(f64,f64,f64,f64,String,bool)>—app.rs:486HoverLinkRects—shell_app.rs:436editor_hover_link_rectsVec<(f64,f64,f64,f64,String)>—app.rs:569HoverLinkRects—shell_app.rs:439tab_switcher_popup_rectis the proof: the TUI already migrated it toquadraui::Rectand GTK did not. The direction works; it was half-finished.The primitives for the hold-outs already exist and are unused:
RichTextPopupLayout—hit_test(x, y)atprimitives/rich_text_popup.rs:262, pluschar_at(x, y, col_width)at:305TooltipLayout—hit_test(x, y, id)atprimitives/tooltip.rs:261vimcode hand-rolls both, in two incompatible tuple shapes, against primitives that ship hit-testing.
Why the appliers diverge
They diverge because the caches diverge. A
(f64,f64,f64,f64)on one side and a(u16,u16,u16,u16)on the other cannot share a hit-test, so each backend writes its own — and once each has its own, the surrounding apply logic follows. Where the cache is a shared*Layout, the router is shared and the apply is one call.Fix
quadraui::Rect, or to the primitive's own*Layoutwhere one exists (RichTextPopupLayoutfor the two hover popups and both link-rect vectors;TooltipLayoutfor tab tooltips).tab_switcher_popup_recton the GTK side to match the TUI's already-migrated field.hit_test/char_at.feedback_cache_paint_layout: cache what paint returned; never re-derive geometry in a click handler.Then re-measure, before assuming more work exists
The 8,000-line figure was a residual estimate — what was left after the other buckets were accounted for — not a census. Re-run the duplication measurement after this lands. The honest expectation is that a substantial part of it collapses once the caches agree, and whatever remains will be specific and nameable (most likely "primitive X returns a layout with no
hit_test"), which is a small upstream issue rather than an architecture.Do not size this work from the 8,000 figure, and do not promise a reduction from it in the PR.
Acceptance
(f64,f64,f64,f64)or(u16,u16,u16,u16)paint-cache field remains in eitherApporTuiShellApp.Some—ScreenLayout.pickerwas populated on GTK for months while nothing painted it (Command palette fails to open silently (broken on develop) #587), and GTK live render path silently drops 13 populated ScreenLayout fields (rest of #587's blast radius) #592 found 13 more fields in the same state.develop.