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

@ -0,0 +1,48 @@
/**
* Tauri adapter for {@link FocusedProjectGateway} (ticket #47). Bridges the
* main-window ⇄ detached-panel focused-project channel to the backend commands
* and event. Like the sibling adapters, this is the only layer allowed to touch
* `@tauri-apps/api`; components reach it exclusively through the port.
*
* Command/event names and payloads mirror the backend contract owned by
* DevBackend: `set_focused_project` / `get_focused_project` and the
* `focused-project://changed` event whose payload is `{ project: FocusedProject
* | null }`.
*/
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import type { Unsubscribe } from "@/domain";
import type { FocusedProject, FocusedProjectGateway } from "@/ports";
/** The Tauri event carrying focused-project transitions. */
const FOCUSED_PROJECT_CHANGED = "focused-project://changed";
/** Backend payload for `focused-project://changed`. */
interface FocusedProjectChanged {
project: FocusedProject | null;
}
export class TauriFocusedProjectGateway implements FocusedProjectGateway {
async setFocusedProject(project: FocusedProject | null): Promise<void> {
// The backend command takes `project: Option<FocusedProjectDto>`; `null`
// clears the focus (no project open).
await invoke("set_focused_project", { project });
}
async getFocusedProject(): Promise<FocusedProject | null> {
const project = await invoke<FocusedProject | null>("get_focused_project");
return project ?? null;
}
async onFocusedProjectChanged(
handler: (project: FocusedProject | null) => void,
): Promise<Unsubscribe> {
const unlisten = await listen<FocusedProjectChanged>(
FOCUSED_PROJECT_CHANGED,
(e) => handler(e.payload.project ?? null),
);
return unlisten;
}
}