"From the web side, native power is one function: invoke. Everything else is just what you pass and what you await."
One Function to Reach the Core
invoke (from @tauri-apps/api/core) is how the webview calls a registered command. You give it the command name and an object of arguments; it returns a Promise that resolves with whatever the command returned. Because it's a Promise, you await it and wrap it in try/catch exactly like a fetch — the mental model you already have for talking to a server transfers directly.
The camelCase ↔ snake_case Bridge
Here's the convention that surprises everyone once: your Rust parameter is user_id (snake_case, idiomatic Rust), but from JavaScript you pass { userId } (camelCase, idiomatic JS). Tauri maps between them automatically. Send snake_case from JS and the argument silently won't bind. Send camelCase and it just works. Let each language keep its own style; Tauri handles the seam.
Type the Return Where You Can
In TypeScript, invoke is generic: invoke<Note>('get_note', { id }) tells the compiler the resolved value is a Note. This is honest only if your Rust command really returns that shape — there's no compile-time link between the two sides, so the type you write is a promise you're making. The typed-ipc lesson shows how to make that promise harder to break.