一键导入
add-ipc-command
Pattern for adding a new Tauri IPC command (request/response or push event) in the deep-cuts monorepo
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Pattern for adding a new Tauri IPC command (request/response or push event) in the deep-cuts monorepo
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Experimental protocol for Deep Cuts research, prototypes, model evaluations, threshold tuning, ablations, metric comparisons, and claims about accuracy or quality. Use before running or interpreting experiments so bots preserve train/validation/test boundaries, avoid leakage, compare against baselines, and report results honestly.
Guidelines for creating, updating, reorganizing, and reviewing Deep Cuts documentation in the project wiki, including page taxonomy, lifecycle status, protected pages, proposal handling, and link verification.
Pattern for multi-agent collaboration sessions in the deep-cuts fam — forge-first coordination over the botfam substrate (Gitea issues/PRs as the coordination plane, the unified `botfam wait` wake loop, bare-actor worktrees), with IRC opt-in for design sprints, plus session-log conventions
Draft, structure, and finalize a Deep Cuts blog-series post so it conforms to the post template, house voice, and cross-post link rules. Use when writing, drafting, assembling, or editing a "Deep Cuts" blog post (the rlupi.com series), preparing a post for publishing, or adding front matter to one. Covers the template, front matter schema, voice, agent crediting, and the link linter.
Checklist and guide for adding a new analysis pass to the trait-based modular pipeline
Safe pattern for adding SQLite schema migrations in the deep-cuts Rust/rusqlite_migration stack
| name | add-ipc-command |
| description | Pattern for adding a new Tauri IPC command (request/response or push event) in the deep-cuts monorepo |
IPC is the only channel between the Svelte frontend and the Rust backend. There are two patterns: request/response (invoke) and push events (emit/listen). Most commands use request/response; push events are used for long-running background work (scan updates, analysis progress).
invoke)Commands live in src-tauri/src/commands/ (one file per domain — library.rs, playlists.rs, analysis.rs, etc.). Add your handler to the appropriate file or create a new one and expose it via commands/mod.rs.
#[tauri::command]
pub fn my_command(
conn_state: tauri::State<'_, Mutex<Connection>>,
some_arg: String,
) -> Result<MyReturnType, String> {
let conn = conn_state.lock().map_err(|e| e.to_string())?;
// ... do work ...
Ok(result)
}
Result<T, String> — the Err string surfaces as a rejected Promise in the frontend.conn_state: tauri::State<'_, Mutex<Connection>> (registered at startup as app.manage(Mutex::new(conn))).app: tauri::AppHandle.Find the tauri::generate_handler![] macro in lib.rs and add your command:
.invoke_handler(tauri::generate_handler![
// ... existing commands ...
my_command,
])
Forgetting this step compiles cleanly but the frontend call will throw at runtime.
src/lib/ipc.tsThis step is required for every new command. $lib/ipc is the single import point for all frontend IPC — it owns local-debug mocks and the command type boundary.
The mock system has two files:
src/lib/mock-data.ts — typed fixture data exported as constants (MOCK_TRACKS, MOCK_PLAYLISTS, etc.). Add new entity arrays or objects here when the command introduces a new return type.src/lib/ipc.ts — the MOCK_RESPONSES map wires command names to handlers that use data from mock-data.ts.For a simple query command:
// In src/lib/mock-data.ts — add fixture data if introducing a new type
export const MOCK_MY_THINGS: MyThing[] = [
{ id: 1, name: "Example Thing", value: 42 },
];
// In src/lib/ipc.ts — wire the command
import { MOCK_MY_THINGS } from "$lib/mock-data";
const MOCK_RESPONSES: Record<string, (args?: any) => unknown> = {
// ... existing entries ...
get_my_things: () => MOCK_MY_THINGS,
get_my_thing: ({ id }: { id: number }) => MOCK_MY_THINGS.find(t => t.id === id) ?? null,
save_my_thing: () => null, // side-effect only — still add entry to suppress warning
};
Rules:
?local_debug=1 dev mode works without Tauri. Reference the ui-debug skill for how to verify this.save_*, delete_*), return null or undefined — still add the entry so the console warning for unhandled commands is suppressed.get_track), pattern-match on args to return something plausible.mock-data.ts; inline lambdas in ipc.ts are for simple derivations from that data.// ✓ correct
import { invoke, listen } from "$lib/ipc";
// ✗ wrong — bypasses mocks and type map
import { invoke } from "@tauri-apps/api/core";
Never import invoke or listen directly from @tauri-apps/api in app code. Only $lib/ipc.ts itself imports from the Tauri package.
import { invoke } from "$lib/ipc";
const result = await invoke<MyReturnType>('my_command', { someArg: 'value' });
Argument names are converted from camelCase (TypeScript) to snake_case (Rust) automatically by Tauri. The return type generic is optional but recommended for type safety.
Use this when the Rust side needs to notify the frontend proactively (e.g. scan progress, analysis completion).
#[tauri::command]
fn start_long_task(app: tauri::AppHandle) -> Result<(), String> {
std::thread::spawn(move || {
for i in 0..100 {
// ... do work ...
app.emit("my-task-progress", serde_json::json!({ "percent": i })).ok();
}
app.emit("my-task-complete", ()).ok();
});
Ok(())
}
app.emit() broadcasts to all WebView windows. The payload is serialized to JSON.
import { listen } from "$lib/ipc";
import { onDestroy } from 'svelte';
const unlisten = await listen<{ percent: number }>('my-task-progress', (event) => {
progress = event.payload.percent;
});
// Clean up on component destroy to avoid listener leaks
onDestroy(() => unlisten());
Always store and call the unlisten function in onDestroy — leaked listeners accumulate across hot-reloads in dev mode.
Add a comment near the listen call (or in the relevant store) that documents:
// Event: 'my-task-progress'
// Payload: { percent: number }
// Emitted by: start_long_task (commands/your_domain.rs)
// Lifecycle: emitted 0–N times between 'my-task-start' and 'my-task-complete'
// Unlisten: owned by ThisComponent, cleaned up in onDestroy
This prevents the event name, payload shape, and lifecycle from becoming implicit tribal knowledge. Push events are harder to discover than commands — document them at the listen site.
DbManager is already registered as managed state in lib.rs. To add new shared state (e.g. a cache, a flag), register it before .run():
.manage(MyState::new())
Then receive it in your command:
fn my_command(my_state: tauri::State<'_, MyState>) -> Result<(), String> { ... }
Rust
#[tauri::command]Result<T, String> (or Result<(), String> for side-effects)tauri::generate_handler![] in lib.rscargo test --manifest-path src-tauri/Cargo.toml still passessrc/lib/ipc.ts + src/lib/mock-data.ts (required — do not skip)
CommandMap in ipc.ts with precise args and result types (use Record<string, unknown> / unknown with a // TODO: tighten comment only if the shape is truly unclear)mock-data.ts as typed exported constantsMOCK_RESPONSES in ipc.ts with a realistic return value for UI-affecting commands, or null/undefined for pure side-effects@tauri-apps/api imports added to app codeFrontend
invoke / listen imported from $lib/ipc (not from @tauri-apps/api directly)unlisten stored and called in onDestroy| Mistake | Symptom | Fix |
|---|---|---|
Forgetting tauri::generate_handler![] | Frontend call throws "command not found" at runtime; Rust compiles fine | Add the command to generate_handler![] in lib.rs |
Importing invoke directly from @tauri-apps/api/core | ?local_debug=1 mock mode silently falls through to real Tauri and errors | Import from $lib/ipc only |
No MOCK_RESPONSES entry for a UI-affecting command | ?local_debug=1 logs a console warning and resolves undefined; UI breaks in ways that are hard to debug | Add a realistic mock to MOCK_RESPONSES |
Missing CommandMap entry | invoke call is untyped; TypeScript won't catch wrong args or return type | Add entry to CommandMap in ipc.ts |
| Mismatched command name string | Frontend call throws "command not found"; Rust compiles fine | The string must match the Rust function name exactly — Tauri does not rename handlers |
| Push-event listener not cleaned up | Duplicate handlers accumulate across hot-reloads; events fire multiple times | Store the unlisten fn and call it in onDestroy |
| Push-event payload type not documented | Future callers guess the shape from runtime logs | Add payload type, event name, emit source, lifecycle, and unlisten ownership as a comment at the listen site |