fix(windows): restauration panel-only des fenêtres détachées suivant le projet en focus (#47)

Partie backend. Les fenêtres/panneaux détachés étaient restaurés avec un
project_id figé au moment du détachement, si bien qu'ils restaient collés à
un projet mort ou incohérent après redémarrage. Ils sont désormais restaurés
en mode panel-only, sans project_id figé, et suivent le projet en focus de la
fenêtre principale via un event focused-project exposé par la couche fenêtre.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 12:37:25 +02:00
parent 1e0cb16ead
commit 6387fac34f
6 changed files with 156 additions and 107 deletions

View File

@ -10,7 +10,7 @@ use domain::ids::{TabId, WindowId};
use std::collections::HashSet;
use domain::layout::{PersistedWindowKind, PersistedWindowState, WindowStateSnapshot, Workspace};
use domain::ports::{IdGenerator, ProjectStore, StoreError, WindowStateStore};
use domain::ports::{IdGenerator, ProjectStore, WindowStateStore};
use crate::error::AppError;
@ -115,21 +115,24 @@ pub struct RestoreOpenWindowsOutput {
/// Loads and filters the latest persisted OS window snapshot.
pub struct RestoreOpenWindows {
windows: Arc<dyn WindowStateStore>,
projects: Arc<dyn ProjectStore>,
_projects: Arc<dyn ProjectStore>,
}
impl RestoreOpenWindows {
/// Builds the use case from its ports.
#[must_use]
pub fn new(windows: Arc<dyn WindowStateStore>, projects: Arc<dyn ProjectStore>) -> Self {
Self { windows, projects }
Self {
windows,
_projects: projects,
}
}
/// Loads restorable windows.
///
/// Views are ignored if their project no longer exists or if their persisted
/// identity is incomplete. Duplicate labels are ignored after the first
/// occurrence.
/// Views are panel-only: their persisted project id, including legacy
/// `view-<panel>-<project>` identities, is ignored. Duplicate labels are ignored
/// after the first occurrence.
///
/// # Errors
/// [`AppError::Store`] on snapshot read failure.
@ -146,17 +149,10 @@ impl RestoreOpenWindows {
match window.kind {
PersistedWindowKind::Main => windows.push(window),
PersistedWindowKind::View => {
let Some(project_id) = window.project_id else {
continue;
};
if window.panel.is_none() || window.url.is_none() {
if window.panel.is_none() {
continue;
}
match self.projects.load_project(project_id).await {
Ok(_) => windows.push(window),
Err(StoreError::NotFound) => {}
Err(e) => return Err(e.into()),
}
windows.push(window);
}
}
}

View File

@ -232,12 +232,14 @@ async fn snapshot_open_windows_persists_the_supplied_snapshot() {
}
#[tokio::test]
async fn restore_open_windows_deduplicates_and_filters_missing_projects() {
async fn restore_open_windows_keeps_panel_views_without_reopening_projects() {
let existing = ProjectId::from_uuid(Uuid::from_u128(42));
let missing = ProjectId::from_uuid(Uuid::from_u128(43));
let valid_label = "view-tickets-0000000000000000000000000000002a";
let mut incomplete = persisted_view("view-tickets-0000000000000000000000000000002c", existing);
incomplete.url = None;
let mut url_missing = persisted_view("view-tickets-0000000000000000000000000000002c", existing);
url_missing.url = None;
let mut no_panel = persisted_view("view-tickets-0000000000000000000000000000002d", existing);
no_panel.panel = None;
let store = FakeWindowStateStore(Arc::new(Mutex::new(WindowStateSnapshot::new(vec![
persisted_main(),
@ -245,9 +247,10 @@ async fn restore_open_windows_deduplicates_and_filters_missing_projects() {
persisted_view(valid_label, existing),
persisted_view(valid_label, existing),
persisted_view("view-tickets-0000000000000000000000000000002b", missing),
incomplete,
url_missing,
no_panel,
]))));
let projects = FakeProjectRegistry::new(vec![existing]);
let projects = FakeProjectRegistry::new(vec![]);
let uc = RestoreOpenWindows::new(Arc::new(store), Arc::new(projects));
let out = uc.execute().await.unwrap();
@ -257,6 +260,11 @@ async fn restore_open_windows_deduplicates_and_filters_missing_projects() {
.iter()
.map(|w| w.label.as_str())
.collect::<Vec<_>>(),
vec!["main", valid_label]
vec![
"main",
valid_label,
"view-tickets-0000000000000000000000000000002b",
"view-tickets-0000000000000000000000000000002c"
]
);
}