mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Center TUI confirmations over their retained backdrop (#50504)
## What changed Render confirmation dialogs as centered overlays while preserving the composer or parent picker behind them. Share dialog sizing and cursor placement across stacked views and full transcript rendering, with input still routed exclusively to the active dialog. Use the shared confirmation preset for prompts including full access, session archive/delete, goal changes, and log upload consent. ## Testing Add coverage for backdrop retention and cursor routing at multiple terminal sizes, and for preserving the compact viewport and composer draft after dismissing a confirmation without replaying history. Update confirmation snapshots to show the retained backdrop. GitOrigin-RevId: 026af89904ec345e2b330317b8cc844512125d0e
This commit is contained in:
committed by
copyberry
parent
9ce35d337a
commit
d42aecc56b
@@ -157,7 +157,7 @@ impl App {
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..SelectionViewParams::picker()
|
||||
..SelectionViewParams::confirmation()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,6 @@ async fn archive_confirmation_number_keys_act_immediately() {
|
||||
)),
|
||||
);
|
||||
app.confirm_agents_overview_action(id, AgentsOverviewAction::Archive);
|
||||
insta::assert_snapshot!(
|
||||
"archive_task_confirmation",
|
||||
render_bottom_popup(&app.chat_widget, /*width*/ 72)
|
||||
);
|
||||
|
||||
app.chat_widget.handle_key_event(KeyCode::Char(key).into());
|
||||
|
||||
@@ -684,7 +680,10 @@ async fn lifecycle_removes_background_and_current_tasks_without_losing_the_dashb
|
||||
Box::pin(app.handle_event(&mut tui, &mut app_server, confirmation)).await?;
|
||||
insta::assert_snapshot!(
|
||||
format!("{snapshot}_confirmation"),
|
||||
render_bottom_popup(&app.chat_widget, /*width*/ 72)
|
||||
normalize_agent_center_snapshot(render_bottom_popup(
|
||||
&app.chat_widget,
|
||||
/*width*/ 80
|
||||
))
|
||||
);
|
||||
app.chat_widget.handle_key_event(KeyCode::Enter.into());
|
||||
assert!(
|
||||
|
||||
@@ -122,7 +122,7 @@ impl App {
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..SelectionViewParams::picker()
|
||||
..SelectionViewParams::confirmation()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,9 +271,23 @@ impl App {
|
||||
feedback_tick =
|
||||
view.render_composer_gap(follow_area, composer_hint.as_ref(), frame.buffer, now);
|
||||
chat_widget.note_rendered_width(screen_size.width);
|
||||
rendered_cursor = bottom.cursor_pos(bottom_area);
|
||||
let dialog = chat_widget.centered_dialog();
|
||||
let (foreground, foreground_area): (&dyn Renderable, Rect) =
|
||||
if let Some(dialog) = &dialog {
|
||||
let area = Rect::new(
|
||||
/*x*/ 0,
|
||||
/*y*/ 0,
|
||||
screen_size.width,
|
||||
screen_size.height,
|
||||
);
|
||||
dialog.render(area, frame.buffer);
|
||||
(dialog, area)
|
||||
} else {
|
||||
(&bottom, bottom_area)
|
||||
};
|
||||
rendered_cursor = foreground.cursor_pos(foreground_area);
|
||||
if let Some(position) = rendered_cursor {
|
||||
frame.set_cursor_style(bottom.cursor_style(bottom_area));
|
||||
frame.set_cursor_style(foreground.cursor_style(foreground_area));
|
||||
frame.set_cursor_position(position);
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -1582,6 +1582,39 @@ async fn fullscreen_composer_mouse_copy_and_input_ownership() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn inline_confirmation_preserves_compact_viewport_without_replaying_history() -> Result<()> {
|
||||
let mut app = crate::app::test_support::make_test_app().await;
|
||||
attach_thread(&mut app, ThreadId::new());
|
||||
app.local_settings.tui.animations = false;
|
||||
app.chat_widget
|
||||
.apply_external_edit("preserved draft".to_string());
|
||||
app.transcript_cells = vec![user_cell("Completed conversation behind the confirmation")];
|
||||
let mut tui = crate::tui::test_support::make_test_tui()?;
|
||||
tui.set_owned_screen(/*owned*/ false)?;
|
||||
let size = Size::new(/*width*/ 80, /*height*/ 24);
|
||||
app.show_replace_thread_goal_confirmation(
|
||||
ThreadId::new(),
|
||||
crate::goal_files::GoalDraft {
|
||||
objective: "A replacement goal".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
app.render_chat_widget_frame(&mut tui, size)?;
|
||||
assert!(tui.terminal.viewport_area.height < size.height);
|
||||
app.chat_widget.handle_key_event(KeyCode::Esc.into());
|
||||
app.render_chat_widget_frame(&mut tui, size)?;
|
||||
assert!(tui.terminal.viewport_area.height < size.height);
|
||||
assert!(app.last_rendered_history_tail.is_none());
|
||||
assert!(
|
||||
buffer_text(crate::custom_terminal::test_support::last_rendered_buffer(
|
||||
&tui.terminal
|
||||
))
|
||||
.contains("preserved draft")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fullscreen_composer_scrolls_without_moving_caret_or_transcript() -> Result<()> {
|
||||
let mut app = crate::app::test_support::make_test_app().await;
|
||||
|
||||
+22
-7
@@ -1,13 +1,28 @@
|
||||
---
|
||||
source: tui/src/app/agents_overview_actions_tests.rs
|
||||
expression: "render_bottom_popup(&app.chat_widget, 72)"
|
||||
expression: "render_bottom_popup(&app.chat_widget, 80)"
|
||||
---
|
||||
Archive “Current task”?
|
||||
This stops any running work in this task and its child agents, then
|
||||
archives them. Their history can be restored from the resume picker.
|
||||
Agent command center Group: Project g
|
||||
All 1 Needs you 0 Working 0 Ready 1 Inactive 0
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
Tasks Status Updated
|
||||
/tmp/project 1
|
||||
› ○ Current task Ready 2m ago
|
||||
|
||||
|
||||
› 1. Cancel
|
||||
2. Archive task and child agents
|
||||
Archive “Current task”?
|
||||
This stops any running work in this task and its child agents, then
|
||||
archives them. Their history can be restored from the resume picker.
|
||||
|
||||
enter select · esc back
|
||||
|
||||
› 1. Cancel
|
||||
2. Archive task and child agents
|
||||
|
||||
enter select · esc back
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
? help esc back ↑/↓ move enter open n new
|
||||
|
||||
+22
-7
@@ -1,13 +1,28 @@
|
||||
---
|
||||
source: tui/src/app/agents_overview_actions_tests.rs
|
||||
expression: "render_bottom_popup(&app.chat_widget, 72)"
|
||||
expression: "render_bottom_popup(&app.chat_widget, 80)"
|
||||
---
|
||||
Permanently delete “Current task”?
|
||||
This stops any running work in this task and its child agents, then
|
||||
permanently deletes their history. This cannot be undone.
|
||||
Agent command center Group: Project g
|
||||
All 1 Needs you 0 Working 0 Ready 1 Inactive 0
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
Tasks Status Updated
|
||||
/tmp/project 1
|
||||
› ○ Current task Ready 2m ago
|
||||
|
||||
|
||||
› 1. Cancel
|
||||
2. Permanently delete task and child agents
|
||||
Permanently delete “Current task”?
|
||||
This stops any running work in this task and its child agents, then
|
||||
permanently deletes their history. This cannot be undone.
|
||||
|
||||
enter select · esc back
|
||||
|
||||
› 1. Cancel
|
||||
2. Permanently delete task and child agents
|
||||
|
||||
enter select · esc back
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
? help esc back ↑/↓ move enter open n new
|
||||
|
||||
+22
-8
@@ -1,15 +1,29 @@
|
||||
---
|
||||
source: tui/src/app/daemon_menu_tests.rs
|
||||
assertion_line: 125
|
||||
expression: "render_bottom_popup(&app.chat_widget,\nwidth).replace(r\"C:\\cli-build\\bin\\codex\", \"/x/cli-build/bin/codex\")"
|
||||
---
|
||||
Update daemon and exit Codex?
|
||||
Use this CLI package v0.0.0 from /x/cli-build/bin/codex. Copy the complete package and pin it
|
||||
against automatic updates.
|
||||
This may restart the daemon and interrupt active or queued work.
|
||||
Codex exits to update in this terminal. Relaunch it afterward.
|
||||
Agent command center Group: Project g
|
||||
All 0 Needs you 0 Working 0 Ready 0 Inactive 0 tab/⇧tab filter
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
No tasks yet │
|
||||
│
|
||||
│
|
||||
|
||||
Update daemon and exit Codex?
|
||||
Use this CLI package v0.0.0 from /x/cli-build/bin/codex. Copy the
|
||||
complete package and pin it against automatic updates.
|
||||
This may restart the daemon and interrupt active or queued work.
|
||||
Codex exits to update in this terminal. Relaunch it afterward.
|
||||
|
||||
|
||||
› 1. Cancel
|
||||
2. Update and exit
|
||||
› 1. Cancel
|
||||
2. Update and exit
|
||||
|
||||
enter select · esc back
|
||||
enter select · esc back
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
Service v0.153.0 < Codex CLI v0.154.0 · /daemon
|
||||
|
||||
+21
-8
@@ -2,14 +2,27 @@
|
||||
source: tui/src/app/daemon_menu_tests.rs
|
||||
expression: "render_bottom_popup(&app.chat_widget,\nwidth).replace(r\"C:\\cli-build\\bin\\codex\", \"/x/cli-build/bin/codex\")"
|
||||
---
|
||||
Update daemon and exit Codex?
|
||||
Install the latest public stable release. Restore production updates; keep
|
||||
your automatic-update setting.
|
||||
This may restart the daemon and interrupt active or queued work.
|
||||
Codex exits to update in this terminal. Relaunch it afterward.
|
||||
Agent command center Group: Project g
|
||||
All 0 Needs you 0 Working 0 Ready 0 Inactive 0
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
No tasks yet
|
||||
|
||||
|
||||
› 1. Cancel
|
||||
2. Update and exit
|
||||
|
||||
enter select · esc back
|
||||
Update daemon and exit Codex?
|
||||
Install the latest public stable release. Restore production
|
||||
updates; keep your automatic-update setting.
|
||||
This may restart the daemon and interrupt active or queued work.
|
||||
Codex exits to update in this terminal. Relaunch it afterward.
|
||||
|
||||
|
||||
› 1. Cancel
|
||||
2. Update and exit
|
||||
|
||||
enter select · esc back
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Service v0.153.0 < Codex CLI v0.154.0 · /daemon
|
||||
|
||||
@@ -3060,15 +3060,15 @@ async fn open_agent_picker_prompts_when_subagents_disabled() -> Result<()> {
|
||||
|
||||
Box::pin(app.open_agent_picker(&mut app_server)).await;
|
||||
assert_snapshot!(render_bottom_popup(&app.chat_widget, /*width*/ 80), @r###"
|
||||
Enable subagents?
|
||||
Subagents are disabled in this TUI session.
|
||||
Enable subagents?
|
||||
› As Subagents are disabled in this TUI session.
|
||||
|
||||
gp
|
||||
› 1. Yes, enable Save on the server for new threads without changing
|
||||
this thread
|
||||
2. Not now Keep subagents disabled
|
||||
|
||||
› 1. Yes, enable Save on the server for new threads without changing this
|
||||
thread
|
||||
2. Not now Keep subagents disabled
|
||||
|
||||
enter select · esc back
|
||||
enter select · esc back
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ impl App {
|
||||
)),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
items,
|
||||
..SelectionViewParams::picker()
|
||||
..SelectionViewParams::confirmation()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,21 @@ pub(crate) enum ViewCompletion {
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
/// How a view is placed over the preceding view in the bottom-pane stack.
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub(crate) enum ViewPresentation {
|
||||
#[default]
|
||||
Inline,
|
||||
Centered,
|
||||
}
|
||||
|
||||
/// Trait implemented by every view that can be shown in the bottom pane.
|
||||
pub(crate) trait BottomPaneView: Renderable {
|
||||
/// Centered views preserve the preceding view as their backdrop.
|
||||
fn presentation(&self) -> ViewPresentation {
|
||||
ViewPresentation::Inline
|
||||
}
|
||||
|
||||
/// Handle a key event while the view is active. A redraw is always
|
||||
/// scheduled after this call.
|
||||
fn handle_key_event(&mut self, _key_event: KeyEvent) {}
|
||||
|
||||
@@ -331,7 +331,7 @@ pub(crate) fn feedback_upload_consent_params(
|
||||
header: Box::new(crate::render::renderable::ColumnRenderable::with(
|
||||
header_lines,
|
||||
)),
|
||||
..super::SelectionViewParams::picker()
|
||||
..super::SelectionViewParams::confirmation()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -193,6 +193,7 @@ pub(crate) struct SelectionItem {
|
||||
/// `row_display` controls whether rows can wrap or stay single-line with ellipsis truncation
|
||||
/// `description_layout` optionally hides descriptions when their column would become too narrow.
|
||||
pub(crate) struct SelectionViewParams {
|
||||
pub presentation: super::ViewPresentation,
|
||||
pub picker_surface: PickerSurface,
|
||||
/// Upper row budget; compact completion menus retain the default of eight.
|
||||
pub max_visible_rows: usize,
|
||||
@@ -257,6 +258,7 @@ impl Default for SelectionViewParams {
|
||||
picker_surface: PickerSurface::default(),
|
||||
max_visible_rows: MAX_POPUP_ROWS,
|
||||
reserve_result_rows: false,
|
||||
presentation: super::ViewPresentation::Inline,
|
||||
view_id: None,
|
||||
title: None,
|
||||
subtitle: None,
|
||||
@@ -294,6 +296,7 @@ impl Default for SelectionViewParams {
|
||||
/// visible rows and source items and for preserving selection while filters
|
||||
/// change.
|
||||
pub(crate) struct ListSelectionView {
|
||||
presentation: super::ViewPresentation,
|
||||
picker_surface: PickerSurface,
|
||||
max_visible_rows: usize,
|
||||
reserve_result_rows: bool,
|
||||
@@ -436,6 +439,7 @@ impl ListSelectionView {
|
||||
picker_surface: params.picker_surface,
|
||||
max_visible_rows: params.max_visible_rows.max(/*other*/ 1),
|
||||
reserve_result_rows: params.reserve_result_rows,
|
||||
presentation: params.presentation,
|
||||
view_id: params.view_id,
|
||||
footer_note: params.footer_note,
|
||||
footer_hint: params.footer_hint,
|
||||
@@ -1085,6 +1089,10 @@ impl ListSelectionView {
|
||||
}
|
||||
|
||||
impl BottomPaneView for ListSelectionView {
|
||||
fn presentation(&self) -> super::ViewPresentation {
|
||||
self.presentation
|
||||
}
|
||||
|
||||
fn keymap_contexts(&self) -> crate::keymap::KeymapContextSet {
|
||||
crate::keymap::KeymapContextSet::new(crate::keymap::KeymapContext::List)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
//!
|
||||
//! The pane owns the [`ChatComposer`] (editable prompt input) and a stack of transient
|
||||
//! [`BottomPaneView`]s (popups/modals) that temporarily replace the composer for focused
|
||||
//! interactions like selection lists.
|
||||
//! interactions like selection lists. Centered views retain earlier views as a backdrop,
|
||||
//! while input remains routed exclusively to the top of the stack.
|
||||
//!
|
||||
//! Input routing is layered: `BottomPane` decides which local surface receives a key (view vs
|
||||
//! composer), while higher-level intent such as "interrupt" or "quit" is decided by the parent
|
||||
@@ -47,6 +48,7 @@ use crate::terminal_palette::effective_stdout_color_level;
|
||||
use crate::tui::FrameRequester;
|
||||
pub(crate) use bottom_pane_view::BottomPaneView;
|
||||
pub(crate) use bottom_pane_view::ViewCompletion;
|
||||
pub(crate) use bottom_pane_view::ViewPresentation;
|
||||
use codex_app_server_protocol::SkillMetadata;
|
||||
use codex_app_server_protocol::ToolRequestUserInputParams;
|
||||
use codex_features::Features;
|
||||
@@ -119,6 +121,9 @@ pub(crate) use voice_strip::VoiceStripState;
|
||||
mod bottom_pane_view;
|
||||
mod composer_gap;
|
||||
mod effort_ignition;
|
||||
mod view_stack;
|
||||
pub(crate) use view_stack::CenteredView;
|
||||
pub(crate) use view_stack::DialogOverlay;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct LocalImageAttachment {
|
||||
@@ -1730,6 +1735,11 @@ impl BottomPane {
|
||||
.is_some_and(bottom_pane_view::BottomPaneView::terminal_title_requires_action)
|
||||
}
|
||||
|
||||
pub(crate) fn has_centered_view(&self) -> bool {
|
||||
self.active_view()
|
||||
.is_some_and(|view| view.presentation() == ViewPresentation::Centered)
|
||||
}
|
||||
|
||||
pub(crate) fn has_active_view(&self) -> bool {
|
||||
self.warnings_view.is_some() || self.has_active_modal()
|
||||
}
|
||||
@@ -2169,8 +2179,36 @@ impl BottomPane {
|
||||
}
|
||||
|
||||
pub(crate) fn as_renderable_with_options<'a>(
|
||||
&'a self,
|
||||
options: ComposerRenderOptions<'a>,
|
||||
) -> RenderableItem<'a> {
|
||||
self.renderable_for_views(options, &self.view_stack)
|
||||
}
|
||||
|
||||
pub(crate) fn centered_dialog(&self) -> Option<CenteredView<'_>> {
|
||||
self.active_view()
|
||||
.filter(|view| {
|
||||
!self.warnings_active() && view.presentation() == ViewPresentation::Centered
|
||||
})
|
||||
.map(CenteredView)
|
||||
}
|
||||
|
||||
pub(crate) fn backdrop_with_options<'a>(
|
||||
&'a self,
|
||||
options: ComposerRenderOptions<'a>,
|
||||
) -> RenderableItem<'a> {
|
||||
let views = if self.centered_dialog().is_some() {
|
||||
&self.view_stack[..self.view_stack.len() - 1]
|
||||
} else {
|
||||
&self.view_stack
|
||||
};
|
||||
self.renderable_for_views(options, views)
|
||||
}
|
||||
|
||||
fn renderable_for_views<'a>(
|
||||
&'a self,
|
||||
mut options: ComposerRenderOptions<'a>,
|
||||
views: &'a [Box<dyn BottomPaneView>],
|
||||
) -> RenderableItem<'a> {
|
||||
if self.warnings_active()
|
||||
&& let Some(warnings) = &self.warnings_view
|
||||
@@ -2182,8 +2220,12 @@ impl BottomPane {
|
||||
{
|
||||
banner.visible.set(false);
|
||||
}
|
||||
if let Some(view) = self.active_view() {
|
||||
RenderableItem::Borrowed(view)
|
||||
if let Some(view) = views.last() {
|
||||
if view.presentation() == ViewPresentation::Centered {
|
||||
RenderableItem::Owned(Box::new(view_stack::ViewStack(views)))
|
||||
} else {
|
||||
RenderableItem::Borrowed(view.as_ref())
|
||||
}
|
||||
} else {
|
||||
let mut flex = FlexRenderable::new();
|
||||
if let Some(banner) = self
|
||||
|
||||
@@ -8,6 +8,15 @@ use super::SelectionDescriptionLayout;
|
||||
use super::SelectionViewParams;
|
||||
|
||||
impl SelectionViewParams {
|
||||
/// A confirmation floats over its retained parent using the shared dialog geometry.
|
||||
/// Actions, initial selection, and cancellation remain the caller's responsibility.
|
||||
pub(crate) fn confirmation() -> Self {
|
||||
Self {
|
||||
presentation: super::ViewPresentation::Centered,
|
||||
..Self::picker()
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a panel picker with stable columns, hiding descriptions when they become too narrow.
|
||||
/// Titles and subtitles wrap; standard hints follow the active list keymap.
|
||||
pub(crate) fn picker() -> Self {
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
---
|
||||
source: tui/src/bottom_pane/view_stack_tests.rs
|
||||
assertion_line: 86
|
||||
expression: "snapshots.join(\"\\n\")"
|
||||
---
|
||||
80x9
|
||||
"................................................................................"
|
||||
"................................................................................"
|
||||
"................................................................................"
|
||||
"....DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD...."
|
||||
".... ...."
|
||||
".... ...."
|
||||
"................................................................................"
|
||||
"................................................................................"
|
||||
"................................................................................"
|
||||
|
||||
24x7
|
||||
"........................"
|
||||
"........................"
|
||||
"..DDDDDDDDDDDDDDDDDDDD.."
|
||||
".. .."
|
||||
".. .."
|
||||
"........................"
|
||||
"........................"
|
||||
|
||||
12x2
|
||||
"..DDDDDDDD.."
|
||||
".. .."
|
||||
@@ -0,0 +1,122 @@
|
||||
//! Shared centered-dialog geometry for retained view stacks and full transcript surfaces.
|
||||
//! Only the top view receives input; painting a backdrop never changes focus.
|
||||
|
||||
use super::BottomPaneView;
|
||||
use super::ViewPresentation;
|
||||
use crate::render::renderable::Renderable;
|
||||
use crate::render::renderable::RenderableItem;
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::widgets::Clear;
|
||||
use ratatui::widgets::Widget;
|
||||
|
||||
pub(super) struct ViewStack<'a>(pub(super) &'a [Box<dyn BottomPaneView>]);
|
||||
|
||||
fn dialog_area(view: &dyn BottomPaneView, area: Rect) -> Rect {
|
||||
let width = area
|
||||
.width
|
||||
.saturating_sub(/*rhs*/ 4)
|
||||
.max(area.width.min(/*other*/ 8))
|
||||
.min(/*other*/ 72);
|
||||
let height = view.desired_height(width).min(area.height);
|
||||
Rect::new(
|
||||
area.x + (area.width - width) / 2,
|
||||
area.y + (area.height - height) / 2,
|
||||
width,
|
||||
height,
|
||||
)
|
||||
}
|
||||
|
||||
impl Renderable for ViewStack<'_> {
|
||||
fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||
let Some((view, previous)) = self.0.split_last() else {
|
||||
return;
|
||||
};
|
||||
if view.presentation() == ViewPresentation::Centered {
|
||||
Self(previous).render(area, buf);
|
||||
CenteredView(view.as_ref()).render(area, buf);
|
||||
} else {
|
||||
view.render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn desired_height(&self, width: u16) -> u16 {
|
||||
let Some((view, previous)) = self.0.split_last() else {
|
||||
return 0;
|
||||
};
|
||||
if view.presentation() == ViewPresentation::Centered {
|
||||
CenteredView(view.as_ref())
|
||||
.desired_height(width)
|
||||
.max(Self(previous).desired_height(width))
|
||||
} else {
|
||||
view.desired_height(width)
|
||||
}
|
||||
}
|
||||
|
||||
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {
|
||||
let view = self.0.last()?;
|
||||
view.cursor_pos(dialog_area(view.as_ref(), area))
|
||||
}
|
||||
|
||||
fn cursor_style(&self, area: Rect) -> SetCursorStyle {
|
||||
self.0
|
||||
.last()
|
||||
.map_or(SetCursorStyle::DefaultUserShape, |view| {
|
||||
view.cursor_style(dialog_area(view.as_ref(), area))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A dialog placed within the supplied viewport, without taking over its backdrop.
|
||||
pub(crate) struct CenteredView<'a>(pub(super) &'a dyn BottomPaneView);
|
||||
|
||||
impl Renderable for CenteredView<'_> {
|
||||
fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||
let dialog = dialog_area(self.0, area);
|
||||
Clear.render(dialog, buf);
|
||||
self.0.render(dialog, buf);
|
||||
}
|
||||
|
||||
fn desired_height(&self, width: u16) -> u16 {
|
||||
dialog_area(self.0, Rect::new(/*x*/ 0, /*y*/ 0, width, u16::MAX)).height
|
||||
}
|
||||
|
||||
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {
|
||||
self.0.cursor_pos(dialog_area(self.0, area))
|
||||
}
|
||||
|
||||
fn cursor_style(&self, area: Rect) -> SetCursorStyle {
|
||||
self.0.cursor_style(dialog_area(self.0, area))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DialogOverlay<'a> {
|
||||
pub(crate) backdrop: RenderableItem<'a>,
|
||||
pub(crate) dialog: CenteredView<'a>,
|
||||
}
|
||||
|
||||
impl Renderable for DialogOverlay<'_> {
|
||||
fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||
self.backdrop.render(area, buf);
|
||||
self.dialog.render(area, buf);
|
||||
}
|
||||
|
||||
fn desired_height(&self, width: u16) -> u16 {
|
||||
self.backdrop
|
||||
.desired_height(width)
|
||||
.max(self.dialog.desired_height(width))
|
||||
}
|
||||
|
||||
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {
|
||||
self.dialog.cursor_pos(area)
|
||||
}
|
||||
|
||||
fn cursor_style(&self, area: Rect) -> SetCursorStyle {
|
||||
self.dialog.cursor_style(area)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "view_stack_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,83 @@
|
||||
//! Exercises dialog layout independently of any production confirmation caller.
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use ratatui::Terminal;
|
||||
use ratatui::backend::TestBackend;
|
||||
|
||||
struct TestPane {
|
||||
presentation: ViewPresentation,
|
||||
}
|
||||
|
||||
impl BottomPaneView for TestPane {
|
||||
fn presentation(&self) -> ViewPresentation {
|
||||
self.presentation
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for TestPane {
|
||||
fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||
for y in area.top()..area.bottom() {
|
||||
for x in area.left()..area.right() {
|
||||
if self.presentation == ViewPresentation::Inline {
|
||||
buf[(x, y)].set_symbol(".");
|
||||
} else if y == area.top() {
|
||||
buf[(x, y)].set_symbol("D");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn desired_height(&self, _width: u16) -> u16 {
|
||||
3
|
||||
}
|
||||
|
||||
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {
|
||||
Some((area.x, area.y))
|
||||
}
|
||||
|
||||
fn cursor_style(&self, _area: Rect) -> SetCursorStyle {
|
||||
SetCursorStyle::SteadyBar
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn centered_dialog_retains_backdrop_and_routes_cursor() {
|
||||
let backdrop = TestPane {
|
||||
presentation: ViewPresentation::Inline,
|
||||
};
|
||||
let dialog = TestPane {
|
||||
presentation: super::super::SelectionViewParams::confirmation().presentation,
|
||||
};
|
||||
let overlay = DialogOverlay {
|
||||
backdrop: RenderableItem::Borrowed(&backdrop),
|
||||
dialog: CenteredView(&dialog),
|
||||
};
|
||||
let mut snapshots = Vec::new();
|
||||
for (width, height, expected_cursor) in [(80, 9, (4, 3)), (24, 7, (2, 2)), (12, 2, (2, 0))] {
|
||||
let mut terminal = Terminal::new(TestBackend::new(width, height)).unwrap();
|
||||
terminal
|
||||
.draw(|frame| overlay.render(frame.area(), frame.buffer_mut()))
|
||||
.unwrap();
|
||||
let area = Rect::new(/*x*/ 0, /*y*/ 0, width, height);
|
||||
assert_eq!(
|
||||
(overlay.cursor_pos(area), overlay.cursor_style(area)),
|
||||
(Some(expected_cursor), SetCursorStyle::SteadyBar)
|
||||
);
|
||||
let views: Vec<Box<dyn BottomPaneView>> = vec![
|
||||
Box::new(TestPane {
|
||||
presentation: ViewPresentation::Inline,
|
||||
}),
|
||||
Box::new(TestPane {
|
||||
presentation: ViewPresentation::Centered,
|
||||
}),
|
||||
];
|
||||
let stack = ViewStack(&views);
|
||||
let mut stacked = Buffer::empty(area);
|
||||
stack.render(area, &mut stacked);
|
||||
assert_eq!(&stacked, terminal.backend().buffer());
|
||||
assert_eq!(stack.cursor_pos(area), overlay.cursor_pos(area));
|
||||
snapshots.push(format!("{width}x{height}\n{}", terminal.backend()));
|
||||
}
|
||||
insta::assert_snapshot!(snapshots.join("\n"));
|
||||
}
|
||||
@@ -1079,7 +1079,7 @@ impl ChatWidget {
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..SelectionViewParams::picker()
|
||||
..SelectionViewParams::confirmation()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ impl ChatWidget {
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..SelectionViewParams::picker()
|
||||
..SelectionViewParams::confirmation()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ impl ChatWidget {
|
||||
},
|
||||
],
|
||||
allow_cancel: false,
|
||||
..SelectionViewParams::picker()
|
||||
..SelectionViewParams::confirmation()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -512,7 +512,7 @@ impl ChatWidget {
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
items,
|
||||
header: Box::new(header),
|
||||
..SelectionViewParams::picker()
|
||||
..SelectionViewParams::confirmation()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@ impl ChatWidget {
|
||||
response: plugins_response_for_on_cancel.clone(),
|
||||
});
|
||||
})),
|
||||
..SelectionViewParams::picker()
|
||||
..SelectionViewParams::confirmation()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,10 +127,30 @@ impl ExternalWriterNotice {
|
||||
|
||||
impl ChatWidget {
|
||||
pub(crate) fn as_renderable(&self) -> RenderableItem<'_> {
|
||||
let backdrop = self.as_backdrop_renderable();
|
||||
match self.centered_dialog() {
|
||||
Some(dialog) => RenderableItem::Owned(Box::new(crate::bottom_pane::DialogOverlay {
|
||||
backdrop,
|
||||
dialog,
|
||||
})),
|
||||
None => backdrop,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn centered_dialog(&self) -> Option<crate::bottom_pane::CenteredView<'_>> {
|
||||
self.bottom_pane.centered_dialog()
|
||||
}
|
||||
|
||||
fn as_backdrop_renderable(&self) -> RenderableItem<'_> {
|
||||
if self
|
||||
.bottom_pane
|
||||
.selected_index_for_active_view(crate::app::AGENTS_OVERVIEW_VIEW_ID)
|
||||
.is_some()
|
||||
|| (self.bottom_pane.has_centered_view()
|
||||
&& self
|
||||
.bottom_pane
|
||||
.selected_index_for_present_view(crate::app::AGENTS_OVERVIEW_VIEW_ID)
|
||||
.is_some())
|
||||
{
|
||||
return self
|
||||
.bottom_pane_renderable(crate::bottom_pane::ComposerRenderOptions::default());
|
||||
@@ -236,7 +256,7 @@ impl ChatWidget {
|
||||
options.textarea_right_reserve = right_reserve;
|
||||
options.separate_status_line = options.command_popup_placement
|
||||
!= crate::bottom_pane::CommandPopupPlacement::AboveComposer;
|
||||
self.bottom_pane.as_renderable_with_options(options)
|
||||
self.bottom_pane.backdrop_with_options(options)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -289,7 +289,7 @@ impl ChatWidget {
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..SelectionViewParams::picker()
|
||||
..SelectionViewParams::confirmation()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ impl ChatWidget {
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..SelectionViewParams::picker()
|
||||
..SelectionViewParams::confirmation()
|
||||
});
|
||||
self.request_redraw();
|
||||
}
|
||||
@@ -291,7 +291,7 @@ impl ChatWidget {
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..SelectionViewParams::picker()
|
||||
..SelectionViewParams::confirmation()
|
||||
});
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
+22
-22
@@ -4,30 +4,30 @@ expression: "snapshots.join(\"\\n\\n\")"
|
||||
---
|
||||
specialty=None 40x10
|
||||
|
||||
Enable full access?
|
||||
When Codex runs with full access, it
|
||||
can edit any file on your computer
|
||||
and run commands with network,
|
||||
without your approval. Exercise
|
||||
[… 9 lines]
|
||||
› 1. Yes, continue anyway
|
||||
2. Cancel
|
||||
enter select · esc back
|
||||
Enable full access?
|
||||
When Codex runs with full
|
||||
access, it can edit any file on
|
||||
your computer and run commands
|
||||
with network, without your
|
||||
[… 10 lines]
|
||||
› 1. Yes, continue anyway
|
||||
2. Cancel
|
||||
enter select · esc back
|
||||
|
||||
specialty=Some("cyber") 40x16
|
||||
|
||||
Enable full access?
|
||||
Enable full access?
|
||||
|
||||
When Codex runs with full access, it
|
||||
can edit any file on your computer
|
||||
and run commands with network,
|
||||
without your approval.
|
||||
When Codex runs with full
|
||||
access, it can edit any file on
|
||||
your computer and run commands
|
||||
with network, without your
|
||||
approval.
|
||||
|
||||
Cyber models carry a higher risk of
|
||||
dangerous actions. Ensure proper
|
||||
safeguards are in place before
|
||||
granting full access. We strongly
|
||||
[… 13 lines]
|
||||
› 1. Yes, continue anyway
|
||||
2. Cancel
|
||||
enter select · esc back
|
||||
Cyber models carry a higher risk
|
||||
of dangerous actions. Ensure
|
||||
proper safeguards are in place
|
||||
[… 14 lines]
|
||||
› 1. Yes, continue anyway
|
||||
2. Cancel
|
||||
enter select · esc back
|
||||
|
||||
+14
-14
@@ -2,20 +2,20 @@
|
||||
source: tui/src/chatwidget/tests/popups_and_settings.rs
|
||||
expression: popup
|
||||
---
|
||||
Upload logs?
|
||||
|
||||
The following files will be sent:
|
||||
• codex-logs.log
|
||||
• client-logs.txt (if available)
|
||||
• codex-doctor-report.json
|
||||
• codex-apps-tools-cache.json (if available)
|
||||
• codex-app-directory-cache.json (if available)
|
||||
• auto-review-rollout-thread-1.jsonl
|
||||
• codex-connectivity-diagnostics.txt
|
||||
Upload logs?
|
||||
› As
|
||||
The following files will be sent:
|
||||
GP • codex-logs.log
|
||||
• client-logs.txt (if available)
|
||||
• codex-doctor-report.json
|
||||
• codex-apps-tools-cache.json (if available)
|
||||
• codex-app-directory-cache.json (if available)
|
||||
• auto-review-rollout-thread-1.jsonl
|
||||
• codex-connectivity-diagnostics.txt
|
||||
|
||||
|
||||
› 1. Yes Share the current Codex session logs and diagnostics with the team for
|
||||
troubleshooting
|
||||
2. No
|
||||
› 1. Yes Share the current Codex session logs and diagnostics with the
|
||||
team for troubleshooting
|
||||
2. No
|
||||
|
||||
enter select · esc back
|
||||
enter select · esc back
|
||||
|
||||
+18
-18
@@ -2,25 +2,25 @@
|
||||
source: tui/src/chatwidget/tests/popups_and_settings.rs
|
||||
expression: popup
|
||||
---
|
||||
Upload logs?
|
||||
Upload logs?
|
||||
› As
|
||||
The following files will be sent:
|
||||
GP • codex-logs.log
|
||||
• client-logs.txt (if available)
|
||||
• codex-doctor-report.json
|
||||
• codex-apps-tools-cache.json (if available)
|
||||
• codex-app-directory-cache.json (if available)
|
||||
• windows-sandbox.log
|
||||
• auto-review-rollout-thread-1.jsonl
|
||||
• codex-connectivity-diagnostics.txt
|
||||
|
||||
The following files will be sent:
|
||||
• codex-logs.log
|
||||
• client-logs.txt (if available)
|
||||
• codex-doctor-report.json
|
||||
• codex-apps-tools-cache.json (if available)
|
||||
• codex-app-directory-cache.json (if available)
|
||||
• windows-sandbox.log
|
||||
• auto-review-rollout-thread-1.jsonl
|
||||
• codex-connectivity-diagnostics.txt
|
||||
|
||||
Connectivity diagnostics
|
||||
- Proxy environment variables are set and may affect connectivity.
|
||||
- HTTPS_PROXY = hello
|
||||
Connectivity diagnostics
|
||||
- Proxy environment variables are set and may affect connectivity.
|
||||
- HTTPS_PROXY = hello
|
||||
|
||||
|
||||
› 1. Yes Share the current Codex session logs and diagnostics with the team for
|
||||
troubleshooting
|
||||
2. No
|
||||
› 1. Yes Share the current Codex session logs and diagnostics with the
|
||||
team for troubleshooting
|
||||
2. No
|
||||
|
||||
enter select · esc back
|
||||
enter select · esc back
|
||||
|
||||
+8
-8
@@ -2,14 +2,14 @@
|
||||
source: tui/src/chatwidget/tests/permissions.rs
|
||||
expression: popup
|
||||
---
|
||||
Enable full access?
|
||||
When Codex runs with full access, it can edit any file on your computer and
|
||||
run commands with network, without your approval. Exercise caution when
|
||||
enabling full access. This significantly increases the risk of data loss,
|
||||
leaks, or unexpected behavior.
|
||||
Enable full access?
|
||||
› As When Codex runs with full access, it can edit any file on your
|
||||
computer and run commands with network, without your approval.
|
||||
GP Exercise caution when enabling full access. This significantly
|
||||
increases the risk of data loss, leaks, or unexpected behavior.
|
||||
|
||||
|
||||
› 1. Yes, continue anyway Apply full access for this session
|
||||
2. Cancel Go back without enabling full access
|
||||
› 1. Yes, continue anyway Apply full access for this session
|
||||
2. Cancel Go back without enabling full access
|
||||
|
||||
enter select · esc back
|
||||
enter select · esc back
|
||||
|
||||
+8
-8
@@ -2,13 +2,13 @@
|
||||
source: tui/src/chatwidget/tests/popups_and_settings.rs
|
||||
expression: "render_bottom_popup(&chat, 80)"
|
||||
---
|
||||
Enable memories?
|
||||
Memories are disabled in this TUI session.
|
||||
Enable memories?
|
||||
› As Memories are disabled in this TUI session.
|
||||
|
||||
GP
|
||||
› 1. Yes, enable Save on the server for new threads without changing
|
||||
this thread
|
||||
2. Not now Keep memories disabled
|
||||
|
||||
› 1. Yes, enable Save on the server for new threads without changing this
|
||||
thread
|
||||
2. Not now Keep memories disabled
|
||||
|
||||
Learn more: https://developers.openai.com/codex/memories
|
||||
enter select · esc back
|
||||
Learn more: https://developers.openai.com/codex/memories
|
||||
enter select · esc back
|
||||
|
||||
+5
-4
@@ -2,8 +2,9 @@
|
||||
source: tui/src/chatwidget/tests/misalignment_policy_tests.rs
|
||||
expression: "render_bottom_popup(&chat, 38)"
|
||||
---
|
||||
Chat paused as a precaution
|
||||
Chat paused as a precaution
|
||||
›
|
||||
|
||||
|
||||
› 1. Acknowledge findings and continue
|
||||
2. Back
|
||||
› 1. Acknowledge findings and
|
||||
continue
|
||||
2. Back
|
||||
|
||||
+7
-7
@@ -2,12 +2,12 @@
|
||||
source: tui/src/chatwidget/tests/popups_and_settings.rs
|
||||
expression: "render_bottom_popup(&chat, 80)"
|
||||
---
|
||||
Enable subagents?
|
||||
Subagents are disabled in this TUI session.
|
||||
Enable subagents?
|
||||
› As Subagents are disabled in this TUI session.
|
||||
|
||||
GP
|
||||
› 1. Yes, enable Save on the server for new threads without changing
|
||||
this thread
|
||||
2. Not now Keep subagents disabled
|
||||
|
||||
› 1. Yes, enable Save on the server for new threads without changing this
|
||||
thread
|
||||
2. Not now Keep subagents disabled
|
||||
|
||||
enter select · esc back
|
||||
enter select · esc back
|
||||
|
||||
+8
-7
@@ -2,12 +2,13 @@
|
||||
source: tui/src/chatwidget/tests/popups_and_settings.rs
|
||||
expression: confirmation
|
||||
---
|
||||
Plugins
|
||||
Remove Repo Marketplace marketplace?
|
||||
This removes the configured marketplace from Codex.
|
||||
Plugins
|
||||
› Ask Codex to Remove Repo Marketplace marketplace?
|
||||
This removes the configured marketplace from Codex.
|
||||
GPT-5.6-Sol
|
||||
|
||||
› 1. Remove marketplace Remove this marketplace from the available
|
||||
plugin list
|
||||
2. Back to plugins Keep this marketplace installed
|
||||
|
||||
› 1. Remove marketplace Remove this marketplace from the available plugin list
|
||||
2. Back to plugins Keep this marketplace installed
|
||||
|
||||
enter select · esc close
|
||||
enter select · esc close
|
||||
|
||||
+8
-7
@@ -2,11 +2,12 @@
|
||||
source: tui/src/chatwidget/tests/usage.rs
|
||||
expression: "render_bottom_popup(&chat, 80)"
|
||||
---
|
||||
Use this reset?
|
||||
Full reset (Weekly + 5 hr) · Expires 09:39 on 18 Jun 2026
|
||||
Use this reset?
|
||||
Us Full reset (Weekly + 5 hr) · Expires 09:39 on 18 Jun 2026
|
||||
1
|
||||
|
||||
|
||||
1. Yes, use reset Reset your weekly and 5-hour usage limits.
|
||||
› 2. No, go back Choose a different reset
|
||||
|
||||
enter confirm · esc back
|
||||
1. Yes, use reset Reset your weekly and 5-hour usage limits.
|
||||
› 1.› 2. No, go back Choose a different reset
|
||||
2.
|
||||
enter confirm · esc back
|
||||
enter choose · esc back
|
||||
|
||||
+7
-6
@@ -2,11 +2,12 @@
|
||||
source: tui/src/chatwidget/tests/goal_menu.rs
|
||||
expression: "render_bottom_popup(&chat, 100)"
|
||||
---
|
||||
Resume paused goal?
|
||||
Goal: Keep improving the bare goal command until it feels calm and useful.
|
||||
Resume paused goal?
|
||||
› Ask Codex to Goal: Keep improving the bare goal command until it feels calm and
|
||||
useful.
|
||||
GPT-5.6-Sol
|
||||
|
||||
› 1. Resume goal Mark it active and continue when idle
|
||||
2. Leave paused Keep it paused; use /goal resume later
|
||||
|
||||
› 1. Resume goal Mark it active and continue when idle
|
||||
2. Leave paused Keep it paused; use /goal resume later
|
||||
|
||||
enter select · esc back
|
||||
enter select · esc back
|
||||
|
||||
+9
-9
@@ -2,14 +2,14 @@
|
||||
source: tui/src/chatwidget/tests/app_server.rs
|
||||
expression: "render_bottom_popup(&chat, 80)"
|
||||
---
|
||||
Stop this attempt and retry?
|
||||
• Wo Stop this attempt and retry?
|
||||
└ ry
|
||||
This will stop the current attempt and retry in a new thread. Any .
|
||||
file changes or other actions already taken will remain.
|
||||
|
||||
This will stop the current attempt and retry in a new thread. Any file
|
||||
changes or other actions already taken will remain.
|
||||
› As Your message will be sent again using Faster Model, which may be
|
||||
less capable on complex tasks.
|
||||
gp
|
||||
|
||||
Your message will be sent again using Faster Model, which may be less
|
||||
capable on complex tasks.
|
||||
|
||||
|
||||
› 1. Keep waiting
|
||||
2. Stop and retry
|
||||
› 1. Keep waiting
|
||||
2. Stop and retry
|
||||
|
||||
+11
-10
@@ -2,17 +2,18 @@
|
||||
source: tui/src/chatwidget/tests/app_server.rs
|
||||
expression: "render_bottom_popup(&chat, 40)"
|
||||
---
|
||||
Stop this attempt and retry?
|
||||
• Stop this attempt and retry?
|
||||
|
||||
This will stop the current attempt
|
||||
and retry in a new thread. Any file
|
||||
changes or other actions already
|
||||
taken will remain.
|
||||
This will stop the current
|
||||
attempt and retry in a new be
|
||||
thread. Any file changes or
|
||||
other actions already taken will
|
||||
remain.
|
||||
|
||||
Your message will be sent again
|
||||
using Faster Model, which may be
|
||||
less capable on complex tasks.
|
||||
› Your message will be sent again
|
||||
using Faster Model, which may be
|
||||
less capable on complex tasks.
|
||||
|
||||
|
||||
› 1. Keep waiting
|
||||
2. Stop and retry
|
||||
› 1. Keep waiting
|
||||
2. Stop and retry
|
||||
|
||||
+6
-6
@@ -2,11 +2,11 @@
|
||||
source: tui/src/chatwidget/tests/slash_commands.rs
|
||||
expression: popup
|
||||
---
|
||||
Archive this session?
|
||||
Are you sure? This will archive the current session
|
||||
Archive this session?
|
||||
› As Are you sure? This will archive the current session
|
||||
|
||||
GP
|
||||
› 1. No, don't archive Return to the current session
|
||||
2. Yes, archive Archive this session now
|
||||
|
||||
› 1. No, don't archive Return to the current session
|
||||
2. Yes, archive Archive this session now
|
||||
|
||||
enter select · esc back
|
||||
enter select · esc back
|
||||
|
||||
+8
-7
@@ -2,12 +2,13 @@
|
||||
source: tui/src/chatwidget/tests/slash_commands.rs
|
||||
expression: popup
|
||||
---
|
||||
Delete this session?
|
||||
Cannot be undone. Subagent threads will also be deleted.
|
||||
Delete this session?
|
||||
› As Cannot be undone. Subagent threads will also be deleted.
|
||||
|
||||
GP
|
||||
› 1. No, keep this session Return to the current
|
||||
session
|
||||
2. Yes, delete and return to command center Permanently delete this
|
||||
session now
|
||||
|
||||
› 1. No, keep this session Return to the current session
|
||||
2. Yes, delete and return to command center Permanently delete this session
|
||||
now
|
||||
|
||||
enter select · esc back
|
||||
enter select · esc back
|
||||
|
||||
+6
-6
@@ -2,11 +2,11 @@
|
||||
source: tui/src/chatwidget/tests/slash_commands.rs
|
||||
expression: popup
|
||||
---
|
||||
Delete this session?
|
||||
Cannot be undone. Subagent threads will also be deleted.
|
||||
Delete this session?
|
||||
› As Cannot be undone. Subagent threads will also be deleted.
|
||||
|
||||
GP
|
||||
› 1. No, keep this session Return to the current session
|
||||
2. Yes, delete and exit Permanently delete this session now
|
||||
|
||||
› 1. No, keep this session Return to the current session
|
||||
2. Yes, delete and exit Permanently delete this session now
|
||||
|
||||
enter select · esc back
|
||||
enter select · esc back
|
||||
|
||||
+6
-6
@@ -2,11 +2,11 @@
|
||||
source: tui/src/chatwidget/tests/worktree_picker_tests.rs
|
||||
expression: "render_bottom_popup(&chat, 80)"
|
||||
---
|
||||
Delete this worktree?
|
||||
/repo/worktree
|
||||
Delete this worktree?
|
||||
› As /repo/worktree
|
||||
|
||||
GP
|
||||
› 1. Cancel
|
||||
2. Delete worktree Keeps thread history; may disrupt other sessions
|
||||
|
||||
› 1. Cancel
|
||||
2. Delete worktree Keeps thread history; may disrupt other sessions
|
||||
|
||||
enter select · esc back
|
||||
enter select · esc back
|
||||
|
||||
@@ -267,6 +267,8 @@ async fn misalignment_review_requires_current_findings() {
|
||||
chat.on_misalignment_error(Some("turn-1".to_string()), Some(review_details()));
|
||||
let review = select_review(&mut chat, &mut rx);
|
||||
chat.show_misalignment_review_confirmation(Arc::clone(&review));
|
||||
chat.bottom_pane
|
||||
.set_status_line(Some("test project".into()));
|
||||
assert_chatwidget_snapshot!(
|
||||
"misalignment_review_narrow",
|
||||
render_bottom_popup(&chat, /*width*/ 38)
|
||||
|
||||
@@ -272,7 +272,7 @@ impl ChatWidget {
|
||||
on_cancel: Some(Box::new(move |_| {
|
||||
confirmation_gate.store(true, Ordering::Release);
|
||||
})),
|
||||
..SelectionViewParams::picker()
|
||||
..SelectionViewParams::confirmation()
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ impl ChatWidget {
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..SelectionViewParams::picker()
|
||||
..SelectionViewParams::confirmation()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
source: tui/src/app/tests.rs
|
||||
expression: "render_bottom_popup(&app.chat_widget, 80)"
|
||||
---
|
||||
Replace goal?
|
||||
New objective: New goal
|
||||
Replace goal?
|
||||
› As New objective: New goal
|
||||
|
||||
GP
|
||||
› 1. Replace current goal Set the new objective and start it now
|
||||
2. Cancel Keep the current goal
|
||||
|
||||
› 1. Replace current goal Set the new objective and start it now
|
||||
2. Cancel Keep the current goal
|
||||
|
||||
enter select · esc back
|
||||
enter select · esc back
|
||||
|
||||
Reference in New Issue
Block a user