mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-11 01:51:34 -10:00
fix(tui): clear modal backdrop centrally
Paint an opaque modal backdrop from ViewStack before rendering overlays so stale transcript glyphs cannot bleed through older or minimal modal views. Keep the existing popup-surface behavior and document the broader #3732 coverage in both changelogs. Verification: - cargo fmt --all --check - git diff --check - CARGO_TARGET_DIR=/Volumes/VIXinSSD/codewhale-target cargo test -p codewhale-tui --bin codewhale-tui --locked view_stack_paints_opaque_backdrop_before_modal -- --nocapture - CARGO_TARGET_DIR=/Volumes/VIXinSSD/codewhale-target cargo test -p codewhale-tui --bin codewhale-tui --locked plan_prompt_paints_an_opaque_modal_surface -- --nocapture - CARGO_TARGET_DIR=/Volumes/VIXinSSD/codewhale-target cargo test -p codewhale-tui --bin codewhale-tui --locked plan_prompt_footer_stays_compact_on_narrow_terminals -- --nocapture - CARGO_TARGET_DIR=/Volumes/VIXinSSD/codewhale-target cargo test -p codewhale-tui --bin codewhale-tui --locked tui::views::tests:: -- --nocapture Refs #3732
This commit is contained in:
1 parent
4cddca0d30
commit
4204029cf8
3 files changed
+87
-7
No files matched your search
+3
-3
@@ -68,9 +68,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Fixed runtime API approval handling so workspace trust no longer auto-resolves
|
||||
ordinary tool approvals; trust now only participates in full-access retry
|
||||
decisions while YOLO/auto-approve remains the approval bypass (#3736).
|
||||
- Fixed Plan/request-input modal surfaces so popup interiors are painted
|
||||
opaquely, and compacted the Plan confirmation footer so action choices stay
|
||||
visible on narrow terminals instead of truncating (#3732).
|
||||
- Fixed modal surfaces so the shared view stack paints an opaque backdrop before
|
||||
any overlay, while Plan/request-input popup interiors stay opaque and the Plan
|
||||
confirmation footer keeps action choices visible on narrow terminals (#3732).
|
||||
- Added a turn-loop Plan-mode guard for file-writing tools and write-capable MCP
|
||||
tools so Plan's "no writes" promise is enforced before approval or execution,
|
||||
not only by the sandbox/catalog layer (#3734).
|
||||
|
||||
@@ -68,9 +68,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Fixed runtime API approval handling so workspace trust no longer auto-resolves
|
||||
ordinary tool approvals; trust now only participates in full-access retry
|
||||
decisions while YOLO/auto-approve remains the approval bypass (#3736).
|
||||
- Fixed Plan/request-input modal surfaces so popup interiors are painted
|
||||
opaquely, and compacted the Plan confirmation footer so action choices stay
|
||||
visible on narrow terminals instead of truncating (#3732).
|
||||
- Fixed modal surfaces so the shared view stack paints an opaque backdrop before
|
||||
any overlay, while Plan/request-input popup interiors stay opaque and the Plan
|
||||
confirmation footer keeps action choices visible on narrow terminals (#3732).
|
||||
- Added a turn-loop Plan-mode guard for file-writing tools and write-capable MCP
|
||||
tools so Plan's "no writes" promise is enforced before approval or execution,
|
||||
not only by the sandbox/catalog layer (#3734).
|
||||
|
||||
@@ -86,6 +86,16 @@ pub(crate) fn render_modal_surface(area: Rect, popup_area: Rect, buf: &mut Buffe
|
||||
.render(popup_area, buf);
|
||||
}
|
||||
|
||||
fn render_modal_backdrop(area: Rect, buf: &mut Buffer) {
|
||||
for y in area.top()..area.bottom() {
|
||||
for x in area.left()..area.right() {
|
||||
buf[(x, y)]
|
||||
.set_symbol(" ")
|
||||
.set_style(Style::default().bg(palette::DEEPSEEK_INK));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CommandPaletteAction {
|
||||
ExecuteCommand { command: String },
|
||||
@@ -350,6 +360,9 @@ impl ViewStack {
|
||||
}
|
||||
|
||||
pub fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||
if !self.views.is_empty() {
|
||||
render_modal_backdrop(area, buf);
|
||||
}
|
||||
for view in &self.views {
|
||||
view.render(area, buf);
|
||||
}
|
||||
@@ -2387,7 +2400,11 @@ mod tests {
|
||||
use crossterm::event::{
|
||||
KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
|
||||
};
|
||||
use ratatui::{buffer::Buffer, layout::Rect};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Color, Style},
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
@@ -3319,6 +3336,69 @@ base_url = "https://api.xiaomimimo.com/v1"
|
||||
assert_eq!(stack.top_kind(), Some(ModalKind::Help));
|
||||
}
|
||||
|
||||
struct BareModal;
|
||||
|
||||
impl ModalView for BareModal {
|
||||
fn kind(&self) -> ModalKind {
|
||||
ModalKind::ContextMenu
|
||||
}
|
||||
|
||||
fn handle_key(&mut self, _key: KeyEvent) -> ViewAction {
|
||||
ViewAction::None
|
||||
}
|
||||
|
||||
fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||
let x = area.x + area.width / 2;
|
||||
let y = area.y + area.height / 2;
|
||||
buf[(x, y)]
|
||||
.set_symbol("M")
|
||||
.set_style(Style::default().fg(Color::White).bg(Color::Red));
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn view_stack_paints_opaque_backdrop_before_modal() {
|
||||
let area = Rect::new(0, 0, 24, 8);
|
||||
let modal_x = area.x + area.width / 2;
|
||||
let modal_y = area.y + area.height / 2;
|
||||
let mut buf = Buffer::empty(area);
|
||||
for y in area.top()..area.bottom() {
|
||||
for x in area.left()..area.right() {
|
||||
buf[(x, y)]
|
||||
.set_symbol("X")
|
||||
.set_style(Style::default().fg(Color::Red).bg(Color::Blue));
|
||||
}
|
||||
}
|
||||
|
||||
let mut stack = ViewStack::new();
|
||||
stack.push(BareModal);
|
||||
stack.render(area, &mut buf);
|
||||
|
||||
assert_eq!(buf[(modal_x, modal_y)].symbol(), "M");
|
||||
for y in area.top()..area.bottom() {
|
||||
for x in area.left()..area.right() {
|
||||
if x == modal_x && y == modal_y {
|
||||
continue;
|
||||
}
|
||||
let cell = &buf[(x, y)];
|
||||
assert_eq!(
|
||||
cell.symbol(),
|
||||
" ",
|
||||
"stale glyph at ({x},{y}) must be cleared"
|
||||
);
|
||||
assert_eq!(
|
||||
cell.bg,
|
||||
palette::DEEPSEEK_INK,
|
||||
"backdrop at ({x},{y}) must be opaque"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn buffer_text(buf: &Buffer, area: Rect) -> String {
|
||||
let mut out = String::new();
|
||||
for y in area.top()..area.bottom() {
|
||||
|
||||
Reference in new issue
Block a user