Client-side web app that converts images between formats using Rust compiled to WebAssembly. See PLANNING.md for architecture details.
- Rust (WASM library):
imagecrate,wasm-bindgen,wasm-pack - TypeScript (frontend): Astro + Preact, Vite bundler
- Styling: Tailwind CSS v4
snake_casefor functions, methods, variables, modules, and file namesCamelCasefor types, traits, and enumsSCREAMING_SNAKE_CASEfor constants and statics- Prefix unused variables with
_(e.g.,_unused) - Use descriptive names — avoid single-letter variables except in short closures or iterators
- Use
Result<T, E>for all fallible operations — neverunwrap()orexpect()in library code unwrap()is only acceptable in tests and examples- For WASM-exported functions, return
Result<T, JsError>so errors propagate to JavaScript - Use the
?operator for error propagation - Provide meaningful error messages:
map_err(|e| JsError::new(&format!("Failed to decode image: {e}")))
- One public type or concept per module where practical
- Keep
lib.rsas a thin entry point — delegate logic to submodules (convert.rs,formats.rs) - Group related
useimports: std first, then external crates, then local modules, separated by blank lines - All
#[wasm_bindgen]exports live inlib.rs— internal logic stays in submodules
- Add doc comments (
///) to all public functions and types - Include a brief description and note any panics, errors, or safety considerations
- Do not add doc comments to private functions unless the logic is non-obvious
- Prefer iterators and combinators over manual loops where they improve clarity
- Use
matchexhaustively — avoid wildcard_catch-alls on enums (so the compiler catches new variants) - Prefer
&stroverStringfor function parameters when ownership isn't needed - Use
impl Into<T>or generics sparingly — prefer concrete types for WASM boundary functions - Prefer
Vec<u8>for byte buffers,&[u8]for borrowed byte slices
- Always set
default-features = falseon theimagecrate (avoidsrayonwhich breaks WASM) - Only enable the format features actually needed in
Cargo.toml - Keep WASM-exported function signatures simple —
&[u8],Vec<u8>,String,JsValue,JsError - Minimize the number of JS↔WASM boundary crossings
- Drop large allocations explicitly when done (e.g.,
drop(input_buffer)before encoding output)
- Write unit tests in a
#[cfg(test)]module at the bottom of each file - Use
wasm-pack test --headless --chromefor WASM-specific tests - Test error paths, not just happy paths
- Unit tests go in
web/tests/unit/(picked up by Vitest) - E2E tests go in
web/tests/e2e/(picked up by Playwright) - Never place test files next to source files in
src/
- Use strict TypeScript (
"strict": truein tsconfig) - Prefer
constoverlet; never usevar - Use explicit types for function signatures; allow inference for local variables
- Use
async/awaitover raw Promises - Avoid using type coercion unless it's absolutely necessary. Try every other option except type coercion.
- Avoid using the
anytype - Use descriptive variable names
- If an expression is very verbose and it can be asigned to a variable to make it easier to understand do it
- Add JSDocs to all functions, what's especially important is a function description
- Rust: Run
cargo fmtbefore committing. Runcargo clippy -- -D warningsand fix all warnings. - TypeScript: Run
cd web && npm run check:all— this runs type checking, ESLint, and Prettier in sequence. - Prettier: Run
cd web && npm run formatto auto-fix formatting. Runcd web && npm run format:checkto verify. - Treat all compiler warnings as errors — do not leave warnings unaddressed.
crates/
└── image-converter/
└── src/ # Rust/WASM library source (lib.rs + submodules)
web/
├── src/ # TypeScript frontend source (Astro pages, Preact components)
├── tests/
│ ├── unit/ # Vitest unit tests — never colocate with source
│ └── e2e/ # Playwright e2e tests
└── public/ # Static assets
plans/ # Implementation plan files (YYYYMMDD-NN-title.md)
research/ # Research documents (YYYYMMDD-NN-title.md)
decisions/ # Decision records (YYYYMMDD-title.md)
Rules:
- New Rust source files go in
crates/image-converter/src/— never in project root orweb/ - New TypeScript components go in
web/src/— follow the existing directory structure inside it - Test files go in
web/tests/unit/orweb/tests/e2e/— never next to source files
# Build WASM
wasm-pack build crates/image-converter --target web --release
# Run Rust tests
cargo test --manifest-path crates/image-converter/Cargo.toml
# Frontend dev
cd web && npm run dev
# Production build
cd web && npm run build