4.5 KiB
4.5 KiB
AI Guidelines — Domotique Rust/Leptos
Project overview
Home automation dashboard built with Leptos (full-stack Rust). Exposes a web UI for managing quick links and controlling shutters via HTTP API to IoT devices. Runs as a systemd service with SSR + WASM hydration.
Tech stack
| Layer | Technology |
|---|---|
| Framework | Leptos 0.6 (SSR + hydration) |
| Server | Axum 0.7 + Tokio |
| Database | MySQL via SQLx 0.8 (compile-time checked queries) |
| Frontend | WASM (wasm-bindgen), Tailwind CSS 4 |
| Build | cargo-leptos, Makefile |
Architecture
Dual-target compilation
ssrfeature: binary target, runs Axum, has DB access, pre-renders HTMLhydratefeature: lib target compiled to WASM, mounts into DOM
Gate all server-only code (database, filesystem, secrets) with #[cfg(feature = "ssr")].
Directory layout
src/
app.rs # App component, router, nav
lib.rs # WASM entry point (hydrate feature)
main.rs # Binary entry point (ssr feature)
setup.rs # Axum server initialization
database.rs # Global MySqlPool (OnceLock)
models/ # Data models + SQL queries
routes/ # Page components + server functions
migrations/ # SQLx migrations (timestamp-named)
style/ # Compiled Tailwind output
input.css # Tailwind source
Data flow
- Page component defines a
Resourcethat calls a server function - Server function (
#[server]) queries the DB viasqlx::query! - Mutations use
create_server_actionorcreate_action - After mutation, increment a version signal to trigger resource refetch
Coding conventions
Language & naming
- UI labels and user-facing strings are in French (e.g.,
Liens,Volets) - Rust identifiers follow standard conventions:
snake_casefor functions/variables,PascalCasefor types and components - Server function names are descriptive PascalCase (e.g.,
GetLinksAction,LinkAction)
Components
- One
#[component]per logical unit, small and single-responsibility - Use
create_rw_signal/create_signalfor local state - Use
Resourcefor async data; include a version signal for cache invalidation - Modal pattern for forms: single component toggled between create/edit mode
Server functions
#[server(MyAction, "/api", "GetJson")] // or "Cbor" for mutations
pub async fn my_action(...) -> Result<T, ServerFnError> {
// DB access gated by ssr feature implicitly via #[server]
}
- Return
Result<T, ServerFnError> - Log errors with
tracing::error!before returning a generic error to the client - Never expose internal error details to the client
Database
- Use
sqlx::query!/sqlx::query_as!for compile-time checked SQL - Access the pool via the global
get_db()helper indatabase.rs - Keep queries in the model file for the relevant type (
models/link.rs) - Connection pool max: 4 connections — avoid holding connections across await points
Styling
- Tailwind utility classes only — no custom CSS except CSS variables in
input.css - Dark-first design; use
dark:variants when needed - Custom color tokens (defined in
input.css, OKLCH color space):prim,prim-light— primary accentsecond,second-dark— secondarythird,third-light— tertiary accentfourth,green— utility
- Responsive breakpoints:
xs:,sm:,md:,lg:
Error handling
- Server functions:
Result<T, ServerFnError>, log then return generic message - Components: handle
None/ error states explicitly in the view - Avoid
unwrap()in production paths; use?or explicit error handling
Development workflow
# Start dev server (auto-reload)
cargo leptos watch
# Watch Tailwind in parallel
npx tailwindcss -i input.css -o ./style/output.css --watch
# Run DB migrations
sqlx migrate run
# Production deploy
make install # builds Tailwind + WASM + binary, restarts systemd service
Environment variables are loaded from .env (DATABASE_URL required).
Key patterns to follow
- No new dependencies without a clear reason — the stack is intentionally minimal
- No JavaScript — all interactivity through Leptos/WASM
- Keep components in
routes/unless genuinely reusable across multiple pages - New DB tables require a migration file in
migrations/ - External API calls (e.g., shutters) go through server functions, never from WASM directly
- Do not add features beyond what is asked; this is a personal tool, not a framework