fix(windows): fenêtres de panneau détachées suivent le projet en focus côté UI (#47)

Partie frontend. Ajoute un adaptateur focusedProject et un port dédié : la
ViewWindow détachée n'est plus liée à un project_id figé, elle s'abonne à
l'event focused-project émis par la fenêtre principale et affiche le panneau
du projet courant. ProjectsView propage le focus ; le détachement crée une
fenêtre panel-only. Couvert par les tests window/ViewWindow/focusedProject/
ProjectsView.focus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 12:37:45 +02:00
parent 6387fac34f
commit 221cc8be78
14 changed files with 522 additions and 130 deletions

View File

@ -102,7 +102,7 @@ function isTerminalBackgroundTaskEvent(
export function ProjectsView() {
const vm = useProjects();
const { system, window: windowGateway } = useGateways();
const { system, window: windowGateway, focusedProject } = useGateways();
const [name, setName] = useState("");
const [root, setRoot] = useState("");
// Placement of every open view (#22): each panel is "closed" (absent),
@ -141,6 +141,17 @@ export function ProjectsView() {
setViewerConversationId(null);
}, [active?.id]);
// Publish the focused project (#47) so detached panel-only windows follow the
// main window: they render this project, or an "open a project" shell when
// none is active. Publish on EVERY change of `active` — including
// switching-away to null — so a restored panel window never shows a stale
// project. Optional-chained: unit tests may omit this gateway.
useEffect(() => {
void focusedProject?.setFocusedProject(
active ? { id: active.id, name: active.name, root: active.root } : null,
);
}, [focusedProject, active?.id, active?.name, active?.root]);
useEffect(() => {
let unsubscribe: (() => void) | undefined;
let cancelled = false;
@ -227,15 +238,16 @@ export function ProjectsView() {
});
}
// Detach a view into its own OS window (#23): ask the backend to open the
// window, then mark the slot "detached" so nothing renders for it in the main
// window. Requires an active project (the window is project-scoped). On
// failure the placement is left untouched (no ghost "detached" slot).
// Detach a view into its own OS window (#23, panel-only in #47): ask the
// backend to open the window, then mark the slot "detached" so nothing renders
// for it in the main window. The window is panel-only and follows the focused
// project (published above), so no project id is passed. Requires an active
// project only so a detached window opens onto something rather than the empty
// shell. On failure the placement is left untouched (no ghost "detached" slot).
function detachPanel(panel: PanelId) {
if (!active) return;
const projectId = active.id;
void windowGateway
.openViewWindow(panel, projectId)
.openViewWindow(panel)
.then(() => setPlacement(panel, "detached"))
.catch(() => {
/* window failed to open; keep the current placement */