mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
## Why Losing an external app-server connection can leave a submission's outcome uncertain. The TUI must preserve local work without retrying it or allowing stale server events to trigger more actions. ## What changed - Enter an offline state when an external app-server transport disconnects during startup, event streaming, or submission. - Keep conversation drafts, queued input, expanded pastes, attachments, and agent-overview input editable while blocking submissions, remote actions, and automatic queue replay. - Show a connection-lost notice that directs users to copy their work and relaunch, while retaining `Ctrl-C` and an empty-composer `Ctrl-D` as quit shortcuts. - Cancel pending views and asynchronous work, and ignore stale completions after the disconnect. Embedded app-server behavior is unchanged. ## Testing Added unit, snapshot, and pseudo-terminal coverage for lost startup and mutation replies, offline draft editing, queued-input recovery, stale-event quarantine, agent-overview behavior, and end-to-end preservation without reconnection or resubmission. GitOrigin-RevId: 7219a95fad676dda447d625db9e465345b469d58
61 lines
2.2 KiB
Rust
61 lines
2.2 KiB
Rust
//! Offline edits retain the same attachment bookkeeping as ordinary text edits.
|
|
|
|
use super::super::tests::new_test_composer;
|
|
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
#[test]
|
|
fn reconnect_expands_pastes_preserving_images_and_cursor() {
|
|
let (mut composer, _) = new_test_composer();
|
|
let paste = "x".repeat(LARGE_PASTE_CHAR_THRESHOLD + 1);
|
|
composer.handle_paste(paste.clone());
|
|
composer.attach_image(PathBuf::from("local.png"));
|
|
composer.handle_paste(paste);
|
|
let expanded = composer.current_text_with_pending();
|
|
let images = composer.draft_snapshot().local_images;
|
|
composer.handle_disconnected_key(KeyEvent::new(KeyCode::Char('!'), KeyModifiers::NONE));
|
|
let draft = composer.draft_snapshot();
|
|
assert_eq!(
|
|
(
|
|
draft.text,
|
|
draft.cursor,
|
|
draft.local_images,
|
|
draft.pending_pastes
|
|
),
|
|
(
|
|
format!("{expanded}!"),
|
|
expanded.len() + 1,
|
|
images,
|
|
Vec::new()
|
|
)
|
|
);
|
|
assert_eq!(draft.text_elements.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn reconnect_edit_removes_deleted_attachment_and_paste_metadata() {
|
|
let (mut composer, _) = new_test_composer();
|
|
composer.attach_image(PathBuf::from("local.png"));
|
|
composer.handle_paste("x".repeat(LARGE_PASTE_CHAR_THRESHOLD + 1));
|
|
composer.handle_disconnected_key(KeyEvent::new(KeyCode::Char('u'), KeyModifiers::CONTROL));
|
|
let draft = composer.draft_snapshot();
|
|
assert_eq!(
|
|
(draft.text, draft.local_images, draft.pending_pastes),
|
|
(String::new(), Vec::new(), Vec::new())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn reconnect_edit_cancels_history_preview_without_losing_original_draft() {
|
|
let (mut composer, _) = new_test_composer();
|
|
composer.set_text_content("original draft".into(), Vec::new(), Vec::new());
|
|
composer.draft.textarea.set_cursor(/*pos*/ 14);
|
|
composer.begin_history_search();
|
|
composer.apply_history_search_result(HistorySearchResult::Found(HistoryEntry::new(
|
|
"history preview".into(),
|
|
)));
|
|
composer.handle_disconnected_key(KeyEvent::new(KeyCode::Char('!'), KeyModifiers::NONE));
|
|
assert_eq!(composer.current_text(), "original draft!");
|
|
assert!(!composer.history_search_active());
|
|
}
|