diff --git a/crates/tui/src/workspace_panel.rs b/crates/tui/src/workspace_panel.rs index b14fe44a..670cd102 100644 --- a/crates/tui/src/workspace_panel.rs +++ b/crates/tui/src/workspace_panel.rs @@ -6,8 +6,8 @@ use std::time::Instant; use protocol::WorkerStatus; use ticket::config::{ - DEFAULT_TICKET_BACKEND_RELATIVE_PATH, TICKET_CONFIG_RELATIVE_PATH, TicketConfig, - TicketOrchestrationConfig, WORKSPACE_SETTINGS_RELATIVE_PATH, + TICKET_CONFIG_RELATIVE_PATH, TicketConfig, TicketOrchestrationConfig, + WORKSPACE_SETTINGS_RELATIVE_PATH, }; use ticket::{ LocalTicketBackend, TicketBackend, TicketError, TicketEvent, TicketFilter, TicketIdOrSlug, @@ -641,13 +641,14 @@ fn load_orchestration_ticket_overlay( } match validate_orchestration_overlay_source(workspace_root, &layout) { Ok(()) => { - load_orchestration_ticket_overlay_states(&layout.path, config.ticket_record_language()) - .unwrap_or_else(|message| OrchestrationTicketOverlay { + load_orchestration_ticket_overlay_states(&layout.path).unwrap_or_else(|message| { + OrchestrationTicketOverlay { states: BTreeMap::new(), diagnostics: vec![bounded_panel_diagnostic(format!( "Orchestration Ticket overlay unavailable: {message}" ))], - }) + } + }) } Err(message) => OrchestrationTicketOverlay { states: BTreeMap::new(), @@ -672,19 +673,25 @@ fn orchestration_worktree_layout( fn load_orchestration_ticket_overlay_states( worktree_root: &Path, - record_language: Option<&str>, ) -> Result { - let ticket_root = worktree_root.join(DEFAULT_TICKET_BACKEND_RELATIVE_PATH); + let overlay_config = TicketConfig::load_workspace(worktree_root).map_err(|error| { + format!( + "orchestration worktree {} has unusable Ticket settings: {error}", + worktree_root.display() + ) + })?; + let ticket_root = overlay_config.backend_root(); if !ticket_root.is_dir() { return Ok(OrchestrationTicketOverlay { states: BTreeMap::new(), diagnostics: vec![bounded_panel_diagnostic(format!( - "Orchestration worktree has no {} directory", - DEFAULT_TICKET_BACKEND_RELATIVE_PATH + "Orchestration worktree configured Ticket backend root {} is not a directory", + ticket_root.display() ))], }); } - let backend = LocalTicketBackend::new(ticket_root).with_record_language(record_language); + let backend = LocalTicketBackend::new(ticket_root.to_path_buf()) + .with_record_language(overlay_config.ticket_record_language()); let partial = backend .list_partial(TicketFilter::all()) .map_err(|error| error.to_string())?; @@ -1796,11 +1803,17 @@ mod tests { } fn write_ticket_config(workspace_root: &Path) { + write_ticket_config_with_root(workspace_root, ".yoi/tickets"); + } + + fn write_ticket_config_with_root(workspace_root: &Path, root: &str) { let config_dir = workspace_root.join(".yoi"); fs::create_dir_all(&config_dir).unwrap(); fs::write( config_dir.join("workspace.toml"), - "[ticket]\n\n[ticket.backend]\nprovider = \"builtin:yoi_local\"\nroot = \".yoi/tickets\"\n", + format!( + "[ticket]\n\n[ticket.backend]\nprovider = \"builtin:yoi_local\"\nroot = \"{root}\"\n" + ), ) .unwrap(); } @@ -1848,8 +1861,12 @@ mod tests { } fn copy_ticket_to_overlay(workspace_root: &Path, orchestration_root: &Path, id: &str) { + copy_ticket_to_overlay_root(workspace_root, &orchestration_root.join(".yoi/tickets"), id); + } + + fn copy_ticket_to_overlay_root(workspace_root: &Path, overlay_root: &Path, id: &str) { let local_ticket_dir = workspace_root.join(".yoi/tickets").join(id); - let overlay_ticket_dir = orchestration_root.join(".yoi/tickets").join(id); + let overlay_ticket_dir = overlay_root.join(id); fs::create_dir_all(overlay_ticket_dir.parent().unwrap()).unwrap(); fs::create_dir_all(&overlay_ticket_dir).unwrap(); fs::copy( @@ -2020,6 +2037,40 @@ mod tests { assert!(model.rows.iter().all(|row| row.title != "Overlay Only")); } + #[test] + fn workspace_panel_orchestration_overlay_uses_configured_backend_root() { + let temp = TempDir::new().unwrap(); + init_git_repo(temp.path()); + write_ticket_config(temp.path()); + let orchestration_root = add_orchestration_worktree(temp.path(), "orchestration"); + write_ticket_config_with_root(&orchestration_root, "configured-overlay-tickets"); + let backend = LocalTicketBackend::new(temp.path().join(".yoi/tickets")); + let id = create_ticket_with_id(&backend, "Overlay Custom Root", |input| { + input.workflow_state = Some(TicketWorkflowState::Queued); + }); + let configured_overlay_root = orchestration_root.join("configured-overlay-tickets"); + copy_ticket_to_overlay_root(temp.path(), &configured_overlay_root, &id); + let overlay_backend = LocalTicketBackend::new(&configured_overlay_root); + set_ticket_state(&overlay_backend, &id, TicketWorkflowState::InProgress); + + let model = build_workspace_panel(temp.path(), &empty_pods()); + + assert!(model.header.diagnostics.is_empty()); + let row = ticket_row_by_title(&model, "Overlay Custom Root"); + assert_eq!(row.status, "q→prog"); + assert_eq!( + row.ticket + .as_ref() + .unwrap() + .orchestration_overlay + .as_ref() + .unwrap() + .workflow_state, + TicketWorkflowState::InProgress + ); + assert!(!orchestration_root.join(".yoi/tickets").exists()); + } + #[test] fn workspace_panel_displays_queued_plus_orchestration_inprogress_without_mutating_local_ticket() {