bodged refactor

This commit is contained in:
2026-03-08 20:10:15 +01:00
parent 71e63e4410
commit b10326b242
12 changed files with 801 additions and 339 deletions
+14 -48
View File
@@ -2,24 +2,17 @@ use crate::queries::{ListTodos, Todo};
use livevue_rs::{RenderContext, global, mangle};
use maud::{html, Markup};
/// Root page component: renders the full todo list with add form.
///
/// Reads signals:
/// - `newTodo` (global, server-relevant): the text input value
///
/// Exercises:
/// - `cx.run()` for data fetching
/// - `cx.signal()` with `global()` scoping + `Signal::val` in expressions
/// - `cx.signal()` with `mangle().client_only()` scoping (in todo_item)
/// - DataStar `data-bind`, `data-on:click`, `data-attr` attributes
/// - `@post()` actions for mutations
pub async fn todo_list(cx: &mut RenderContext) -> anyhow::Result<Markup> {
// Fetch all todos
// Fetch all todos (registers CacheKey::Table { table: "todos" })
let todos = cx.run(ListTodos).await?;
// Global signal for the "new todo" input. This call does two things:
// 1. Records "newTodo" as a touched signal (for demangling tracking)
// 2. Returns a Signal struct whose .val is "$newTodo" for expressions
// Read the clock from KV (registers CacheKey::Channel { key: "clock" })
let clock = cx
.kv_run("clock")
.await
.and_then(|v| v.as_str().map(|s| s.to_string()))
.unwrap_or_else(|| "".to_string());
let new_todo = cx.signal("newTodo", global());
let todo_count = todos.len();
@@ -29,21 +22,17 @@ pub async fn todo_list(cx: &mut RenderContext) -> anyhow::Result<Markup> {
div.todo-app {
h1 { "LiveVue.rs — Todo List" }
// Navigation link to About page (exercises tree signal + page routing)
nav {
span.nav-current { "Todos" }
" | "
button.nav-link
data-on:click="$tree = {page: 'About'};" {
data-on:click="$tree = {page: 'About'}" {
"About"
}
}
// Add todo form.
// data-bind="newTodo" binds the input to the $newTodo signal.
// Value syntax is used instead of data-bind-newTodo because HTML
// normalises attribute names to lowercase, which would corrupt the
// camelCase signal name (newTodo → newtodo).
p.clock { "🕐 " (clock) }
div.add-form {
input
type="text"
@@ -57,12 +46,10 @@ pub async fn todo_list(cx: &mut RenderContext) -> anyhow::Result<Markup> {
}
}
// Stats
p.stats {
(format!("{} items, {} completed", todo_count, done_count))
}
// Todo items
ul.todo-list {
@for todo in &todos {
(todo_item(cx, todo))
@@ -76,21 +63,9 @@ pub async fn todo_list(cx: &mut RenderContext) -> anyhow::Result<Markup> {
})
}
/// Renders a single todo item.
///
/// This is a static child component — called directly from `todo_list`,
/// not resolved through the component registry.
///
/// Demonstrates per-instance, client-only signal via `mangle().client_only()`.
fn todo_item(cx: &mut RenderContext, todo: &Todo) -> Markup {
// Per-instance, client-only signal for edit mode.
// Not used in v0 rendering, but demonstrates the mangled signal API
// and ensures it's tracked as touched. Produces: "_editing__<id>"
let _editing = cx.signal("editing", mangle(todo.id).client_only());
// Build class string server-side. Since we do a full rerender on every
// state change, there's no need for data-class here — the server knows
// whether the todo is completed.
let title_class = if todo.completed {
"todo-title completed"
} else {
@@ -99,25 +74,16 @@ fn todo_item(cx: &mut RenderContext, todo: &Todo) -> Markup {
html! {
li.todo-item id=(format!("todo-{}", todo.id)) {
// Checkbox to toggle completion.
// `checked[expr]` is maud's syntax for conditional boolean attributes.
input
type="checkbox"
checked[todo.completed]
data-on:click=(format!("@post('/action/toggle_todo?id={}')", todo.id))
;
// Title with strikethrough when completed (class set server-side)
span class=(title_class) {
(todo.title)
}
// Delete button
span class=(title_class) { (todo.title) }
button.delete-btn
data-on:click=(format!("@post('/action/delete_todo?id={}')", todo.id))
{
data-on:click=(format!("@post('/action/delete_todo?id={}')", todo.id)) {
"×"
}
}
}
}
}