tauri-ipc-commands
Skill4 starsChanged 6 months agoMITView on GitHub
vibe-editor で新しい Tauri IPC コマンド (Rust → Renderer) を追加・変更するときに必ず使うチェックリスト skill。`src/types/shared.ts` の型・`src-tauri/src/commands/<領域>.rs` の Rust 構造体 (`#[serde(rename_all = "camelCase")]`)・`src-tauri/src/commands/mod.rs` のモジュール宣言・`src-tauri/src/lib.rs` の `invoke_handler!` 登録・`src/renderer/src/lib/tauri-api.ts` の wrapper の **5 点同期** が外れると runtime エラーや silent 失敗を起こすため、必ずこの skill の手順で進める。ユーザーが「IPC を足す」「invoke を追加」「tauri command を新規」「Rust から呼べるように」「shared.ts に型を足して」「window.api.◯◯ を追加」「Rust ↔ TS の型同期」「emit / listen を追加」「PTY コマンドを追加」「git コマンドを追加」「settings に保存項目を追加」等を言ったとき、また `#[tauri::command]` を新規に書きそうなとき、`commands/<領域>.rs` を編集するときには必ずこの skill を起動すること。
Files
tauri-ipc-commands
vibe-editor で「Rust 側の機能を Renderer から呼べるようにする」ときの 5 点同期チェックリスト。
1 か所でも漏れると、window.api.xxx is not a function / invoke "xxx" not found / camelCase ⇄ snake_case の型不一致 / runtime エラーが発生する。
既存コマンドは
src-tauri/src/commands/{app,git,terminal,settings,dialog,sessions,team_history,files,fs_watch,atomic_write,role_profiles,vibe_team_skill}.rs。 Renderer 側ラッパはsrc/renderer/src/lib/tauri-api.ts。 型はsrc/types/shared.ts。
5 点同期 (どれも必須)
┌──────────────────────────────────────────┐
│ 1. src/types/shared.ts │ TS 型 (Request / Response, camelCase)
│ 2. src-tauri/src/commands/<領域>.rs │ Rust 構造体 + #[tauri::command]
│ 3. src-tauri/src/commands/mod.rs │ pub mod 宣言 (新ファイルを足したとき)
│ 4. src-tauri/src/lib.rs │ invoke_handler! に function 名を登録
│ 5. src/renderer/src/lib/tauri-api.ts │ invoke('xxx', { ... }) wrapper
└──────────────────────────────────────────┘この順番で書くと最後に typecheck で必ず噛み合う。逆順に書くと型不一致を検出するタイミングが遅れる。
Step 1: src/types/shared.ts に Request / Response 型を追加
camelCase で書く (Rust 側で #[serde(rename_all = "camelCase")] するので、TS 側は素直な camelCase)。
// 例: ファイルのハッシュを取得する hashFile コマンド
export interface HashFileRequest {
path: string;
/** sha256 / md5。省略時は sha256 */
algorithm?: 'sha256' | 'md5';
}
export interface HashFileResult {
hash: string;
byteLen: number;
modifiedMs: number;
}既存型の流用判断
path系は string で書く (Rust 側でPathBufに from する)。- 「成功 or エラー文字列」は
Result<T, String>(= TS では throw に化ける) で表す。{ ok: boolean, ... }形式は新規で増やさず、既存の慣例に合わせる。 - バイナリは
Vec<u8>↔number[]ではなく base64 文字列 で渡す (SavePastedImageResultなど先例あり)。
Step 2: Rust 側 commands/<領域>.rs に handler を実装
構造体は必ず #[serde(rename_all = "camelCase")]
commands/mod.rs の冒頭コメントで明文化されている厳守ルール。
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HashFileRequest {
pub path: String,
#[serde(default)]
pub algorithm: Option<String>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HashFileResult {
pub hash: String,
pub byte_len: u64,
pub modified_ms: i64,
}関数シグネチャの流儀
#[tauri::command]
pub async fn hash_file(req: HashFileRequest) -> Result<HashFileResult, String> {
// 1. 入力検証 (path traversal / 空文字 / 巨大ファイルへの対処)
// 2. tokio::fs で非同期読み込み (sync な std::fs はメインスレッドで使わない)
// 3. エラーは map_err(|e| e.to_string()) で String 化
let bytes = tokio::fs::read(&req.path).await.map_err(|e| e.to_string())?;
// ...
Ok(HashFileResult { /* ... */ })
}コマンド名の慣習
- snake_case (
hash_file、team_history_list、session_resume)。 - 領域 prefix を付ける (
settings_*/git_*/team_history_*/pty_*)。mod.rsのpingのような無 prefix は基本やらない。 - AppState (グローバル) を触るなら
tauri::State<'_, AppState>を引数に。src-tauri/src/state.rs参照。
よくある罠
- path 引数を引数 1 個で書きたい誘惑:
pub async fn hash_file(path: String)は動くが、後で第 2 引数を増やす際に破壊変更になる。最初から Request 構造体で受ける。 - 同期 IO を
#[tauri::command] pub fn(非 async) で書く: メインスレッドをブロックすると UI が固まる。read/write が絡むなら基本async。 Result<T, anyhow::Error>を直接返す: serde で死ぬ。必ずResult<T, String>に揃える。#[serde(rename_all = "camelCase")]漏れ: 一見動くが、フィールドがbyte_lenのままだと TS 側byteLenと噛み合わずundefined。
Step 3: commands/mod.rs への登録 (新ファイルを作ったときのみ)
// 例: commands/hashing.rs を新規作成したら
pub mod hashing;既存ファイル (files.rs など) に追加するなら Step 3 は不要。
Step 4: src-tauri/src/lib.rs の invoke_handler! に登録
tauri::generate_handler![...] の すべての 関数を列挙する場所がある。忘れると invoke 時に command "xxx" not found で失敗する。
.invoke_handler(tauri::generate_handler![
commands::ping,
commands::settings::settings_load,
commands::settings::settings_save,
// ...
commands::hashing::hash_file, // ← 追加
])ファイル全体を grep して既存登録の流儀 (フルパス vs
use文) に合わせる。途中で書式を変えない。
Step 5: tauri-api.ts に wrapper を追加
import type { HashFileRequest, HashFileResult } from '../../../types/shared';
export const api = {
// ...
hashFile(req: HashFileRequest): Promise<HashFileResult> {
return invoke<HashFileResult>('hash_file', { req });
},
};引数の渡し方の罠
- Tauri は
invoke('cmd', { 引数名: 値 })の形 で渡す。Rust 側のパラメータ名と完全一致させる。 - Rust 側
pub async fn hash_file(req: HashFileRequest)なら TS 側は{ req }。 - パラメータ名が camelCase でも snake_case でも、Rust 側の引数名そのまま を使う (Tauri はここだけ rename しない)。
- 例: Rust 側
pub async fn x(some_arg: String)→ TS 側{ someArg }で動く (Tauri 2 はパラメータ名は camelCase 化される)。ただし型と関数名のときは別 — フィールドは#[serde(rename_all="camelCase")]で camelCase。 - 不安なら最初に
tracing::info!でログを仕込んで実機で受信値を確認する。
- 例: Rust 側
イベント (Rust → Renderer push) の場合
// Rust 側
app_handle.emit("pty:data", PtyDataEvent { session_id, chunk })?;// Renderer 側 (tauri-api.ts に subscribe ヘルパを足す)
onPtyData(cb: (e: PtyDataEvent) => void): () => void {
return subscribeEvent<PtyDataEvent>('pty:data', cb);
}subscribeEvent (tauri-api.ts:21-40) は 早期 cleanup の orphan 対策を含む既存ヘルパ。直接 listen() を呼ばない — ここを通すこと。
Step 6: 検証
npm run typecheck
cargo check --manifest-path src-tauri/Cargo.toml両方通らないと噛み合いが取れていない。さらに npm run dev で実機起動して、
tracing::info!のログが Tauri DevTools 側 console に出るか- 戻り値が JS 側で camelCase で受け取れているか (フィールド名を
console.logで確認) - エラー時の
Promise.reject(string)が catch で拾えるか
を 1 度だけ手動確認する (CLAUDE.md「動作の証明」原則)。
やってはいけないこと
- 5 点のうち 1〜2 点だけ書いて typecheck も通したつもりで終える: invoke_handler! 登録漏れは typecheck では落ちず、起動後 invoke で初めて落ちる。必ず実機で 1 度呼ぶ。
#[serde(rename_all = "camelCase")]を省略: silent にundefined化して原因不明バグになる。Result<T, anyhow::Error>を返す: 必ずResult<T, String>に変換する。subscribeEventを経由せず素のlisten()を呼ぶ: 早期 cleanup で orphan listener が残る。- 既存コマンドの引数を破壊変更する: 永続化された値 (settings.json の旧スキーマ) と齟齬が出る。Issue #75 の
APP_SETTINGS_SCHEMA_VERSIONに倣って migration を書く。
既存実装の参照先 (作業前にチラ見すると速い)
- 単純な save/load 例:
src-tauri/src/commands/settings.rs(atomic_write + Mutex 直列化) - async + 戻り値構造体例:
src-tauri/src/commands/git.rs - イベント emit + State 共有例:
src-tauri/src/commands/terminal.rs+src-tauri/src/pty/ - Mutex 連携 / 並列直列化の罠: settings.rs の SAVE_LOCK (Issue #37)
- Parse 失敗時のバックアップ戦略: settings.rs settings_load (Issue #170)
関連 skill
- 全体の地図と厳守ワークフロー →
vibeeditorskill - PR を出すフェーズ →
pullrequestskill - 設定永続化スキーマを変えるなら →
APP_SETTINGS_SCHEMA_VERSIONの bump も忘れず (vibeeditor 参照)
In this repository20 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- agent-teamSkill
- canvas-nodecard-patternSkill
- claude-designSkill
- issue-autopilot-batchSkill
- issue-planSkill
- monaco-language-setupSkill
- pty-portable-debuggingSkill
- pullrequestSkill
- subagentSkill
- test-setup-vitestSkill
- theme-customizationSkill
- vibeeditorSkill
- vibe-teamSkill
- vibe-autopilot-batchSkill
- vibe-fortress-implementSkill
- vibe-fortress-reviewSkill
- vibe-issue-plannerSkill
- vibe-shared-rolesSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
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.

