diff --git a/backend/internal/web/handlers_discussion.go b/backend/internal/web/handlers_discussion.go index e843b5e..07ca9a9 100644 --- a/backend/internal/web/handlers_discussion.go +++ b/backend/internal/web/handlers_discussion.go @@ -125,6 +125,18 @@ func DiscussionMessageCreateHandler(deps DiscussionDeps) http.HandlerFunc { message := templates.DiscussionMessageFromRow(row, user.ID) data := templates.DiscussionTabData{Messages: []templates.DiscussionMessageView{message}} 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 { // SSE recipients are never the author — always render as IsOwn: false. sseMessage := message @@ -143,12 +155,9 @@ func DiscussionMessageCreateHandler(deps DiscussionDeps) http.HandlerFunc { } } - 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 + if r.Header.Get("HX-Request") != "true" { + http.Redirect(w, r, templates.DiscussionURL(tablo.ID), http.StatusSeeOther) } - http.Redirect(w, r, templates.DiscussionURL(tablo.ID), http.StatusSeeOther) } }