feat(domain,application,infrastructure,backend,app-tauri,web-server,frontend): restaurer les projets ouverts au redémarrage dans l'ordre mémorisé — #303 (état versionné OpenProjectRestoreState persisté sous <app_data_dir>/open-projects.json via port dédié OpenProjectRestoreStore, distinct du registre projects.json et sans recycler workspace.json; sept use cases cadrés: read/record opened/record closed/save order/restore/retry/remove, mutations sérialisées par mutex applicatif contre les lost updates, tombstones conservés à leur position avec lastFailure et codes stables notFound/inaccessible/store/invalid; prévalidation root par metadata+listing sans interrompre les autres restaurations; activation centralisée BackendCore::activate_project réutilisée par ouverture Tauri/HTTP, création, restore et retry: reconciliation layouts/live-state/background tasks, watcher orchestrateur + serveur MCP, réparation run dirs Claude; cinq commandes exposées Tauri et HTTP avec contrats DTO identiques: restore_open_projects, retry_open_project_restore, remove_project_from_restore, save_open_project_restore_order, read_open_project_restore_state; frontend: DTO de reprise + ProjectGateway étendu, useProjects initialise par listProjects+restoreOpenProjects et reconstruit openTabs dans l'ordre opened sans notification quand tout réussit, notification unique singulier/pluriel avec Voir les détails/Réessayer, détail par projet avec cause française et Retirer de la reprise, retry réussi réinséré à la position mémorisée, onglet conservé si closeProject échoué; saveOpenProjectRestoreOrder exposé non appelé car l'UI ne permet pas encore de réordonnancer; QA verte reconfirmée par Git: rustup run stable cargo fmt --all -- --check, git diff --check, cargo test -p backend open_project_restore_tests 1/1 couvrant absence initiale/ordre/fermeture/inaccessible/retry/notFound/remove/watcher+MCP, cargo test -p web-server authorized_open_project_restore_commands_share_the_tauri_contract 1/1, cargo test -p domain 311+ suites vertes, cargo check -p application -p infrastructure -p backend -p app-tauri -p web-server OK, npm run typecheck OK, npm test ciblé 3 fichiers/37 tests OK, npm run build OK; réserves hors périmètre documentées au carnet: application tests/layout_usecases.rs 2 échecs fixtures plugin package NotFound, infrastructure 1 timing test cancellation process, npm test large 32 échecs web shell/LS7/tickets web — préexistants sans lien avec #303; restant: rebuild AppImage demandé après validation puis validation live fermeture/relance; fichiers tickets/296/*, tickets/299-303/*, counter/index, attachments/ et scripts/ hors périmètre; aucun push)
This commit is contained in:
@ -11,6 +11,7 @@ mod embedder;
|
||||
mod live_state;
|
||||
mod mcp_tool_permission;
|
||||
mod memory;
|
||||
mod open_project_restore;
|
||||
mod permission;
|
||||
mod profile;
|
||||
mod project;
|
||||
@ -37,6 +38,7 @@ pub use embedder::{
|
||||
pub use live_state::FsLiveStateStore;
|
||||
pub use mcp_tool_permission::FsMcpToolPermissionStore;
|
||||
pub use memory::{index_token_size, FsMemoryStore, NaiveMemoryRecall};
|
||||
pub use open_project_restore::FsOpenProjectRestoreStore;
|
||||
pub use permission::FsPermissionStore;
|
||||
pub use profile::{FsEmbedderProfileStore, FsProfileStore};
|
||||
pub use project::FsProjectStore;
|
||||
|
||||
76
crates/infrastructure/src/store/open_project_restore.rs
Normal file
76
crates/infrastructure/src/store/open_project_restore.rs
Normal file
@ -0,0 +1,76 @@
|
||||
//! JSON persistence for the ordered open-project restore intent.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use domain::ports::{FileSystem, OpenProjectRestoreStore, RemotePath, StoreError};
|
||||
use domain::{OpenProjectRestoreState, OPEN_PROJECT_RESTORE_VERSION};
|
||||
|
||||
const FILE_NAME: &str = "open-projects.json";
|
||||
|
||||
/// Filesystem-backed restore store under the machine-local app-data directory.
|
||||
#[derive(Clone)]
|
||||
pub struct FsOpenProjectRestoreStore {
|
||||
fs: Arc<dyn FileSystem>,
|
||||
app_data_dir: String,
|
||||
}
|
||||
|
||||
impl FsOpenProjectRestoreStore {
|
||||
/// Builds the store.
|
||||
#[must_use]
|
||||
pub fn new(fs: Arc<dyn FileSystem>, app_data_dir: impl Into<String>) -> Self {
|
||||
Self {
|
||||
fs,
|
||||
app_data_dir: app_data_dir.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> RemotePath {
|
||||
RemotePath::new(format!(
|
||||
"{}/{}",
|
||||
self.app_data_dir.trim_end_matches(['/', '\\']),
|
||||
FILE_NAME
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl OpenProjectRestoreStore for FsOpenProjectRestoreStore {
|
||||
async fn load_open_project_restore_state(&self) -> Result<OpenProjectRestoreState, StoreError> {
|
||||
match self.fs.read(&self.path()).await {
|
||||
Ok(bytes) => {
|
||||
let mut state: OpenProjectRestoreState = serde_json::from_slice(&bytes)
|
||||
.map_err(|error| StoreError::Serialization(error.to_string()))?;
|
||||
if state.version != OPEN_PROJECT_RESTORE_VERSION {
|
||||
return Err(StoreError::Invalid(format!(
|
||||
"unsupported open-project restore version {}",
|
||||
state.version
|
||||
)));
|
||||
}
|
||||
state.normalize_positions();
|
||||
Ok(state)
|
||||
}
|
||||
Err(domain::ports::FsError::NotFound(_)) => Ok(OpenProjectRestoreState::default()),
|
||||
Err(error) => Err(StoreError::Io(error.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_open_project_restore_state(
|
||||
&self,
|
||||
state: &OpenProjectRestoreState,
|
||||
) -> Result<(), StoreError> {
|
||||
self.fs
|
||||
.create_dir_all(&RemotePath::new(
|
||||
self.app_data_dir.trim_end_matches(['/', '\\']).to_owned(),
|
||||
))
|
||||
.await
|
||||
.map_err(|error| StoreError::Io(error.to_string()))?;
|
||||
let mut bytes = serde_json::to_vec_pretty(state)
|
||||
.map_err(|error| StoreError::Serialization(error.to_string()))?;
|
||||
bytes.push(b'\n');
|
||||
self.fs
|
||||
.write(&self.path(), &bytes)
|
||||
.await
|
||||
.map_err(|error| StoreError::Io(error.to_string()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user