test(kanban): prove multiplayer milestone journey

req: ms/001

req: ms/002

req: ms/003
This commit is contained in:
slhx agent
2026-07-13 23:51:51 +02:00
parent 2f1b7703f9
commit 2a74a28d3b
4 changed files with 175 additions and 19 deletions
+33 -2
View File
@@ -1,8 +1,8 @@
use axum::extract::{Query, Request, State};
use axum::extract::{Form, Query, Request, State};
use axum::http::{HeaderMap, StatusCode};
use axum::middleware::{self, Next};
use axum::response::sse::{Event, KeepAlive, Sse};
use axum::response::{IntoResponse, Response};
use axum::response::{IntoResponse, Redirect, Response};
use axum::routing::{get, post};
use axum::{Json, Router};
use futures_util::{stream, StreamExt};
@@ -500,6 +500,7 @@ async fn main() {
let ordinary_routes = Router::new()
.route("/", get(home).post(interact))
.route("/move", post(move_card_without_script))
.route("/events", get(events))
.route("/sync/broadcast", get(sync_broadcast))
.route("/sync/ack", get(sync_ack))
@@ -554,6 +555,36 @@ async fn home(State(state): State<Arc<AppState>>, request: PageRequest) -> impl
.fingerprint(ui::BUILD_FINGERPRINT)
}
#[derive(Deserialize)]
#[serde(rename_all = "lowercase")]
enum MoveDirection {
Left,
Right,
}
#[derive(Deserialize)]
struct MoveCardForm {
card_id: u64,
direction: MoveDirection,
}
// req: accessibility/001 req: ms/001
async fn move_card_without_script(
State(state): State<Arc<AppState>>,
Form(command): Form<MoveCardForm>,
) -> Result<Redirect, StatusCode> {
let mut board = state.board.lock().unwrap();
let moved = update_card(&mut board, Some(command.card_id), |card| {
card.column = match command.direction {
MoveDirection::Left => card.column.saturating_sub(1),
MoveDirection::Right => (card.column + 1).min(COLUMNS.len() - 1),
};
});
moved
.then(|| Redirect::to("/"))
.ok_or(StatusCode::BAD_REQUEST)
}
async fn runtime() -> impl IntoResponse {
runtime_js()
}
@@ -1,10 +1,16 @@
<article class="card" +data-key="self.id">
<strong>{+ self.title +}</strong>
<menu>
<button h-if="self.left_disabled" type="button" data-hemx-handle="move_left" +data-card-id="self.id" disabled="disabled">←</button>
<button h-else type="button" data-hemx-handle="move_left" +data-card-id="self.id">←</button>
<button h-if="self.right_disabled" type="button" data-hemx-handle="move_right" +data-card-id="self.id" disabled="disabled">→</button>
<button h-else type="button" data-hemx-handle="move_right" +data-card-id="self.id"></button>
<button h-if="self.left_disabled" type="button" aria-label="Move card left" data-hemx-handle="move_left" +data-card-id="self.id" disabled="disabled">←</button>
<form h-else method="post" action="/move">
<input type="hidden" name="card_id" +value="self.id">
<button type="submit" name="direction" value="left" aria-label="Move card left" data-hemx-handle="move_left" +data-card-id="self.id"></button>
</form>
<button h-if="self.right_disabled" type="button" aria-label="Move card right" data-hemx-handle="move_right" +data-card-id="self.id" disabled="disabled">→</button>
<form h-else method="post" action="/move">
<input type="hidden" name="card_id" +value="self.id">
<button type="submit" name="direction" value="right" aria-label="Move card right" data-hemx-handle="move_right" +data-card-id="self.id">→</button>
</form>
<button type="button" data-hemx-handle="delete_card" +data-card-id="self.id">Delete</button>
</menu>
</article>