fix(17): flush HTTP response before SSE publish to fix own-message alignment race

The server was publishing to the SSE broker before writing the HTMX response,
causing a race: if the SSE event (IsOwn=false, left-aligned) arrived at the
browser before HTMX appended the response (IsOwn=true, right-aligned), the
SSE path won and messageExists() then blocked the correct HTMX append.

Fix: write and flush the HTMX response first, then publish to SSE. This ensures
the sender's own message lands in the DOM right-aligned before the SSE event fires.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Arthur Belleville 2026-05-17 12:26:13 +02:00
parent 30256895b2
commit 96a58ef0ea
No known key found for this signature in database

View file

@ -125,6 +125,18 @@ func DiscussionMessageCreateHandler(deps DiscussionDeps) http.HandlerFunc {
message := templates.DiscussionMessageFromRow(row, user.ID) message := templates.DiscussionMessageFromRow(row, user.ID)
data := templates.DiscussionTabData{Messages: []templates.DiscussionMessageView{message}} data := templates.DiscussionTabData{Messages: []templates.DiscussionMessageView{message}}
markDiscussionRead(r, deps.Queries, tablo, user.ID, data) markDiscussionRead(r, deps.Queries, tablo, user.ID, data)
// Send HTTP response before publishing to SSE so the HTMX append (IsOwn=true)
// lands in the DOM before the SSE event fires. This prevents the race where SSE
// wins, appends IsOwn=false, and messageExists() then blocks the HTMX append.
if r.Header.Get("HX-Request") == "true" {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_ = templates.DiscussionMessageRow(message).Render(r.Context(), w)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}
if deps.Realtime != nil { if deps.Realtime != nil {
// SSE recipients are never the author — always render as IsOwn: false. // SSE recipients are never the author — always render as IsOwn: false.
sseMessage := message sseMessage := message
@ -143,13 +155,10 @@ func DiscussionMessageCreateHandler(deps DiscussionDeps) http.HandlerFunc {
} }
} }
if r.Header.Get("HX-Request") == "true" { if r.Header.Get("HX-Request") != "true" {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_ = templates.DiscussionMessageRow(message).Render(r.Context(), w)
return
}
http.Redirect(w, r, templates.DiscussionURL(tablo.ID), http.StatusSeeOther) http.Redirect(w, r, templates.DiscussionURL(tablo.ID), http.StatusSeeOther)
} }
}
} }
func DiscussionStreamHandler(deps DiscussionDeps) http.HandlerFunc { func DiscussionStreamHandler(deps DiscussionDeps) http.HandlerFunc {