Rust WebAssembly Agent Rules
Project Context
You are building a Rust library compiled to WebAssembly for use in browsers or Node.js runtimes. The codebase uses wasm-bindgen for JS interop, wasm-pack for building, and the web-sys/js-sys ecosystem for browser API access.
Code Style & Structure
- Run `cargo fmt` and `cargo clippy -- -D warnings` before every commit; use `#![warn(clippy::all, clippy::pedantic)]` in `lib.rs`.
- Set `crate-type = ["cdylib", "rlib"]` in `Cargo.toml` — `cdylib` for the wasm binary, `rlib` for native tests.
- Separate pure Rust logic (no wasm-bindgen dependencies) in `src/core/` from JS-facing wrappers in `src/bindings/`.
- Call `console_error_panic_hook::set_once()` in an exported `init()` function so panics show in the browser console.
- Write `///` doc comments for all `#[wasm_bindgen]` items with usage examples in JavaScript.
wasm-bindgen & JS Interop
- Annotate all JS-facing functions and types with `#[wasm_bindgen]`.
- Keep the wasm boundary thin: accept and return primitives (`u32`, `f64`, `bool`), `String`, `Vec<u8>`, or `JsValue`.
- Use `serde-wasm-bindgen` with `serde::Serialize/Deserialize` for complex types instead of manual `JsValue` construction.
- Use `#[wasm_bindgen(js_name = "camelCaseName")]` to expose idiomatic JavaScript API names.
- Return `Result<T, JsError>` from exported functions so JavaScript callers receive proper `Error` objects.
- Use `#[wasm_bindgen(typescript_custom_section)]` to provide accurate TypeScript type declarations for exported items.
- Avoid passing large data structures across the boundary repeatedly; prefer bulk transfer operations.
Browser APIs (web-sys & js-sys)
- Enable only the web-sys features you use in `Cargo.toml` — the full feature set inflates compile time significantly.
- Wrap browser API calls in helper functions that return `Result<T, JsValue>` to avoid naked `.unwrap()` on optional DOM elements.
- Use `wasm_bindgen_futures::spawn_local` to drive async Rust futures from synchronous JS context.
- Use `js_sys::Promise` and `wasm_bindgen_futures::JsFuture` to `await` JavaScript promises inside Rust async functions.
- Gate `web_sys::console::log_1` calls behind a `#[cfg(debug_assertions)]` check to exclude them from release builds.
Memory Management
- Use `wee_alloc` or `lol_alloc` as the global allocator to minimize the allocator's contribution to binary size.
- Use `js_sys::Uint8Array::view` (unsafe) for zero-copy reading of wasm memory from JavaScript; document lifetime invariants.
- Drop large resources explicitly; `#[wasm_bindgen]` exported structs transferred to JavaScript are GC-managed — ensure you do not hold duplicate Rust references.
- Profile memory with browser DevTools heap snapshots; look for linear growth across user interactions as a signal of leaks.
Error Handling
- Set up `console_error_panic_hook` in the `init()` function before any other wasm code runs.
- Never panic across the wasm boundary — catch panics with a wrapper and convert to `Result` types.
- Define a crate-level `WasmError` enum with `thiserror::Error`; implement `Into<JsValue>` to convert for export.
- Use `?` operator internally; convert to `JsError::new(&e.to_string())` only at exported function boundaries.
Binary Size Optimization
- Set `opt-level = "z"` and `lto = true` in `[profile.release]` for minimum binary size.
- Use `wasm-opt` (run automatically by `wasm-pack build --release`) to shrink the binary further via Binaryen passes.
- Audit binary size with `twiggy top -n 20 pkg/app_bg.wasm` to identify the largest contributors.
- Gate heavy features with Cargo feature flags so callers can opt in to larger bundles only when needed.
- Avoid pulling in `std::fmt` formatting machinery for error strings in size-critical paths; use fixed error codes instead.
- Target under 200 KB gzipped for utility libraries; under 500 KB for full interactive applications.
Testing
- Write pure Rust unit tests in `#[cfg(test)]` modules for all core logic — no browser required.
- Write wasm integration tests with `#[wasm_bindgen_test]` in `tests/` and run with `wasm-pack test --headless --chrome`.
- Test that exported functions return proper JS `Error` objects by calling them from `#[wasm_bindgen_test]` with invalid inputs.
- Set up CI to run both `cargo test` (native) and `wasm-pack test --headless --firefox` on every pull request.
- Benchmark with `criterion` for native tests and browser `performance.measure` APIs for wasm-specific hotpaths.