A ticking clock serves as an implicit connection indicator - if it
stops updating, users know the connection is stale. Simpler and more
useful than a status dot.
The ConnectionIndicator component caused PatchElementsNoTargetsFound
errors due to complex nested IDs. Removing it for now - we can design
a better solution later if needed.
Add ConnectionIndicator to Connect 4 game page (Snake already had it).
Both games now patch the indicator on initial connect and every 10s
heartbeat, giving users visual feedback that SSE is connected.
Apply the same service pattern from Connect 4 to Snake game.
Handlers now receive the service and call its methods instead of
managing NATS connections directly. Also aligns heartbeat to 10s
and removes ConnectionIndicator patching (matching C4 changes).
Move NATS subscription and chat room management into a dedicated
GameService, following the portigo service pattern. Handlers now
receive the service and call its methods instead of managing
NATS connections directly.
- Reorder HandleGameEvents to create NATS subscriptions before SSE
- Use chi's middleware.NewWrapResponseWriter for proper http.Flusher support
- Add slog-zerolog adapter for unified logging
- Add ErrorLog to HTTP server for better error visibility
- Change session Cookie.Secure to false for HTTP support
- Change heartbeat from 15s to 10s
- Remove ConnectionIndicator patching (was causing PatchElementsNoTargetsFound)
The key fix was using chi's response writer wrapper which properly
implements http.Flusher, allowing SSE data to be flushed immediately
instead of being buffered.
Replace ConnectionIndicatorWithScript wrapper with a single ConnectionIndicator
component that uses templ.NewOnceHandle() to ensure the watcher script is only
rendered once per page, even when the indicator is patched via SSE.
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.
- Add ConnectionIndicator component showing green/red dot
- Send lastPing signal every 15 seconds via SSE
- Indicator turns red if no ping received in 20 seconds
- Gives users confidence the live connection is active
Room.Subscribe() now returns a channel of parsed Message structs
instead of raw NATS messages. The room handles NATS subscription
and message parsing internally, so callers no longer need to call
Receive() separately.
- Add version package with build-time variables
- Inject version via ldflags in Dockerfile using git describe
- Show version in footer on every page
- Log version and commit on server startup
Previously patchAll() re-rendered the full GameContent on every chat
message, which was inefficient and could cause UI glitches. Now we
append just the new ChatMessage to the chat history element.
The default Datastar requestCancellation:'auto' was causing SSE
connections to be cancelled whenever users interacted with the page
(making moves, sending chat messages, etc.), breaking live updates.
Replace invalid .key_enter and .enter modifiers with evt.key === 'Enter'
guard in the expression, per Datastar docs. Also fix __stop and __throttle
modifier syntax to use double underscores.
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.
Move SaveMessage/LoadMessages logic into Room as private methods.
NewPersistentRoom auto-loads history and auto-saves on Send, removing
the need for handlers to coordinate persistence separately.
Update binary name, DB path, session cookie, deploy scripts, systemd
service, Docker config, CI workflow, and .dockerignore. Remove stale
Claude command and settings files.
Create chat/ package with Message type, Room (NATS pub/sub + buffer),
DB persistence helpers, and a unified templ component parameterized by
Config (CSS prefix, post URL, color function, key propagation).
Both c4game and snakegame now use chat.Room for message management and
chatcomponents.Chat for rendering, eliminating the duplicated
ChatMessage types, chat templ components, chatAutoScroll scripts,
color functions, and inline buffer management.
Add GetPlayerID, GetUserID, GetNickname to the sessions package.
Remove the inline player-ID-from-session pattern duplicated across
every handler in c4game and snakegame, and the local getPlayerID
helper in snakegame.
Both game and snake packages had identical PlayerID types and the snake
package imported game.GenerateID. Now both use player.ID and
player.GenerateID from the shared player package.
Wrap free persistence functions in instance methods for cleaner call
sites (gi.save() instead of saveGame(gi.queries, gi.game)). Methods
log errors via zerolog before returning them.
- Replace Create+Get+Update with UpsertGame/UpsertSnakeGame queries
- Extract free functions (saveGame, loadGame, etc.) from duplicated
receiver methods on Store and Instance types
- Remove duplicate generateID from snake package, reuse game.GenerateID
- Throttle snake game DB writes to every 2s instead of every tick
- Fix double-lock in c4game chat handler
- Update all code for sqlc pointer types (*string instead of sql.NullString)
Generated _templ.go files are deterministic output from .templ sources,
same as output.css from input.css. Remove them from version control to
reduce diff noise and merge conflicts. Add build:templ and live:templ
tasks to the Taskfile so generation happens as part of the build.
Pressing Enter on the username field in login/register or the nickname
field in the join-game prompt now submits the form, matching user
expectations. Also add *.templ to the gitignore allowlist.