/** * 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 startupWindowHtml = (): string => `<!doctype html><html> <head> <meta charset="utf-8" /> <title>Executor</title> <style> :root { color-scheme: light dark; --background: #0a0a0a; --foreground: #fafafa; --muted: #a1a1aa; --line: #27272a; --pulse: #fafafa; } @media (prefers-color-scheme: light) { :root { --background: #f7f7f4; --foreground: #18181b; --muted: #60646c; --line: #d8d8d0; --pulse: #18181b; } } body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: var(--background); color: var(--foreground); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } main { width: min(22rem, calc(100vw - 4rem)); display: grid; gap: 1rem; justify-items: center; text-align: center; } .mark { width: 2.25rem; height: 2.25rem; border: 1px solid var(--line); border-radius: 8px; display: grid; place-items: center; font-size: 0.82rem; font-weight: 650; letter-spacing: 0; } h1 { margin: 0; font-size: 1.05rem; font-weight: 600; letter-spacing: 0; } p { margin: -0.35rem 0 0; color: var(--muted); font-size: 0.82rem; line-height: 1.5; } .activity { width: 8rem; height: 2px; overflow: hidden; border-radius: 999px; background: var(--line); } .activity::after { content: ""; display: block; width: 45%; height: 100%; border-radius: inherit; background: var(--pulse); animation: slide 1.15s ease-in-out infinite; } @keyframes slide { 0% { transform: translateX(-110%); opacity: 0.35; } 50% { opacity: 0.9; } 100% { transform: translateX(245%); opacity: 0.35; } } @media (prefers-reduced-motion: reduce) { .activity::after { animation: none; transform: none; width: 100%; opacity: 0.55; } } </style> </head> <body> <main> <div class="mark">Ex</div> <h1>Starting Executor…</h1> <p>Preparing your local workspace.</p> <div class="activity" aria-hidden="true"></div> </main> </body></html>`; 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; } .reset-row { margin-top: 1.75rem; font-size: 0.75rem; color: #71717a; } .reset-row a { color: #f87171; text-decoration: underline; cursor: pointer; } </style> </head> <body> <main class="card"> <div class="icon">⚠️</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> <p class="reset-row"> Still won't start after restarting? <a id="reset" href="#">Reset data</a> clears a damaged database to get the app running again. </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."; } }); document.getElementById("reset").addEventListener("click", async (event) => { event.preventDefault(); try { // Native confirm runs in main; on accept, state is backed up and // the server restarts (window reloads on success). false = cancel. const didReset = await window.executor.resetState(); if (!didReset) return; status.textContent = ""; } catch { status.textContent = "Reset failed \\u2014 see the log file."; } }); </script> </body></html>`;
/**
* 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 startupWindowHtml = (): string => `<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Executor</title>
<style>
:root {
color-scheme: light dark;
--background: #0a0a0a;
--foreground: #fafafa;
--muted: #a1a1aa;
--line: #27272a;
--pulse: #fafafa;
}
@media (prefers-color-scheme: light) {
:root {
--background: #f7f7f4;
--foreground: #18181b;
--muted: #60646c;
--line: #d8d8d0;
--pulse: #18181b;
}
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background: var(--background);
color: var(--foreground);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
main {
width: min(22rem, calc(100vw - 4rem));
display: grid;
gap: 1rem;
justify-items: center;
text-align: center;
}
.mark {
width: 2.25rem;
height: 2.25rem;
border: 1px solid var(--line);
border-radius: 8px;
display: grid;
place-items: center;
font-size: 0.82rem;
font-weight: 650;
letter-spacing: 0;
}
h1 {
margin: 0;
font-size: 1.05rem;
font-weight: 600;
letter-spacing: 0;
}
p {
margin: -0.35rem 0 0;
color: var(--muted);
font-size: 0.82rem;
line-height: 1.5;
}
.activity {
width: 8rem;
height: 2px;
overflow: hidden;
border-radius: 999px;
background: var(--line);
}
.activity::after {
content: "";
display: block;
width: 45%;
height: 100%;
border-radius: inherit;
background: var(--pulse);
animation: slide 1.15s ease-in-out infinite;
}
@keyframes slide {
0% { transform: translateX(-110%); opacity: 0.35; }
50% { opacity: 0.9; }
100% { transform: translateX(245%); opacity: 0.35; }
}
@media (prefers-reduced-motion: reduce) {
.activity::after { animation: none; transform: none; width: 100%; opacity: 0.55; }
}
</style>
</head>
<body>
<main>
<div class="mark">Ex</div>
<h1>Starting Executor…</h1>
<p>Preparing your local workspace.</p>
<div class="activity" aria-hidden="true"></div>
</main>
</body>
</html>`;
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; }
.reset-row { margin-top: 1.75rem; font-size: 0.75rem; color: #71717a; }
.reset-row a { color: #f87171; text-decoration: underline; cursor: pointer; }
</style>
</head>
<body>
<main class="card">
<div class="icon">⚠️</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>
<p class="reset-row">
Still won't start after restarting?
<a id="reset" href="#">Reset data</a>
clears a damaged database to get the app running again.
</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.";
}
});
document.getElementById("reset").addEventListener("click", async (event) => {
event.preventDefault();
try {
// Native confirm runs in main; on accept, state is backed up and
// the server restarts (window reloads on success). false = cancel.
const didReset = await window.executor.resetState();
if (!didReset) return;
status.textContent = "";
} catch {
status.textContent = "Reset failed \\u2014 see the log file.";
}
});
</script>
</body>
</html>`;