Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

README.md

tasks — "Momentum", a solid-rs task manager

A client-side task manager written in Rust and transpiled to SolidJS 2.0. Unlike the comparison lab (which is a feature checklist), this is shaped like a real app: a sidebar with views, a reactive task list, a progress panel that is code-split, and a modal composer — all styled with Tailwind and fed by a fetch of local seed data.

Nine components across nine files in src/:

File Component Demonstrates
src/main.rs App entry, create_store, create_signal, create_memo, two-closure create_effect, on_cleanup, reconcile, use_head, hash-based navigation, the hide done toggle, and the page layout (Errored / Show / Loading / Portal)
src/sidebar.rs Sidebar, NavItem a context provider (<ThemeContext value=…>), component children, a computed class={…} for the active view, per-view count badges
src/task_list.rs TaskList Show empty-state fallback, keyed For over the store
src/task_item.rs TaskItem a match on priority, a reactive if-expression class, a namespaced class:done, a For over tags
src/stats.rs StatsPanel (+ LazyStats) lazy! code-splitting, memos over the store, a namespaced style:width progress bar
src/composer.rs Composer <Portal> modal, a ref-backed input (event handlers are zero-arg), priority buttons, calling a callback prop
src/actions.rs add_task #[action] — a generator transaction that yields, pushing to the store
src/api.rs Task, load_tasks a Loader const: an async fetch of /data/tasks.json
src/theme.rs ThemeContext a module-level const holding a create_context, shared across files

Run it

# from the repo root (build the CLI first)
cargo build -p solid-rs

cd examples/tasks

# emit JSX only (no node/npm needed) — prints the generated .jsx
../../target/debug/solid-rs check

# full build (needs Node.js >= 18):
../../target/debug/solid-rs install
../../target/debug/solid-rs build      # or: solid-rs build
../../target/debug/solid-rs dev        # build + serve on :3000, rebuild on change

Open http://localhost:3000. The build also compiles css/styles.css with Tailwind (@tailwindcss/cli) into dist/styles.css and copies data/ to dist/data/ so the app's fetch("/data/tasks.json") resolves.

End-to-end tests

e2e/render.mjs asserts the initial DOM (tasks, sidebar, the lazy stats panel) and e2e/reactivity.mjs toggles tasks, switches views, flips the hide-completed toggle, and drives the Portal composer to add a task (jsdom, no browser needed; the task fetch is mocked):

solid-rs install
solid-rs build
node e2e/render.mjs && node e2e/reactivity.mjs

Notes

  • Tailwind. css/styles.css starts with @import "tailwindcss" and points @source at ../src, because the class names live in Rust source (transpiled to JSX), not in a .jsx/.html file Tailwind would scan by default. A couple of one-off styles (the .tick checkbox, the .pill) are plain CSS utilities.
  • Event handlers are zero-arg. The transpiler only accepts onclick=move || … (no event parameter). The composer therefore reads its input through a ref: the signal setter is passed as ref={set_input}, which Solid calls with the node on mount, and the submit handler reads input().value on demand.
  • No live text search. For the same reason there is no oninput; the reactive filters are the active view and the hide done toggle instead.
  • if inside a for-loop is avoided. An if statement is emitted as return cond ? … : undefined;, so inside a loop it ends the loop after the first item. The memos use tasks.filter(…).length and the toggle finds the row by findIndex and mutates it in the draft, rather than looping.
  • Method names pass through to JS verbatim. Only JS-compatible methods are used (.filter, .findIndex, .push, .length, .replace); Rust-only ones (.is_empty, String::new, ===) are not, and !=/== are the supported comparison operators.
  • lazy! splits StatsPanel into its own chunk, loaded on demand and wrapped in a <Loading> boundary. The e2e proves the split is real by asserting main.js does not contain the stats markup.