sse: seq plumbing + subscribe-first dedupe dance

This commit is contained in:
müde 2026-05-17 12:26:00 +02:00
parent 8c186d4fb7
commit 1340a654e7
5 changed files with 197 additions and 37 deletions

View file

@ -191,6 +191,12 @@ async fn serve_shared_js() -> impl IntoResponse {
#[derive(Serialize)]
struct StateSnapshot {
/// Bus seq at the moment this snapshot was assembled. Clients dedupe
/// their buffered SSE traffic against this value: events with
/// `seq <= snapshot.seq` are already reflected (or pre-date the
/// snapshot); `seq > snapshot.seq` is post-snapshot. Reset to 0 on
/// harness restart — clients treat reconnect as a fresh world.
seq: u64,
label: String,
dashboard_port: u16,
/// `"online"` | `"needs_login_idle"` | `"needs_login_in_progress"`.
@ -226,6 +232,9 @@ struct SessionView {
}
async fn api_state(State(state): State<AppState>) -> axum::Json<StateSnapshot> {
// Capture seq *before* any reads so the dedupe contract is
// "events with seq > snapshot.seq are post-snapshot, never missed."
let seq = state.bus.current_seq();
drop_if_finished(&state.session);
let login = *state.login.lock().unwrap();
let session_snapshot = state.session.lock().unwrap().clone();
@ -251,6 +260,7 @@ async fn api_state(State(state): State<AppState>) -> axum::Json<StateSnapshot> {
let model = state.bus.model();
let token_usage = state.bus.last_usage();
axum::Json(StateSnapshot {
seq,
label: state.label.clone(),
dashboard_port,
status,
@ -338,10 +348,15 @@ async fn post_send(State(state): State<AppState>, Form(form): Form<SendForm>) ->
}
}
async fn events_history(
State(state): State<AppState>,
) -> axum::Json<Vec<crate::events::LiveEvent>> {
axum::Json(state.bus.history())
async fn events_history(State(state): State<AppState>) -> axum::Json<serde_json::Value> {
// Capture seq *before* the read so dedupe is "drop buffered events
// you've already seen in history", never "lose an event that fired
// between the read and the timestamp." Historical rows have no
// per-row seq; only the high-water mark matters for the dedupe
// window.
let seq = state.bus.current_seq();
let events = state.bus.history();
axum::Json(serde_json::json!({ "seq": seq, "events": events }))
}
async fn events_stream(