diff --git a/features/c4game/handlers.go b/features/c4game/handlers.go
index 8c37d01..7b153d9 100644
--- a/features/c4game/handlers.go
+++ b/features/c4game/handlers.go
@@ -16,6 +16,7 @@ import (
"github.com/ryanhamamura/games/connect4"
"github.com/ryanhamamura/games/db/repository"
"github.com/ryanhamamura/games/features/c4game/pages"
+ sharedcomponents "github.com/ryanhamamura/games/features/common/components"
"github.com/ryanhamamura/games/sessions"
)
@@ -119,7 +120,7 @@ func HandleGameEvents(store *connect4.Store, nc *nats.Conn, sm *scs.SessionManag
}
sendPing := func() error {
- return sse.PatchSignals([]byte(fmt.Sprintf(`{"lastPing":%d}`, time.Now().UnixMilli())))
+ return sse.PatchElementTempl(sharedcomponents.ConnectionIndicator(time.Now().UnixMilli()))
}
// Send initial render and ping
diff --git a/features/c4game/pages/game.templ b/features/c4game/pages/game.templ
index 16204cf..455c91d 100644
--- a/features/c4game/pages/game.templ
+++ b/features/c4game/pages/game.templ
@@ -15,10 +15,10 @@ templ GamePage(g *connect4.Game, myColor int, messages []chat.Message, chatCfg c
@layouts.Base("Connect 4") {
- @sharedcomponents.ConnectionIndicator()
+ @sharedcomponents.ConnectionIndicator(0)
@GameContent(g, myColor, messages, chatCfg)
}
diff --git a/features/common/components/shared.templ b/features/common/components/shared.templ
index 76d67b6..de24752 100644
--- a/features/common/components/shared.templ
+++ b/features/common/components/shared.templ
@@ -1,6 +1,10 @@
package components
-import "github.com/starfederation/datastar-go/datastar"
+import (
+ "fmt"
+
+ "github.com/starfederation/datastar-go/datastar"
+)
templ BackToLobby() {
← Back
@@ -45,18 +49,29 @@ templ NicknamePrompt(returnPath string) {
}
// ConnectionIndicator shows a small dot indicating SSE connection status.
-// It requires a `lastPing` signal (unix ms timestamp) to be set by the server.
-templ ConnectionIndicator() {
+// Server patches this with a timestamp; client JS detects staleness.
+templ ConnectionIndicator(lastPing int64) {
-
+
+ @connectionWatcher()
+}
+
+script connectionWatcher() {
+ setInterval(function() {
+ var el = document.getElementById('connection-indicator');
+ var dot = document.getElementById('connection-dot');
+ if (!el || !dot) return;
+ var lastPing = parseInt(el.dataset.lastPing, 10) || 0;
+ var stale = Date.now() - lastPing > 20000;
+ dot.classList.toggle('bg-green-500', !stale);
+ dot.classList.toggle('bg-red-500', stale);
+ }, 1000);
}
templ GameJoinPrompt(loginURL string, registerURL string, gamePath string) {
diff --git a/features/snakegame/handlers.go b/features/snakegame/handlers.go
index d5aac7b..4448beb 100644
--- a/features/snakegame/handlers.go
+++ b/features/snakegame/handlers.go
@@ -13,6 +13,7 @@ import (
"github.com/ryanhamamura/games/chat"
chatcomponents "github.com/ryanhamamura/games/chat/components"
+ sharedcomponents "github.com/ryanhamamura/games/features/common/components"
"github.com/ryanhamamura/games/features/snakegame/pages"
"github.com/ryanhamamura/games/sessions"
"github.com/ryanhamamura/games/snake"
@@ -125,7 +126,7 @@ func HandleSnakeEvents(snakeStore *snake.SnakeStore, nc *nats.Conn, sm *scs.Sess
}
sendPing := func() error {
- return sse.PatchSignals([]byte(fmt.Sprintf(`{"lastPing":%d}`, time.Now().UnixMilli())))
+ return sse.PatchElementTempl(sharedcomponents.ConnectionIndicator(time.Now().UnixMilli()))
}
// Send initial render and ping
diff --git a/features/snakegame/pages/game.templ b/features/snakegame/pages/game.templ
index f8fe244..a1b2cd4 100644
--- a/features/snakegame/pages/game.templ
+++ b/features/snakegame/pages/game.templ
@@ -32,12 +32,12 @@ templ GamePage(sg *snake.SnakeGame, mySlot int, messages []chat.Message, chatCfg
@layouts.Base("Snake") {
- @components.ConnectionIndicator()
+ @components.ConnectionIndicator(0)
@GameContent(sg, mySlot, messages, chatCfg, gameID)
}