Engineering / tauri-stinky
Detect Rust and Tauri maintainability smells across the backend crate, the IPC boundary, and the app shell, explain the cost of each, and propose a fix with a source link.
Signed, graded provenance for this skill. Integrity, authorship, capability, and freshness as evidence you weigh, not a verified badge.
A holistic code-smell detector and quality gate for Rust and Tauri. It exists as a counterweight, agents trained mostly on frontend code write Rust confidently and at volume, and the volume is where the smells hide. It finds the patterns that make a crate, a command, or the IPC boundary hard to read, reason about, and trust, explains the cost of each, and proposes a concrete fix. The full catalogs with detection signals, fixes, exceptions, and sources are in rust-catalog.md and tauri-catalog.md; read the relevant one before running a scan.
It defers neighboring concerns to sibling skills so it does not duplicate them: React component, hook, and TypeScript smells on the frontend half to react-stinky, and color literals to theme-colors. If those are not installed, note the finding in one line and move on. Everything about Rust discipline, the Tauri shell, and the boundary between them is in scope.
Targets Tauri v2. On a v1 project, say so and adapt the Tauri-layer advice rather than prescribing v2 config verbatim.
Thirteen pillars, 57 categories. Detection signals, fixes, and sources live in the two catalogs.
Rust discipline (rust-catalog.md):
unwrap in production paths, Result<T, String> and message-substring matching, swallowed results, context-free errors, panic as control flow, catch-all match arms.&String params, needless collects, the Arc<Mutex<T>> reflex, index loops.pub sprawl, version drift, a dependency for a function..await, detached tasks that swallow errors, block_on bridges, needless async.unsafe, blanket lint suppression, untested pure cores and unguarded boundary logic.The Tauri layer (tauri-catalog.md):
manage, unmanaged-state panics, the wrong mutex flavor.invoke sprawl, unhandled rejections.| Mode | Trigger | What to scan |
|---|---|---|
| Repo sweep | "smell-check the app" | src-tauri/**/*.rs, tauri.conf.json, capabilities/, Cargo.toml, plus the frontend boundary module and every invoke/listen call site. Skip target/, generated code, and #[cfg(test)] bodies for production-only categories. |
| Crate or folder scan | directories named | Same, scoped to those directories. |
| File scan | specific files named | Read each fully; check every function, type, command, and config block. |
| Fragment sniff | a pasted function or snippet | Check only that surface. State what you assumed about anything off-screen. |
| Gate setup | "add quality gates", "set up clippy/CI" | Skip the scan; apply gates.md to the project's toolchain and report what each gate will start catching. |
Repo-sweep and boundary-inclusive scopes additionally run the boundary pass, the cross-check of commands, invokes, events, types, and capabilities against each other that per-file scans cannot see. Narrower scopes cannot, so say the boundary contract was not checked rather than implying it is clean.
unwrap a user input can reach, a sync command doing network IO on the main thread, a mutex guard across .await, a $HOME/** capability scope, a path from the webview used without containment, csp: null, a secret in the JS bundle.)#[allow], events used as a stream firehose.)&String parameter, a needless mid-chain collect, comment noise, a default release profile before anyone downloads the app.)The catalogs carry a per-smell exception line. These cut across all of them. Honor them or this skill becomes a nuisance.
unwrap, expect, and panics are fine inside #[cfg(test)] and integration tests..lock().unwrap_or_else(|e| e.into_inner()) is poison recovery, not an unwrap smell. std::sync::Mutex is the right default for Tauri managed state.spawn_blocking is a legitimate architecture; do not demand tokio::fs where std::fs is consistently wrapped.invoke string is the pattern working, not stringly sprawl. Browser/test mocks beside it are a feature.react-stinky, color literals to theme-colors.Tauri Stinky report, <scope>src-tauri/src/export.rs[Rancid] sync-command-blocking (command design), line 41Smell: #[tauri::command] fn export_bundle does zip compression synchronously.Cost: runs on the main thread; the window freezes for the whole export.Fix: make it async and wrap the compression in spawn_blocking, returning progressover a Channel.Source: Tauri v2 async commands (https://v2.tauri.app/develop/calling-rust/#async-commands)[Funky] stringly-errors (error handling), line 58Smell: returns Result<PathBuf, String> via map_err(|e| e.to_string()).Cost: the frontend can only substring-match; any reword silently breaks handling.Fix: one thiserror enum deriving Serialize with a stable kind field; map once atthe boundary.Source: Tauri v2 error handling (https://v2.tauri.app/develop/calling-rust/#error-handling)Boundary contract: 52 commands, 49 invoked, 3 orphaned; 5 events, all paired.Summary: 1 rancid, 1 funky across 1 file, 3 orphaned commands.
When the scope is clean, say so plainly: "Smells fresh. No Rust or Tauri smells found in <scope>."
The Rust categories are sourced to the Rust Book, the Rust API Guidelines, Clippy lint docs, and the Tokio docs; the Tauri categories to the Tauri v2 documentation (develop, security, and config references). Each entry in rust-catalog.md and tauri-catalog.md carries its own link.