refactor: patch entire game content instead of individual components
Some checks failed
CI / Deploy / test (pull_request) Successful in 14s
CI / Deploy / lint (pull_request) Failing after 21s
CI / Deploy / deploy (pull_request) Has been skipped

Extract GameContent from GamePage so the SSE handler can patch a single
element and let DOM morphing diff the changes, replacing the per-component
sendGameComponents helper.
This commit is contained in:
Ryan Hamamura
2026-03-02 21:43:25 -10:00
parent 42211439c9
commit 0808c4d972
2 changed files with 31 additions and 30 deletions

View File

@@ -15,7 +15,6 @@ import (
chatcomponents "github.com/ryanhamamura/games/chat/components"
"github.com/ryanhamamura/games/connect4"
"github.com/ryanhamamura/games/db/repository"
"github.com/ryanhamamura/games/features/c4game/components"
"github.com/ryanhamamura/games/features/c4game/pages"
"github.com/ryanhamamura/games/sessions"
)
@@ -113,8 +112,16 @@ func HandleGameEvents(store *connect4.Store, nc *nats.Conn, sm *scs.SessionManag
chatCfg := c4ChatConfig(gameID)
room := chat.NewPersistentRoom(nc, connect4.ChatSubject(gameID), queries, gameID)
patchAll := func() error {
myColor = gi.GetPlayerColor(playerID)
g := gi.GetGame()
return sse.PatchElementTempl(pages.GameContent(g, myColor, room.Messages(), chatCfg))
}
// Send initial render
sendGameComponents(sse, gi, myColor, room, chatCfg)
if err := patchAll(); err != nil {
return
}
// Subscribe to game state updates
gameCh := make(chan *nats.Msg, 64)
@@ -137,14 +144,12 @@ func HandleGameEvents(store *connect4.Store, nc *nats.Conn, sm *scs.SessionManag
case <-ctx.Done():
return
case <-gameCh:
myColor = gi.GetPlayerColor(playerID)
sendGameComponents(sse, gi, myColor, room, chatCfg)
case msg := <-chatCh:
_, snapshot := room.Receive(msg.Data)
if snapshot == nil {
continue
if err := patchAll(); err != nil {
return
}
if err := sse.PatchElementTempl(chatcomponents.Chat(snapshot, chatCfg)); err != nil {
case msg := <-chatCh:
room.Receive(msg.Data)
if err := patchAll(); err != nil {
return
}
}
@@ -300,13 +305,3 @@ func HandleRematch(store *connect4.Store, sm *scs.SessionManager) http.HandlerFu
}
}
}
// sendGameComponents patches all game-related SSE components.
func sendGameComponents(sse *datastar.ServerSentEventGenerator, gi *connect4.Instance, myColor int, room *chat.Room, chatCfg chatcomponents.Config) {
g := gi.GetGame()
sse.PatchElementTempl(components.Board(g, myColor)) //nolint:errcheck
sse.PatchElementTempl(components.StatusBanner(g, myColor)) //nolint:errcheck
sse.PatchElementTempl(components.PlayerInfo(g, myColor)) //nolint:errcheck
sse.PatchElementTempl(chatcomponents.Chat(room.Messages(), chatCfg)) //nolint:errcheck
}

View File

@@ -17,21 +17,27 @@ templ GamePage(g *connect4.Game, myColor int, messages []chat.Message, chatCfg c
data-signals="{chatMsg: ''}"
data-init={ datastar.GetSSE("/games/%s/events", g.ID) }
>
@sharedcomponents.BackToLobby()
@sharedcomponents.StealthTitle("text-3xl font-bold")
@components.PlayerInfo(g, myColor)
@components.StatusBanner(g, myColor)
<div class="c4-game-area">
@components.Board(g, myColor)
@chatcomponents.Chat(messages, chatCfg)
</div>
if g.Status == connect4.StatusWaitingForPlayer {
@components.InviteLink(g.ID)
}
@GameContent(g, myColor, messages, chatCfg)
</main>
}
}
templ GameContent(g *connect4.Game, myColor int, messages []chat.Message, chatCfg chatcomponents.Config) {
<div id="game-content">
@sharedcomponents.BackToLobby()
@sharedcomponents.StealthTitle("text-3xl font-bold")
@components.PlayerInfo(g, myColor)
@components.StatusBanner(g, myColor)
<div class="c4-game-area">
@components.Board(g, myColor)
@chatcomponents.Chat(messages, chatCfg)
</div>
if g.Status == connect4.StatusWaitingForPlayer {
@components.InviteLink(g.ID)
}
</div>
}
templ JoinPage(gameID string) {
@layouts.Base("Connect 4 - Join") {
@sharedcomponents.GameJoinPrompt(