Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/publish-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ jobs:
working-directory: apps/desktop

- name: Build Electron main/preload/renderer
env:
# Crash-report DSN baked into the main bundle (see the define in
# electron.vite.config.ts). Unset (forks, local) → crash reporting
# is compiled out and dumps stay local.
DESKTOP_SENTRY_DSN: ${{ vars.DESKTOP_SENTRY_DSN }}
run: bunx --bun electron-vite build
working-directory: apps/desktop

Expand Down
7 changes: 7 additions & 0 deletions apps/desktop/electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@ const ELECTRON_EXTERNALS = [
"electron-store",
"electron-updater",
"electron-window-state",
"@sentry/electron",
"@sentry/electron/main",
];

export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()],
define: {
// Crash-report DSN baked in at build time (publish-desktop.yml).
// Empty in local/dev builds → Sentry stays fully disabled.
__EXECUTOR_SENTRY_DSN__: JSON.stringify(process.env.DESKTOP_SENTRY_DSN ?? ""),
},
build: {
rollupOptions: {
input: { index: "src/main/index.ts" },
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"typecheck:slow": "tsc --noEmit"
},
"dependencies": {
"@sentry/bun": "^10.57.0",
"@sentry/electron": "^7.13.0",
"electron-log": "^5",
"electron-store": "^10",
"electron-updater": "^6",
Expand All @@ -41,6 +43,7 @@
"@jitl/quickjs-wasmfile-release-sync": "catalog:",
"@modelcontextprotocol/sdk": "^1.29.0",
"@types/node": "catalog:",
"@zip.js/zip.js": "^2.8.26",
"bun-types": "catalog:",
"electron": "41.2.1",
"electron-builder": "^26",
Expand Down
97 changes: 97 additions & 0 deletions apps/desktop/src/main/crash-screen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* In-window screen shown when the sidecar dies under a running window.
* Replaces the dead web UI (which would otherwise sit there failing every
* fetch) with an explanation and a recovery path. Rendered as a data: URL
* in the existing BrowserWindow, so the preload bridge stays available —
* the buttons drive the same `window.executor` IPC the settings page uses.
*/

export interface CrashScreenOptions {
/** Whether a crash report was sent upstream (DSN build, not opted out). */
readonly reported: boolean;
}

export const sidecarCrashHtml = ({ reported }: CrashScreenOptions): string => `<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Executor</title>
<style>
:root { color-scheme: dark; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background: #0a0a0a;
color: #fafafa;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
.card { max-width: 26rem; padding: 2rem; text-align: center; }
.icon { font-size: 2rem; margin-bottom: 0.75rem; }
h1 { font-size: 1.15rem; font-weight: 600; margin: 0 0 0.5rem; }
p { font-size: 0.875rem; color: #a1a1aa; line-height: 1.5; margin: 0 0 1.5rem; }
.row { display: flex; gap: 0.6rem; justify-content: center; }
button {
padding: 0.55rem 1.1rem;
border-radius: 6px;
border: 1px solid transparent;
background: #fafafa;
color: #0a0a0a;
font: inherit;
font-size: 0.875rem;
cursor: pointer;
white-space: nowrap;
}
button.secondary { background: transparent; color: #fafafa; border-color: #3f3f46; }
#status { margin-top: 1.25rem; min-height: 1.2em; font-size: 0.8rem; color: #a1a1aa; }
</style>
</head>
<body>
<main class="card">
<div class="icon">&#9888;&#65039;</div>
<h1>The local Executor server stopped unexpectedly</h1>
<p>
Your data is safe.${reported ? " A crash report was sent automatically so this can get fixed." : ""}
Restart the server to keep working.
</p>
<div class="row">
<button id="restart">Restart server</button>
<button id="update" class="secondary">Check for updates</button>
<button id="export" class="secondary">Export diagnostics</button>
</div>
<p id="status"></p>
</main>
<script>
const status = document.getElementById("status");
document.getElementById("restart").addEventListener("click", async () => {
status.textContent = "Restarting\\u2026";
try {
// Main restarts the sidecar and reloads this window on success.
await window.executor.restartServer();
} catch {
status.textContent = "Restart failed \\u2014 try quitting and reopening Executor.";
}
});
document.getElementById("update").addEventListener("click", async () => {
status.textContent = "Checking for updates\\u2026";
try {
// Outcomes surface as native dialogs (install prompt / no updates).
await window.executor.checkForUpdates();
status.textContent = "";
} catch {
status.textContent = "Update check failed \\u2014 check your network.";
}
});
document.getElementById("export").addEventListener("click", async () => {
status.textContent = "Exporting\\u2026";
try {
await window.executor.exportDiagnostics();
status.textContent = "Diagnostics saved to Downloads.";
} catch {
status.textContent = "Export failed \\u2014 see the log file.";
}
});
</script>
</body>
</html>`;
Loading
Loading