用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/robertolupi/deep-cuts --skill add-ipc-command命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
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
基于 SOC 职业分类
正在显示 SKILL.md
| 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 |