Keep command center errors visible and preserve drafts (#44651)

## Why

Command center failures need to remain visible while users retain their task selection and composer draft, including when a task is already managed by another app server.

## What changed

- Show action errors above the retained dashboard with a return option and a clear explanation for tasks open elsewhere.
- Keep the dashboard open until task attachment succeeds, and return to the conversation when selecting the already displayed task.
- Offer “View unsent task” in a pager when restoring a failed submission would overwrite newer input.
- Show task refresh failures inline and clear the notice after a successful refresh.

## Testing

Add regression tests and snapshots for attachment conflicts, preserved selection and drafts, successful attachment, inline refresh recovery, unsent task viewing, and visible action failures.

GitOrigin-RevId: 19670c5ea506414a835296141004db567b0cafef
This commit is contained in:
Eric Traut
2026-09-10 21:42:01 +00:00
committed by copyberry
parent 1aaa453ce2
commit db2e09106c
14 changed files with 411 additions and 65 deletions
@@ -0,0 +1,61 @@
//! Reports command-center action failures above the retained dashboard, with access
//! to unsent drafts when restoring them would overwrite newer input.
use super::*;
use crate::wrapping::word_wrap_lines;
use ratatui::buffer::Buffer;
struct AgentsOverviewErrorHeader(Vec<Line<'static>>);
impl Renderable for AgentsOverviewErrorHeader {
fn render(&self, area: Rect, buf: &mut Buffer) {
Renderable::render(
&Paragraph::new(word_wrap_lines(&self.0, usize::from(area.width))),
area,
buf,
);
}
fn desired_height(&self, width: u16) -> u16 {
word_wrap_lines(&self.0, usize::from(width)).len() as u16
}
}
impl App {
pub(in crate::app) fn add_agents_overview_error(&mut self, message: String) {
let unsent_prompt = self.agents_overview.unsent_prompt.take();
if self
.chat_widget
.selected_index_for_present_view(AGENTS_OVERVIEW_VIEW_ID)
.is_some()
{
let mut items = vec![SelectionItem {
name: "Return to command center".to_string(),
dismiss_on_select: true,
..Default::default()
}];
if let Some(text) = unsent_prompt {
items.push(SelectionItem {
name: "View unsent task".to_string(),
description: Some(
"Your newer draft has been kept in the composer.".to_string(),
),
actions: vec![Box::new(move |tx| {
tx.send(AppEvent::ViewAgentsOverviewUnsentPrompt(text.clone()));
})],
dismiss_on_select: false,
..Default::default()
});
}
self.chat_widget.show_selection_view(SelectionViewParams {
header: Box::new(AgentsOverviewErrorHeader(vec![
Line::from("Unable to complete action".bold()),
Line::from(message.clone().dim()),
])),
items,
..Default::default()
});
}
self.chat_widget.add_error_message(message);
}
}