feat(desktop): add QuantLab desktop shell prototype#190
Conversation
Reviewer's GuideAdds an initial Electron-based QuantLab Desktop shell that auto-starts the existing research_ui server, exposes a chat-centric command bus, and embeds Launch/Runs/Compare/Paper Ops as tabs with a runtime status strip, plus documentation on how to run and position this prototype. Sequence diagram for QuantLab Desktop startup and research_ui server integrationsequenceDiagram
participant OS
participant ElectronApp as Electron_app
participant Main as Electron_main_process
participant Window as BrowserWindow_renderer
participant Preload as Preload_bridge
participant ResearchServer as research_ui_server.py
OS->>ElectronApp: user runs npm start (electron .)
ElectronApp->>Main: initialize main.js
Main->>Main: createMainWindow()
Main->>Window: new BrowserWindow(preload=preload.js, index.html)
Window->>Preload: load preload.js
Preload->>Window: expose window.quantlabDesktop API
Main->>Main: startResearchUiServer()
Main->>ResearchServer: spawn(python, server.py)
Main->>Main: workspaceState.status = starting
Main-->>Window: IPC quantlab:workspace-state(status=starting)
ResearchServer-->>Main: stdout "URL: http://localhost:PORT"
Main->>Main: extractServerUrl(line)
Main->>Main: workspaceState.status = ready
Main->>Main: workspaceState.serverUrl = discoveredUrl
Main-->>Window: IPC quantlab:workspace-state(status=ready, serverUrl)
Note over Window: Renderer setup after DOMContentLoaded
Window->>Preload: window.quantlabDesktop.getWorkspaceState()
Preload->>Main: IPC quantlab:get-workspace-state
Main-->>Preload: workspaceState
Preload-->>Window: workspaceState
Window->>Window: renderWorkspaceState()
Window->>Preload: window.quantlabDesktop.onWorkspaceState(callback)
alt serverUrl present
Window->>Preload: window.quantlabDesktop.requestJson("/outputs/runs/runs_index.json")
Preload->>Main: IPC quantlab:request-json(relativePath)
Main->>ResearchServer: HTTP GET serverUrl + relativePath
ResearchServer-->>Main: JSON runs_index
Main-->>Preload: runs_index JSON
Preload-->>Window: runs_index JSON
Window->>Window: update snapshot, runtime strip, tabs
end
OS-->>ElectronApp: app close signal
ElectronApp->>Main: before-quit
Main->>Main: stopResearchUiServer()
Main->>ResearchServer: kill()
Sequence diagram for chat command handling and tab openingsequenceDiagram
actor User
participant Renderer as Renderer_app.js
participant Preload as Preload_bridge
participant Main as Electron_main_process
participant ResearchUI as Embedded_research_ui_iframe
User->>Renderer: type "open runs" in chat and submit
Renderer->>Renderer: handleChatPrompt(prompt)
Renderer->>Renderer: pushMessage(role=user, content=prompt)
Renderer->>Renderer: normalized = prompt.toLowerCase()
alt prompt matches "open runs"
Renderer->>Renderer: openResearchTab(kind=runs, title=Runs, hash="#/")
alt workspaceState.serverUrl not set
Renderer->>Renderer: pushMessage(assistant, "surface is still starting")
else serverUrl ready
Renderer->>Renderer: id = "runs:#/"
Renderer->>Renderer: check existing tab
Renderer->>Renderer: state.tabs.push({ id, kind, title, url })
Renderer->>Renderer: state.activeTabId = id
Renderer->>Renderer: renderTabs()
Renderer->>Renderer: pushMessage(assistant, "Opened the run explorer.")
Renderer->>ResearchUI: create iframe src="{serverUrl}/research_ui/index.html#/"
end
else other deterministic command
Renderer->>Renderer: route to openResearchTab / openLatestRunTab / summarizeRuntimeInChat
else unknown command
Renderer->>Renderer: pushMessage(assistant, deterministic help message)
end
User->>Renderer: click "Open In Browser"
Renderer->>Preload: window.quantlabDesktop.openExternal(serverUrl + "/research_ui/index.html#/")
Preload->>Main: IPC quantlab:open-external(url)
Main->>Main: shell.openExternal(url)
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 10 security issues, 1 other issue, and left some high level feedback:
Security issues:
- User controlled data in methods like
innerHTML,outerHTMLordocument.writeis an anti-pattern that can lead to XSS vulnerabilities (link) - User controlled data in a
elements.runtimeChips.innerHTMLis an anti-pattern that can lead to XSS vulnerabilities (link) - User controlled data in methods like
innerHTML,outerHTMLordocument.writeis an anti-pattern that can lead to XSS vulnerabilities (link) - User controlled data in a
elements.chatLog.innerHTMLis an anti-pattern that can lead to XSS vulnerabilities (link) - User controlled data in methods like
innerHTML,outerHTMLordocument.writeis an anti-pattern that can lead to XSS vulnerabilities (link) - User controlled data in a
elements.tabsBar.innerHTMLis an anti-pattern that can lead to XSS vulnerabilities (link) - User controlled data in methods like
innerHTML,outerHTMLordocument.writeis an anti-pattern that can lead to XSS vulnerabilities (link) - User controlled data in a
elements.tabContent.innerHTMLis an anti-pattern that can lead to XSS vulnerabilities (link) - User controlled data in methods like
innerHTML,outerHTMLordocument.writeis an anti-pattern that can lead to XSS vulnerabilities (link) - User controlled data in a
elements.paletteResults.innerHTMLis an anti-pattern that can lead to XSS vulnerabilities (link)
General comments:
- In
main.js, thequantlab:request-jsonhandler relies on a globalfetchin the Electron main process; to avoid runtime surprises across Node/Electron versions, consider explicitly importing a fetch implementation (e.g.,node:undiciornode-fetch) or using the built‑inhttpsmodule instead. - The
quantlab:request-jsonIPC handler currently accepts an arbitraryrelativePathfrom the renderer and concatenates it directly ontoserverUrl; you may want to validate/normalize this argument (e.g., enforce a leading/and restrict to expected prefixes) to reduce the risk of unexpected or malformed requests.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `main.js`, the `quantlab:request-json` handler relies on a global `fetch` in the Electron main process; to avoid runtime surprises across Node/Electron versions, consider explicitly importing a fetch implementation (e.g., `node:undici` or `node-fetch`) or using the built‑in `https` module instead.
- The `quantlab:request-json` IPC handler currently accepts an arbitrary `relativePath` from the renderer and concatenates it directly onto `serverUrl`; you may want to validate/normalize this argument (e.g., enforce a leading `/` and restrict to expected prefixes) to reduce the risk of unexpected or malformed requests.
## Individual Comments
### Comment 1
<location path="desktop/renderer/app.js" line_range="379-382" />
<code_context>
+ if (target) target.classList.add("is-active");
+}
+
+function pushMessage(role, content) {
+ state.chatMessages.push({ role, content });
+ renderChat();
+}
</code_context>
<issue_to_address>
**suggestion (performance):** Consider bounding the chat history length to avoid unbounded in-memory growth over long sessions.
`pushMessage` always appends to `state.chatMessages` so long-running sessions may grow this array indefinitely and hurt performance or memory. Consider keeping only the last N messages (e.g., 100–200), for example:
```js
state.chatMessages = [
...state.chatMessages.slice(-99),
{ role, content },
];
```
before calling `renderChat()`.
```suggestion
function pushMessage(role, content) {
// Keep only the last 100 messages (99 previous + the new one)
state.chatMessages = [
...state.chatMessages.slice(-99),
{ role, content },
];
renderChat();
}
```
</issue_to_address>
### Comment 2
<location path="desktop/renderer/app.js" line_range="166-173" />
<code_context>
elements.runtimeChips.innerHTML = [
runtimeChip("QuantLab", status === "ready" ? "up" : status === "starting" ? "starting" : "down", status === "ready" ? "up" : status === "starting" ? "warn" : "down"),
runtimeChip("Runs", `${runs.length} indexed`, runs.length ? "up" : "warn"),
runtimeChip("Paper", String(paperCount), paperCount ? "up" : "warn"),
runtimeChip("Broker", String(brokerCount), brokerCount ? "up" : "warn"),
runtimeChip("Stepbit app", stepbit.frontend_reachable ? "up" : "down", stepbit.frontend_reachable ? "up" : "down"),
runtimeChip("Stepbit core", stepbit.core_ready ? "ready" : stepbit.core_reachable ? "up" : "down", stepbit.core_ready ? "up" : stepbit.core_reachable ? "warn" : "down"),
].join("");
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities
*Source: opengrep*
</issue_to_address>
### Comment 3
<location path="desktop/renderer/app.js" line_range="166-173" />
<code_context>
elements.runtimeChips.innerHTML = [
runtimeChip("QuantLab", status === "ready" ? "up" : status === "starting" ? "starting" : "down", status === "ready" ? "up" : status === "starting" ? "warn" : "down"),
runtimeChip("Runs", `${runs.length} indexed`, runs.length ? "up" : "warn"),
runtimeChip("Paper", String(paperCount), paperCount ? "up" : "warn"),
runtimeChip("Broker", String(brokerCount), brokerCount ? "up" : "warn"),
runtimeChip("Stepbit app", stepbit.frontend_reachable ? "up" : "down", stepbit.frontend_reachable ? "up" : "down"),
runtimeChip("Stepbit core", stepbit.core_ready ? "ready" : stepbit.core_reachable ? "up" : "down", stepbit.core_ready ? "up" : stepbit.core_reachable ? "warn" : "down"),
].join("");
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `elements.runtimeChips.innerHTML` is an anti-pattern that can lead to XSS vulnerabilities
*Source: opengrep*
</issue_to_address>
### Comment 4
<location path="desktop/renderer/app.js" line_range="177-186" />
<code_context>
elements.chatLog.innerHTML = state.chatMessages
.map(
(message) => `
<article class="message ${message.role}">
<div class="message-role">${escapeHtml(message.role)}</div>
<div class="message-body">${escapeHtml(message.content)}</div>
</article>
`
)
.join("");
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities
*Source: opengrep*
</issue_to_address>
### Comment 5
<location path="desktop/renderer/app.js" line_range="177-186" />
<code_context>
elements.chatLog.innerHTML = state.chatMessages
.map(
(message) => `
<article class="message ${message.role}">
<div class="message-role">${escapeHtml(message.role)}</div>
<div class="message-body">${escapeHtml(message.content)}</div>
</article>
`
)
.join("");
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `elements.chatLog.innerHTML` is an anti-pattern that can lead to XSS vulnerabilities
*Source: opengrep*
</issue_to_address>
### Comment 6
<location path="desktop/renderer/app.js" line_range="204-213" />
<code_context>
elements.tabsBar.innerHTML = state.tabs
.map(
(tab) => `
<button class="tab-pill ${tab.id === state.activeTabId ? "is-active" : ""}" data-tab-id="${escapeHtml(tab.id)}" type="button">
<span>${escapeHtml(tab.title)}</span>
<span class="tab-close" data-close-tab="${escapeHtml(tab.id)}">×</span>
</button>
`
)
.join("");
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities
*Source: opengrep*
</issue_to_address>
### Comment 7
<location path="desktop/renderer/app.js" line_range="204-213" />
<code_context>
elements.tabsBar.innerHTML = state.tabs
.map(
(tab) => `
<button class="tab-pill ${tab.id === state.activeTabId ? "is-active" : ""}" data-tab-id="${escapeHtml(tab.id)}" type="button">
<span>${escapeHtml(tab.title)}</span>
<span class="tab-close" data-close-tab="${escapeHtml(tab.id)}">×</span>
</button>
`
)
.join("");
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `elements.tabsBar.innerHTML` is an anti-pattern that can lead to XSS vulnerabilities
*Source: opengrep*
</issue_to_address>
### Comment 8
<location path="desktop/renderer/app.js" line_range="220-222" />
<code_context>
elements.tabContent.innerHTML = activeTab.kind === "placeholder"
? `<div class="tab-placeholder">${escapeHtml(activeTab.content)}</div>`
: `<iframe class="tab-frame" src="${escapeHtml(activeTab.url)}" title="${escapeHtml(activeTab.title)}"></iframe>`;
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities
*Source: opengrep*
</issue_to_address>
### Comment 9
<location path="desktop/renderer/app.js" line_range="220-222" />
<code_context>
elements.tabContent.innerHTML = activeTab.kind === "placeholder"
? `<div class="tab-placeholder">${escapeHtml(activeTab.content)}</div>`
: `<iframe class="tab-frame" src="${escapeHtml(activeTab.url)}" title="${escapeHtml(activeTab.title)}"></iframe>`;
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `elements.tabContent.innerHTML` is an anti-pattern that can lead to XSS vulnerabilities
*Source: opengrep*
</issue_to_address>
### Comment 10
<location path="desktop/renderer/app.js" line_range="243-252" />
<code_context>
elements.paletteResults.innerHTML = paletteActions
.map(
(action) => `
<button class="palette-item" data-palette-action="${escapeHtml(action.id)}" type="button">
<strong>${escapeHtml(action.label)}</strong>
<span>${escapeHtml(action.description)}</span>
</button>
`
)
.join("");
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities
*Source: opengrep*
</issue_to_address>
### Comment 11
<location path="desktop/renderer/app.js" line_range="243-252" />
<code_context>
elements.paletteResults.innerHTML = paletteActions
.map(
(action) => `
<button class="palette-item" data-palette-action="${escapeHtml(action.id)}" type="button">
<strong>${escapeHtml(action.label)}</strong>
<span>${escapeHtml(action.description)}</span>
</button>
`
)
.join("");
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `elements.paletteResults.innerHTML` is an anti-pattern that can lead to XSS vulnerabilities
*Source: opengrep*
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| function pushMessage(role, content) { | ||
| state.chatMessages.push({ role, content }); | ||
| renderChat(); | ||
| } |
There was a problem hiding this comment.
suggestion (performance): Consider bounding the chat history length to avoid unbounded in-memory growth over long sessions.
pushMessage always appends to state.chatMessages so long-running sessions may grow this array indefinitely and hurt performance or memory. Consider keeping only the last N messages (e.g., 100–200), for example:
state.chatMessages = [
...state.chatMessages.slice(-99),
{ role, content },
];before calling renderChat().
| function pushMessage(role, content) { | |
| state.chatMessages.push({ role, content }); | |
| renderChat(); | |
| } | |
| function pushMessage(role, content) { | |
| // Keep only the last 100 messages (99 previous + the new one) | |
| state.chatMessages = [ | |
| ...state.chatMessages.slice(-99), | |
| { role, content }, | |
| ]; | |
| renderChat(); | |
| } |
| elements.runtimeChips.innerHTML = [ | ||
| runtimeChip("QuantLab", status === "ready" ? "up" : status === "starting" ? "starting" : "down", status === "ready" ? "up" : status === "starting" ? "warn" : "down"), | ||
| runtimeChip("Runs", `${runs.length} indexed`, runs.length ? "up" : "warn"), | ||
| runtimeChip("Paper", String(paperCount), paperCount ? "up" : "warn"), | ||
| runtimeChip("Broker", String(brokerCount), brokerCount ? "up" : "warn"), | ||
| runtimeChip("Stepbit app", stepbit.frontend_reachable ? "up" : "down", stepbit.frontend_reachable ? "up" : "down"), | ||
| runtimeChip("Stepbit core", stepbit.core_ready ? "ready" : stepbit.core_reachable ? "up" : "down", stepbit.core_ready ? "up" : stepbit.core_reachable ? "warn" : "down"), | ||
| ].join(""); |
There was a problem hiding this comment.
security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities
Source: opengrep
| elements.runtimeChips.innerHTML = [ | ||
| runtimeChip("QuantLab", status === "ready" ? "up" : status === "starting" ? "starting" : "down", status === "ready" ? "up" : status === "starting" ? "warn" : "down"), | ||
| runtimeChip("Runs", `${runs.length} indexed`, runs.length ? "up" : "warn"), | ||
| runtimeChip("Paper", String(paperCount), paperCount ? "up" : "warn"), | ||
| runtimeChip("Broker", String(brokerCount), brokerCount ? "up" : "warn"), | ||
| runtimeChip("Stepbit app", stepbit.frontend_reachable ? "up" : "down", stepbit.frontend_reachable ? "up" : "down"), | ||
| runtimeChip("Stepbit core", stepbit.core_ready ? "ready" : stepbit.core_reachable ? "up" : "down", stepbit.core_ready ? "up" : stepbit.core_reachable ? "warn" : "down"), | ||
| ].join(""); |
There was a problem hiding this comment.
security (javascript.browser.security.insecure-innerhtml): User controlled data in a elements.runtimeChips.innerHTML is an anti-pattern that can lead to XSS vulnerabilities
Source: opengrep
| elements.chatLog.innerHTML = state.chatMessages | ||
| .map( | ||
| (message) => ` | ||
| <article class="message ${message.role}"> | ||
| <div class="message-role">${escapeHtml(message.role)}</div> | ||
| <div class="message-body">${escapeHtml(message.content)}</div> | ||
| </article> | ||
| ` | ||
| ) | ||
| .join(""); |
There was a problem hiding this comment.
security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities
Source: opengrep
| elements.chatLog.innerHTML = state.chatMessages | ||
| .map( | ||
| (message) => ` | ||
| <article class="message ${message.role}"> | ||
| <div class="message-role">${escapeHtml(message.role)}</div> | ||
| <div class="message-body">${escapeHtml(message.content)}</div> | ||
| </article> | ||
| ` | ||
| ) | ||
| .join(""); |
There was a problem hiding this comment.
security (javascript.browser.security.insecure-innerhtml): User controlled data in a elements.chatLog.innerHTML is an anti-pattern that can lead to XSS vulnerabilities
Source: opengrep
| elements.tabsBar.innerHTML = state.tabs | ||
| .map( | ||
| (tab) => ` | ||
| <button class="tab-pill ${tab.id === state.activeTabId ? "is-active" : ""}" data-tab-id="${escapeHtml(tab.id)}" type="button"> | ||
| <span>${escapeHtml(tab.title)}</span> | ||
| <span class="tab-close" data-close-tab="${escapeHtml(tab.id)}">×</span> | ||
| </button> | ||
| ` | ||
| ) | ||
| .join(""); |
There was a problem hiding this comment.
security (javascript.browser.security.insecure-innerhtml): User controlled data in a elements.tabsBar.innerHTML is an anti-pattern that can lead to XSS vulnerabilities
Source: opengrep
| elements.tabContent.innerHTML = activeTab.kind === "placeholder" | ||
| ? `<div class="tab-placeholder">${escapeHtml(activeTab.content)}</div>` | ||
| : `<iframe class="tab-frame" src="${escapeHtml(activeTab.url)}" title="${escapeHtml(activeTab.title)}"></iframe>`; |
There was a problem hiding this comment.
security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities
Source: opengrep
| elements.tabContent.innerHTML = activeTab.kind === "placeholder" | ||
| ? `<div class="tab-placeholder">${escapeHtml(activeTab.content)}</div>` | ||
| : `<iframe class="tab-frame" src="${escapeHtml(activeTab.url)}" title="${escapeHtml(activeTab.title)}"></iframe>`; |
There was a problem hiding this comment.
security (javascript.browser.security.insecure-innerhtml): User controlled data in a elements.tabContent.innerHTML is an anti-pattern that can lead to XSS vulnerabilities
Source: opengrep
| elements.paletteResults.innerHTML = paletteActions | ||
| .map( | ||
| (action) => ` | ||
| <button class="palette-item" data-palette-action="${escapeHtml(action.id)}" type="button"> | ||
| <strong>${escapeHtml(action.label)}</strong> | ||
| <span>${escapeHtml(action.description)}</span> | ||
| </button> | ||
| ` | ||
| ) | ||
| .join(""); |
There was a problem hiding this comment.
security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities
Source: opengrep
| elements.paletteResults.innerHTML = paletteActions | ||
| .map( | ||
| (action) => ` | ||
| <button class="palette-item" data-palette-action="${escapeHtml(action.id)}" type="button"> | ||
| <strong>${escapeHtml(action.label)}</strong> | ||
| <span>${escapeHtml(action.description)}</span> | ||
| </button> | ||
| ` | ||
| ) | ||
| .join(""); |
There was a problem hiding this comment.
security (javascript.browser.security.insecure-innerhtml): User controlled data in a elements.paletteResults.innerHTML is an anti-pattern that can lead to XSS vulnerabilities
Source: opengrep
Summary
This PR introduces the first
QuantLab Desktopshell as a focused prototype.It adds:
desktop/research_ui/server.pyfrom the desktop main processLaunch,Runs,Compare, andPaper OpsWhy This Exists
QuantLab already has useful local surfaces, but they are still fragmented across browser flows and manual process management.
This block starts moving the product toward a single desktop workspace by:
research_uiinstead of rewriting too much at oncechat + tabs + runtime visibilityWhat This Block Delivers
sidebar + chat + tabs + runtime strip.research_uisurfaces.open runs,open compare,open latest run, andshow runtime status.Out Of Scope
This PR does not try to do all of QuantLab Desktop at once.
Still out of scope:
research_uiFiles Of Interest
desktop/main.jsdesktop/preload.jsdesktop/renderer/index.htmldesktop/renderer/app.jsdesktop/renderer/styles.cssdesktop/README.mddocs/quantlab-desktop-v1.mdValidation
npm installindesktop/npm exec electron -- --versionnode --check desktop/main.jsnode --check desktop/preload.jsnode --check desktop/renderer/app.jspython -m pytest test/test_research_ui_server.pyNotes
This is intentionally a
v0shell.The goal is not to finish QuantLab Desktop in one PR, but to establish the first desktop foundation so the next blocks can add: