tui: clarify panel composer target and row selection
This commit is contained in:
parent
f13ab29456
commit
c5ef6f794f
|
|
@ -516,13 +516,6 @@ fn commit_intake_registry_update(update: IntakeRegistryUpdate) -> Option<String>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
enum PanelFocus {
|
|
||||||
GlobalComposer,
|
|
||||||
Row,
|
|
||||||
ItemAction,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
struct PanelDiagnostic {
|
struct PanelDiagnostic {
|
||||||
title: String,
|
title: String,
|
||||||
|
|
@ -534,7 +527,6 @@ pub(crate) struct MultiPodApp {
|
||||||
pub(crate) panel: WorkspacePanelViewModel,
|
pub(crate) panel: WorkspacePanelViewModel,
|
||||||
pub(crate) input: InputBuffer,
|
pub(crate) input: InputBuffer,
|
||||||
selected_row: Option<PanelRowKey>,
|
selected_row: Option<PanelRowKey>,
|
||||||
focus: PanelFocus,
|
|
||||||
composer_target: ComposerTarget,
|
composer_target: ComposerTarget,
|
||||||
notice: Option<String>,
|
notice: Option<String>,
|
||||||
panel_diagnostic: Option<PanelDiagnostic>,
|
panel_diagnostic: Option<PanelDiagnostic>,
|
||||||
|
|
@ -566,7 +558,6 @@ impl MultiPodApp {
|
||||||
panel,
|
panel,
|
||||||
input: InputBuffer::new(),
|
input: InputBuffer::new(),
|
||||||
selected_row: None,
|
selected_row: None,
|
||||||
focus: PanelFocus::GlobalComposer,
|
|
||||||
composer_target: ComposerTarget::Companion,
|
composer_target: ComposerTarget::Companion,
|
||||||
notice: None,
|
notice: None,
|
||||||
panel_diagnostic: None,
|
panel_diagnostic: None,
|
||||||
|
|
@ -728,7 +719,7 @@ impl MultiPodApp {
|
||||||
.clone()
|
.clone()
|
||||||
.or_else(|| row.key_hint.clone())
|
.or_else(|| row.key_hint.clone())
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
"Enter dispatches this Ticket action; Right marks action focus; stale Tickets are re-checked before any mutation."
|
"Enter dispatches this Ticket action after re-checking current Ticket authority."
|
||||||
.to_string()
|
.to_string()
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -822,37 +813,11 @@ impl MultiPodApp {
|
||||||
self.list.selected_name = Some(name.clone());
|
self.list.selected_name = Some(name.clone());
|
||||||
}
|
}
|
||||||
self.selected_row = Some(key);
|
self.selected_row = Some(key);
|
||||||
self.focus = PanelFocus::Row;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_panel_focus(&mut self) {
|
fn clear_panel_selection(&mut self) {
|
||||||
self.selected_row = None;
|
self.selected_row = None;
|
||||||
self.list.selected_name = None;
|
self.list.selected_name = None;
|
||||||
self.focus = PanelFocus::GlobalComposer;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn effective_focus(&self) -> PanelFocus {
|
|
||||||
if self.selected_row.is_none() {
|
|
||||||
PanelFocus::GlobalComposer
|
|
||||||
} else {
|
|
||||||
self.focus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_item_action(&mut self) {
|
|
||||||
if self.selected_row.is_some() {
|
|
||||||
self.focus = PanelFocus::ItemAction;
|
|
||||||
} else {
|
|
||||||
self.notice = Some("No row selected; use ↑/↓ to select a row first.".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_selected_row(&mut self) {
|
|
||||||
if self.selected_row.is_some() {
|
|
||||||
self.focus = PanelFocus::Row;
|
|
||||||
} else {
|
|
||||||
self.focus = PanelFocus::GlobalComposer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_composer_target_available(&mut self) {
|
fn ensure_composer_target_available(&mut self) {
|
||||||
|
|
@ -1334,15 +1299,16 @@ impl MultiPodApp {
|
||||||
KeyCode::Char('d') if ctrl => MultiPodAction::Quit,
|
KeyCode::Char('d') if ctrl => MultiPodAction::Quit,
|
||||||
KeyCode::Char('c') if ctrl => MultiPodAction::Quit,
|
KeyCode::Char('c') if ctrl => MultiPodAction::Quit,
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
self.clear_panel_focus();
|
self.clear_panel_selection();
|
||||||
self.notice = Some("Focus: global composer target; Ctrl+C quits.".to_string());
|
self.notice = Some(
|
||||||
|
"Row selection cleared; composer draft and target are unchanged.".to_string(),
|
||||||
|
);
|
||||||
MultiPodAction::None
|
MultiPodAction::None
|
||||||
}
|
}
|
||||||
KeyCode::Tab => {
|
KeyCode::Tab => {
|
||||||
// Completion owns Tab before panel target switching when a
|
// Completion owns Tab before panel target switching when a
|
||||||
// completion popup exists. The workspace panel currently has
|
// completion popup exists. The workspace panel currently has
|
||||||
// no completion source, so this is the target switch path.
|
// no completion source, so this is the target switch path.
|
||||||
self.clear_panel_focus();
|
|
||||||
self.cycle_composer_target();
|
self.cycle_composer_target();
|
||||||
MultiPodAction::None
|
MultiPodAction::None
|
||||||
}
|
}
|
||||||
|
|
@ -1354,22 +1320,6 @@ impl MultiPodApp {
|
||||||
self.select_next();
|
self.select_next();
|
||||||
MultiPodAction::None
|
MultiPodAction::None
|
||||||
}
|
}
|
||||||
KeyCode::Left
|
|
||||||
if self.composer_is_blank() && self.effective_focus() == PanelFocus::ItemAction =>
|
|
||||||
{
|
|
||||||
self.focus_selected_row();
|
|
||||||
MultiPodAction::None
|
|
||||||
}
|
|
||||||
KeyCode::Left
|
|
||||||
if self.composer_is_blank() && self.effective_focus() == PanelFocus::Row =>
|
|
||||||
{
|
|
||||||
self.clear_panel_focus();
|
|
||||||
MultiPodAction::None
|
|
||||||
}
|
|
||||||
KeyCode::Right if self.composer_is_blank() => {
|
|
||||||
self.focus_item_action();
|
|
||||||
MultiPodAction::None
|
|
||||||
}
|
|
||||||
KeyCode::Enter
|
KeyCode::Enter
|
||||||
if self.composer_is_blank()
|
if self.composer_is_blank()
|
||||||
&& self.selected_ticket_action() == Some(NextUserAction::Clarify) =>
|
&& self.selected_ticket_action() == Some(NextUserAction::Clarify) =>
|
||||||
|
|
@ -1385,11 +1335,11 @@ impl MultiPodApp {
|
||||||
.map(MultiPodAction::DispatchTicketAction)
|
.map(MultiPodAction::DispatchTicketAction)
|
||||||
.unwrap_or(MultiPodAction::None)
|
.unwrap_or(MultiPodAction::None)
|
||||||
}
|
}
|
||||||
|
KeyCode::Enter if self.composer_is_blank() => MultiPodAction::Open,
|
||||||
KeyCode::Enter if self.composer_target == ComposerTarget::TicketIntake => self
|
KeyCode::Enter if self.composer_target == ComposerTarget::TicketIntake => self
|
||||||
.prepare_intake_launch()
|
.prepare_intake_launch()
|
||||||
.map(MultiPodAction::LaunchIntake)
|
.map(MultiPodAction::LaunchIntake)
|
||||||
.unwrap_or(MultiPodAction::None),
|
.unwrap_or(MultiPodAction::None),
|
||||||
KeyCode::Enter if self.composer_is_blank() => MultiPodAction::Open,
|
|
||||||
KeyCode::Enter => self
|
KeyCode::Enter => self
|
||||||
.prepare_companion_send()
|
.prepare_companion_send()
|
||||||
.map(MultiPodAction::SendCompanion)
|
.map(MultiPodAction::SendCompanion)
|
||||||
|
|
@ -3214,8 +3164,7 @@ fn open_disabled_reason(entry: &PodListEntry) -> String {
|
||||||
}
|
}
|
||||||
return match live.status {
|
return match live.status {
|
||||||
Some(PodStatus::Running) => {
|
Some(PodStatus::Running) => {
|
||||||
"Selected Pod is running; Enter opens/attaches; Right marks action focus."
|
"Selected Pod is running; Enter opens/attaches for inspection.".to_string()
|
||||||
.to_string()
|
|
||||||
}
|
}
|
||||||
Some(PodStatus::Paused) => {
|
Some(PodStatus::Paused) => {
|
||||||
"Selected Pod is paused; open it explicitly to resume or start a new turn."
|
"Selected Pod is paused; open it explicitly to resume or start a new turn."
|
||||||
|
|
@ -3226,8 +3175,7 @@ fn open_disabled_reason(entry: &PodListEntry) -> String {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if entry.stored.is_some() {
|
if entry.stored.is_some() {
|
||||||
return "Selected Pod is stopped; Enter restores/opens; Right marks action focus."
|
return "Selected Pod is stopped; Enter restores/opens for inspection.".to_string();
|
||||||
.to_string();
|
|
||||||
}
|
}
|
||||||
entry
|
entry
|
||||||
.actions
|
.actions
|
||||||
|
|
@ -3241,7 +3189,7 @@ fn selected_ticket_notice(row: Option<&PanelRow>) -> String {
|
||||||
Some(row) if row.is_ticket_action() => {
|
Some(row) if row.is_ticket_action() => {
|
||||||
let action = row.next_action.map(NextUserAction::label).unwrap_or("View");
|
let action = row.next_action.map(NextUserAction::label).unwrap_or("View");
|
||||||
format!(
|
format!(
|
||||||
"Enter dispatches {action} for Ticket '{}' after re-checking current Ticket authority; Right marks action focus.",
|
"Enter dispatches {action} for Ticket '{}' after re-checking current Ticket authority.",
|
||||||
row.title
|
row.title
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -3509,11 +3457,11 @@ fn draw_title(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) {
|
||||||
.composer
|
.composer
|
||||||
.is_available(ComposerTarget::TicketIntake)
|
.is_available(ComposerTarget::TicketIntake)
|
||||||
{
|
{
|
||||||
" Row focus: Enter dispatches row action · Right action focus · Tab target"
|
" Row selection: blank Enter opens/dispatches · text Enter uses target · Tab target"
|
||||||
} else if app.panel.header.ticket_configured {
|
} else if app.panel.header.ticket_configured {
|
||||||
" Row focus: Enter opens/dispatches · Right action focus"
|
" Row selection: blank Enter opens/dispatches · text Enter sends to Companion"
|
||||||
} else {
|
} else {
|
||||||
" Pod-centric view · Row focus: Enter opens · Right action focus"
|
" Pod-centric view · Row selection: blank Enter opens · text Enter sends to Companion"
|
||||||
};
|
};
|
||||||
let mut spans = vec![
|
let mut spans = vec![
|
||||||
Span::styled(
|
Span::styled(
|
||||||
|
|
@ -3850,73 +3798,71 @@ fn draw_target_status(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) {
|
||||||
fn target_status_line(app: &MultiPodApp) -> Line<'static> {
|
fn target_status_line(app: &MultiPodApp) -> Line<'static> {
|
||||||
if !app.composer_is_blank() {
|
if !app.composer_is_blank() {
|
||||||
return Line::from(vec![
|
return Line::from(vec![
|
||||||
Span::styled("focus ", Style::default().fg(Color::DarkGray)),
|
Span::styled("composer target ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled("global composer", Style::default().fg(Color::Cyan)),
|
|
||||||
Span::styled(" · composer ", Style::default().fg(Color::DarkGray)),
|
|
||||||
Span::styled(
|
Span::styled(
|
||||||
app.composer_target().label(),
|
app.composer_target().label(),
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(Color::Green)
|
.fg(Color::Green)
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
),
|
),
|
||||||
Span::styled(" · Enter ", Style::default().fg(Color::DarkGray)),
|
Span::styled(" · draft Enter ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(
|
Span::styled(
|
||||||
composer_enter_status_text(app),
|
composer_enter_status_text(app),
|
||||||
Style::default().fg(Color::Green),
|
Style::default().fg(Color::Green),
|
||||||
),
|
),
|
||||||
|
Span::styled(
|
||||||
|
" · row selection waits until composer is blank",
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let focus_label = match app.effective_focus() {
|
|
||||||
PanelFocus::GlobalComposer => "global composer",
|
|
||||||
PanelFocus::Row => "selected row",
|
|
||||||
PanelFocus::ItemAction => "item action",
|
|
||||||
};
|
|
||||||
if let Some(row) = app
|
if let Some(row) = app
|
||||||
.selected_panel_row()
|
.selected_panel_row()
|
||||||
.filter(|row| row.is_ticket_action())
|
.filter(|row| row.is_ticket_action())
|
||||||
{
|
{
|
||||||
let action = row.next_action.map(NextUserAction::label).unwrap_or("View");
|
let action = row.next_action.map(NextUserAction::label).unwrap_or("View");
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled("focus ", Style::default().fg(Color::DarkGray)),
|
Span::styled("composer target ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(focus_label, Style::default().fg(Color::Cyan)),
|
|
||||||
Span::styled(" · composer ", Style::default().fg(Color::DarkGray)),
|
|
||||||
Span::styled(
|
Span::styled(
|
||||||
app.composer_target().label(),
|
app.composer_target().label(),
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(Color::Magenta)
|
.fg(Color::Magenta)
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
),
|
),
|
||||||
Span::styled(" · ticket ", Style::default().fg(Color::DarkGray)),
|
Span::styled(" · selected Ticket ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(row.status.clone(), panel_priority_style(row.priority)),
|
Span::styled(row.status.clone(), panel_priority_style(row.priority)),
|
||||||
Span::styled(" · action ", Style::default().fg(Color::DarkGray)),
|
Span::styled(" · blank Enter ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(action, Style::default().fg(Color::Magenta)),
|
Span::styled(action, Style::default().fg(Color::Magenta)),
|
||||||
])
|
])
|
||||||
} else if let Some(entry) = app.selected_pod_entry() {
|
} else if let Some(entry) = app.selected_pod_entry() {
|
||||||
let (status, status_style) = row_status_label(entry);
|
let (status, status_style) = row_status_label(entry);
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled("focus ", Style::default().fg(Color::DarkGray)),
|
Span::styled("composer target ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(focus_label, Style::default().fg(Color::Cyan)),
|
|
||||||
Span::styled(" · composer ", Style::default().fg(Color::DarkGray)),
|
|
||||||
Span::styled(
|
Span::styled(
|
||||||
app.composer_target().label(),
|
app.composer_target().label(),
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(Color::Green)
|
.fg(Color::Green)
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
),
|
),
|
||||||
Span::styled(" · pod ", Style::default().fg(Color::DarkGray)),
|
Span::styled(" · selected Pod ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(status.to_string(), status_style),
|
Span::styled(status.to_string(), status_style),
|
||||||
|
Span::styled(
|
||||||
|
" · blank Enter open/attach",
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled("focus ", Style::default().fg(Color::DarkGray)),
|
Span::styled("composer target ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(focus_label, Style::default().fg(Color::Cyan)),
|
|
||||||
Span::styled(" · composer ", Style::default().fg(Color::DarkGray)),
|
|
||||||
Span::styled(
|
Span::styled(
|
||||||
app.composer_target().label(),
|
app.composer_target().label(),
|
||||||
Style::default().fg(Color::DarkGray),
|
Style::default().fg(Color::DarkGray),
|
||||||
),
|
),
|
||||||
Span::styled(" · no selection", Style::default().fg(Color::DarkGray)),
|
Span::styled(
|
||||||
|
" · no row selected · ↑/↓ selects a row",
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4050,11 +3996,11 @@ fn actionbar_left_text(app: &MultiPodApp) -> String {
|
||||||
} else {
|
} else {
|
||||||
match app.composer_target() {
|
match app.composer_target() {
|
||||||
ComposerTarget::Companion => {
|
ComposerTarget::Companion => {
|
||||||
"Companion target pending; non-empty Enter keeps draft and reports a diagnostic"
|
"Composer target: Companion; type text to send, or use ↑/↓ then blank Enter for rows"
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
ComposerTarget::TicketIntake => {
|
ComposerTarget::TicketIntake => {
|
||||||
"Ticket Intake target: Enter launches Intake with composer text".to_string()
|
"Composer target: Ticket Intake; type a request, then Enter launches Intake".to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4064,25 +4010,25 @@ fn actionbar_right_text(app: &MultiPodApp) -> &'static str {
|
||||||
if app.panel_diagnostic_open {
|
if app.panel_diagnostic_open {
|
||||||
"F2/Esc close details Ctrl+C quit"
|
"F2/Esc close details Ctrl+C quit"
|
||||||
} else if app.panel_diagnostic.is_some() {
|
} else if app.panel_diagnostic.is_some() {
|
||||||
"F2 details ↑/↓ row Enter row action/open Right action focus Tab target Esc composer Ctrl+C quit"
|
"F2 details ↑/↓ select row Enter selected row Tab target Esc clear selection Left/Right cursor Ctrl+C quit"
|
||||||
} else if !app.composer_is_blank() {
|
} else if !app.composer_is_blank() {
|
||||||
if app
|
if app
|
||||||
.panel
|
.panel
|
||||||
.composer
|
.composer
|
||||||
.is_available(ComposerTarget::TicketIntake)
|
.is_available(ComposerTarget::TicketIntake)
|
||||||
{
|
{
|
||||||
"↑/↓ row Enter composer target Tab target Esc composer Ctrl+C quit"
|
"↑/↓ draft lines Left/Right cursor Enter composer target Tab target Esc clear selection Ctrl+C quit"
|
||||||
} else {
|
} else {
|
||||||
"↑/↓ row Enter composer target Esc composer Ctrl+C quit"
|
"↑/↓ draft lines Left/Right cursor Enter composer target Esc clear selection Ctrl+C quit"
|
||||||
}
|
}
|
||||||
} else if app
|
} else if app
|
||||||
.panel
|
.panel
|
||||||
.composer
|
.composer
|
||||||
.is_available(ComposerTarget::TicketIntake)
|
.is_available(ComposerTarget::TicketIntake)
|
||||||
{
|
{
|
||||||
"↑/↓ row Enter row action/open Right action focus Tab target Esc composer Ctrl+C quit"
|
"↑/↓ select row Enter selected row Tab target Esc clear selection Left/Right cursor Ctrl+C quit"
|
||||||
} else {
|
} else {
|
||||||
"↑/↓ row Enter row action/open Right action focus Esc composer Ctrl+C quit"
|
"↑/↓ select row Enter selected row Esc clear selection Left/Right cursor Ctrl+C quit"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4809,10 +4755,11 @@ mod tests {
|
||||||
assert!(actionbar_left.contains("Companion target: Enter sends composer text"));
|
assert!(actionbar_left.contains("Companion target: Enter sends composer text"));
|
||||||
assert!(actionbar_right.contains("Enter composer target"));
|
assert!(actionbar_right.contains("Enter composer target"));
|
||||||
assert!(!actionbar_left.contains("Queue"));
|
assert!(!actionbar_left.contains("Queue"));
|
||||||
assert!(!actionbar_right.contains("row action/open"));
|
assert!(!actionbar_right.contains("selected row"));
|
||||||
assert!(target_status.contains("focus global composer"));
|
assert!(target_status.contains("composer target Companion"));
|
||||||
assert!(target_status.contains("Enter send composer text to workspace Companion"));
|
assert!(target_status.contains("draft Enter send composer text to workspace Companion"));
|
||||||
assert!(!target_status.contains("action Queue"));
|
assert!(target_status.contains("row selection waits until composer is blank"));
|
||||||
|
assert!(!target_status.contains("blank Enter Queue"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -5723,21 +5670,23 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multi_esc_clears_panel_focus_without_quitting() {
|
fn multi_esc_clears_row_selection_without_quitting_and_preserves_draft() {
|
||||||
let mut app = ticket_enabled_app(vec![live_info("alpha", PodStatus::Idle)]);
|
let mut app = ticket_enabled_app(vec![live_info("alpha", PodStatus::Idle)]);
|
||||||
|
app.input.insert_str("draft message");
|
||||||
|
|
||||||
assert!(app.selected_row.is_some());
|
assert!(app.selected_row.is_some());
|
||||||
assert!(matches!(
|
|
||||||
app.handle_key(key(KeyCode::Right)),
|
|
||||||
MultiPodAction::None
|
|
||||||
));
|
|
||||||
assert_eq!(app.effective_focus(), PanelFocus::ItemAction);
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
app.handle_key(key(KeyCode::Esc)),
|
app.handle_key(key(KeyCode::Esc)),
|
||||||
MultiPodAction::None
|
MultiPodAction::None
|
||||||
));
|
));
|
||||||
assert!(app.selected_row.is_none());
|
assert!(app.selected_row.is_none());
|
||||||
assert_eq!(app.effective_focus(), PanelFocus::GlobalComposer);
|
assert_eq!(input_text(&app), "draft message");
|
||||||
|
assert!(
|
||||||
|
app.notice
|
||||||
|
.as_deref()
|
||||||
|
.unwrap()
|
||||||
|
.contains("Row selection cleared")
|
||||||
|
);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
app.handle_key(modified_key(KeyCode::Char('c'), KeyModifiers::CONTROL)),
|
app.handle_key(modified_key(KeyCode::Char('c'), KeyModifiers::CONTROL)),
|
||||||
MultiPodAction::Quit
|
MultiPodAction::Quit
|
||||||
|
|
@ -5750,6 +5699,7 @@ mod tests {
|
||||||
app.input.insert_str("draft intake request");
|
app.input.insert_str("draft intake request");
|
||||||
|
|
||||||
assert!(matches!(app.composer_target(), ComposerTarget::Companion));
|
assert!(matches!(app.composer_target(), ComposerTarget::Companion));
|
||||||
|
let selected_before = app.selected_row.clone();
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
app.handle_key(key(KeyCode::Tab)),
|
app.handle_key(key(KeyCode::Tab)),
|
||||||
MultiPodAction::None
|
MultiPodAction::None
|
||||||
|
|
@ -5759,6 +5709,7 @@ mod tests {
|
||||||
app.composer_target(),
|
app.composer_target(),
|
||||||
ComposerTarget::TicketIntake
|
ComposerTarget::TicketIntake
|
||||||
));
|
));
|
||||||
|
assert_eq!(app.selected_row, selected_before);
|
||||||
assert_eq!(input_text(&app), "draft intake request");
|
assert_eq!(input_text(&app), "draft intake request");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5794,14 +5745,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multi_ticket_intake_rejects_empty_input() {
|
fn multi_blank_ticket_intake_enter_uses_selected_row_and_preserves_input() {
|
||||||
let mut app = ticket_enabled_app(vec![live_info("idle", PodStatus::Idle)]);
|
let mut app = ticket_enabled_app(vec![live_info("idle", PodStatus::Idle)]);
|
||||||
app.cycle_composer_target();
|
app.cycle_composer_target();
|
||||||
app.input.insert_str(" \n\t");
|
app.input.insert_str(" \n\t");
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
app.handle_key(key(KeyCode::Enter)),
|
app.handle_key(key(KeyCode::Enter)),
|
||||||
MultiPodAction::None
|
MultiPodAction::Open
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
|
@ -5810,7 +5761,12 @@ mod tests {
|
||||||
));
|
));
|
||||||
assert!(!app.sending);
|
assert!(!app.sending);
|
||||||
assert_eq!(input_text(&app), " \n\t");
|
assert_eq!(input_text(&app), " \n\t");
|
||||||
assert!(app.notice.as_deref().unwrap().contains("input is empty"));
|
assert!(
|
||||||
|
!app.notice
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.contains("input is empty")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -6092,7 +6048,6 @@ mod tests {
|
||||||
panel,
|
panel,
|
||||||
input: InputBuffer::new(),
|
input: InputBuffer::new(),
|
||||||
selected_row: None,
|
selected_row: None,
|
||||||
focus: PanelFocus::GlobalComposer,
|
|
||||||
composer_target: ComposerTarget::Companion,
|
composer_target: ComposerTarget::Companion,
|
||||||
notice: None,
|
notice: None,
|
||||||
panel_diagnostic: None,
|
panel_diagnostic: None,
|
||||||
|
|
|
||||||
|
|
@ -894,7 +894,7 @@ fn pod_row(entry: &PodListEntry) -> PanelRow {
|
||||||
ticket: None,
|
ticket: None,
|
||||||
related_pods: Vec::new(),
|
related_pods: Vec::new(),
|
||||||
disabled_reason: entry.actions.disabled_reason.clone(),
|
disabled_reason: entry.actions.disabled_reason.clone(),
|
||||||
key_hint: Some("Enter opens/attaches; Right marks action focus".to_string()),
|
key_hint: Some("Enter opens/attaches for inspection".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user