fix: fence pending queue controls

This commit is contained in:
2026-09-05 23:16:19 +09:00
parent bb56283063
commit 5b0a6691f8
11 changed files with 347 additions and 68 deletions
+43
View File
@@ -754,6 +754,28 @@ impl App {
Some(self.method_for_run(segments))
}
pub fn submit_notify_input(&mut self) -> Option<Method> {
let segments = self.input.submit_segments();
if segments_are_blank(&segments) {
return None;
}
if segments
.iter()
.any(|segment| matches!(segment, Segment::UploadedFile { .. }))
{
self.push_error("Notify accepts text only; remove attachments or queue a Submit.");
return None;
}
let message = Segment::flatten_to_text(&segments);
self.record_input_history(segments);
self.input.clear();
Some(Method::Notify {
notification_request_id: protocol::new_submission_request_id(),
message,
auto_run: true,
})
}
pub fn restore_unsent_run(&mut self, method: &Method) {
let Method::Submit { input, .. } = method else {
return;
@@ -890,6 +912,26 @@ impl App {
}
}
pub fn continue_pending_method(&self) -> Option<Method> {
Some(Method::ContinuePending {
expected_revision: self.pending_submissions.revision,
expected_head_id: self.pending_submissions.head_id.clone()?,
})
}
pub fn clear_pending_method(&self) -> Method {
Method::ClearPendingSubmissions {
expected_revision: self.pending_submissions.revision,
}
}
pub fn cancel_pending_method(&self, submission_id: String) -> Method {
Method::CancelPendingSubmission {
submission_id,
expected_revision: self.pending_submissions.revision,
}
}
pub fn next_queued_input_preview(&self) -> Option<&str> {
self.pending_submissions
.submissions
@@ -3410,6 +3452,7 @@ mod completion_flow_tests {
pending: protocol::PendingSubmissionsSnapshot {
revision: 3,
notification_count: 0,
head_id: Some("submission-1".into()),
submissions: vec![protocol::PendingSubmissionSummary {
submission_id: "submission-1".into(),
accepted_at_ms: 7,
+66 -4
View File
@@ -1150,13 +1150,27 @@ fn handle_key(app: &mut App, key: KeyEvent) -> Option<Method> {
app.clear_command_input();
Some(None)
}
KeyCode::Char(c)
if c.eq_ignore_ascii_case(&'d') && alt && !ctrl && !app.is_command_mode() =>
{
Some(
app.next_queued_input_preview()
.map(str::to_owned)
.map(|submission_id| app.cancel_pending_method(submission_id)),
)
}
KeyCode::Char(c)
if c.eq_ignore_ascii_case(&'n') && alt && !ctrl && !app.is_command_mode() =>
{
Some(app.submit_notify_input())
}
KeyCode::Char(c)
if c.eq_ignore_ascii_case(&'q') && alt && !ctrl && !app.is_command_mode() =>
{
Some(Some(Method::ContinuePending))
Some(app.continue_pending_method())
}
KeyCode::Char(c) if c.eq_ignore_ascii_case(&'c') && alt && !ctrl => {
Some(Some(Method::ClearPendingSubmissions))
Some(Some(app.clear_pending_method()))
}
KeyCode::Char('c') if ctrl => Some(handle_pause_or_quit(app)),
KeyCode::Char('x') if ctrl => Some(handle_cancel_or_shutdown(app)),
@@ -1976,6 +1990,29 @@ mod tests {
assert_eq!(input_text(&app), "");
}
#[test]
fn running_alt_n_sends_explicit_notify_without_implicit_submit_conversion() {
let mut app = App::new("test".into());
app.set_worker_status(WorkerStatus::Running);
for character in "progress".chars() {
app.insert_char(character);
}
let method = handle_key(
&mut app,
KeyEvent::new(KeyCode::Char('n'), KeyModifiers::ALT),
);
assert!(matches!(
method,
Some(Method::Notify {
ref message,
auto_run: true,
..
}) if message == "progress"
));
assert_eq!(input_text(&app), "");
}
#[test]
fn pending_queue_shortcuts_send_worker_operations() {
let mut app = App::new("test".into());
@@ -1983,6 +2020,7 @@ mod tests {
pending: protocol::PendingSubmissionsSnapshot {
revision: 2,
notification_count: 0,
head_id: Some("submission-1".into()),
submissions: vec![protocol::PendingSubmissionSummary {
submission_id: "submission-1".into(),
accepted_at_ms: 1,
@@ -1996,14 +2034,37 @@ mod tests {
&mut app,
KeyEvent::new(KeyCode::Char('q'), KeyModifiers::ALT),
);
assert!(matches!(continue_next, Some(Method::ContinuePending)));
assert!(matches!(
continue_next,
Some(Method::ContinuePending {
expected_revision: 2,
ref expected_head_id,
}) if expected_head_id == "submission-1"
));
assert_eq!(app.queued_input_count(), 1);
let cancel = handle_key(
&mut app,
KeyEvent::new(KeyCode::Char('d'), KeyModifiers::ALT),
);
assert!(matches!(
cancel,
Some(Method::CancelPendingSubmission {
expected_revision: 2,
ref submission_id,
}) if submission_id == "submission-1"
));
let clear = handle_key(
&mut app,
KeyEvent::new(KeyCode::Char('c'), KeyModifiers::ALT),
);
assert!(matches!(clear, Some(Method::ClearPendingSubmissions)));
assert!(matches!(
clear,
Some(Method::ClearPendingSubmissions {
expected_revision: 2
})
));
assert_eq!(app.queued_input_count(), 1);
}
@@ -2014,6 +2075,7 @@ mod tests {
pending: protocol::PendingSubmissionsSnapshot {
revision: 2,
notification_count: 0,
head_id: Some("submission-1".into()),
submissions: vec![protocol::PendingSubmissionSummary {
submission_id: "submission-1".into(),
accepted_at_ms: 1,
+3 -2
View File
@@ -1880,7 +1880,7 @@ fn actionbar_left_item(app: &App, now: Instant) -> Option<(String, Style)> {
}
if app.queued_input_count() > 0 {
return Some((
"Alt-q continue queued Alt-c clear queued".to_string(),
"Alt-n notify Alt-q continue Alt-d cancel queued Alt-c clear queued".to_string(),
Style::default().fg(Color::DarkGray),
));
}
@@ -2144,6 +2144,7 @@ mod tests {
pending: protocol::PendingSubmissionsSnapshot {
revision: 1,
notification_count: 0,
head_id: Some(id.into()),
submissions: vec![protocol::PendingSubmissionSummary {
submission_id: id.into(),
accepted_at_ms: 1,
@@ -2303,7 +2304,7 @@ mod tests {
set_pending_submission(&mut app, "submission-1");
assert_eq!(
actionbar_left_item(&app, now).map(|(text, _)| text),
Some("Alt-q continue queued Alt-c clear queued".into())
Some("Alt-n notify Alt-q continue Alt-d cancel queued Alt-c clear queued".into())
);
app.enter_command_mode();