Protect startup drafts through first render

This commit is contained in:
Charlie Marsh
2026-07-05 21:24:59 -04:00
parent ed333328a1
commit 44cd6c8ccb
5 changed files with 29 additions and 2 deletions
+1
View File
@@ -1311,6 +1311,7 @@ See the Codex keymap documentation for supported actions and examples."
// Allow widgets to process any pending timers before rendering.
self.chat_widget.pre_draw_tick();
let rendered_area = self.render_chat_widget_frame(tui)?;
self.chat_widget.mark_startup_draft_rendered();
if self.chat_widget.ambient_pet_image_enabled() {
let terminal_size = tui.terminal.size()?;
let ambient_pet_area = Rect::new(
+13
View File
@@ -204,6 +204,12 @@ async fn restored_startup_draft_waits_for_effective_mcp_settlement() {
}),
));
app.chat_widget.handle_key_event(enter);
assert_eq!(
app.chat_widget.composer_text_with_pending(),
"captured during startup"
);
app.chat_widget.mark_startup_draft_rendered();
app.chat_widget.handle_key_event(enter);
match next_user_turn_op(&mut op_rx) {
Op::UserTurn { items, .. } => assert_eq!(
@@ -255,6 +261,13 @@ async fn restored_startup_draft_loads_mcp_names_from_older_app_servers() {
.expect("older app-server startup should attach");
app.chat_widget
.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
assert_eq!(
app.chat_widget.composer_text_with_pending(),
"captured during startup"
);
app.chat_widget.mark_startup_draft_rendered();
app.chat_widget
.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
match next_user_turn_op(&mut op_rx) {
Op::UserTurn { items, .. } => assert_eq!(
+3 -1
View File
@@ -601,8 +601,10 @@ pub(crate) struct ChatWidget {
/// Initial MCP servers that must settle before a restored startup draft can be submitted.
///
/// `Some(_)` also records that startup text was actually restored. The set may become empty
/// before session configuration arrives, but protection remains until both conditions settle.
/// before session configuration arrives, but protection remains until startup has rendered and
/// both conditions settle.
startup_draft_pending_mcp_servers: Option<HashSet<String>>,
startup_draft_rendered: bool,
/// After startup settles, ignore stale updates until enough notifications confirm a new round.
mcp_startup_ignore_updates_until_next_start: bool,
/// A lag signal for the next round means terminal-only updates are enough to settle it.
@@ -160,6 +160,7 @@ impl ChatWidget {
mcp_startup_status: None,
mcp_startup_expected_servers: None,
startup_draft_pending_mcp_servers: None,
startup_draft_rendered: false,
mcp_startup_ignore_updates_until_next_start: false,
mcp_startup_allow_terminal_only_next_round: false,
mcp_startup_pending_next_round: HashMap::new(),
+11 -1
View File
@@ -10,6 +10,7 @@ impl ChatWidget {
pub(crate) fn restore_startup_draft(&mut self, text: &str) {
self.insert_str(text);
self.startup_draft_pending_mcp_servers = Some(HashSet::new());
self.startup_draft_rendered = false;
self.bottom_pane
.set_startup_draft_submission_blocked(/*blocked*/ true);
}
@@ -30,7 +31,8 @@ impl ChatWidget {
}
pub(super) fn finish_startup_draft_protection_if_ready(&mut self) {
let startup_ready = self.is_session_configured()
let startup_ready = self.startup_draft_rendered
&& self.is_session_configured()
&& self
.startup_draft_pending_mcp_servers
.as_ref()
@@ -42,11 +44,19 @@ impl ChatWidget {
pub(super) fn clear_startup_draft_protection(&mut self) {
if self.startup_draft_pending_mcp_servers.take().is_some() {
self.startup_draft_rendered = false;
self.bottom_pane
.set_startup_draft_submission_blocked(/*blocked*/ false);
}
}
pub(crate) fn mark_startup_draft_rendered(&mut self) {
if self.startup_draft_pending_mcp_servers.is_some() {
self.startup_draft_rendered = true;
self.finish_startup_draft_protection_if_ready();
}
}
pub(super) fn handle_composer_input_result(
&mut self,
input_result: InputResult,