Skip to content

#735: Converge frame composition — ~4,500 lines laying down the same surfaces in two hand-kept orders - #748

Merged
JDonaghy merged 3 commits into
developfrom
issue-735-converge-frame-composition-4-500-lines-l
Sep 2, 2026
Merged

#735: Converge frame composition — ~4,500 lines laying down the same surfaces in two hand-kept orders#748
JDonaghy merged 3 commits into
developfrom
issue-735-converge-frame-composition-4-500-lines-l

Conversation

@JDonaghy

@JDonaghy JDonaghy commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Closes #735

Automated PR opened by coordinator for review of issue #735.

JDonaghy and others added 2 commits September 2, 2026 06:32
Frame composition was written twice. The overlay band — the app-level
surfaces both backends lay down on top of the editor and chrome — had
already inverted against itself twice, in exactly the way #592 predicted:

  GTK  menu dropdown -> command centre -> find/replace -> picker ->
       tab switcher -> **dialog -> context menu** -> window controls -> toasts
  TUI  find/replace -> picker -> tab switcher -> **context menu -> dialog** ->
       menu dropdown -> command centre -> toasts

1. dialog vs. context menu. `route_modal_overlay_click` hands a dialog every
   event the moment it opens, ahead of every rung but toasts — yet GTK
   painted it *under* the context menu, so a context menu left open when a
   dialog opens covers the surface that owns the input. Paint and input
   disagreeing: the #587/#592 shape.
2. menu dropdown / command centre vs. the modal stack. TUI painted the
   title-bar band on top of every modal; GTK painted it underneath.

`render::OVERLAY_Z_ORDER` is now the single artefact both backends walk, so
the order is no longer something either one holds an opinion about. The
canonical order takes GTK's placement for the title-bar chrome (chrome, so
modals cover it) and TUI's for the modal stack (it agrees with
`route_modal_overlay_click`). Geometry and rasterisation stay per backend —
pixels vs cells, Cairo painter-order vs ratatui cell coalescence are the
intrinsic differences #735 preserves.

Both backends record what they actually painted into `painted_overlay_band`,
which is the observable the z-order tests assert on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lus the staging-1 answer

Black-box coverage per CLAUDE.md, both backends, asserting on painted output:

  gtk/testing.rs   overlay_band_z_order::overlay_band_paints_dialog_above_context_menu_via_gtk_driver
  shell_app.rs     tests::overlay_band_paints_dialog_above_context_menu_via_shell_app

Both assert the *same* expected band — [MenuDropdown, CommandCenter,
ContextMenu, Dialog] — for the same engine state. A single test cannot drive
both backends (the GTK App lives in the vimcode bin target, TuiShellApp in
vcd), so "both backends emit the same FrameOp sequence" is two tests sharing
one expected value.

RED-verified, one backend at a time, by swapping two arm patterns in the
OVERLAY_Z_ORDER walk — which reproduces each backend's own pre-#735 order:

  GTK  Dialog before ContextMenu  -> gtk_driver test FAILS
  TUI  MenuDropdown after Dialog  -> shell_app test FAILS

Both also trip check_overlay_band_order's debug_assert inside render_content
before the assertion is even reached. Restored before committing.

The recorder names the variant it painted (push(OverlayOp::Dialog)) rather
than push(op). With push(op) the record follows the *pattern* the walk is at,
so swapping two arms' bodies paints them wrongly while still recording them
rightly — the first cut did exactly that and the RED check passed, which is
CLAUDE.md's "a test that cannot fail is not coverage" caught in the act.

Also answers the issue's staging item 1 (PROJECT_STATE.md): draw_frame is
#[cfg(test)]-gated and dead in production, so the raw-Buffer residue never
blocked anything. Every rung of it is either owned by quadraui's AppShell,
already trait-pure on the live path, or cursor placement the runner applies
after render_content. The remaining slices do not have to compose around it.

And records a verdict for each of the five ScreenLayout fields GTK never
reads, on the fields themselves: menu_dropdown_open (deliberately not
composed — MenuSystem owns its open state; the field feeds the parity
harnesses), group_dividers (composed on TUI, hit-tested on both, unpainted on
GTK — an editor-band gap, not an overlay-band one), and diff_toolbar /
tab_scroll_offset / tab_bar_primitive (all superseded by the per-group
GroupTabBar equivalents since #551).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eping

- overlay_band_paints_dialog_above_context_menu_via_gtk_driver no longer
  checks dialog_layout.borrow().is_some() (set unconditionally in the same
  match arm, so it can't disprove a paint that never reached the surface).
  It now asserts h.driver.screen_contains("ZQXW735DIALOG") like the TUI
  twin, plus a context-menu item label, so it actually proves both
  surfaces reached the Cairo surface.
- overlay_band_holds_only_the_title_bar_when_no_overlay_is_open_via_gtk_driver
  gets the same screen_contains("File") treatment for symmetry.
- Added render::overlay_band_dialog_over_context_menu_fixture() and
  render::overlay_band_title_bar_only_fixture(), two #[cfg(test)] fns
  built on compose_overlay_band(), so the GTK and TUI cross-backend tests
  compute their shared expected Vec<OverlayOp> from one function instead
  of two independently hand-copied literals.

RED-verified the fixed assertion by temporarily removing frame.draw(backend)
from the dialog arm in src/gtk/mod.rs: the test failed with the expected
"recorder and painter disagree" message, then passed again once restored.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@JDonaghy
JDonaghy merged commit 8c29ffe into develop Sep 2, 2026
2 checks passed
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.

Converge frame composition — ~4,500 lines laying down the same surfaces in two hand-kept orders

1 participant