Skip to content

Finish the paint-cache migration to quadraui Rect/*Layout — the raw-tuple hold-outs are why the mouse appliers diverge #831

Description

@JDonaghy

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::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 HoverLinkRectsshell_app.rs:436
editor_hover_link_rects Vec<(f64,f64,f64,f64,String)>app.rs:569 HoverLinkRectsshell_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:

  • RichTextPopupLayouthit_test(x, y) at primitives/rich_text_popup.rs:262, plus char_at(x, y, col_width) at :305
  • TooltipLayouthit_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

  1. 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).
  2. Finish tab_switcher_popup_rect on the GTK side to match the TUI's already-migrated field.
  3. Delete the per-backend hit-test arithmetic these fields exist to feed, and route through the primitive's hit_test / char_at.
  4. 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

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 pipelineplatformPlatform-specific (macOS, Windows, Linux)status:readyRefined and ready to enter the work pipeline

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions