diff --git a/.agents/session-log.md b/.agents/session-log.md index e5d229b..392eb68 100644 --- a/.agents/session-log.md +++ b/.agents/session-log.md @@ -389,7 +389,8 @@ - 2026-04-10: Completed issue #342 primary workbench ownership. The renderer now marks workbench-heavy surfaces explicitly and collapses the outer support lane plus internal two-column workbench grids earlier, so `Runs`, `Compare`, `Candidates`, `Run Detail`, and `Paper Ops` stop holding two side rails at the same laptop-width breakpoint. Validation passed with `node --check desktop/renderer/app.js`, `npm run smoke:fallback`, and `npm run smoke:real-path`. - 2026-04-10: Started issue #346 to harden `desktop-smoke` result persistence after CI repeatedly failed with raw `ENOENT` on missing `result.json` in a planning-only PR. The slice is limited to `desktop/main.js`, `desktop/scripts/smoke.js`, and `.agents` continuity so smoke emits structured failures instead of crashing when Electron exits too early. - 2026-04-10: Added the desktop layout regression remediation block after reviewing the post-merge desktop state in real screenshots. Opened issues #342, #343, and #344 to target empty-pane collapse, stronger active-surface focus and context containment, and better runs-family density plus right-rail space budgeting without reopening core or `research_ui` scope. -<<<<<<< HEAD - 2026-04-10: Added the Desktop architecture migration block after accepting the ADR direction for the new shell foundation. Opened issues #350, #354, #353, #355, #352, #359, #351, #357, #358, and #356 so the migration is tracked as real slices rather than dozens of micro-backlog items. - 2026-04-10: Started the desktop shared contracts foundation slice on branch `codex/desktop-shared-workspace-contract`. The goal is to establish the first real shared Desktop contract layer before opening the larger `desktop-typescript-base` slice, limited to workspace, runtime/snapshot, and smoke boundaries. - 2026-04-10: Completed the desktop shared contracts foundation slice. Added `desktop/shared` contracts for IPC channels, envelopes, workspace state, runtime state, snapshot status, and smoke results, and connected them back into `desktop/main.js`, `desktop/preload.js`, `desktop/renderer/app.js`, and `desktop/scripts/smoke.js` via narrow JSDoc typing only, without changing desktop behavior. +- 2026-04-10: Started issue #354 on branch `codex/desktop-typescript-base`. This slice is limited to the real TypeScript base for `desktop/`: `tsconfig.json`, `types/global.d.ts`, package wiring, and only the minimum edits required in `main.js`, `preload.js`, and `renderer/app.js` so typecheck passes without changing desktop behavior. +- 2026-04-10: Completed issue #354 TypeScript base real across Desktop. Added `desktop/tsconfig.json`, `desktop/types/global.d.ts`, and package wiring for `typecheck`, then applied only the minimum `@ts-check` and DOM/type narrowing changes in `desktop/main.js`, `desktop/preload.js`, and `desktop/renderer/app.js` so `npm run typecheck`, `node --check`, `npm run smoke:fallback`, and `npm run smoke:real-path` all pass without changing visible desktop behavior. diff --git a/.agents/tasks/issue-354-typescript-base-across-desktop.md b/.agents/tasks/issue-354-typescript-base-across-desktop.md index 234379e..2631eb0 100644 --- a/.agents/tasks/issue-354-typescript-base-across-desktop.md +++ b/.agents/tasks/issue-354-typescript-base-across-desktop.md @@ -1,15 +1,60 @@ # Issue #354 — TypeScript Base Across Desktop ## Goal -Establish the real TypeScript base for `desktop/` without changing runtime behavior. +Establish the real TypeScript base for `desktop/` without changing runtime behavior or opening the renderer migration yet. + +--- + +## Why this matters +- turns `desktop/shared` into a first-class typed contract layer +- prepares the modularization of `main.js` without spreading JS debt into more files first +- keeps the migration sequence disciplined: contracts first, tooling second, architecture refactor third + +--- + +## Scope + +### In scope +- `desktop/tsconfig.json` +- `desktop/types/global.d.ts` +- `desktop/package.json` +- minimal typecheck-driven edits in `desktop/main.js`, `desktop/preload.js`, and `desktop/renderer/app.js` +- native consumption of `desktop/shared/ipc` and `desktop/shared/models` + +### Out of scope +- modularizing `main.js` +- React +- Vite +- `research_ui` +- new shared models +- functional behavior changes +- visible UI changes + +--- + +## Relevant files + +- `desktop/package.json` +- `desktop/tsconfig.json` +- `desktop/types/global.d.ts` +- `desktop/main.js` +- `desktop/preload.js` +- `desktop/renderer/app.js` +- `desktop/shared/` + +--- ## Expected deliverable -- `tsconfig.json` -- `types/global.d.ts` -- `typecheck` -- native use of `desktop/shared` + +- TypeScript base configuration for the current Desktop shell +- current Desktop entrypoints passing `typecheck` while staying in JS +- no runtime behavior change + +--- ## Done when -- current Desktop JS entrypoints pass typecheck -- smoke remains green -- no functional change is introduced + +- `npm run typecheck` passes in `desktop/` +- `node --check` passes for touched JS entrypoints +- `smoke:fallback` and `smoke:real-path` stay green +- `main.js`, `preload.js`, and `renderer/app.js` consume `desktop/shared` natively through the TS base diff --git a/desktop/main.js b/desktop/main.js index d29660e..66c09bc 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -1,3 +1,5 @@ +// @ts-check + const { app, BrowserWindow, ipcMain, shell } = require("electron"); const fs = require("fs"); const fsp = require("fs/promises"); diff --git a/desktop/package-lock.json b/desktop/package-lock.json index 777afc9..8756745 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -9,7 +9,9 @@ "version": "0.1.0", "devDependencies": { "@modelcontextprotocol/sdk": "^1.20.0", + "@types/node": "^24.6.1", "electron": "^37.2.0", + "typescript": "^5.9.3", "zod": "^4.1.12" } }, @@ -146,13 +148,13 @@ } }, "node_modules/@types/node": { - "version": "22.19.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", - "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/responselike": { @@ -583,6 +585,23 @@ "node": ">= 12.20.55" } }, + "node_modules/electron/node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -1914,10 +1933,24 @@ "node": ">= 0.6" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, diff --git a/desktop/package.json b/desktop/package.json index b4d996c..fbf3b9c 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -7,6 +7,7 @@ "scripts": { "start": "electron .", "dev": "electron .", + "typecheck": "tsc --project tsconfig.json --noEmit", "smoke": "npm run smoke:fallback", "smoke:fallback": "node scripts/smoke.js --mode=fallback", "smoke:real-path": "node scripts/smoke.js --mode=real-path", @@ -14,7 +15,9 @@ }, "devDependencies": { "@modelcontextprotocol/sdk": "^1.20.0", + "@types/node": "^24.6.1", "electron": "^37.2.0", + "typescript": "^5.9.3", "zod": "^4.1.12" } } diff --git a/desktop/preload.js b/desktop/preload.js index 55ce161..c1a8ec2 100644 --- a/desktop/preload.js +++ b/desktop/preload.js @@ -1,3 +1,5 @@ +// @ts-check + const { contextBridge, ipcRenderer } = require("electron"); /** @typedef {import("./shared/models/workspace").WorkspaceState} WorkspaceState */ diff --git a/desktop/renderer/app.js b/desktop/renderer/app.js index 2dee368..3f5fb8d 100644 --- a/desktop/renderer/app.js +++ b/desktop/renderer/app.js @@ -1,3 +1,5 @@ +// @ts-check + import * as decisionStore from "./modules/decision-store.js"; import { NAV_ACTION_BY_KIND, @@ -41,6 +43,8 @@ import { /** @typedef {import("../shared/models/runtime").RuntimeStatus} RuntimeStatus */ /** @typedef {import("../shared/models/runtime").RuntimeChipState} RuntimeChipState */ /** @typedef {import("../shared/models/snapshot").SnapshotStatus} SnapshotStatus */ +/** @typedef {import("../shared/models/snapshot").SnapshotSource} SnapshotSource */ +/** @typedef {import("../shared/models/workspace").WorkspaceState} WorkspaceState */ const CONFIG = { runsIndexPath: "/outputs/runs/runs_index.json", @@ -70,7 +74,8 @@ const CONFIG = { let unsubscribeWorkspaceState = null; const state = { - workspace: { status: "starting", serverUrl: null, logs: [], error: null }, + /** @type {WorkspaceState} */ + workspace: { status: "starting", serverUrl: null, logs: [], error: null, source: null }, snapshot: null, candidatesStore: defaultCandidatesStore(), candidatesLoaded: false, @@ -124,52 +129,52 @@ const WORKBENCH_PRIORITY_KINDS = new Set([ ]); const elements = { - runtimeSummary: document.getElementById("runtime-summary"), - runtimeMeta: document.getElementById("runtime-meta"), - runtimeAlert: document.getElementById("runtime-alert"), - runtimeRetry: document.getElementById("runtime-retry"), - runtimeChips: document.getElementById("runtime-chips"), - topbarEyebrow: document.getElementById("topbar-eyebrow"), - chatLog: document.getElementById("chat-log"), - chatForm: document.getElementById("chat-form"), - chatInput: document.getElementById("chat-input"), - chatStepbit: document.getElementById("chat-stepbit"), - chatAdapterStatus: document.getElementById("chat-adapter-status"), - tabsBar: document.getElementById("tabs-bar"), - tabContent: document.getElementById("tab-content"), - topbarTitle: document.getElementById("topbar-title"), - topbarSurfaceChip: document.getElementById("topbar-surface-chip"), - topbarRuntimeChip: document.getElementById("topbar-runtime-chip"), - topbarServerChip: document.getElementById("topbar-server-chip"), - paletteSearch: document.getElementById("palette-search"), - paletteInput: document.getElementById("palette-input"), - runCommand: document.getElementById("run-command"), - commandPaletteTrigger: document.getElementById("command-palette-trigger"), - openBrowserRuns: document.getElementById("open-browser-runs"), - paletteOverlay: document.getElementById("palette-overlay"), - closePalette: document.getElementById("close-palette"), - paletteResults: document.getElementById("palette-results"), - workflowLaunchForm: document.getElementById("workflow-launch-form"), - workflowLaunchCommand: document.getElementById("workflow-launch-command"), - workflowRunFields: document.getElementById("workflow-run-fields"), - workflowSweepFields: document.getElementById("workflow-sweep-fields"), - workflowLaunchTicker: document.getElementById("workflow-launch-ticker"), - workflowLaunchStart: document.getElementById("workflow-launch-start"), - workflowLaunchEnd: document.getElementById("workflow-launch-end"), - workflowLaunchInterval: document.getElementById("workflow-launch-interval"), - workflowLaunchCash: document.getElementById("workflow-launch-cash"), - workflowLaunchPaper: document.getElementById("workflow-launch-paper"), - workflowLaunchConfigPath: document.getElementById("workflow-launch-config-path"), - workflowLaunchOutDir: document.getElementById("workflow-launch-out-dir"), - workflowLaunchMeta: document.getElementById("workflow-launch-meta"), - workflowLaunchFeedback: document.getElementById("workflow-launch-feedback"), - workflowLaunchSubmit: document.getElementById("workflow-launch-submit"), - workflowJobsList: document.getElementById("workflow-jobs-list"), - workflowRunsMeta: document.getElementById("workflow-runs-meta"), - workflowRunsList: document.getElementById("workflow-runs-list"), - workflowOpenCompare: document.getElementById("workflow-open-compare"), - workflowClearSelection: document.getElementById("workflow-clear-selection"), - workspaceGrid: document.querySelector(".workspace-grid"), + runtimeSummary: /** @type {HTMLDivElement} */ (document.getElementById("runtime-summary")), + runtimeMeta: /** @type {HTMLDivElement} */ (document.getElementById("runtime-meta")), + runtimeAlert: /** @type {HTMLDivElement} */ (document.getElementById("runtime-alert")), + runtimeRetry: /** @type {HTMLButtonElement} */ (document.getElementById("runtime-retry")), + runtimeChips: /** @type {HTMLDivElement} */ (document.getElementById("runtime-chips")), + topbarEyebrow: /** @type {HTMLDivElement} */ (document.getElementById("topbar-eyebrow")), + chatLog: /** @type {HTMLDivElement} */ (document.getElementById("chat-log")), + chatForm: /** @type {HTMLFormElement} */ (document.getElementById("chat-form")), + chatInput: /** @type {HTMLTextAreaElement} */ (document.getElementById("chat-input")), + chatStepbit: /** @type {HTMLButtonElement} */ (document.getElementById("chat-stepbit")), + chatAdapterStatus: /** @type {HTMLDivElement} */ (document.getElementById("chat-adapter-status")), + tabsBar: /** @type {HTMLDivElement} */ (document.getElementById("tabs-bar")), + tabContent: /** @type {HTMLDivElement} */ (document.getElementById("tab-content")), + topbarTitle: /** @type {HTMLHeadingElement} */ (document.getElementById("topbar-title")), + topbarSurfaceChip: /** @type {HTMLDivElement} */ (document.getElementById("topbar-surface-chip")), + topbarRuntimeChip: /** @type {HTMLDivElement} */ (document.getElementById("topbar-runtime-chip")), + topbarServerChip: /** @type {HTMLDivElement} */ (document.getElementById("topbar-server-chip")), + paletteSearch: /** @type {HTMLInputElement} */ (document.getElementById("palette-search")), + paletteInput: /** @type {HTMLInputElement} */ (document.getElementById("palette-input")), + runCommand: /** @type {HTMLButtonElement} */ (document.getElementById("run-command")), + commandPaletteTrigger: /** @type {HTMLButtonElement} */ (document.getElementById("command-palette-trigger")), + openBrowserRuns: /** @type {HTMLButtonElement} */ (document.getElementById("open-browser-runs")), + paletteOverlay: /** @type {HTMLDivElement} */ (document.getElementById("palette-overlay")), + closePalette: /** @type {HTMLButtonElement} */ (document.getElementById("close-palette")), + paletteResults: /** @type {HTMLDivElement} */ (document.getElementById("palette-results")), + workflowLaunchForm: /** @type {HTMLFormElement} */ (document.getElementById("workflow-launch-form")), + workflowLaunchCommand: /** @type {HTMLSelectElement} */ (document.getElementById("workflow-launch-command")), + workflowRunFields: /** @type {HTMLDivElement} */ (document.getElementById("workflow-run-fields")), + workflowSweepFields: /** @type {HTMLDivElement} */ (document.getElementById("workflow-sweep-fields")), + workflowLaunchTicker: /** @type {HTMLInputElement} */ (document.getElementById("workflow-launch-ticker")), + workflowLaunchStart: /** @type {HTMLInputElement} */ (document.getElementById("workflow-launch-start")), + workflowLaunchEnd: /** @type {HTMLInputElement} */ (document.getElementById("workflow-launch-end")), + workflowLaunchInterval: /** @type {HTMLInputElement} */ (document.getElementById("workflow-launch-interval")), + workflowLaunchCash: /** @type {HTMLInputElement} */ (document.getElementById("workflow-launch-cash")), + workflowLaunchPaper: /** @type {HTMLInputElement} */ (document.getElementById("workflow-launch-paper")), + workflowLaunchConfigPath: /** @type {HTMLInputElement} */ (document.getElementById("workflow-launch-config-path")), + workflowLaunchOutDir: /** @type {HTMLInputElement} */ (document.getElementById("workflow-launch-out-dir")), + workflowLaunchMeta: /** @type {HTMLDivElement} */ (document.getElementById("workflow-launch-meta")), + workflowLaunchFeedback: /** @type {HTMLDivElement} */ (document.getElementById("workflow-launch-feedback")), + workflowLaunchSubmit: /** @type {HTMLButtonElement} */ (document.getElementById("workflow-launch-submit")), + workflowJobsList: /** @type {HTMLDivElement} */ (document.getElementById("workflow-jobs-list")), + workflowRunsMeta: /** @type {HTMLDivElement} */ (document.getElementById("workflow-runs-meta")), + workflowRunsList: /** @type {HTMLDivElement} */ (document.getElementById("workflow-runs-list")), + workflowOpenCompare: /** @type {HTMLButtonElement} */ (document.getElementById("workflow-open-compare")), + workflowClearSelection: /** @type {HTMLButtonElement} */ (document.getElementById("workflow-clear-selection")), + workspaceGrid: /** @type {HTMLElement} */ (document.querySelector(".workspace-grid")), }; const paletteActions = PALETTE_ACTION_SPECS.map((action) => ({ @@ -228,8 +233,9 @@ window.addEventListener("beforeunload", () => { function bindEvents() { document.querySelectorAll("[data-action]").forEach((button) => { - button.addEventListener("click", () => { - const action = button.dataset.action; + const actionButton = /** @type {HTMLElement} */ (button); + actionButton.addEventListener("click", () => { + const action = actionButton.dataset.action; if (action === "open-assistant") focusAssistant(); if (action === "open-system") openSystemTab(); if (action === "open-experiments") openExperimentsTab(); @@ -241,8 +247,9 @@ function bindEvents() { }); }); document.querySelectorAll("[data-prompt]").forEach((button) => { - button.addEventListener("click", () => { - const prompt = button.dataset.prompt || ""; + const promptButton = /** @type {HTMLElement} */ (button); + promptButton.addEventListener("click", () => { + const prompt = promptButton.dataset.prompt || ""; elements.chatInput.value = prompt; handleChatPrompt(prompt); }); @@ -408,18 +415,26 @@ async function refreshSnapshot() { } } +/** + * @returns {Promise<{ runsRegistry: any, source: SnapshotSource, primaryError: Error | null }>} + */ async function loadRunsRegistrySnapshot() { + /** @type {Error | null} */ let primaryError = null; if (state.workspace.serverUrl) { try { const runsRegistry = await window.quantlabDesktop.requestJson(CONFIG.runsIndexPath); - return { runsRegistry, source: "api", primaryError: null }; + /** @type {SnapshotSource} */ + const source = "api"; + return { runsRegistry, source, primaryError: null }; } catch (error) { - primaryError = error; + primaryError = /** @type {Error} */ (error); } } const runsRegistry = await window.quantlabDesktop.readProjectJson(CONFIG.localRunsIndexPath); - return { runsRegistry, source: "local", primaryError }; + /** @type {SnapshotSource} */ + const source = "local"; + return { runsRegistry, source, primaryError }; } function renderAll() { @@ -1001,9 +1016,9 @@ function renderTabs() { } else if (activeTab.kind === "candidates") { renderMarkupInto(elements.tabContent, renderCandidatesTab(activeTab)); } else if (activeTab.kind === "paper") { - renderMarkupInto(elements.tabContent, renderPaperOpsTab(activeTab)); + renderMarkupInto(elements.tabContent, renderPaperOpsTab()); } else if (activeTab.kind === "system") { - renderMarkupInto(elements.tabContent, renderSystemTab(activeTab)); + renderMarkupInto(elements.tabContent, renderSystemTab()); } else if (activeTab.kind === "job") { renderMarkupInto(elements.tabContent, renderJobTab(activeTab)); } else { @@ -1054,8 +1069,9 @@ function renderPalette() { ); } elements.paletteResults.querySelectorAll("[data-palette-action]").forEach((button) => { - button.addEventListener("click", () => { - const action = paletteActions.find((entry) => entry.id === button.dataset.paletteAction); + const paletteButton = /** @type {HTMLElement} */ (button); + paletteButton.addEventListener("click", () => { + const action = paletteActions.find((entry) => entry.id === paletteButton.dataset.paletteAction); if (!action) return; action.run(); state.paletteOpen = false; @@ -1270,16 +1286,18 @@ function renderRunsWorklist(runs) { function bindTabChromeEvents() { elements.tabsBar.querySelectorAll("[data-tab-id]").forEach((button) => { - button.addEventListener("click", () => { - state.activeTabId = button.dataset.tabId; + const tabButton = /** @type {HTMLElement} */ (button); + tabButton.addEventListener("click", () => { + state.activeTabId = tabButton.dataset.tabId; renderTabs(); scheduleShellWorkspacePersist(); }); }); elements.tabsBar.querySelectorAll("[data-close-tab]").forEach((button) => { - button.addEventListener("click", (event) => { + const closeButton = /** @type {HTMLElement} */ (button); + closeButton.addEventListener("click", (event) => { event.stopPropagation(); - closeTab(button.dataset.closeTab); + closeTab(closeButton.dataset.closeTab); }); }); } diff --git a/desktop/tsconfig.json b/desktop/tsconfig.json new file mode 100644 index 0000000..acdf200 --- /dev/null +++ b/desktop/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "preserve", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM"], + "allowJs": true, + "checkJs": false, + "noEmit": true, + "strict": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "types": ["node"] + }, + "include": [ + "main.js", + "preload.js", + "renderer/app.js", + "shared/**/*.ts", + "types/**/*.d.ts" + ] +} diff --git a/desktop/types/global.d.ts b/desktop/types/global.d.ts new file mode 100644 index 0000000..a943b7c --- /dev/null +++ b/desktop/types/global.d.ts @@ -0,0 +1,30 @@ +import type { WorkspaceState, WorkspaceStateListener } from "../shared/models/workspace"; + +interface QuantlabDesktopBridge { + getWorkspaceState(): Promise; + requestJson(relativePath: string): Promise; + requestText(relativePath: string): Promise; + getCandidatesStore(): Promise; + saveCandidatesStore(store: any): Promise; + getSweepDecisionStore(): Promise; + saveSweepDecisionStore(store: any): Promise; + getShellWorkspaceStore(): Promise; + saveShellWorkspaceStore(store: any): Promise; + listDirectory(targetPath: string, maxDepth?: number): Promise; + readProjectText(targetPath: string): Promise; + readProjectJson(targetPath: string): Promise; + postJson(relativePath: string, payload: any): Promise; + restartWorkspaceServer(): Promise; + askStepbitChat(payload: any): Promise; + openExternal(url: string): Promise<{ ok: true }>; + openPath(targetPath: string): Promise<{ ok: true }>; + onWorkspaceState(callback: WorkspaceStateListener): () => void; +} + +declare global { + interface Window { + quantlabDesktop: QuantlabDesktopBridge; + } +} + +export {};