Skip to content

#595 Stage 6b — cutover prerequisites: consume quadraui#532/#533, port the raw-Buffer holdouts, live ShellConfig, run() responsibilities #635

Description

@JDonaghy

Part of epic #595, milestone #9 TUI ShellApp Migration.

Split out of #634 on 2026-08-07. #634 originally carried both the A–F prerequisite
work and the entry-point flip. That is the same over-scoping that already forced #605
to be rescoped once (to Stage 6a, the parity sweep) — and #634's own acceptance bar
rejects any PR that leaves event_loop() in place, so a partial attempt cannot land.
This issue carries the prerequisites only; #634 is now the flip alone.

Why this is safe to do in one pass

Nothing here touches the live path. tui_main::run() / event_loop() remain the
live TUI entry point for the whole of this issue. TuiShellApp stays the dormant
scaffold it has been since Stage 0, exercised only by its own #[cfg(test)]
driver_with_shell tests. So there is no regression surface: if a port is wrong, the
driver_with_shell test catches it, and the running editor is unaffected either way.
That is exactly what makes this separable from the flip, which is all risk and no
mechanical work.

The upstream blockers are now clear

All three quadraui gaps that blocked #634 have landed on develop:

gap status
quadraui#531Backend::draw_settings_chrome landed (b586086, 2ccea55)
quadraui#532AppShell::set_title_bar_visible landed (7d4ba53, 5f06867)
quadraui#533Backend::draw_terminal_divider landed

Before writing code, confirm all three are in this machine's checkout
git -C ~/src/quadraui log --oneline -20 and grep for the trait methods. If any is
absent, git -C ~/src/quadraui pull first. Do not proceed on a stale checkout.

Scope — items A–F

Lettering is preserved from #634 / src/tui_main/shell_app.rs's module doc, which
Stage 6a wrote precisely so the next session starts from a scoped list rather than a
re-survey. Read that module doc first — it is the authoritative per-item detail.

A. Menu bar + command centre + menu dropdown — unblocked by quadraui#532

draw_frame carves a full-width row 0 for these above the activity bar, and
vimcode's menu bar is runtime-toggleable (engine.menu_bar_visible,
menu_bar_toggleable = true). AppShell used to reserve its title-bar row at
construction with no runtime toggle — set_title_bar_visible is exactly that toggle.
Drive it from engine.menu_bar_visible. draw_menu_bar, draw_command_center and
MenuSystem::render are already trait calls, so this should collapse to wiring.

B. Split terminal panes — unblocked by quadraui#533

render_terminal_panel's split arm drew its divider via the free
quadraui::tui::draw_terminal_divider. Call Backend::draw_terminal_divider(rect)
instead. Note the trait method is geometry-neutral (rect: Rect), not the free
function's raw x/y/height — convert at the call site. Also retire
panels::draw_settings_chrome_via_backend (the #531 stand-in) down to the real
Backend::draw_settings_chrome call, which was always the stated plan for it.

C. Plugin extension panel + AI sidebar panel

render_ext_panel's help-popup overlay and manual scrollbar are raw set_cell box
drawing; render_ai_sidebar takes buf: &mut ratatui::buffer::Buffer outright.

Check the existing trait surface before filing anything upstream
Backend::draw_tooltip and Backend::draw_scrollbar both already exist in
quadraui/src/backend.rs, so the popup and scrollbar most likely need no new
primitive. The fill_row / fill_rect / draw_rule_row rule-row trick that Stages 2a
and 2c used covers plain box drawing. render_ai_sidebar should lose its Buffer
parameter and take &mut dyn Backend, one implementation shared by draw_frame and
render_content — the same shape the settings / source-control / extensions sidebar
renderers already converted to.

D. The three unported keyboard tiers

Activity-bar-focused, sidebar-focused, and cmd_sel. Follow the dispatch pattern
Stages 3 and 4 established in TuiShellApp::handle.

E. ShellConfig build-out

Today's config() is #[cfg(test)]-only and declares a single panel:explorer
PanelDefinition, so AppShell would render one activity-bar icon. A live config needs
every panel render::build_activity_bar emits (src/render.rs:8147 — menu/hamburger,
explorer, search, debug, source control, extensions, and the bottom items), split across
ShellConfig.panels / with_bottom_items, plus an on_shell_event arm treating the
hamburger item as "open the menu" rather than "switch panel".

build_activity_bar is the source of truth for the list — derive from it, don't
hand-transcribe. (AppShell::build_activity_bar leaves active_accent/selection_bg
None where vimcode's sets them from the theme; the TUI rasteriser falls back to
theme.cursor — cosmetic, not a blocker, but note it if you touch it.)

F. run()'s own non-loop responsibilities

The panic hook, core::swap::register_emergency_engine, the emergency swap flush and
the custom crash message all live in run() around event_loop. run_with_shell
tui::run::run does its own catch_unwind and resume_unwinds after restoring the
terminal, so an outer catch_unwind in run() still sees the payload — the ordering
just has to be re-established deliberately. Establish it here, so the flip in #634
is purely an entry-point swap.

keyboard_enhanced is not a blocker: setup() can call
crossterm::terminal::supports_keyboard_enhancement() itself and get the same answer
the runner's push used.

The Platform-Neutrality Rule still applies

If an item genuinely needs surface quadraui does not have, file the upstream issue and
stop on that item
— do not write a local stand-in. This repo has scar tissue for
exactly this (#319, three reverted attempts; #605's review caught another). Land whatever
else is complete and say plainly in the PR which item is parked on which new upstream
issue. A partial PR is fine on this issue — unlike #634, there is no all-or-nothing
acceptance bar here.

The one sanctioned exception already in the tree is draw_settings_chrome_via_backend,
which was explicitly written as a temporary stand-in with its upstream issue filed —
and item B retires it.

Acceptance

  • A–F each either done, or parked with a filed upstream issue named in the PR.
  • draw_settings_chrome_via_backend is gone (collapsed into Backend::draw_settings_chrome).
  • render_content paints the terminal-panel split divider through
    Backend::draw_terminal_divider.
  • A live (non-#[cfg(test)]) ShellConfig covering every build_activity_bar panel.
  • New/extended driver_with_shell tests covering each newly painted surface — the
    same bar Stages 2a/2b/2c met. In-crate #[cfg(test)], not tests/.
  • event_loop() is still present and still live. Flipping the entry point is
    #595 Stage 6 — cutover TUI entry point to run_with_shell; delete event_loop() #634's job, not this one — a PR that flips it here will be sent back.
  • cargo build / clippy / fmt --check / test --no-default-features all EXIT=0.
  • src/tui_main/shell_app.rs's module doc is updated to match reality, so #595 Stage 6 — cutover TUI entry point to run_with_shell; delete event_loop() #634
    starts from an accurate list.

Do not touch

README, CHANGELOG, or any shared docs — the coordinator handles those.

Blocks: #634. Part of: #595.

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 requesttest-mode:smokecoord: per-issue test-mode policy

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions