fix(view-windows): réconcilier l'état des fenêtres détachées au boot (#50)

Branche le consommateur frontend sur la commande backend
`list_open_view_windows` : le port window expose la liste des fenêtres
de panneaux détachées réellement ouvertes côté OS, l'adaptateur Tauri
(et son double mock) l'implémente, et `ProjectsView` réconcilie le
placement des vues au démarrage à partir de ce snapshot au lieu de
supposer un état. Couvre `viewPlacement` et la réconciliation par des
tests dédiés.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 18:14:45 +02:00
parent afbf315e97
commit b4a01e0ae9
7 changed files with 300 additions and 1 deletions

View File

@ -61,6 +61,7 @@ import {
isDockedTo,
panelsDockedTo,
placementOf,
reconcileDetachedWindows,
type PanelId,
type ViewPlacement,
type ViewPlacements,
@ -281,6 +282,31 @@ export function ProjectsView() {
};
}, [windowGateway]);
// On mount, reconcile `placements` with detached windows already open at the
// OS level (#50): a panel-only window restored by the OS before the main
// window mounted is unknown to `placements`, so the Panneaux menu would wrongly
// show it "Fermé". Enumerate them once and mark each as "detached" — but only
// best-effort and without clobbering more specific state: skip non-visible
// snapshots, skip `projects`/unknown panels, and never overwrite a placement
// the user (or a UI restore) already set to something other than "closed". A
// failed enumeration leaves `placements` untouched (no ghost slots). Independent
// of `active`: a panel-only window can be open with no active project.
useEffect(() => {
let cancelled = false;
void windowGateway
.listOpenViewWindows()
.then((snapshots) => {
if (cancelled) return;
setPlacements((prev) => reconcileDetachedWindows(prev, snapshots));
})
.catch(() => {
/* best-effort only: no reconciliation on failure */
});
return () => {
cancelled = true;
};
}, [windowGateway]);
// Opening a conversation viewer takes over the main area — dismiss floating
// windows so they don't obscure the viewer (LS7). Docked views stay beside it.
function openConversation(conversationId: string) {