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:
@ -219,6 +219,7 @@ describe("loadPlugins", () => {
|
||||
globalThis.__eventServiceKeys = Object.keys(ctx.services.events).sort();
|
||||
globalThis.__configServiceKeys = Object.keys(ctx.services.config).sort();
|
||||
globalThis.__terminalServiceKeys = Object.keys(ctx.services.terminal).sort();
|
||||
globalThis.__windowServiceKeys = Object.keys(ctx.services.windows).sort();
|
||||
}
|
||||
`);
|
||||
const { failures } = await loadPlugins(
|
||||
@ -253,6 +254,7 @@ describe("loadPlugins", () => {
|
||||
"tasks",
|
||||
"terminal",
|
||||
"tooling",
|
||||
"windows",
|
||||
"workspace",
|
||||
]);
|
||||
expect((globalThis as Record<string, unknown>).__workspaceServiceKeys).toEqual([
|
||||
@ -294,6 +296,94 @@ describe("loadPlugins", () => {
|
||||
"open",
|
||||
"reattach",
|
||||
]);
|
||||
expect((globalThis as Record<string, unknown>).__windowServiceKeys).toEqual([
|
||||
"open",
|
||||
]);
|
||||
});
|
||||
|
||||
it("injects services for UI plugins and validates plugin layout windows against declared layouts", async () => {
|
||||
const calls: unknown[] = [];
|
||||
const windowGateways = {
|
||||
...gateways,
|
||||
window: {
|
||||
async openViewWindow() {},
|
||||
async openPluginLayoutWindow(input: unknown) {
|
||||
calls.push(input);
|
||||
return {
|
||||
label: "view-plugin-layout",
|
||||
url: "index.html?pluginLayout=1",
|
||||
alreadyOpen: false,
|
||||
providerPluginDisplayName: "React Plugin",
|
||||
layoutLabel: "Dashboard",
|
||||
surface: {
|
||||
pluginId: "dev.acme.react",
|
||||
layoutType: "dev.acme.react.dashboard",
|
||||
state: { tab: "overview" },
|
||||
},
|
||||
};
|
||||
},
|
||||
async closeViewWindow() {},
|
||||
async listOpenViewWindows() {
|
||||
return [];
|
||||
},
|
||||
async onViewWindowClosed() {
|
||||
return () => {};
|
||||
},
|
||||
},
|
||||
} as PluginGatewaySet;
|
||||
const bundle = dataUrl(`
|
||||
export async function activate(ctx) {
|
||||
globalThis.__uiServicesAvailable = Boolean(ctx.services);
|
||||
globalThis.__pluginWindowResult = await ctx.services.windows.open({
|
||||
layoutType: "dev.acme.react.dashboard",
|
||||
state: { tab: "overview" },
|
||||
});
|
||||
try {
|
||||
await ctx.services.windows.open({ layoutType: "dev.acme.react.missing" });
|
||||
} catch (e) {
|
||||
globalThis.__pluginWindowValidationError = String(e);
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const { failures } = await loadPlugins(
|
||||
[
|
||||
entry({
|
||||
id: "dev.acme.react",
|
||||
displayName: "React Plugin",
|
||||
capabilities: ["ui"],
|
||||
bundleUrl: bundle,
|
||||
contributes: {
|
||||
...emptyContributes(),
|
||||
layouts: [
|
||||
{
|
||||
type: "dev.acme.react.dashboard",
|
||||
label: "Dashboard",
|
||||
component: "Dashboard",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
windowGateways,
|
||||
);
|
||||
|
||||
expect(failures).toEqual([]);
|
||||
expect((globalThis as Record<string, unknown>).__uiServicesAvailable).toBe(true);
|
||||
expect((globalThis as Record<string, unknown>).__pluginWindowResult).toMatchObject({
|
||||
label: "view-plugin-layout",
|
||||
surface: { layoutType: "dev.acme.react.dashboard" },
|
||||
});
|
||||
expect((globalThis as Record<string, unknown>).__pluginWindowValidationError).toMatch(
|
||||
/undeclared layout/,
|
||||
);
|
||||
expect(calls).toEqual([
|
||||
{
|
||||
pluginId: "dev.acme.react",
|
||||
layoutType: "dev.acme.react.dashboard",
|
||||
state: { tab: "overview" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("injects plugin-owned storage scoped to the activating plugin id", async () => {
|
||||
|
||||
Reference in New Issue
Block a user