commit a2f3e8c23e385234e7173baef4e8fc443a107c4b
parent 4f6a4d78d497583e07040969a4e3b1cf47b92539
Author: Silas Brack <silasbrack@gmail.com>
Date: Sun, 14 Jun 2026 14:50:11 +0200
fix: update score display when voting
Vote SSE response now sends two patch events — one for the vote
button and one for the score span. Previously only the button
updated, leaving the score unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
2 files changed, 33 insertions(+), 5 deletions(-)
diff --git a/src/handlers/vote.rs b/src/handlers/vote.rs
@@ -11,7 +11,7 @@ use crate::sse;
use crate::state::AppState;
use crate::templates::VoteButtonTemplate;
-fn render_vote_button(
+fn render_vote_response(
target_type: &str,
target_id: i64,
score: i64,
@@ -23,9 +23,15 @@ fn render_vote_button(
score,
voted,
};
+ let button_html = template.render()?;
- let html = template.render()?;
- Ok(sse::patch_elements(&html))
+ let score_class = if target_type == "comment" { "comment-score" } else { "story-score" };
+ let score_html = format!(
+ "<span class=\"{score_class}\" id=\"score-{target_type}-{target_id}\">{score} point{}</span>",
+ if score == 1 { "" } else { "s" }
+ );
+
+ Ok(sse::patch_multiple(&[&button_html, &score_html]))
}
/// Vote on a story (Datastar endpoint)
@@ -39,7 +45,7 @@ pub async fn vote_story(
let (voted, new_score) = state.db().toggle_vote(user_id, TARGET_TYPE_STORY, story_id).await?;
- render_vote_button("story", story_id, new_score, voted)
+ render_vote_response("story", story_id, new_score, voted)
}
/// Vote on a comment (Datastar endpoint)
@@ -53,5 +59,5 @@ pub async fn vote_comment(
let (voted, new_score) = state.db().toggle_vote(user_id, TARGET_TYPE_COMMENT, comment_id).await?;
- render_vote_button("comment", comment_id, new_score, voted)
+ render_vote_response("comment", comment_id, new_score, voted)
}
diff --git a/src/sse.rs b/src/sse.rs
@@ -4,6 +4,28 @@ pub fn patch_elements(html: &str) -> Response {
patch_elements_with_options(html, None, None)
}
+/// Send multiple patch events in a single SSE response.
+pub fn patch_multiple(fragments: &[&str]) -> Response {
+ let mut events = Vec::new();
+ for html in fragments {
+ let mut data_lines = Vec::new();
+ for line in html.lines() {
+ data_lines.push(format!("data: elements {line}"));
+ }
+ events.push(format!("event: datastar-patch-elements\n{}", data_lines.join("\n")));
+ }
+
+ let body = format!("{}\n\n", events.join("\n\n"));
+
+ Response::builder()
+ .status(StatusCode::OK)
+ .header(header::CONTENT_TYPE, "text/event-stream")
+ .header(header::CACHE_CONTROL, "no-cache")
+ .header("X-Accel-Buffering", "no")
+ .body(Body::from(body))
+ .unwrap()
+}
+
fn patch_elements_with_options(
html: &str,
selector: Option<&str>,