feat(frontend): host les fenêtres plugin et l'API publique windows.open

Ajoute la vue window host qui charge le layout plugin dans une fenêtre OS
détachée, expose react/react-dom/jsx-runtime hébergés sous
frontend/public/plugin-host pour que le runtime SDK y résolve React, et
câble services.windows.open() côté runtime plugin (loader/services/registry).

npm test -- --run src/adapters/window.test.ts src/app/ViewWindow.test.tsx
  src/plugins/runtime/loader.test.ts src/plugins/runtime/services.test.ts : 39/39
npm run typecheck : OK
npm run build : OK

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 00:39:50 +02:00
parent 551eb09ad2
commit d8df466fba
20 changed files with 678 additions and 10 deletions

View File

@ -29,6 +29,41 @@ describe("TauriWindowGateway (#23)", () => {
});
});
it("opens plugin layout windows through the backend command", async () => {
invoke.mockResolvedValueOnce({
label: "view-plugin-layout",
url: "index.html?pluginLayout=1",
alreadyOpen: false,
providerPluginDisplayName: "Plugin",
layoutLabel: "Dashboard",
surface: {
pluginId: "dev.acme.plugin",
layoutType: "dev.acme.plugin.dashboard",
state: { tab: "overview" },
},
});
const gw = new TauriWindowGateway();
await expect(
gw.openPluginLayoutWindow({
pluginId: "dev.acme.plugin",
layoutType: "dev.acme.plugin.dashboard",
state: { tab: "overview" },
}),
).resolves.toMatchObject({
label: "view-plugin-layout",
surface: { layoutType: "dev.acme.plugin.dashboard" },
});
expect(invoke).toHaveBeenCalledWith("open_plugin_layout_window", {
input: {
pluginId: "dev.acme.plugin",
layoutType: "dev.acme.plugin.dashboard",
state: { tab: "overview" },
},
});
});
it("onViewWindowClosed listens on the lifecycle channel and forwards only closes", async () => {
// Capture the raw Tauri listener so we can drive lifecycle events at will.
let raw: ((e: { payload: unknown }) => void) | undefined;