Files
sim/apps/desktop/static/offline.html
T
Waleed b478c8a5b1 fix(desktop): harden browser and runtime hygiene (#7152)
* fix(desktop): harden browser and runtime hygiene

* fix(desktop): close review edge cases

* fix(desktop): make server relaunch transactional

* fix(desktop): close final lifecycle edge cases

* fix(desktop): preserve committed relaunch recovery

* fix(desktop): make committed relaunch non-vetoable
2026-08-27 11:23:29 -07:00

261 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none'; font-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'"
/>
<title>Sim - Can’t connect</title>
<style>
@font-face {
font-family: 'Season Sans';
src:
url('../../sim/public/brand/fonts/SeasonSansUprightsVF.woff2') format('woff2'),
url('./SeasonSansUprightsVF.woff2') format('woff2');
font-style: normal;
font-weight: 300 800;
font-display: block;
}
:root {
color-scheme: light;
--bg: #fefefe;
--text-primary: #1a1a1a;
--text-body: #434343;
--text-muted: #7a7a7a;
--text-inverse: #ffffff;
--surface-hover: #f2f2f2;
}
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
margin: 0;
}
body {
display: flex;
flex-direction: column;
min-width: 320px;
padding-top: max(env(titlebar-area-height, 38px), 38px);
background: var(--bg);
color: var(--text-primary);
font-family: 'Season Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
-webkit-font-smoothing: antialiased;
user-select: none;
}
header {
flex: none;
width: 100%;
max-width: 1460px;
margin: 0 auto;
padding: 16px 80px;
-webkit-app-region: drag;
}
.wordmark {
display: block;
width: 37px;
height: 18px;
margin: 6px 0;
color: var(--text-body);
}
main {
display: flex;
flex: 1;
width: 100%;
align-items: center;
justify-content: center;
padding: 0 16px 64px;
}
.content {
display: flex;
width: 100%;
max-width: 410px;
flex-direction: column;
align-items: center;
gap: 12px;
text-align: center;
}
h1 {
max-width: 100%;
margin: 0;
font-size: 40px;
font-weight: 400;
letter-spacing: -0.02em;
line-height: 1.1;
text-wrap: balance;
}
p {
max-width: 100%;
margin: 0;
color: var(--text-muted);
font-size: 18px;
line-height: 28px;
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
margin-top: 12px;
}
button {
display: inline-flex;
height: 30px;
align-items: center;
gap: 6px;
appearance: none;
border: 0;
background: transparent;
color: var(--text-body);
border-radius: 8px;
padding: 0 8px;
font: inherit;
font-size: 14px;
line-height: 20px;
text-align: left;
cursor: pointer;
outline: none;
transition:
color 150ms cubic-bezier(0.4, 0, 0.2, 1),
background-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
border-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
text-decoration-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
fill 150ms cubic-bezier(0.4, 0, 0.2, 1),
stroke 150ms cubic-bezier(0.4, 0, 0.2, 1);
-webkit-app-region: no-drag;
}
button:focus-visible {
outline: 2px solid var(--text-primary);
outline-offset: 2px;
}
/* `button { display: inline-flex }` is an author rule, so it beats the UA
stylesheet's `[hidden] { display: none }` no matter the specificity —
without this the hidden status button renders anyway. */
button[hidden] {
display: none;
}
button .label {
min-width: 0;
flex: 1;
overflow: hidden;
color: currentColor;
text-overflow: ellipsis;
white-space: nowrap;
}
button.primary {
background: var(--text-primary);
color: var(--text-inverse);
}
@media (hover: hover) and (pointer: fine) {
button.primary:hover {
background: var(--text-body);
}
button.secondary:hover {
background: var(--surface-hover);
}
}
#detail {
min-height: 17px;
margin-top: 4px;
color: var(--text-muted);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 11px;
line-height: 1.5;
}
@media (max-width: 720px) {
header {
padding-right: 32px;
padding-left: 32px;
}
}
@media (max-width: 480px) {
header {
padding-right: 20px;
padding-left: 20px;
}
h1 {
font-size: 34px;
}
}
</style>
</head>
<body>
<header aria-label="Sim">
<svg class="wordmark" viewBox="0 0 441 212" role="img" aria-label="Sim">
<g fill="currentColor">
<path d="M0 160.9H29.51C29.51 169.08 32.46 175.61 38.37 180.48C44.27 185.12 52.25 187.44 62.31 187.44C73.24 187.44 81.65 185.34 87.56 181.14C93.46 176.71 96.41 170.85 96.41 163.55C96.41 158.24 94.77 153.82 91.49 150.28C88.43 146.74 82.75 143.86 74.44 141.65L46.24 135.01C32.03 131.47 21.42 126.05 14.43 118.75C7.65 111.45 4.26 101.83 4.26 89.88C4.26 79.93 6.78 71.3 11.81 64C17.05 56.7 24.16 51.06 33.12 47.08C42.3 43.09 52.8 41.1 64.6 41.1C76.41 41.1 86.57 43.2 95.1 47.41C103.84 51.61 110.62 57.47 115.43 64.99C120.46 72.52 123.08 81.48 123.3 91.87H93.79C93.57 83.47 90.84 76.94 85.59 72.3C80.34 67.65 73.02 65.33 63.62 65.33C54 65.33 46.57 67.43 41.32 71.63C36.07 75.83 33.45 81.59 33.45 88.89C33.45 99.73 41.32 107.14 57.06 111.12L85.26 118.09C98.81 121.19 108.98 126.28 115.76 133.35C122.53 140.21 125.92 149.61 125.92 161.56C125.92 171.74 123.19 180.7 117.73 188.44C112.26 195.96 104.72 201.82 95.1 206.03C85.7 210.01 74.55 212 61.65 212C42.85 212 27.87 207.35 16.72 198.06C5.57 188.77 0 176.38 0 160.9Z" />
<path d="M232.8 212H202.13L202.13 49.76H229.54V77.39C232.8 68.34 239.11 60.66 247.81 54.7C256.73 48.52 267.5 45.43 280.12 45.43C294.26 45.43 306.01 49.29 315.36 57.02C324.72 64.75 330.81 75.01 333.64 87.82H328.09C330.27 75.01 336.25 64.75 346.04 57.02C355.83 49.29 367.9 45.43 382.26 45.43C400.54 45.43 414.89 50.84 425.34 61.66C435.78 72.47 441 87.26 441 106.03V212H410.98V113.65C410.98 100.84 407.71 91.02 401.19 84.17C394.88 77.11 386.29 73.58 375.41 73.58C367.79 73.58 361.05 75.34 355.17 78.88C349.52 82.19 345.06 87.04 341.8 93.45C338.53 99.85 336.9 107.36 336.9 115.97V212H306.55V113.32C306.55 100.51 303.4 90.8 297.09 84.17C290.78 77.33 282.19 73.91 271.31 73.91C263.69 73.91 256.95 75.67 251.08 79.21C245.42 82.52 240.96 87.38 237.7 93.78C234.43 99.96 232.8 107.36 232.8 115.97V212Z" />
<path d="M184.83 20.55C184.83 31.9 175.64 41.1 164.29 41.1C152.95 41.1 143.76 31.9 143.76 20.55C143.76 9.2 152.95 0 164.29 0C175.64 0 184.83 9.2 184.83 20.55Z" />
<path d="M179.43 212H149.16V49.76C153.76 51.91 158.88 53.12 164.29 53.12C169.7 53.12 174.83 51.91 179.43 49.76V212Z" />
</g>
</svg>
</header>
<main>
<div class="content">
<h1 id="title">Can’t connect to Sim</h1>
<p id="message">
Sim couldn’t reach the server. Check your internet connection, then try again.
</p>
<div class="actions">
<button type="button" class="primary" id="retry"><span class="label">Retry</span></button>
<button type="button" class="secondary" id="status" hidden><span class="label">Check status</span></button>
<button type="button" class="secondary" id="server"><span class="label">Change server</span></button>
</div>
<div id="detail" role="status"></div>
</div>
</main>
<script>
const params = new URLSearchParams(location.search)
const copy = {
offline: {
title: 'You’re offline',
message: 'Sim needs an internet connection. Reconnect, then try again. We’ll also retry automatically.',
},
dns: {
title: 'Can’t find the server',
message: 'The server address couldn’t be resolved. Check your connection or the configured server.',
},
tls: {
title: 'Connection isn’t secure',
message: 'The server’s TLS certificate couldn’t be verified, so the connection was refused.',
},
timeout: {
title: 'The server isn’t responding',
message: 'The connection timed out. The server may be down or your network may be blocking it.',
},
unreachable: {
title: 'Can’t connect to Sim',
message: 'Sim couldn’t reach the server. Check your internet connection, then try again.',
},
}
const kind = params.get('kind') || 'unreachable'
const chosen = copy[kind] || copy.unreachable
document.getElementById('title').textContent = chosen.title
document.getElementById('message').textContent = chosen.message
const detail = params.get('detail')
if (detail) document.getElementById('detail').textContent = detail
const bridge = window.simDesktop
const statusButton = document.getElementById('status')
document.getElementById('retry').addEventListener('click', () => bridge?.offlineRetry())
statusButton.addEventListener('click', () => bridge?.openExternal('https://status.sim.ai'))
// The recovery path for a shell pointed at a server it cannot reach —
// a mistyped self-hosted origin strands the app here with nothing else
// to click.
document.getElementById('server').addEventListener('click', () => bridge?.server?.open())
// Hidden in the markup and only ever revealed, never the reverse: an
// older shell with no `server` bridge, or a configuration read that
// fails, must not leave a status link a self-hoster cannot use.
bridge?.server
?.getConfiguration()
.then(({ isSimCloud }) => {
if (isSimCloud) statusButton.hidden = false
})
.catch(() => {})
</script>
</body>
</html>