Agent Skillsopenfootmanager/openfootmanager › add-tauri-command

add-tauri-command

GitHub

指导如何在Tauri应用中新增后端命令,涵盖内部函数拆分、原子性状态变更、错误国际化、注册流程、TS服务封装及Store更新。

.claude/skills/add-tauri-command/SKILL.md openfootmanager/openfootmanager

触发场景

需要在Tauri应用中添加新的IPC命令 前端需要调用后端逻辑并处理状态更新

安装

npx skills add openfootmanager/openfootmanager --skill add-tauri-command -g -y
更多选项

非标准路径

npx skills add https://github.com/openfootmanager/openfootmanager/tree/develop/.claude/skills/add-tauri-command -g -y

不安装直接使用

npx skills use openfootmanager/openfootmanager@add-tauri-command

指定 Agent (Claude Code)

npx skills add openfootmanager/openfootmanager --skill add-tauri-command -a claude-code -g -y

安装 repo 全部 skill

npx skills add openfootmanager/openfootmanager --all -g -y

预览 repo 内 skill

npx skills add openfootmanager/openfootmanager --list

SKILL.md

Frontmatter
{
    "name": "add-tauri-command",
    "description": "Expose new backend behaviour to the frontend over Tauri IPC. Covers the _internal function split so MCP tools can share the logic, atomic mutation via mutate_active_game, translation-key errors, registration in the invoke handler, the typed service wrapper, store update, tests on both sides, and the docs\/ARCHITECTURE.md command table.",
    "when_to_use": "Adding a new Tauri command, changing an existing command's signature, or wiring a new backend action into the UI.",
    "allowed-tools": "Read, Edit, Write, Grep, Glob, Bash(cargo test*), Bash(cargo clippy*), Bash(npx vitest run src\/services), Bash(npx tsc --noEmit)",
    "argument-hint": "[command name and what it does]"
}

Adding a Tauri command

Every frontend↔backend interaction goes through invoke(). The layering is strict, and the _internal split is what keeps the MCP server from becoming a second copy of the game.


1. Write the command in src-tauri/src/commands/

One module per area — squad.rs, transfers.rs, contracts.rs, club.rs, time.rs, … Put it where its siblings live rather than starting a new module.

Split it in two. The #[tauri::command] wrapper does IPC plumbing only; the real work lives in a plain *_internal function that takes &StateManager:

pub fn set_play_style_internal(state: &StateManager, play_style: &str) -> Result<Game, String> {
    mutate_active_game(state, |game| {
        // validate first — see below
        // then mutate
        Ok(())
    })
}

#[tauri::command]
pub fn set_play_style(state: State<Arc<StateManager>>, play_style: String) -> Result<Game, String> {
    set_play_style_internal(&state, &play_style)
}

This is not ceremony. The MCP server (src-tauri/src/mcp_server/tools_impl/) calls the same _internal function, so an agent playing the game and a human clicking a button run identical logic. A command whose body lives inside the #[tauri::command] fn cannot be reached from MCP without duplicating it.

Mutate atomically

Use mutate_active_game from src-tauri/src/commands/util.rs. It borrows the game under the lock, mutates in place, and clones once for the response — the old get_game(clone) → mutate → set_game pattern deep-cloned the whole world twice per command and lost updates when the GUI and an MCP agent acted concurrently.

Validate before you mutate. The closure operates on the live game, so anything changed before an early Err return persists. Check preconditions up front, then mutate.

Errors are translation keys

Return "be.error.noTeamAssigned", not "No team assigned". The frontend resolves these through src/utils/backendI18n.ts. A new error key must be added to all 11 locale files — use /add-ui-string.

2. Register it

Add the command to the tauri::generate_handler![…] list in src-tauri/src/lib.rs. If you created a new module, add pub mod and the pub use re-export in src-tauri/src/commands/mod.rs.

Forgetting registration compiles fine and fails at runtime with an unhelpful IPC error.

3. Wrap it in a service

Components never call invoke(). Add a typed wrapper to the matching src/services/<area>Service.ts:

export async function setPlayStyle(playStyle: PlayStyle): Promise<GameStateData> {
  return invoke<GameStateData>("set_play_style", { playStyle });
}

Argument names are camelCase on the TypeScript side and snake_case in Rust — Tauri converts between them. Getting this wrong produces a runtime "missing required key" error, not a compile error, so mirror an existing wrapper exactly.

Types come from src/store/types.ts or src/store/gameStore.ts. No any.

4. Update the store

Commands that return game state feed gameStore. Follow the existing call sites: call the service, then set store state. Never mutate store state in place.

5. Test both sides

Backend, in a #[cfg(test)] module in the same file — test the _internal function, which needs no Tauri runtime:

cargo test --manifest-path src-tauri/Cargo.toml --lib      # command tests live in the lib target
cargo test --manifest-path src-tauri/Cargo.toml --workspace

cargo test --bin matches zero tests and exits 0. It is not a check.

Frontend: src/services/<area>Service.test.ts, mocking @tauri-apps/api/core. Existing service tests show the shape. Cover the failure path — a command that returns Err must surface a usable message.

Write the failing test first.

6. Update the docs

docs/ARCHITECTURE.md has command tables (Game Lifecycle, Match, Team Management, Settings). Add your row. An out-of-date table is worse than none.

If the command should also be available to AI agents, continue with /add-mcp-tool.


Checklist

  • Logic in a *_internal(&StateManager, …) function; #[tauri::command] is a thin wrapper
  • Mutations go through mutate_active_game, with validation before mutation
  • Errors are translation keys, added to all 11 locales
  • Registered in generate_handler![…] in src-tauri/src/lib.rs
  • New modules re-exported from commands/mod.rs
  • Typed wrapper in src/services/, camelCase args, no any
  • Store updated without in-place mutation
  • Backend test on the _internal fn (cargo test --lib), written first
  • Frontend service test including the error path
  • docs/ARCHITECTURE.md command table updated
  • cargo clippy --workspace --all-targets and npx tsc --noEmit green

版本历史

  • 9d401d6 当前 2026-07-30 22:45

同 Skill 集合

.claude/skills/add-domain-field/SKILL.md
.claude/skills/add-mcp-tool/SKILL.md
.claude/skills/add-ui-string/SKILL.md
.claude/skills/new-ui-surface/SKILL.md
.claude/skills/preflight/SKILL.md

元信息

文件数
0
版本
3ff2385
Hash
fcbdd364
收录时间
2026-07-30 22:45

首页 - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-04 01:55
浙ICP备14020137号-1 $访客地图$