From 749ecb1778e2ec4ffbb59b4a20c7da57bfe4bae8 Mon Sep 17 00:00:00 2001 From: slhx agent Date: Mon, 1 Jun 2026 22:44:19 +0200 Subject: [PATCH] refactor(v0): shorten generated resource paths Import component-scoped generated modules at the example boundary so handlers and tests read as local slots/forms/handles without hiding ownership or adding framework machinery. req: component/003 req: dx/006 --- examples/v0/src/lib.rs | 42 ++++++++++++++++++------------------ examples/v0/src/main.rs | 47 +++++++++++++++++++++++------------------ 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/examples/v0/src/lib.rs b/examples/v0/src/lib.rs index 1c68942..9a187ad 100644 --- a/examples/v0/src/lib.rs +++ b/examples/v0/src/lib.rs @@ -3,7 +3,7 @@ pub mod ui {} #[cfg(test)] mod tests { - use super::ui; + use super::ui::{auth, counter, notifications, page_swap, todos, wizard}; use hemplate::Hemplate; use slhx::{push, Effect, IntoEffect, NavigateMode, Payload, RenderKeyedSlotExt, RenderSlotExt}; use slhx_test::inspect; @@ -49,12 +49,12 @@ mod tests { #[test] fn counter_updates_a_generated_slot() { fn increment(count: u64) -> impl IntoEffect { - ui::counter::slots::counter_value.text(count + 1) + counter::slots::counter_value.text(count + 1) } let effect = inspect(increment(1)); - assert!(effect.has_slot(ui::counter::slots::counter_value)); + assert!(effect.has_slot(counter::slots::counter_value)); assert!(matches!(effect.ops(), [Effect::Put { .. }])); } @@ -63,12 +63,12 @@ mod tests { fn form_handler_is_checked_against_hemplate_form() { #[slhx::handler] fn add_todo(_form: slhx::Form) -> impl IntoEffect { - ui::todos::slots::todo_list.text("queued") + todos::slots::todo_list.text("queued") } let effect = inspect(add_todo(TodoInput::FORM)); - assert!(effect.has_slot(ui::todos::slots::todo_list)); + assert!(effect.has_slot(todos::slots::todo_list)); assert!(matches!(effect.ops(), [Effect::Put { payload: Payload::Text(_), .. }])); } @@ -77,7 +77,7 @@ mod tests { fn todos_append_keyed_rows_from_form_input() { fn add_todo(input: TodoInput) -> impl IntoEffect { let todo = Todo { id: 7, title: input.title }; - ui::todos::slots::todo_row.append( + todos::slots::todo_row.append( todo.id.to_string(), &TodoRow { title: todo.title }, ) @@ -85,7 +85,7 @@ mod tests { let effect = inspect(add_todo(TodoInput { title: "Ship v0".into() })); - assert!(effect.has_resource(ui::todos::slots::todo_row.id())); + assert!(effect.has_resource(todos::slots::todo_row.id())); assert!(matches!(effect.ops(), [Effect::Insert { key, payload, .. }] if key == "7" && matches!(payload, Payload::Html(_)))); } @@ -94,12 +94,12 @@ mod tests { fn wizard_form_handler_is_checked_against_hemplate_form() { #[slhx::handler] fn next_step(_form: slhx::Form) -> impl IntoEffect { - ui::wizard::slots::wizard_step.text("queued") + wizard::slots::wizard_step.text("queued") } let effect = inspect(next_step(WizardInput::FORM)); - assert!(effect.has_slot(ui::wizard::slots::wizard_step)); + assert!(effect.has_slot(wizard::slots::wizard_step)); assert!(matches!(effect.ops(), [Effect::Put { payload: Payload::Text(_), .. }])); } @@ -107,12 +107,12 @@ mod tests { #[test] fn wizard_form_swaps_the_current_step() { fn next_step(input: WizardInput) -> impl IntoEffect { - ui::wizard::slots::wizard_step.text(format!("Step {}", input.step + 1)) + wizard::slots::wizard_step.text(format!("Step {}", input.step + 1)) } let effect = inspect(next_step(WizardInput { step: 1 })); - assert!(effect.has_slot(ui::wizard::slots::wizard_step)); + assert!(effect.has_slot(wizard::slots::wizard_step)); assert!(matches!(effect.ops(), [Effect::Put { .. }])); } @@ -121,18 +121,18 @@ mod tests { fn page_swap_updates_content_and_history() { fn load_docs() -> impl IntoEffect { ( - ui::page_swap::slots::content.render(&DocsContent { + page_swap::slots::content.render(&DocsContent { message: "This page was swapped.", }), - ui::page_swap::slots::title.text("Docs"), + page_swap::slots::title.text("Docs"), push("/docs"), ) } let effect = inspect(load_docs()); - assert!(effect.has_slot(ui::page_swap::slots::content)); - assert!(effect.has_slot(ui::page_swap::slots::title)); + assert!(effect.has_slot(page_swap::slots::content)); + assert!(effect.has_slot(page_swap::slots::title)); assert!(matches!(effect.ops().first(), Some(Effect::Put { payload: Payload::Html(_), .. }))); assert!(matches!(effect.ops().last(), Some(Effect::Navigate { url, mode: NavigateMode::Push, .. }) if url == "/docs")); } @@ -142,12 +142,12 @@ mod tests { fn auth_form_handler_is_checked_against_hemplate_form() { #[slhx::handler] fn login(_form: slhx::Form) -> impl IntoEffect { - ui::auth::slots::login_status.text("queued") + auth::slots::login_status.text("queued") } let effect = inspect(login(Credentials::FORM)); - assert!(effect.has_slot(ui::auth::slots::login_status)); + assert!(effect.has_slot(auth::slots::login_status)); assert!(matches!(effect.ops(), [Effect::Put { payload: Payload::Text(_), .. }])); } @@ -161,12 +161,12 @@ mod tests { "Try again" }; - ui::auth::slots::login_status.text(status) + auth::slots::login_status.text(status) } let effect = inspect(login(Credentials { email: "demo@example.com".into(), password: "secret".into() })); - assert!(effect.has_slot(ui::auth::slots::login_status)); + assert!(effect.has_slot(auth::slots::login_status)); assert!(matches!(effect.ops(), [Effect::Put { .. }])); } @@ -174,12 +174,12 @@ mod tests { #[test] fn sse_notifications_update_a_generated_slot() { fn notification(message: &str) -> impl IntoEffect { - ui::notifications::slots::notifications.text(message) + notifications::slots::notifications.text(message) } let effect = inspect(notification("Build finished")); - assert!(effect.has_slot(ui::notifications::slots::notifications)); + assert!(effect.has_slot(notifications::slots::notifications)); assert!(matches!(effect.ops(), [Effect::Put { .. }])); } } diff --git a/examples/v0/src/main.rs b/examples/v0/src/main.rs index 86b4ba9..825a102 100644 --- a/examples/v0/src/main.rs +++ b/examples/v0/src/main.rs @@ -7,6 +7,13 @@ use hemplate::Hemplate; use slhx::{push, IntoEffect, RenderSlotExt, SafeHtml}; use slhx_axum::{runtime_js, sse, EffectResponse, HandlerRegistry, InteractionForm, PageRequest}; use slhx_v0_examples::ui; +use slhx_v0_examples::ui::{auth, counter, notifications, page_swap, todos, wizard}; +use slhx_v0_examples::ui::auth::{handles as auth_handles, slots as auth_slots}; +use slhx_v0_examples::ui::counter::{handles as counter_handles, slots as counter_slots}; +use slhx_v0_examples::ui::notifications::slots as notification_slots; +use slhx_v0_examples::ui::page_swap::{handles as page_handles, slots as page_slots}; +use slhx_v0_examples::ui::todos::{forms as todo_forms, handles as todo_handles, slots as todo_slots}; +use slhx_v0_examples::ui::wizard::{handles as wizard_handles, slots as wizard_slots}; use std::collections::BTreeMap; use std::convert::Infallible; use std::net::SocketAddr; @@ -101,13 +108,13 @@ async fn runtime() -> impl IntoResponse { // req: push/001, req: push/003, req: examples/001 async fn events(Query(params): Query>) -> impl IntoResponse { if params.contains_key("once") { - let effect = ui::notifications::slots::notifications.text("Server event #1"); + let effect = notification_slots::notifications.text("Server event #1"); return sse(stream::iter([Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT))]).boxed()); } let batches = stream::unfold(1_u64, |count| async move { tokio::time::sleep(Duration::from_secs(3)).await; - let effect = ui::notifications::slots::notifications.text(format!("Server event #{count}")); + let effect = notification_slots::notifications.text(format!("Server event #{count}")); Some((Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT)), count + 1)) }) .boxed(); @@ -116,16 +123,16 @@ async fn events(Query(params): Query>) -> impl IntoResp fn registry(state: Arc) -> HandlerRegistry { HandlerRegistry::new(ui::BUILD_FINGERPRINT) - .register_handle(ui::counter::handles::increment, { + .register_handle(counter_handles::increment, { let state = state.clone(); move |_| { // req: examples/001 let mut counter = state.counter.lock().unwrap(); *counter += 1; - ui::counter::slots::counter_value.text(*counter) + counter_slots::counter_value.text(*counter) } }) - .register_handle(ui::todos::handles::add_todo, { + .register_handle(todo_handles::add_todo, { let state = state.clone(); move |form| { // req: examples/001 @@ -136,37 +143,37 @@ fn registry(state: Arc) -> HandlerRegistry { todos.push(Todo { id, title: title.into() }); } ( - ui::todos::slots::todo_list.render(&todos_view(&todos)), - ui::todos::forms::new_todo.clear("title"), + todo_slots::todo_list.render(&todos_view(&todos)), + todo_forms::new_todo.clear("title"), ) } }) - .register_handle(ui::wizard::handles::next_step, { + .register_handle(wizard_handles::next_step, { let state = state.clone(); move |_| { // req: examples/001 let mut step = state.wizard_step.lock().unwrap(); *step += 1; - ui::wizard::slots::wizard_step.text(format!("Step {}", *step + 1)) + wizard_slots::wizard_step.text(format!("Step {}", *step + 1)) } }) - .register_handle(ui::auth::handles::login, |form| { + .register_handle(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()); - ui::auth::slots::login_status.text(if ok { + auth_slots::login_status.text(if ok { "Signed in as demo@example.com" } else { "Try demo@example.com with any password" }) }) - .register_handle(ui::page_swap::handles::load_docs, |_| { + .register_handle(page_handles::load_docs, |_| { // req: page_swap/002, req: examples/001 ( - ui::page_swap::slots::content.render(&DocsContent { + page_slots::content.render(&DocsContent { message: "This content came from an EffectBatch.", }), - ui::page_swap::slots::title.text("Docs"), + page_slots::title.text("Docs"), push("/docs"), ) }) @@ -174,12 +181,12 @@ fn registry(state: Arc) -> HandlerRegistry { fn all_examples() -> String { [ - ui::counter::lower_html(include_str!("../templates/counter.heml")), - ui::todos::lower_html(include_str!("../templates/todos.heml")), - ui::wizard::lower_html(include_str!("../templates/wizard.heml")), - ui::auth::lower_html(include_str!("../templates/auth.heml")), + counter::lower_html(include_str!("../templates/counter.heml")), + todos::lower_html(include_str!("../templates/todos.heml")), + wizard::lower_html(include_str!("../templates/wizard.heml")), + auth::lower_html(include_str!("../templates/auth.heml")), render_page_swap("Welcome", "Welcome"), - ui::notifications::lower_html(include_str!("../templates/notifications.heml")), + notifications::lower_html(include_str!("../templates/notifications.heml")), ] .join("\n") } @@ -208,7 +215,7 @@ fn todos_view(todos: &[Todo]) -> TodoItems { fn render_page_swap(title: &'static str, message: &'static str) -> String { // req: html_safety/002 req: view/001 req: component/003 - ui::page_swap::render_html(&PageSwap { + page_swap::render_html(&PageSwap { content: render_docs_content(message), title, })