This document outlines the patterns and constraints for the Rigorstarter Rust-Tauri-Leptos project. Follow these rules to avoid common pitfalls related to WASM, Leptos reactivity, Tauri integration, and cross-platform compatibility.
- State: Use
signal()for local state. PassReadSignalandWriteSignalto child components to maintain a single source of truth. - Updates: Use
.set()for direct updates and.update(|v| ...)for updates based on the current value.
- Conditional Rendering: Prefer the
<Show />component overmatchexpressions that return.into_any(). - Avoiding Flicker: Avoid returning
into_any()inside a reactive closure in the rootAppcomponent. This often causes the entire subtree to unmount and remount, leading to flickering and "endless re-renders." - Component Isolation: Move complex switching logic (like the main content area) into a separate component (e.g.,
MainContent) to isolate re-renders and maintain component identity.
- Invocation:
Callback<T>is a struct, not a function. To trigger it, you must use the.run(value)method.- ❌
on_click(()) - ✅
on_click.run(())
- ❌
- Effects: Never trigger side effects (like
spawn_localor DOM manipulation) directly inside aview!render closure. This can cause unstable rendering or runtime panics. - Effect Placement: Place side effects inside
Effect::new(move |_| { ... }). - Async Calls: Use
spawn_localfor anyasyncblock (like Tauriinvokecalls) to ensure they run on the WASM event loop.
- Owned Data: When mapping over signals (e.g.,
registry.get().iter()), avoid returning references (&str) in the resulting collection. - The Fix: Use
.clone()or.to_string()to return ownedStrings. Returning references to temporary values created during.get()will cause compilation errors (E0515).
- The Problem: In some environments, state changes may occur, but the UI doesn't repaint immediately (the app feels "frozen" until a global event like a theme toggle occurs).
- The Fix (The "Wake Up" Pattern):
- Lazy Rendering: Never render all pages and hide them with
display: none. Only render the active component to keep the DOM lean. - Schedule Updates: For critical UI transitions (like navigation), wrap the state update in
requestAnimationFrameto sync with the browser's paint cycle.- ✅
requestAnimationFrame(|_| active_page.set(Some(id)))
- ✅
- Force Reflow: If the UI still lags, use a "forced reflow" in an
Effectto kick the renderer.- ✅
Effect::new(move |_| { active_page.get(); let _ = document.body().offset_height(); })
- ✅
- Lazy Rendering: Never render all pages and hide them with
- Error Handling: All Tauri commands should return a
Result<T, AppError>to ensure frontend errors are caught and handled. - Data Transfer: Use
serde_wasm_bindgenfor efficient conversion between Rust types and JS values. - Single Responsibility: Commands should perform a single task. If a command is doing complex logic (like registry construction + file reading), split it into smaller functions.
- Avoid Hardcoded Relative Paths: Never use hardcoded paths relative to the project root (e.g.,
src/utils/...) in Tauri commands, as these paths will not exist in a distributed package. - Platform Abstraction: Avoid direct access to OS-specific filesystems (like
/procon Linux). Use crates likesysinfoordirsto achieve cross-platform compatibility. - Path Resolution: Use
tauri::AppHandleortauri::PathResolverto resolve paths to application data or configuration files.
- Modular Initialization: Avoid "God functions" in
lib.rs. Break down therun()function into smaller, testable setup modules (e.g.,db::init,menu::setup,tray::setup). - FFI Safety: Minimize use of
unsafeand manual FFI. Prefer high-level, safe Rust wrappers for system calls.
- Terminal Logging: Use the
log_messageTauri command to print frontend logs to the Rust terminal. - Example:
spawn_local(async move { log_to_terminal("Message").await; }). - Browser Console: Always check the browser's developer console for WASM panics or failed
invokecalls.
- Use
./run.shfor development. - If the build fails with strange errors after a major change, try
rm -rf distto clear frontend artifacts.