//! Window/tab use cases (ARCHITECTURE §6, §10; L10). //! //! [`MoveTabToNewWindow`] detaches a tab into a new OS window. The topology //! change is the pure [`Workspace::move_tab_to_new_window`] domain operation; the //! use case only loads/persists the workspace and mints the new window id. use std::sync::Arc; use domain::ids::{TabId, WindowId}; use domain::layout::Workspace; use domain::ports::{IdGenerator, ProjectStore}; use crate::error::AppError; /// Input for [`MoveTabToNewWindow::execute`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct MoveTabToNewWindowInput { /// The tab to detach into its own new window. pub tab_id: TabId, } /// Output of [`MoveTabToNewWindow::execute`]. #[derive(Debug, Clone, PartialEq)] pub struct MoveTabToNewWindowOutput { /// The id minted for the new window. pub new_window_id: WindowId, /// The resulting workspace (already persisted). pub workspace: Workspace, } /// Detaches a tab into a freshly-created window and persists the workspace. pub struct MoveTabToNewWindow { store: Arc, ids: Arc, } impl MoveTabToNewWindow { /// Builds the use case from its ports. #[must_use] pub fn new(store: Arc, ids: Arc) -> Self { Self { store, ids } } /// Executes the detach. /// /// # Errors /// - [`AppError::NotFound`] if the tab is not in the workspace, /// - [`AppError::Invalid`] if the resulting window is invalid, /// - [`AppError::Store`] on persistence failure. pub async fn execute( &self, input: MoveTabToNewWindowInput, ) -> Result { let workspace = self.store.load_workspace().await?; let new_window_id = WindowId::from_uuid(self.ids.new_uuid()); let workspace = workspace .move_tab_to_new_window(input.tab_id, new_window_id) .map_err(|e| match e { domain::layout::LayoutError::TabNotFound(t) => { AppError::NotFound(format!("tab {t}")) } other => AppError::Invalid(other.to_string()), })?; self.store.save_workspace(&workspace).await?; Ok(MoveTabToNewWindowOutput { new_window_id, workspace, }) } }