.claude/skills/tauri-2/tauri-syntax/tauri-syntax-events/SKILL.md
Use when implementing event-driven communication between Rust and JavaScript or between windows. Prevents event listener memory leaks from missing unlisten cleanup and payload serialization failures. Covers emit/listen/once patterns, Emitter and Listener traits, global vs window-scoped events, and frontend event API. Keywords: tauri events, emit, listen, unlisten, event payload, Emitter trait, Listener trait, window events.
npx skillsauth add OpenAEC-Foundation/OpenAEC-Workspace-Composer tauri-syntax-eventsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Trait | Implemented By | Purpose |
|-------|---------------|---------|
| Emitter | App, AppHandle, Webview, WebviewWindow, Window | Send events |
| Listener | App, AppHandle, Webview, WebviewWindow, Window | Receive events |
| Method | Scope | Description |
|--------|-------|-------------|
| emit(event, payload) | Global | Broadcast to ALL listeners |
| emit_str(event, payload) | Global | Broadcast with pre-serialized JSON string |
| emit_to(target, event, payload) | Targeted | Send to a specific window/webview label |
| emit_filter(event, payload, filter_fn) | Filtered | Send to targets matching a predicate |
| Method | Fires | Returns |
|--------|-------|---------|
| listen(event, handler) | Every time | EventId |
| once(event, handler) | Once, then auto-removes | EventId |
| listen_any(event, handler) | Every time, any source | EventId |
| once_any(event, handler) | Once, any source | EventId |
| unlisten(id) | N/A | () |
| Function | Import | Description |
|----------|--------|-------------|
| listen<T>(event, handler, options?) | @tauri-apps/api/event | Listen for events, returns Promise<UnlistenFn> |
| once<T>(event, handler, options?) | @tauri-apps/api/event | Listen once, returns Promise<UnlistenFn> |
| emit(event, payload?) | @tauri-apps/api/event | Broadcast event globally |
| emitTo(target, event, payload?) | @tauri-apps/api/event | Send event to specific target |
| Member | Value |
|--------|-------|
| WINDOW_RESIZED | "tauri://resize" |
| WINDOW_MOVED | "tauri://move" |
| WINDOW_CLOSE_REQUESTED | "tauri://close-requested" |
| WINDOW_DESTROYED | "tauri://destroyed" |
| WINDOW_FOCUS | "tauri://focus" |
| WINDOW_BLUR | "tauri://blur" |
| WINDOW_SCALE_FACTOR_CHANGED | "tauri://scale-change" |
| WINDOW_THEME_CHANGED | "tauri://theme-changed" |
| WINDOW_CREATED | "tauri://window-created" |
| WEBVIEW_CREATED | "tauri://webview-created" |
| DRAG_ENTER | "tauri://drag-enter" |
| DRAG_OVER | "tauri://drag-over" |
| DRAG_DROP | "tauri://drag-drop" |
| DRAG_LEAVE | "tauri://drag-leave" |
NEVER forget Clone on event payload structs — emit() requires Serialize + Clone. Omitting Clone causes a compile error that does not clearly indicate the source.
NEVER use special characters in event names — only alphanumeric, -, /, :, and _ are allowed. Other characters cause a runtime panic.
NEVER forget to call the unlisten function in frontend components — this causes memory leaks. In React, ALWAYS clean up in the useEffect return function.
NEVER forget to await the listen() call in TypeScript — listen() returns Promise<UnlistenFn>, not UnlistenFn directly.
ALWAYS import the Emitter trait (use tauri::Emitter;) before calling emit() in Rust — the method is not available without the trait import.
ALWAYS import the Listener trait (use tauri::Listener;) before calling listen() in Rust.
// Tauri 2.x — Broadcast from Rust to all frontend listeners
use tauri::Emitter;
#[derive(Clone, serde::Serialize)]
struct ProgressUpdate {
task_id: String,
percent: f64,
message: String,
}
// In a command or setup hook:
app_handle.emit("progress", ProgressUpdate {
task_id: "download-1".into(),
percent: 45.5,
message: "Downloading file...".into(),
})?;
// Tauri 2.x — Listen in TypeScript
import { listen } from '@tauri-apps/api/event';
interface ProgressUpdate {
taskId: string;
percent: number;
message: string;
}
const unlisten = await listen<ProgressUpdate>('progress', (event) => {
console.log(`${event.payload.taskId}: ${event.payload.percent}%`);
});
// When done:
unlisten();
// Tauri 2.x — Frontend emits
import { emit } from '@tauri-apps/api/event';
await emit('user-action', { action: 'save', documentId: 42 });
// Tauri 2.x — Rust listens (typically in setup hook)
use tauri::Listener;
app.listen("user-action", |event| {
println!("User action: {:?}", event.payload());
});
// Tauri 2.x — Emit to specific window
use tauri::Emitter;
app_handle.emit_to("main", "notification", "Update available")?;
// Emit with filter
app_handle.emit_filter("sync", payload, |target| {
matches!(target, tauri::EventTarget::WebviewWindow { label } if label != "settings")
})?;
// Tauri 2.x — Frontend targeted emit
import { emitTo } from '@tauri-apps/api/event';
await emitTo('settings', 'config-changed', { key: 'theme', value: 'dark' });
// Tauri 2.x — Correct cleanup in React useEffect
import { listen } from '@tauri-apps/api/event';
useEffect(() => {
let unlisten: (() => void) | undefined;
listen<string>('update', (event) => {
setState(event.payload);
}).then((fn) => { unlisten = fn; });
return () => {
if (unlisten) unlisten();
};
}, []);
// Tauri 2.x — Rust: listen once, auto-removes
use tauri::Listener;
app.once("initialization-complete", |event| {
println!("App initialized: {:?}", event.payload());
});
// Tauri 2.x — TypeScript: listen once
import { once } from '@tauri-apps/api/event';
await once<string>('app-ready', (event) => {
console.log('App initialized:', event.payload);
});
type EventName = string; // alphanumeric, hyphens, slashes, colons, underscores
type EventCallback<T> = (event: Event<T>) => void;
type UnlistenFn = () => void;
interface Event<T> {
event: EventName;
id: number;
payload: T;
}
interface Options {
target?: string | EventTarget;
}
type EventTarget =
| { kind: 'Any' }
| { kind: 'AnyLabel'; label: string }
| { kind: 'Window'; label: string }
| { kind: 'Webview'; label: string }
| { kind: 'WebviewWindow'; label: string };
Events require core:event:default in the capability file:
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:event:default"
]
}
development
Use when integrating Vite with a backend framework, rendering Vite assets from server-side templates, or setting up dev/production HTML serving. Prevents incorrect manifest.json traversal and missing CSS chunk resolution in production. Covers build.manifest configuration, .vite/manifest.json structure, ManifestChunk properties, dev mode HTML setup, production rendering, CSS/JS chunk resolution, and modulepreload polyfill. Keywords: backend integration, manifest.json, ManifestChunk, Django, Laravel, Rails, modulepreload.
development
Use when encountering dev server startup failures, HMR issues, proxy errors, CORS blocks, or module not found errors during development. Prevents misconfiguring server.hmr behind reverse proxies and forgetting appType: 'custom' in middleware mode. Covers HMR full-reload debugging, proxy configuration, CORS setup, HTTPS certificates, server.fs.strict violations, port conflicts, WebSocket failures, file watcher issues, and middleware mode. Keywords: dev server, HMR, proxy, CORS, HTTPS, WebSocket, port conflict, server.fs.strict, middleware mode, file watcher.
development
Use when encountering pre-bundling errors, dependency resolution failures, stale cache issues, or slow development server startup. Prevents excluding CJS dependencies from pre-bundling (which breaks runtime module resolution) and misconfiguring optimizeDeps. Covers CJS/ESM conversion failures, missing dependency auto-discovery, optimizeDeps configuration, monorepo linked dependencies, cache invalidation, browser cache staleness, and large dependency tree performance. Keywords: pre-bundling, optimizeDeps, CJS, ESM, cache, dependency resolution, monorepo, node_modules/.vite.
development
Use when encountering Vite build failures, chunk size warnings, or version-specific build errors. Prevents the common mistake of using deprecated rollupOptions in v8 or misconfiguring build targets and minifiers. Covers Rolldown/Rollup bundling failures, CSS minification errors, sourcemap problems, library mode build failures, BundleError handling, and asset processing errors. Keywords: build error, Rolldown, chunk size, sourcemap, library mode, minify, BundleError, rollupOptions, build.target.