Files
tutti/apps/desktop/test/fixtures/workspaceAppPopupRenderer.electron.fixture.tsx
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

167 lines
5.5 KiB
TypeScript

import { ipcRenderer } from "electron";
import { createRoot } from "react-dom/client";
import type { BrowserNodeEvent } from "@tutti-os/browser-node";
import { createBrowserWorkbenchLaunchHandler } from "@tutti-os/browser-node/workbench";
import {
createWorkbenchSnapshotFromState,
WorkbenchHost,
type WorkbenchHostHandle,
type WorkbenchHostNodeDefinition
} from "@tutti-os/workbench-surface";
import { createBrowserDesktopApi } from "../../src/preload/api/browser.ts";
import { createWorkspaceAppDesktopApi } from "../../src/preload/api/workspaceApp.ts";
import { createTranslator } from "../../src/shared/i18n/index.ts";
import { registerWorkspaceAppPopupNotifications } from "../../src/renderer/src/app/windows/workspace/workspaceAppPopupNotifications.ts";
import { createWorkspaceBrowserService } from "../../src/renderer/src/features/workspace-workbench/services/internal/workspaceBrowserService.ts";
import { createWorkspaceAppBrowserFeature } from "../../src/renderer/src/features/workspace-workbench/services/internal/contributions/workspaceAppBrowserFeature.ts";
import { createWorkbenchWorkspaceBrowserPresenter } from "../../src/renderer/src/features/workspace-workbench/services/workbenchWorkspaceBrowserPresenter.ts";
import { registerWorkspaceBrowserLaunchHandler } from "../../src/renderer/src/features/workspace-workbench/services/workspaceBrowserLaunchCoordinator.ts";
const rendererReadyChannel = "workspace-app-popup-test:renderer-ready";
const rendererAckChannel = "workspace-app-popup-test:browser-event";
const rendererObservationChannel = "workspace-app-popup-test:observation";
const workspaceId = "workspace-app-popup-integration";
const browserTypeId = "browser";
let browserEvents = 0;
let rejectionNotifications = 0;
let workbenchLaunches = 0;
let workbenchHost: WorkbenchHostHandle | null = null;
let releaseLaunchHandler: (() => void) | null = null;
const browserApi = createBrowserDesktopApi();
const workspaceAppApi = createWorkspaceAppDesktopApi();
browserApi.onEvent((browserEvent: BrowserNodeEvent) => {
browserEvents += 1;
ipcRenderer.send(rendererAckChannel, browserEvent);
reportObservation();
void reportWhenBrowserSurfacesReach(browserEvents);
});
const workspaceBrowserService = createWorkspaceBrowserService({ browserApi });
createWorkspaceAppBrowserFeature({
browserApi,
browserService: workspaceBrowserService,
runtimeApi: {
logRendererDiagnostic() {
return Promise.resolve();
}
},
workspaceId
});
registerWorkspaceAppPopupNotifications({
notifications: {
error() {
rejectionNotifications += 1;
}
} as never,
translate: createTranslator("en").t,
workspaceAppApi
});
const browserNodeDefinition: WorkbenchHostNodeDefinition = {
frame: { height: 560, width: 900, x: 220, y: 130 },
instance: { mode: "multi" },
renderBody: () => null,
title: "Browser",
typeId: browserTypeId,
window: {
defaultOpen: false,
restoreOnLoad: true
}
};
const resolveBrowserLaunch = createBrowserWorkbenchLaunchHandler({
browserInstancePrefix: "workspace-app-popup-integration",
typeId: browserTypeId
});
const rootElement = document.querySelector("#root");
if (!(rootElement instanceof HTMLElement)) {
throw new Error("Workspace App popup fixture root is unavailable");
}
createRoot(rootElement).render(
<WorkbenchHost
nodes={[browserNodeDefinition]}
onHandleReady={(host) => {
workbenchHost = host;
releaseLaunchHandler?.();
releaseLaunchHandler = null;
if (!host) {
return;
}
const countingHost = {
activateNode: host.activateNode.bind(host),
focusNode: host.focusNode.bind(host),
getSnapshot: host.getSnapshot.bind(host),
async launchNode(request) {
workbenchLaunches += 1;
reportObservation();
const nodeId = await host.launchNode(request);
reportObservation();
return nodeId;
}
} as WorkbenchHostHandle;
releaseLaunchHandler = registerWorkspaceBrowserLaunchHandler(
workspaceId,
createWorkbenchWorkspaceBrowserPresenter({
browserPages: {
openPage: (request) => workspaceBrowserService.openPage(request)
},
host: countingHost
})
);
ipcRenderer.send(rendererReadyChannel);
}}
onLaunchRequest={resolveBrowserLaunch}
snapshotRepository={{
async load() {
return createWorkbenchSnapshotFromState(
{ nodeStack: [], nodes: [] },
{ metadata: { workbenchHostInitialized: true } }
);
},
save(_workspaceId, snapshot) {
return snapshot;
}
}}
workspaceId={workspaceId}
/>
);
async function reportWhenBrowserSurfacesReach(
expectedBrowserSurfaces: number
): Promise<void> {
const deadline = Date.now() + 5_000;
while (
workbenchLaunches < expectedBrowserSurfaces ||
countBrowserSurfaces() < expectedBrowserSurfaces
) {
if (Date.now() >= deadline) {
reportObservation();
throw new Error("timed out waiting for Workbench Browser surfaces");
}
await new Promise((resolve) => setTimeout(resolve, 10));
}
reportObservation();
}
function countBrowserSurfaces(): number {
return (
workbenchHost
?.getSnapshot()
.nodes.filter((node) => node.data.typeId === browserTypeId).length ?? 0
);
}
function reportObservation(): void {
ipcRenderer.send(rendererObservationChannel, {
browserEvents,
browserSurfaces: countBrowserSurfaces(),
rejectionNotifications,
workbenchLaunches
});
}