From df4b4cc649b5610edceb304a939d9fa39a0bfac7 Mon Sep 17 00:00:00 2001 From: slhx agent Date: Tue, 2 Jun 2026 00:24:32 +0200 Subject: [PATCH] feat(axum): add interaction registry alias Add an interactions()/on() registration path and move canonical examples off explicit register_handle naming while keeping low-level registry types available underneath. req: axum_integration/003 req: ceremony/001 req: dx/003 req: examples/003 --- REQUIREMENTS.md | 2 +- examples/kanban/src/main.rs | 12 ++++++------ examples/techdemo/src/main.rs | 18 +++++++++--------- examples/v0/src/main.rs | 14 +++++++------- slhx-axum/src/lib.rs | 15 +++++++++++++++ slhx-axum/tests/response.rs | 4 ++-- slhx-test/tests/examples_contract.rs | 1 + 7 files changed, 41 insertions(+), 25 deletions(-) diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 19fb8aa..0db09f8 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -441,7 +441,7 @@ what a valid business email is. 002 Existing Axum routes remain normal Axum routes. slhx does not own routing. slhx-axum only mounts handler dispatch, runtime assets, and optional push endpoints. ### req: axum/003 -003 Interactive fragments that would traditionally be implemented as `/demo/...` HTMX endpoints should be expressible as `#[slhx::handler]` functions returning generated slot commands. +003 Interactive fragments that would traditionally be implemented as `/demo/...` HTMX endpoints should be expressible as `#[slhx::handler]` functions returning generated slot commands. Integration registration should read as interactions over generated handles, not low-level registry wiring. ### req: axum/004 004 Query-string demo endpoints may be migrated to typed handler params from `data-*` attributes or forms. `Query` remains available in normal Axum routes but is not the slhx happy path. diff --git a/examples/kanban/src/main.rs b/examples/kanban/src/main.rs index 7fd2260..470a996 100644 --- a/examples/kanban/src/main.rs +++ b/examples/kanban/src/main.rs @@ -6,7 +6,7 @@ use futures_util::{stream, StreamExt}; use hemplate::Hemplate; use slhx::{IntoEffect, RenderSlotExt, SafeHtml}; use slhx_axum::{ - handlers, runtime_js, sse, DispatchRegistry, DispatchRejection, EffectResponse, + interactions, runtime_js, sse, DispatchRegistry, DispatchRejection, EffectResponse, InteractionRequest, PageRequest, }; use slhx_kanban_example::ui::{self, board as board_ui}; @@ -152,8 +152,8 @@ async fn events(Query(params): Query>) -> impl IntoResp } fn registry(state: Arc) -> impl DispatchRegistry { - handlers(ui::BUILD_FINGERPRINT) - .register_handle(handles::create_card, { + interactions(ui::BUILD_FINGERPRINT) + .on(handles::create_card, { let state = state.clone(); move |form| { // req: examples/001 req: form/002 @@ -168,7 +168,7 @@ fn registry(state: Arc) -> impl DispatchRegistry { board_effects(&board, "Card added") } }) - .register_handle(card_handles::move_left, { + .on(card_handles::move_left, { let state = state.clone(); move |form| { // req: examples/001 req: list/003 @@ -179,7 +179,7 @@ fn registry(state: Arc) -> impl DispatchRegistry { board_effects(&board, if moved { "Card moved left" } else { "Card not found" }) } }) - .register_handle(card_handles::move_right, { + .on(card_handles::move_right, { let state = state.clone(); move |form| { // req: examples/001 req: list/003 @@ -190,7 +190,7 @@ fn registry(state: Arc) -> impl DispatchRegistry { board_effects(&board, if moved { "Card moved right" } else { "Card not found" }) } }) - .register_handle(card_handles::delete_card, { + .on(card_handles::delete_card, { let state = state.clone(); move |form| { // req: examples/001 req: list/003 diff --git a/examples/techdemo/src/main.rs b/examples/techdemo/src/main.rs index 7d30479..62ccd06 100644 --- a/examples/techdemo/src/main.rs +++ b/examples/techdemo/src/main.rs @@ -6,7 +6,7 @@ use futures_util::{stream, StreamExt}; use hemplate::Hemplate; use slhx::{CssClass, CssClasses, IntoEffect, RenderSlotExt, SafeHtml}; use slhx_axum::{ - handlers, runtime_js, sse, DispatchRegistry, DispatchRejection, EffectResponse, + interactions, runtime_js, sse, DispatchRegistry, DispatchRejection, EffectResponse, InteractionRequest, PageRequest, }; use slhx_techdemo::ui; @@ -283,8 +283,8 @@ async fn events(Query(params): Query>) -> impl IntoResp } fn registry(shared: Arc) -> impl DispatchRegistry { - handlers(ui::BUILD_FINGERPRINT) - .register_handle(handles::launch_work, { + interactions(ui::BUILD_FINGERPRINT) + .on(handles::launch_work, { let shared = shared.clone(); move |form| { // req: form/002 req: examples/001 @@ -303,7 +303,7 @@ fn registry(shared: Arc) -> impl DispatchRegistry { demo_effects(&demo, "Launch accepted ยท 4 targets updated") } }) - .register_handle(card_handles::advance_work, { + .on(card_handles::advance_work, { let shared = shared.clone(); move |form| { // req: list/003 req: examples/001 @@ -321,7 +321,7 @@ fn registry(shared: Arc) -> impl DispatchRegistry { demo_effects(&demo, "Pipeline advanced") } }) - .register_handle(lane_handles::move_to_lane, { + .on(lane_handles::move_to_lane, { let shared = shared.clone(); move |form| { // req: list/003 req: examples/001 @@ -343,7 +343,7 @@ fn registry(shared: Arc) -> impl DispatchRegistry { demo_effects(&demo, "Drag-and-drop move persisted") } }) - .register_handle(card_handles::delete_work, { + .on(card_handles::delete_work, { let shared = shared.clone(); move |form| { // req: list/003 req: examples/001 @@ -362,7 +362,7 @@ fn registry(shared: Arc) -> impl DispatchRegistry { demo_effects(&demo, "Card removed") } }) - .register_handle(card_handles::spotlight_work, { + .on(card_handles::spotlight_work, { let shared = shared.clone(); move |form| { // req: examples/001 @@ -377,7 +377,7 @@ fn registry(shared: Arc) -> impl DispatchRegistry { demo_effects(&demo, "Inspector focused") } }) - .register_handle(handles::simulate_push, { + .on(handles::simulate_push, { let shared = shared.clone(); move |_| { // req: push/003 req: examples/001 @@ -393,7 +393,7 @@ fn registry(shared: Arc) -> impl DispatchRegistry { ) } }) - .register_handle(handles::reset_demo, { + .on(handles::reset_demo, { let shared = shared.clone(); move |_| { // req: examples/001 diff --git a/examples/v0/src/main.rs b/examples/v0/src/main.rs index 2377cee..5bb83c6 100644 --- a/examples/v0/src/main.rs +++ b/examples/v0/src/main.rs @@ -6,7 +6,7 @@ use futures_util::{stream, StreamExt}; use hemplate::Hemplate; use slhx::{push, IntoEffect, RenderSlotExt, SafeHtml}; use slhx_axum::{ - handlers, runtime_js, sse, DispatchRegistry, EffectResponse, InteractionRequest, PageRequest, + interactions, runtime_js, sse, DispatchRegistry, EffectResponse, InteractionRequest, PageRequest, }; use slhx_v0_examples::ui; use slhx_v0_examples::ui::{auth, counter, notifications, page_swap, todos, wizard}; @@ -124,8 +124,8 @@ async fn events(Query(params): Query>) -> impl IntoResp } fn registry(state: Arc) -> impl DispatchRegistry { - handlers(ui::BUILD_FINGERPRINT) - .register_handle(counter_handles::increment, { + interactions(ui::BUILD_FINGERPRINT) + .on(counter_handles::increment, { let state = state.clone(); move |_| { // req: examples/001 @@ -134,7 +134,7 @@ fn registry(state: Arc) -> impl DispatchRegistry { counter_slots::counter_value.text(*counter) } }) - .register_handle(todo_handles::add_todo, { + .on(todo_handles::add_todo, { let state = state.clone(); move |form| { // req: examples/001 @@ -150,7 +150,7 @@ fn registry(state: Arc) -> impl DispatchRegistry { ) } }) - .register_handle(wizard_handles::next_step, { + .on(wizard_handles::next_step, { let state = state.clone(); move |_| { // req: examples/001 @@ -159,7 +159,7 @@ fn registry(state: Arc) -> impl DispatchRegistry { wizard_slots::wizard_step.text(format!("Step {}", *step + 1)) } }) - .register_handle(auth_handles::login, |form| { + .on(auth_handles::login, |form| { // req: examples/001 let ok = form.value("email") == Some("demo@example.com") && form.value("password").is_some_and(|password| !password.is_empty()); @@ -169,7 +169,7 @@ fn registry(state: Arc) -> impl DispatchRegistry { "Try demo@example.com with any password" }) }) - .register_handle(page_handles::load_docs, |_| { + .on(page_handles::load_docs, |_| { // req: page_swap/002, req: examples/001 ( page_slots::content.render(&DocsContent { diff --git a/slhx-axum/src/lib.rs b/slhx-axum/src/lib.rs index d94372b..7b71acc 100644 --- a/slhx-axum/src/lib.rs +++ b/slhx-axum/src/lib.rs @@ -266,6 +266,10 @@ pub const fn handlers(fingerprint: BuildFingerprint) -> HandlerRegistry { HandlerRegistry::new(fingerprint) } +pub const fn interactions(fingerprint: BuildFingerprint) -> HandlerRegistry { + HandlerRegistry::new(fingerprint) +} + impl InteractionRequest { pub fn dispatch( self, @@ -320,6 +324,17 @@ impl HandlerRegistry { self.register(handle.id().id, handler) } + pub fn on( + self, + handle: Handle, + handler: impl Fn(InteractionForm) -> E + Send + Sync + 'static, + ) -> Self + where + E: IntoEffect, + { + self.register_handle(handle, handler) + } + pub fn dispatch(&self, form: InteractionForm) -> Result { let handle_id = form.handle_id; let Some(handler) = self.handlers.get(&handle_id) else { diff --git a/slhx-axum/tests/response.rs b/slhx-axum/tests/response.rs index 6ea3423..053bd30 100644 --- a/slhx-axum/tests/response.rs +++ b/slhx-axum/tests/response.rs @@ -2,7 +2,7 @@ use axum::http::{header, HeaderMap}; use axum::response::IntoResponse; use scraper::{Html, Selector}; use slhx_axum::{ - handlers, runtime_js, DispatchRejection, EffectResponse, HandlerRegistry, InteractionForm, + interactions, runtime_js, DispatchRejection, EffectResponse, HandlerRegistry, InteractionForm, InteractionFormRejection, InteractionRequest, PageMode, PageRequest, PageResponse, SLHX_CONTENT_TYPE, SLHX_FINGERPRINT_HEADER, SLHX_PARTIAL_HEADER, SLHX_RUNTIME_CONTENT_TYPE, SLHX_TITLE_HEADER, @@ -148,7 +148,7 @@ fn interaction_request_dispatches_with_concise_handlers_helper() { Vec::new(), )); let response = request - .dispatch(handlers(BuildFingerprint(4)).register_handle(Handle::<()>::new(7), |_| { + .dispatch(interactions(BuildFingerprint(4)).on(Handle::<()>::new(7), |_| { Slot::::new(3).text("ok") })) .unwrap(); diff --git a/slhx-test/tests/examples_contract.rs b/slhx-test/tests/examples_contract.rs index d8fe33a..335cd79 100644 --- a/slhx-test/tests/examples_contract.rs +++ b/slhx-test/tests/examples_contract.rs @@ -65,6 +65,7 @@ fn canonical_examples_do_not_author_low_level_resource_plumbing() { "render_html(", "HandlerRegistry", "InteractionForm", + "register_handle(", "name=\"__h\"", "name='__h'", ];