Skip to content

GTK: present confirmation dialogs through quadraui's native seam, not the in-canvas Dialog primitive #727

Description

@JDonaghy

Summary

vimcode's "Unsaved Changes" confirmation is an in-canvas box painted by quadraui::Dialog; VS Code's equivalent is a real OS dialog. Once quadraui grows a native message-dialog seam (quadraui#666), route GTK's confirmation dialogs through it. TUI keeps the in-canvas primitive — that is the point of the split, not a limitation.

Depends on quadraui#666. Needs a src/quadraui_pin.rs bump; do not start until that has landed.

The edge-trigger problem — this is the actual work

vimcode's dialogs are frame-driven state. engine.dialog: Option<Dialog> (src/core/engine/mod.rs:363) is converted to a quadraui::Dialog by dialog_panel_to_quadraui_dialog (src/render.rs:4887) and repainted every frame via backend.draw_dialog (src/gtk/mod.rs:9333). A native dialog cannot be re-presented every frame — it must be presented once, on the transition from no-dialog to dialog.

So:

  1. Present once, keyed on the dialog's identity, on the no-dialog to dialog edge.
  2. While a native dialog is in flight, suppress the in-canvas draw.
  3. Map the returned WidgetId back to exactly the action the in-canvas DialogHit::Button(id) path already produces at src/gtk/mod.rs:4399, so the engine sees one code path and one set of outcomes.

The blocking call shape is already established here — backend.services().show_file_open_dialog(...) at src/gtk/mod.rs:1773 blocks on quadraui's nested-mainloop pump, and PendingFileDialog (src/gtk/mod.rs:1089) already models "a dialog is in flight". Follow that, don't invent a second mechanism.

What stays in-canvas

  • TUI, always. src/tui_main/** is untouched by this issue and its snapshots must not move.
  • Any dialog quadraui reports as not natively expressible — i.e. carrying a table or a text input. That covers the help dialog (src/render.rs:8177 sc_help_dialog, a DialogTable), the text-input dialogs (src/render.rs:4915), and source control's (src/core/engine/source_control.rs:269). These must keep working exactly as today.

The native/in-canvas decision comes from quadraui's native_dialog_options returning Nonedo not hand-maintain a list of dialog kinds in this repo.

Acceptance

  • Closing a modified buffer in the GTK build shows a real OS dialog. Save / Don't Save / Cancel produce the same engine outcomes as today's in-canvas buttons.
  • The dialog is presented exactly once per open, not once per frame — assert the present count stays at 1 across N frames with the dialog unchanged.
  • Both entry points go native: :q with unsaved changes, and the tab-x close path (Msg variants at src/gtk/mod.rs:1380-1388).
  • TUI behaviour byte-identical.
  • Help dialog and text-input dialogs still render in-canvas on GTK.

Files

  • src/gtk/mod.rs — present-once edge trigger, response-to-action mapping, in-canvas suppression while native is in flight
  • src/quadraui_pin.rs — pin bump
  • src/render.rs — only if the descriptor must carry the native/in-canvas decision through

Supersedes

#209, option 3 ("native widget escape hatch"). When this lands, either close #209 or retitle it to the styling-only options that remain.

Surfaced by

Operator smoke against VS Code, 2026-09-01.

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 pipelineenhancementNew feature or requestplatformPlatform-specific (macOS, Windows, Linux)status:readyRefined and ready to enter the work pipelineuiUI/rendering

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions