tui: clarify panel composer enter hints
This commit is contained in:
parent
20f06b3541
commit
573b02fbfc
|
|
@ -2860,12 +2860,35 @@ fn draw_separator(frame: &mut Frame<'_>, area: Rect) {
|
|||
}
|
||||
|
||||
fn draw_target_status(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) {
|
||||
frame.render_widget(Paragraph::new(target_status_line(app)), area);
|
||||
}
|
||||
|
||||
fn target_status_line(app: &MultiPodApp) -> Line<'static> {
|
||||
if !app.composer_is_blank() {
|
||||
return Line::from(vec![
|
||||
Span::styled("focus ", Style::default().fg(Color::DarkGray)),
|
||||
Span::styled("global composer", Style::default().fg(Color::Cyan)),
|
||||
Span::styled(" · composer ", Style::default().fg(Color::DarkGray)),
|
||||
Span::styled(
|
||||
app.composer_target().label(),
|
||||
Style::default()
|
||||
.fg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::styled(" · Enter ", Style::default().fg(Color::DarkGray)),
|
||||
Span::styled(
|
||||
composer_enter_status_text(app),
|
||||
Style::default().fg(Color::Green),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
let focus_label = match app.effective_focus() {
|
||||
PanelFocus::GlobalComposer => "global composer",
|
||||
PanelFocus::Row => "selected row",
|
||||
PanelFocus::ItemAction => "item action",
|
||||
};
|
||||
let target = if let Some(row) = app
|
||||
if let Some(row) = app
|
||||
.selected_panel_row()
|
||||
.filter(|row| row.is_ticket_action())
|
||||
{
|
||||
|
|
@ -2911,8 +2934,7 @@ fn draw_target_status(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) {
|
|||
),
|
||||
Span::styled(" · no selection", Style::default().fg(Color::DarkGray)),
|
||||
])
|
||||
};
|
||||
frame.render_widget(Paragraph::new(target), area);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_input(frame: &mut Frame<'_>, render: &crate::input::InputRender, area: Rect) {
|
||||
|
|
@ -2933,8 +2955,97 @@ fn draw_input(frame: &mut Frame<'_>, render: &crate::input::InputRender, area: R
|
|||
}
|
||||
}
|
||||
|
||||
fn draw_actionbar(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) {
|
||||
let left = if app.sending && app.composer_target() == ComposerTarget::TicketIntake {
|
||||
fn composer_enter_status_text(app: &MultiPodApp) -> String {
|
||||
match app.composer_target() {
|
||||
ComposerTarget::Companion => companion_enter_status_text(app),
|
||||
ComposerTarget::TicketIntake => "launch Intake with composer text".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn composer_enter_actionbar_text(app: &MultiPodApp) -> String {
|
||||
match app.composer_target() {
|
||||
ComposerTarget::Companion => companion_enter_actionbar_text(app),
|
||||
ComposerTarget::TicketIntake => {
|
||||
"Ticket Intake target: Enter launches Intake with composer text".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn companion_enter_status_text(app: &MultiPodApp) -> String {
|
||||
match companion_send_availability(app) {
|
||||
CompanionSendAvailability::Ready => "send composer text to workspace Companion".to_string(),
|
||||
CompanionSendAvailability::Unavailable(reason) => format!("keep draft; {reason}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn companion_enter_actionbar_text(app: &MultiPodApp) -> String {
|
||||
match companion_send_availability(app) {
|
||||
CompanionSendAvailability::Ready => {
|
||||
"Companion target: Enter sends composer text to workspace Companion".to_string()
|
||||
}
|
||||
CompanionSendAvailability::Unavailable(reason) => {
|
||||
format!("Companion target: Enter keeps draft; {reason}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum CompanionSendAvailability {
|
||||
Ready,
|
||||
Unavailable(String),
|
||||
}
|
||||
|
||||
fn companion_send_availability(app: &MultiPodApp) -> CompanionSendAvailability {
|
||||
let Some(companion) = app.panel.header.companion.as_ref() else {
|
||||
return CompanionSendAvailability::Unavailable(
|
||||
"workspace Companion is unavailable".to_string(),
|
||||
);
|
||||
};
|
||||
if matches!(
|
||||
companion.status,
|
||||
CompanionPanelStatus::Unavailable
|
||||
| CompanionPanelStatus::Missing
|
||||
| CompanionPanelStatus::Stopped
|
||||
) {
|
||||
return CompanionSendAvailability::Unavailable(format!(
|
||||
"workspace Companion is {}",
|
||||
companion.status.label()
|
||||
));
|
||||
}
|
||||
let Some(entry) = app
|
||||
.list
|
||||
.entries
|
||||
.iter()
|
||||
.find(|entry| entry.name == companion.pod_name)
|
||||
else {
|
||||
return CompanionSendAvailability::Unavailable(format!(
|
||||
"workspace Companion `{}` is not in the Pod list",
|
||||
companion.pod_name
|
||||
));
|
||||
};
|
||||
let Some(live) = entry.live.as_ref() else {
|
||||
return CompanionSendAvailability::Unavailable(format!(
|
||||
"workspace Companion `{}` is stopped",
|
||||
companion.pod_name
|
||||
));
|
||||
};
|
||||
if !live.reachable {
|
||||
return CompanionSendAvailability::Unavailable(format!(
|
||||
"workspace Companion `{}` is unreachable",
|
||||
companion.pod_name
|
||||
));
|
||||
}
|
||||
if live.status == Some(PodStatus::Running) {
|
||||
return CompanionSendAvailability::Unavailable(format!(
|
||||
"workspace Companion `{}` is running",
|
||||
companion.pod_name
|
||||
));
|
||||
}
|
||||
CompanionSendAvailability::Ready
|
||||
}
|
||||
|
||||
fn actionbar_left_text(app: &MultiPodApp) -> String {
|
||||
if app.sending && app.composer_target() == ComposerTarget::TicketIntake {
|
||||
"launching Ticket Intake…".to_string()
|
||||
} else if app.sending {
|
||||
"working…".to_string()
|
||||
|
|
@ -2946,6 +3057,8 @@ fn draw_actionbar(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) {
|
|||
Some(notice) => format!("{notice} Refreshing workspace…"),
|
||||
None => "Refreshing workspace…".to_string(),
|
||||
}
|
||||
} else if !app.composer_is_blank() {
|
||||
composer_enter_actionbar_text(app)
|
||||
} else if let Some(notice) = app.notice.as_deref() {
|
||||
notice.to_string()
|
||||
} else if let Some(reason) = app.selected_open_disabled_reason() {
|
||||
|
|
@ -2960,8 +3073,21 @@ fn draw_actionbar(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) {
|
|||
"Ticket Intake target: Enter launches Intake with composer text".to_string()
|
||||
}
|
||||
}
|
||||
};
|
||||
let right = if app
|
||||
}
|
||||
}
|
||||
|
||||
fn actionbar_right_text(app: &MultiPodApp) -> &'static str {
|
||||
if !app.composer_is_blank() {
|
||||
if app
|
||||
.panel
|
||||
.composer
|
||||
.is_available(ComposerTarget::TicketIntake)
|
||||
{
|
||||
"↑/↓ row Enter composer target Tab target Esc composer Ctrl+C quit"
|
||||
} else {
|
||||
"↑/↓ row Enter composer target Esc composer Ctrl+C quit"
|
||||
}
|
||||
} else if app
|
||||
.panel
|
||||
.composer
|
||||
.is_available(ComposerTarget::TicketIntake)
|
||||
|
|
@ -2969,7 +3095,12 @@ fn draw_actionbar(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) {
|
|||
"↑/↓ row Enter row action/open Right action focus Tab target Esc composer Ctrl+C quit"
|
||||
} else {
|
||||
"↑/↓ row Enter row action/open Right action focus Esc composer Ctrl+C quit"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_actionbar(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) {
|
||||
let left = actionbar_left_text(app);
|
||||
let right = actionbar_right_text(app);
|
||||
let left_width = area
|
||||
.width
|
||||
.saturating_sub(right.width() as u16)
|
||||
|
|
@ -3269,8 +3400,9 @@ mod tests {
|
|||
|
||||
let message = orchestrator_queue_notification_message(ticket);
|
||||
|
||||
assert!(message.contains("Ticket `route-ticket` (`20260606-000000-route-ticket`)"));
|
||||
assert!(message.contains("title `Route queued Ticket`"));
|
||||
assert!(
|
||||
message.contains("Ticket `20260606-000000-route-ticket`, title `Route queued Ticket`")
|
||||
);
|
||||
assert!(message.contains("human authorized Orchestrator routing"));
|
||||
assert!(message.contains("not an unattended scheduler"));
|
||||
assert!(message.contains("Read the Ticket"));
|
||||
|
|
@ -3291,7 +3423,7 @@ mod tests {
|
|||
assert!(message.contains("merge-ready dossier"));
|
||||
assert!(message.contains("without merge/close/final approval"));
|
||||
assert!(message.contains("If blocked, record a concise reason"));
|
||||
assert!(message.contains("leave the Ticket queued or explicitly defer"));
|
||||
assert!(message.contains("leave the Ticket queued or return it to planning"));
|
||||
assert!(!message.contains("Do not start implementation directly"));
|
||||
}
|
||||
|
||||
|
|
@ -3405,6 +3537,49 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_ticket_row_with_non_empty_composer_shows_composer_enter_behavior() {
|
||||
let mut panel = WorkspacePanelViewModel::empty(Path::new("test"));
|
||||
panel.header.companion = Some(CompanionPanelState::new(
|
||||
"yoi",
|
||||
CompanionPanelStatus::Live,
|
||||
None,
|
||||
));
|
||||
panel.rows.push(panel_test_ticket_row(
|
||||
"queue-me",
|
||||
"Queue Me",
|
||||
ActionPriority::ReadyForQueue,
|
||||
NextUserAction::Queue,
|
||||
"ready",
|
||||
));
|
||||
let list = PodList::from_sources(
|
||||
PodVisibilitySource::ResumePicker,
|
||||
vec![],
|
||||
vec![live_info("yoi", PodStatus::Idle)],
|
||||
None,
|
||||
10,
|
||||
);
|
||||
let mut app = app_with_panel(list, panel);
|
||||
app.input.insert_str("draft to companion");
|
||||
|
||||
assert_eq!(
|
||||
app.selected_ticket_action(),
|
||||
Some(NextUserAction::Queue),
|
||||
"selected row remains a Ticket action row"
|
||||
);
|
||||
let actionbar_left = actionbar_left_text(&app);
|
||||
let actionbar_right = actionbar_right_text(&app);
|
||||
let target_status = plain_line(&target_status_line(&app));
|
||||
|
||||
assert!(actionbar_left.contains("Companion target: Enter sends composer text"));
|
||||
assert!(actionbar_right.contains("Enter composer target"));
|
||||
assert!(!actionbar_left.contains("Queue"));
|
||||
assert!(!actionbar_right.contains("row action/open"));
|
||||
assert!(target_status.contains("focus global composer"));
|
||||
assert!(target_status.contains("Enter send composer text to workspace Companion"));
|
||||
assert!(!target_status.contains("action Queue"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_bare_panel_letters_append_to_composer_and_arrows_select_when_blank() {
|
||||
let mut app = test_app(vec![
|
||||
|
|
@ -3737,11 +3912,16 @@ mod tests {
|
|||
assert!(!review_line.starts_with("▶ Workspace panel composer targets"));
|
||||
assert_eq!(display_column(&review_line, "inprogress"), state_start);
|
||||
assert_eq!(display_column(&ready_line, "ready"), state_start);
|
||||
let review_id = review_row.ticket.as_ref().unwrap().id.as_str();
|
||||
let ready_id = ready_row.ticket.as_ref().unwrap().id.as_str();
|
||||
assert_eq!(
|
||||
display_column(&review_line, "workspace-panel-composer-targets"),
|
||||
display_column(
|
||||
&review_line,
|
||||
&truncate_with_ellipsis(review_id, TICKET_ID_COLUMN_WIDTH)
|
||||
),
|
||||
id_start
|
||||
);
|
||||
assert_eq!(display_column(&ready_line, "ticket-id"), id_start);
|
||||
assert_eq!(display_column(&ready_line, ready_id), id_start);
|
||||
assert_eq!(
|
||||
display_column(&review_line, "Workspace panel composer targets"),
|
||||
title_start
|
||||
|
|
@ -3766,8 +3946,9 @@ mod tests {
|
|||
let title_start = 2 + TICKET_STATE_COLUMN_WIDTH + 1 + TICKET_ID_COLUMN_WIDTH + 1;
|
||||
|
||||
assert_eq!(line.width(), 112);
|
||||
let row_id = row.ticket.as_ref().unwrap().id.as_str();
|
||||
assert_eq!(
|
||||
display_column(&line, "ticket-id"),
|
||||
display_column(&line, row_id),
|
||||
title_start - TICKET_ID_COLUMN_WIDTH - 1
|
||||
);
|
||||
assert_eq!(display_column(&line, "Very long Ticket"), title_start);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user