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" chatcomponents "github.com/ryanhamamura/games/chat/components"
"github.com/ryanhamamura/games/connect4" "github.com/ryanhamamura/games/connect4"
"github.com/ryanhamamura/games/db/repository" "github.com/ryanhamamura/games/db/repository"
"github.com/ryanhamamura/games/features/c4game/components"
"github.com/ryanhamamura/games/features/c4game/pages" "github.com/ryanhamamura/games/features/c4game/pages"
"github.com/ryanhamamura/games/sessions" "github.com/ryanhamamura/games/sessions"
) )
@@ -113,8 +112,16 @@ func HandleGameEvents(store *connect4.Store, nc *nats.Conn, sm *scs.SessionManag
chatCfg := c4ChatConfig(gameID) chatCfg := c4ChatConfig(gameID)
room := chat.NewPersistentRoom(nc, connect4.ChatSubject(gameID), queries, 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 // Send initial render
sendGameComponents(sse, gi, myColor, room, chatCfg) if err := patchAll(); err != nil {
return
}
// Subscribe to game state updates // Subscribe to game state updates
gameCh := make(chan *nats.Msg, 64) gameCh := make(chan *nats.Msg, 64)
@@ -137,14 +144,12 @@ func HandleGameEvents(store *connect4.Store, nc *nats.Conn, sm *scs.SessionManag
case <-ctx.Done(): case <-ctx.Done():
return return
case <-gameCh: case <-gameCh:
myColor = gi.GetPlayerColor(playerID) if err := patchAll(); err != nil {
sendGameComponents(sse, gi, myColor, room, chatCfg) return
case msg := <-chatCh:
_, snapshot := room.Receive(msg.Data)
if snapshot == nil {
continue
} }
if err := sse.PatchElementTempl(chatcomponents.Chat(snapshot, chatCfg)); err != nil { case msg := <-chatCh:
room.Receive(msg.Data)
if err := patchAll(); err != nil {
return 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,6 +17,13 @@ templ GamePage(g *connect4.Game, myColor int, messages []chat.Message, chatCfg c
data-signals="{chatMsg: ''}" data-signals="{chatMsg: ''}"
data-init={ datastar.GetSSE("/games/%s/events", g.ID) } data-init={ datastar.GetSSE("/games/%s/events", 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.BackToLobby()
@sharedcomponents.StealthTitle("text-3xl font-bold") @sharedcomponents.StealthTitle("text-3xl font-bold")
@components.PlayerInfo(g, myColor) @components.PlayerInfo(g, myColor)
@@ -28,8 +35,7 @@ templ GamePage(g *connect4.Game, myColor int, messages []chat.Message, chatCfg c
if g.Status == connect4.StatusWaitingForPlayer { if g.Status == connect4.StatusWaitingForPlayer {
@components.InviteLink(g.ID) @components.InviteLink(g.ID)
} }
</main> </div>
}
} }
templ JoinPage(gameID string) { templ JoinPage(gameID string) {