feat(axum): start stateful interactions tersely

Add state_interactions as a small helper for app-owned stateful registries, and use it in the workout exemplar to remove one layer of repetitive route wiring without hiding generated handles or app handlers.

req: axum_integration/003

req: ceremony/001

req: dx/003

req: codegen/002

req: examples/001
This commit is contained in:
slhx agent
2026-06-11 21:55:29 +02:00
parent d991addbb5
commit 0f8d07d36f
3 changed files with 51 additions and 3 deletions
+2 -3
View File
@@ -1,6 +1,6 @@
use hemplate::Hemplate;
use hemx::{Html, IntoEffect};
use hemx_axum::{interactions, Form, HandlerRegistry};
use hemx_axum::{state_interactions, Form, HandlerRegistry};
use hemx_host::{
browser_pwa_host_profile, native_shell_host_profile, Capability, CapabilityManifest,
CapabilityShape, CapabilityUse, HapticPattern, HostCall, HostCallId, HostEvent,
@@ -365,8 +365,7 @@ pub struct RecordNoteInput {
pub fn registry(state: AppState) -> HandlerRegistry {
// req: codegen/002 req: examples/001
interactions(ui::BUILD_FINGERPRINT)
.with_state(state)
state_interactions(ui::BUILD_FINGERPRINT, state)
.on_state(ui::workout::complete_set, complete_set)
.on(ui::workout::change_weight, change_weight_form)
.on_state(ui::workout::skip_exercise, skip_exercise)
+8
View File
@@ -460,6 +460,14 @@ pub const fn interactions(fingerprint: BuildFingerprint) -> HandlerRegistry {
HandlerRegistry::new(fingerprint)
}
pub fn state_interactions<S>(fingerprint: BuildFingerprint, state: S) -> StateHandlerRegistry<S>
where
S: Clone + Send + Sync + 'static,
{
// req: axum_integration/003 req: ceremony/001
interactions(fingerprint).with_state(state)
}
impl InteractionRequest {
pub fn dispatch(
self,
+41
View File
@@ -232,6 +232,47 @@ fn interaction_request_dispatches_with_concise_handlers_helper() {
assert_eq!(response.batch.ops, vec![Slot::<String>::new(3).text("ok")]);
}
#[test]
fn state_interactions_starts_stateful_wiring_without_nested_closures() {
// req: axum_integration/003 req: ceremony/001 req: dx/003
fn ping(prefix: String) -> impl IntoEffect {
Slot::<String>::new(3).text(format!("{prefix}: ping"))
}
fn open(prefix: String, input: OpenProject) -> impl IntoEffect {
Slot::<String>::new(3).text(format!("{prefix}: {}", input.project_id.0))
}
let registry = || {
hemx_axum::state_interactions(BuildFingerprint(4), "project".to_owned())
.on_state(Handle::<()>::new(7), ping)
.on(Handle::<()>::new(8), open)
.into_registry()
};
let ping_response = InteractionRequest::from(InteractionForm::for_handle(
Handle::<()>::new(7),
Vec::new(),
))
.dispatch(registry())
.unwrap();
assert_eq!(
ping_response.batch.ops,
vec![Slot::<String>::new(3).text("project: ping")]
);
let open_response = InteractionRequest::from(InteractionForm::for_handle(
Handle::<()>::new(8),
vec![("project_id".to_owned(), "42".to_owned())],
))
.dispatch(registry())
.unwrap();
assert_eq!(
open_response.batch.ops,
vec![Slot::<String>::new(3).text("project: 42")]
);
}
#[test]
fn interaction_request_dispatches_typed_form_inputs() {
// req: axum_integration/003 req: form/004 req: canonical_authoring/003