1 Commits

Author SHA1 Message Date
Ryan Hamamura
06b3839c3a refactor: patch connection indicator with timestamp
All checks were successful
CI / Deploy / test (pull_request) Successful in 14s
CI / Deploy / lint (pull_request) Successful in 26s
CI / Deploy / deploy (pull_request) Has been skipped
Server patches the ConnectionIndicator element with a timestamp on
each heartbeat. Client-side JS checks every second if the timestamp
is stale (>20s) and toggles red/green accordingly.

This properly detects connection loss since the indicator will turn
red if no patches are received.
2026-03-03 10:30:55 -10:00

View File

@@ -48,16 +48,36 @@ templ NicknamePrompt(returnPath string) {
</main> </main>
} }
func isStale(lastPing int64) bool {
return lastPing == 0
}
// ConnectionIndicator shows a small dot indicating SSE connection status. // ConnectionIndicator shows a small dot indicating SSE connection status.
// Server patches this with a timestamp; client JS detects staleness. // Server patches this with a timestamp; client JS detects staleness.
templ ConnectionIndicator(lastPing int64) { templ ConnectionIndicator(lastPing int64) {
<div <div
id="connection-indicator" id="connection-indicator"
class="fixed top-2 right-2 flex items-center gap-1 text-xs text-gray-500" class="fixed top-2 right-2"
title="Connection status"
data-last-ping={ fmt.Sprintf("%d", lastPing) } data-last-ping={ fmt.Sprintf("%d", lastPing) }
> >
<span id="connection-dot" class="w-2 h-2 rounded-full bg-green-500"></span> <div class="inline-grid *:[grid-area:1/1]">
<div
id="connection-ping"
class={
"status status-sm",
templ.KV("status-success animate-ping", !isStale(lastPing)),
templ.KV("status-error", isStale(lastPing)),
}
></div>
<div
id="connection-dot"
class={
"status status-sm",
templ.KV("status-success", !isStale(lastPing)),
templ.KV("status-error", isStale(lastPing)),
}
></div>
</div>
</div> </div>
@connectionWatcher() @connectionWatcher()
} }
@@ -66,11 +86,17 @@ script connectionWatcher() {
setInterval(function() { setInterval(function() {
var el = document.getElementById('connection-indicator'); var el = document.getElementById('connection-indicator');
var dot = document.getElementById('connection-dot'); var dot = document.getElementById('connection-dot');
if (!el || !dot) return; var ping = document.getElementById('connection-ping');
if (!el || !dot || !ping) return;
var lastPing = parseInt(el.dataset.lastPing, 10) || 0; var lastPing = parseInt(el.dataset.lastPing, 10) || 0;
var stale = Date.now() - lastPing > 20000; var stale = Date.now() - lastPing > 20000;
dot.classList.toggle('bg-green-500', !stale);
dot.classList.toggle('bg-red-500', stale); dot.classList.toggle('status-success', !stale);
dot.classList.toggle('status-error', stale);
ping.classList.toggle('status-success', !stale);
ping.classList.toggle('status-error', stale);
ping.classList.toggle('animate-ping', !stale);
}, 1000); }, 1000);
} }