adding-ipc-commands
Skill2 starsChanged 48 days agoMITView on GitHub
Adds or changes a Tauri IPC command in Teitunnel, from the Rust handler in apps/desktop/src-tauri through generated TypeScript bindings to a TanStack Query hook, the dev mock and tests. Use when the desktop UI needs new data or a new action from Rust, or when a command's arguments or return type change.
In this repository11 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- adding-cli-commandsSkill
- adding-doctor-checksSkill
- adding-mcp-toolsSkill
- building-uiSkill
- changing-cloudflare-resourcesSkill
- reviewing-pull-requestsSkill
- verifying-changesSkill
- writing-docsSkill
- writing-user-facing-textSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 0 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 39
Never put a secret in an IPC type.
- Very specific: line 86
Add the function to collect_commands![…] in apps/desktop/src-tauri/src/ipc.rs, then run pnpm bindings.
- Very specific: line 122
Run pnpm verify. If bindings are stale, run pnpm bindings again.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Adding an IPC command
The UI talks to Rust only through generated commands. A request travels:
component → hook in features/<area>/queries.ts → commands.x() in
src/lib/ipc/bindings.ts (generated) → #[tauri::command] in src-tauri/src/ipc/<area>.rs
→ teitunnel_core.
Copy this checklist and tick it off:
- [ ] 1. Logic lives in crates/core (not in src-tauri)
- [ ] 2. Command in src-tauri/src/ipc/<area>.rs
- [ ] 3. Registered in collect_commands! in src-tauri/src/ipc.rs
- [ ] 4. pnpm bindings
- [ ] 5. Query key and hook in the feature's queries.ts
- [ ] 6. Dev mock answers it
- [ ] 7. Tests (Rust for the logic, Vitest for the hook or component)
- [ ] 8. pnpm verify1. Put the logic in core
src-tauri has no business logic. Write the behaviour in crates/core with its own unit
tests; the command only validates input, calls core and maps errors to AppError.
Types that cross IPC derive specta::Type behind the specta feature:
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[serde(rename_all = "camelCase")]
pub struct RouteStats { /* … */ }Never put a secret in an IPC type. Return a boolean such as hasToken instead.
2. Write the command
Name it <area>_<verb> (inspect_stats, routes_plan_add). Document it: the doc comment
becomes the TypeScript doc.
/// A tap's traffic over `range` (null: a tap this app doesn't know).
#[tauri::command]
#[specta::specta]
pub fn inspect_stats(
state: State<'_, AppState>,
tap: TapId,
range: AnalyticsRange,
) -> Option<RouteStats> {
LensSource::new(state.inspector.clone()).tap_stats(&tap, range)
}Sync or async: a synchronous command runs on the main thread. It may only read memory.
Anything that touches the disk, the keychain, another process, the network or builds a
window must be async and run blocking work through ipc::off_main:
/// Puts the command line tool on the PATH.
#[tauri::command]
#[specta::specta]
pub async fn cli_install() -> Result<CliState, AppError> {
super::off_main(|| {
let Some(layout) = layout() else {
return Ok(CliState::Unavailable);
};
layout.install().map_err(|err| failed(&err))
})
.await?
}On macOS a blocking sync command freezes every window; on Windows building a webview from the main thread deadlocks WebView2.
Changes to Cloudflare never happen directly in a command: they go through the engine
(see the changing-cloudflare-resources skill).
3–4. Register and generate
Add the function to collect_commands![…] in apps/desktop/src-tauri/src/ipc.rs, then run
pnpm bindings. Never edit bindings.ts by hand; CI fails when it drifts.
5. The hook
Query keys come only from src/lib/ipc/query-keys.ts. Wrap the command in call() so
errors become typed IpcErrors:
export function useTapStats(tap: TapId | null, range: AnalyticsRange) {
return useQuery({
queryKey: keys.stats(tap ?? "", range),
queryFn: () => call(commands.inspectStats(tap ?? "", range)),
enabled: tap !== null,
});
}Components never call commands.* or invoke directly. Mutations invalidate through the
EntityChanged events Rust emits; don't mirror server data into Zustand.
6. The dev mock
The browser-only dev server and the screenshot tool run on src/dev/mock-ipc.ts and the
per-area mocks (mock-inspector.ts, mock-analytics.ts, …). Add a case "<command_name>"
returning realistic data, or the screen stays empty in pnpm --filter @teitunnel/desktop vite
and in screenshots.
7. Tests
- Rust: unit tests in core for the behaviour.
- UI: Vitest with
mockIPCfrom@tauri-apps/api/mocks; record calls and assert the arguments the hook sends (seefeatures/inspector/inspector-panels.test.tsx).
8. Verify
Run pnpm verify. If bindings are stale, run pnpm bindings again; if a test fails, fix
the cause and re-run until it passes.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

