fix(e2e): close worker environment review gaps

This commit is contained in:
2026-09-22 23:28:28 +09:00
parent 0869ef0cc6
commit a689072756
7 changed files with 509 additions and 959 deletions
+96 -4
View File
@@ -2,7 +2,7 @@ use std::io;
use std::time::Duration;
use client::{BackendWorkspaceProductClient, ObjectiveSummary};
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
use ratatui::{
Frame, Terminal,
backend::CrosstermBackend,
@@ -79,8 +79,26 @@ async fn run_loop(
app: &mut BackendDashboard,
client: BackendWorkspaceProductClient,
) -> Result<(), Box<dyn std::error::Error>> {
let mut first_frame = true;
let mut initial_snapshot_pending = false;
loop {
terminal.draw(|frame| draw(frame, app))?;
if first_frame {
first_frame = false;
#[cfg(feature = "e2e-test")]
{
crate::e2e_observer::emit(
"backend_dashboard",
"panel_ready",
serde_json::json!({ "workspace_id": app.workspace_id }),
);
initial_snapshot_pending = true;
}
} else if initial_snapshot_pending {
#[cfg(feature = "e2e-test")]
emit_e2e_snapshot(app, "backend_dashboard_content_ready");
initial_snapshot_pending = false;
}
if !event::poll(Duration::from_millis(100))? {
continue;
}
@@ -91,21 +109,50 @@ async fn run_loop(
continue;
}
match key.code {
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
KeyCode::Char('q') | KeyCode::Esc => {
#[cfg(feature = "e2e-test")]
crate::e2e_observer::emit(
"backend_dashboard",
"quit_requested",
serde_json::json!({}),
);
return Ok(());
}
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
#[cfg(feature = "e2e-test")]
crate::e2e_observer::emit(
"backend_dashboard",
"quit_requested",
serde_json::json!({}),
);
return Ok(());
}
KeyCode::Tab | KeyCode::BackTab => {
app.focus = match app.focus {
Focus::Tickets => Focus::Objectives,
Focus::Objectives => Focus::Tickets,
};
#[cfg(feature = "e2e-test")]
emit_e2e_snapshot(app, "backend_dashboard_selection_changed");
}
KeyCode::Down | KeyCode::Char('j') => {
app.select_next();
#[cfg(feature = "e2e-test")]
emit_e2e_snapshot(app, "backend_dashboard_selection_changed");
}
KeyCode::Up | KeyCode::Char('k') => {
app.select_previous();
#[cfg(feature = "e2e-test")]
emit_e2e_snapshot(app, "backend_dashboard_selection_changed");
}
KeyCode::Down | KeyCode::Char('j') => app.select_next(),
KeyCode::Up | KeyCode::Char('k') => app.select_previous(),
KeyCode::Char('r') => match load(&client).await {
Ok((tickets, objectives)) => {
app.tickets = tickets;
app.objectives = objectives;
app.clamp_selection();
app.status = "Reloaded from Backend authority".to_string();
#[cfg(feature = "e2e-test")]
emit_e2e_snapshot(app, "backend_dashboard_reloaded");
}
Err(error) => app.status = format!("Backend reload failed: {error}"),
},
@@ -144,6 +191,51 @@ async fn run_loop(
}
}
#[cfg(feature = "e2e-test")]
fn emit_e2e_snapshot(app: &BackendDashboard, event: &'static str) {
let focus = match app.focus {
Focus::Tickets => "tickets",
Focus::Objectives => "objectives",
};
let tickets = app
.tickets
.iter()
.map(|ticket| {
serde_json::json!({
"id": ticket.id,
"resource_key": ticket.resource_key,
"title": ticket.title,
"workflow_state": ticket.workflow_state.as_str(),
})
})
.collect::<Vec<_>>();
let objectives = app
.objectives
.iter()
.map(|objective| {
serde_json::json!({
"id": objective.id,
"resource_key": objective.resource_key,
"title": objective.title,
"state": objective.state,
})
})
.collect::<Vec<_>>();
crate::e2e_observer::emit(
"backend_dashboard",
event,
serde_json::json!({
"workspace_id": app.workspace_id,
"focus": focus,
"selected_ticket_id": app.selected_ticket_id(),
"selected_objective_id": app.objectives.get(app.selected_objective).map(|objective| &objective.id),
"tickets": tickets,
"objectives": objectives,
"status": app.status,
}),
);
}
impl BackendDashboard {
fn select_next(&mut self) {
match self.focus {
+1 -21
View File
@@ -2,12 +2,11 @@ use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::PathBuf;
use std::sync::{Mutex, OnceLock};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::time::{SystemTime, UNIX_EPOCH};
use serde::Serialize;
const EVENT_PATH_ENV: &str = "YOI_TUI_TEST_EVENTS";
const HOLD_BACKGROUND_TASK_ENV: &str = "YOI_TUI_TEST_HOLD_BACKGROUND_TASK";
static EVENT_WRITER: OnceLock<Option<Mutex<File>>> = OnceLock::new();
@@ -44,25 +43,6 @@ where
}
}
pub(crate) async fn hold_background_task_if_requested(task: &'static str) {
let requested = std::env::var(HOLD_BACKGROUND_TASK_ENV).unwrap_or_default();
if !requested
.split(',')
.map(str::trim)
.any(|requested| requested == task)
{
return;
}
emit(
"panel",
"background_task_hold_started",
serde_json::json!({ "task": task }),
);
loop {
tokio::time::sleep(Duration::from_millis(25)).await;
}
}
fn open_event_writer() -> Option<Mutex<File>> {
let path = std::env::var_os(EVENT_PATH_ENV).map(PathBuf::from)?;
if let Some(parent) = path.parent() {