feat(ui): fenêtres View système séparées — WebviewWindow Tauri + port WindowGateway (#23)
Détache les vues dans de vraies fenêtres système Tauri (multi-écran, fullscreen). Backend : commandes de gestion de WebviewWindow, capabilities et composition root. Frontend : nouveau port WindowGateway et son adaptateur window, entrée panel-only ViewWindow/ViewPanelBody, détachement câblé dans ProjectsView. QA vert : app-tauri 63, frontend typecheck + vitest 546/546. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -84,6 +84,8 @@ import type {
|
||||
TicketListQuery,
|
||||
CreateTicketInput,
|
||||
UpdateTicketInput,
|
||||
ViewWindowClosed,
|
||||
WindowGateway,
|
||||
WorkStateGateway,
|
||||
} from "@/ports";
|
||||
import { normalizeProjectWorkState } from "../workStateNormalization";
|
||||
@ -1086,6 +1088,51 @@ class MockRemoteGateway implements RemoteGateway {
|
||||
async connect(): Promise<void> {}
|
||||
}
|
||||
|
||||
/**
|
||||
* In-memory {@link WindowGateway} for tests/dev (#23). Tracks which views are
|
||||
* "detached" in a set keyed by `panel|projectId`, and lets tests simulate the
|
||||
* OS closing a window via {@link simulateOsClose}. `closeViewWindow` also emits
|
||||
* the close event, mirroring the backend (a programmatic close still notifies).
|
||||
*/
|
||||
export class MockWindowGateway implements WindowGateway {
|
||||
/** Currently-open detached windows, keyed `panel|projectId`. */
|
||||
readonly open = new Set<string>();
|
||||
private readonly listeners = new Set<(e: ViewWindowClosed) => void>();
|
||||
|
||||
private key(panel: string, projectId: string): string {
|
||||
return `${panel}|${projectId}`;
|
||||
}
|
||||
|
||||
async openViewWindow(panel: string, projectId: string): Promise<void> {
|
||||
this.open.add(this.key(panel, projectId));
|
||||
}
|
||||
|
||||
async closeViewWindow(panel: string, projectId: string): Promise<void> {
|
||||
if (this.open.delete(this.key(panel, projectId))) {
|
||||
this.emit({ panel, projectId });
|
||||
}
|
||||
}
|
||||
|
||||
async onViewWindowClosed(
|
||||
handler: (event: ViewWindowClosed) => void,
|
||||
): Promise<Unsubscribe> {
|
||||
this.listeners.add(handler);
|
||||
return () => {
|
||||
this.listeners.delete(handler);
|
||||
};
|
||||
}
|
||||
|
||||
/** Test hook: simulate the user closing the OS window (fires the event). */
|
||||
simulateOsClose(panel: string, projectId: string): void {
|
||||
this.open.delete(this.key(panel, projectId));
|
||||
this.emit({ panel, projectId });
|
||||
}
|
||||
|
||||
private emit(event: ViewWindowClosed): void {
|
||||
for (const l of this.listeners) l(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The pre-filled reference catalogue the mock serves — mirror of the backend's
|
||||
* **selectable** catalogue (§17.3/D7): only profiles drivable in structured mode
|
||||
@ -2400,6 +2447,7 @@ export function createMockGateways(): Gateways {
|
||||
workState: new MockWorkStateGateway(),
|
||||
conversation: new MockConversationGateway(),
|
||||
ticket: new MockTicketGateway(systemGateway),
|
||||
window: new MockWindowGateway(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -30,6 +30,7 @@ describe("createMockGateways", () => {
|
||||
"template",
|
||||
"terminal",
|
||||
"ticket",
|
||||
"window",
|
||||
"workState",
|
||||
]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user