@dennisrongo/skills 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -4
- package/package.json +1 -1
- package/skills/tauri-2-app/SKILL.md +381 -0
- package/skills/tauri-2-app/references/anti-patterns.md +434 -0
- package/skills/tauri-2-app/references/folder-layout.md +161 -0
- package/skills/tauri-2-app/references/good-patterns.md +477 -0
- package/skills/tauri-2-app/references/templates/build-rs.md +51 -0
- package/skills/tauri-2-app/references/templates/capabilities.md +68 -0
- package/skills/tauri-2-app/references/templates/cargo-toml.md +118 -0
- package/skills/tauri-2-app/references/templates/ci-workflow.md +99 -0
- package/skills/tauri-2-app/references/templates/encryption-mod.md +228 -0
- package/skills/tauri-2-app/references/templates/error-mod.md +126 -0
- package/skills/tauri-2-app/references/templates/lib-rs.md +98 -0
- package/skills/tauri-2-app/references/templates/macos-plist.md +89 -0
- package/skills/tauri-2-app/references/templates/main-rs.md +21 -0
- package/skills/tauri-2-app/references/templates/platform-traits.md +217 -0
- package/skills/tauri-2-app/references/templates/storage-mod.md +172 -0
- package/skills/tauri-2-app/references/templates/tauri-conf.md +136 -0
- package/skills/tauri-2-app/references/templates/use-tauri-command.md +194 -0
- package/skills/tauri-2-app/references/templates/vite-and-tsconfig.md +189 -0
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# claude-skills
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The [Claude Code](https://docs.claude.com/en/docs/claude-code/overview) skills I ([Dennis Rongo](https://github.com/dennisrongo)) use every day — not shelfware, not theory. Each one earns its place by surviving real work: shipping .NET APIs and Next.js frontends, reviewing PRs, debugging production, and keeping commits clean.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
They're small, composable, and meant to be tuned. Install the ones you want, edit them in-place, send a PR if yours sharpens mine.
|
|
6
|
+
|
|
7
|
+
> Skills are reusable bundles of instructions that Claude consults when relevant. This repo is my personal daily-driver library — grow it over time, tune the ones that misfire, install the set you want on any machine.
|
|
6
8
|
|
|
7
9
|
## Quick start
|
|
8
10
|
|
|
@@ -99,6 +101,7 @@ node bin/claude-skills.js list
|
|
|
99
101
|
| [`nextjs-app-router`](./skills/nextjs-app-router/SKILL.md) | Scaffold a new Next.js (App Router) frontend with TypeScript, Redux Toolkit + RTK Query, Tailwind + shadcn/ui (Radix), and React Hook Form + Zod. Codifies route-group auth boundaries, a single injected RTK Query API, schema-driven forms with introspected defaults, server-side `middleware.ts`, and Vitest + Playwright + CI defaults — while forbidding common pitfalls (`'use client'` on root pages, `router.push` in `useEffect`, `serializableCheck: false`, `@ts-ignore`, mixed `moment`/`date-fns`, `styled-components` alongside Tailwind, case-sensitive folder dupes, `dangerouslySetInnerHTML` without sanitization, tokens outside `httpOnly` cookies). Three modes — full project scaffold, add-a-feature slice, add-an-API-slice. Resolves package versions at scaffold time (not hard-coded). |
|
|
100
102
|
| [`plan-and-build`](./skills/plan-and-build/SKILL.md) | Plan-first feature builder. Grills the user about the feature (à la `grill-with-docs`) until the design is unambiguous, detects the project's stack and conventions, presents a plan, and gates on `ExitPlanMode` approval before writing any code. Builds TDD-first with NUnit when a .NET API changes — appending to the matching test class if one already exists rather than forking a parallel one — reuses existing patterns, keeps comments minimal, and generates EF Core / migration files **without ever** running `dotnet ef database update` or any DDL/SQL against the user's database. Triggers on "build/add/implement a feature", "/plan-and-build", or a pasted feature spec. |
|
|
101
103
|
| [`pr-review`](./skills/pr-review/SKILL.md) | Conduct a structured PR / diff review prioritized correctness → design → tests → security → performance → readability, with categorized feedback (`blocking` / `suggestion` / `question` / `nit` / `praise`). |
|
|
104
|
+
| [`tauri-2-app`](./skills/tauri-2-app/SKILL.md) | Scaffold a new Tauri 2 desktop app (Rust backend + TypeScript/React frontend) using a thin-frontend / rich-Rust-backend architecture with modular `commands/`, `state/`, `storage/`, `platform/` traits, `error/` macros, single-instance + updater plugins wired correctly, capability JSON per window, encrypted secrets at rest, `spawn_blocking` for sync work, and typed frontend command hooks — while forbidding common pitfalls (committed `.backup`/`.orig`/`.temp` files, plaintext API keys in `settings.json`, tokens in `localStorage`, `cfg!(target_os)` in command bodies instead of trait-based platform code, hand-rolled date math instead of `chrono`, raw `std::fs` bypassing capability checks, blocking I/O inside async commands, missing `windows_subsystem = "windows"` in `main.rs`, `devtools: true` in release, hardcoded bundle identifiers / updater pubkeys / CDN URLs). Three modes — full project scaffold, add-a-command end-to-end, add-a-Rust-module slice. Resolves Cargo + npm versions at scaffold time (not hard-coded). |
|
|
102
105
|
| [`write-a-skill`](./skills/write-a-skill/SKILL.md) | Author a new Claude Code skill — interview-driven scaffolding that produces a properly-structured `SKILL.md` (trigger-rich YAML description, "When to use", workflow, examples, anti-patterns), drops it in the right location (library `skills/`, project `./.claude/skills/`, or global `~/.claude/skills/`), updates the README skills table when extending this library, and runs a review checklist focused on the failure mode that matters most — under-triggering descriptions. Triggers on "create/write/add a skill", "/write-a-skill", or a pasted SKILL.md URL with "one like this". |
|
|
103
106
|
|
|
104
107
|
Run `skills list` to see this list with install status, or browse [`skills/`](./skills) directly.
|
|
@@ -182,8 +185,24 @@ Edit the file at `~/.claude/skills/<name>/SKILL.md` directly while you're iterat
|
|
|
182
185
|
**Tune in the repo, reinstall:**
|
|
183
186
|
Edit `skills/<name>/SKILL.md` in your clone, then run `npx github:dennisrongo/claude-skills install <name> --force` to push it to your install location.
|
|
184
187
|
|
|
185
|
-
**Pull upstream updates:**
|
|
186
|
-
|
|
188
|
+
**Pull upstream updates (npm-global install):**
|
|
189
|
+
If you installed the CLI globally (`npm install -g @dennisrongo/skills`), updating is a two-step process — bumping the CLI does *not* automatically refresh the skills already copied into `~/.claude/skills/`.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# 1. Update the CLI to the latest published version
|
|
193
|
+
npm install -g @dennisrongo/skills@latest
|
|
194
|
+
|
|
195
|
+
# 2. Re-copy the bundled skills over your existing installs
|
|
196
|
+
skills install --all --force
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
- Step 1 replaces the `skills` binary and the bundled skill files inside the global node_modules.
|
|
200
|
+
- Step 2 overwrites everything in `~/.claude/skills/` with the new bundled versions. Without `--force` the CLI skips skills that already exist.
|
|
201
|
+
- Add `-p` / `--project` to step 2 if the skills live in `./.claude/skills` instead.
|
|
202
|
+
- To update just one skill instead of all: `skills install <name> --force`.
|
|
203
|
+
|
|
204
|
+
**Pull upstream updates (npx-from-GitHub):**
|
|
205
|
+
If you're using `npx github:dennisrongo/claude-skills` without a global install, force-reinstall with a cache-bust:
|
|
187
206
|
|
|
188
207
|
```bash
|
|
189
208
|
# Single skill
|
package/package.json
CHANGED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tauri-2-app
|
|
3
|
+
description: Scaffold a new Tauri 2 desktop app (Rust backend + TypeScript/React frontend) using a thin-frontend / rich-Rust-backend architecture with modular commands, trait-based platform abstractions, encrypted secrets at rest, single-instance enforcement, an updater wired to a self-hosted manifest, and a cross-platform CI matrix. Use this skill whenever the user asks to "create a new Tauri app", "scaffold a Tauri 2 project", "new desktop app with Tauri", "Tauri + React project", "add a Tauri command end-to-end", "add a Rust module to my Tauri app", or mentions "the Tauri patterns I like" / "my Tauri conventions" — even if they don't explicitly say "tauri-2-app". Three modes — (1) full project scaffold, (2) add a Tauri command slice (Rust command + capability + typed frontend hook), (3) add a Rust module slice (state + storage + tests). Codifies the good patterns (modular `src-tauri/src/`, `commands/`, `state/`, `storage/`, `platform/` traits, `error/` macros, single-instance + updater + global-shortcut plugins, capability files, encrypted API keys, `spawn_blocking` for sync work, typed frontend command hooks) and forbids the common pitfalls (committed `.backup`/`.orig`/`.temp` files, secrets in plaintext, `localStorage` for tokens, dev-tools enabled in release, hand-rolled date math instead of `chrono`, raw `std::fs` bypassing capability checks, `'use client'` analog flaws like skipping `isTauriReady` guards, `cfg!(target_os)` in commands instead of trait-based platform code, missing `windows_subsystem = "windows"` in `main.rs`, multi-instance apps with no `tauri-plugin-single-instance`, hardcoded company URLs / updater pubkeys / bundle identifiers).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tauri 2 App Scaffolder
|
|
7
|
+
|
|
8
|
+
Generate a production-grade Tauri 2 desktop app that keeps the **good** patterns from a battle-tested codebase (thin TS frontend that only invokes commands, rich Rust backend with modular `commands/`, `state/`, `storage/`, `platform/`, `error/`, plugin-based features wired in `lib.rs`, capability JSON per window, encrypted secrets at rest, single-instance enforcement, updater wired to a self-hosted JSON manifest, cross-platform CI matrix, target-specific Cargo dependencies, release-profile LTO, typed frontend command hooks) and eliminates the **bad** ones often seen in Tauri codebases (committed `.backup` / `.orig` / `.temp` files in `src-tauri/src/`, API keys in plaintext settings JSON, tokens in `localStorage`, `cfg!(target_os)` scattered through command bodies instead of trait-based platform code, hand-rolled date math instead of `chrono::Utc::now()`, raw `std::fs` reads/writes that bypass Tauri's capability checks, blocking I/O inside `#[tauri::command]` without `tokio::task::spawn_blocking`, missing `#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]` in `main.rs` causing a console flash on Windows, multi-instance apps without `tauri-plugin-single-instance`, `devtools: true` in release config, generic catch-all `String` errors with no `thiserror` enum at module boundaries, hardcoded company-specific bundle identifiers / R2 URLs / updater pubkeys / signing identities baked into the skill).
|
|
9
|
+
|
|
10
|
+
## When to use this skill
|
|
11
|
+
|
|
12
|
+
Trigger on any of:
|
|
13
|
+
|
|
14
|
+
- "create a new Tauri app" / "scaffold a Tauri 2 project" / "new desktop app with Tauri"
|
|
15
|
+
- "Tauri + React project" / "Tauri + vanilla TS project" / "Rust backend desktop app"
|
|
16
|
+
- "use my Tauri patterns" / "the Tauri conventions I like" (in a Tauri context)
|
|
17
|
+
- "add a Tauri command end-to-end" / "wire up a command from Rust to React"
|
|
18
|
+
- "add a Rust module to my Tauri app" / "add a state slice"
|
|
19
|
+
- The user pastes a feature spec for a desktop feature (hotkey, tray, file I/O, system notification, OS-specific behavior) and asks you to wire it through the Rust backend and frontend
|
|
20
|
+
|
|
21
|
+
If unsure whether the user wants a brand-new app vs. an addition to an existing one, ask once — don't guess.
|
|
22
|
+
|
|
23
|
+
## Three operating modes
|
|
24
|
+
|
|
25
|
+
Pick the mode from the user's request. If ambiguous, ask.
|
|
26
|
+
|
|
27
|
+
| Mode | Trigger | Output |
|
|
28
|
+
|------|---------|--------|
|
|
29
|
+
| **`scaffold-app`** | "new Tauri app", "scaffold Tauri 2 project", empty directory | Full Tauri 2 app: `package.json`, `vite.config.ts`, `tsconfig.json`, `index.html`, `src/` (React or vanilla TS), `src-tauri/` with modular `commands/`, `state/`, `storage/`, `platform/`, `error/`, `settings/`, capability JSON, `Info.plist` + `entitlements.plist` (macOS), `build.rs`, GitHub Actions CI, single-instance + updater plugins wired. |
|
|
30
|
+
| **`add-command`** | "add a `<name>` command end-to-end", "wire up a command from Rust to TS" | New `#[tauri::command]` in the right `src-tauri/src/commands/*.rs` module, registered in `lib.rs`'s `invoke_handler`, granted permissions in `capabilities/default.json` if it uses a plugin, plus a typed frontend hook in `src/hooks/` that wraps `invoke()` with loading/error state. |
|
|
31
|
+
| **`add-module`** | "add a `<name>` module", "add a state/storage slice for X" | New `src-tauri/src/<name>/mod.rs` (+ optional submodules), with `serde`-derived types, a `thiserror` error enum, JSON persistence via the shared `storage` module, and a unit-test module + an integration test file in `src-tauri/tests/`. |
|
|
32
|
+
|
|
33
|
+
## Workflow
|
|
34
|
+
|
|
35
|
+
### Step 1 — Resolve versions (don't hard-code)
|
|
36
|
+
|
|
37
|
+
The user does **not** want hard-coded Cargo / npm versions baked into the skill. Before writing `Cargo.toml` or `package.json`:
|
|
38
|
+
|
|
39
|
+
1. Check the user's environment first: run `cargo --version`, `rustc --version`, `node --version`, `npm --version` (or `pnpm --version`).
|
|
40
|
+
2. Resolve the latest stable versions of the core stack at scaffold time — never hand-paste:
|
|
41
|
+
- **Rust crates** (look up on crates.io or via context7): `tauri` (must be 2.x), `tauri-build` (2.x), `tauri-plugin-opener`, `tauri-plugin-notification`, `tauri-plugin-dialog`, `tauri-plugin-fs`, `tauri-plugin-global-shortcut`, `tauri-plugin-single-instance`, `tauri-plugin-updater`, `tauri-plugin-process`, `serde`, `serde_json`, `thiserror`, `anyhow`, `tokio` (with `sync`, `rt-multi-thread`, `macros`), `tracing`, `tracing-subscriber` (with `env-filter`), `chrono` (don't hand-roll date math), `dirs`.
|
|
42
|
+
- **npm packages**: `@tauri-apps/api` (2.x), `@tauri-apps/cli` (2.x), `@tauri-apps/plugin-*` matching the Rust plugins, `react` + `react-dom` (if React), `typescript`, `vite`, `@vitejs/plugin-react` (if React).
|
|
43
|
+
3. Pin the Rust edition to `2021` unless the user asks otherwise.
|
|
44
|
+
4. Quote the resolved versions back to the user before generating, so they can object.
|
|
45
|
+
5. Default to **npm** unless `pnpm` or `bun` is present and the user prefers it.
|
|
46
|
+
|
|
47
|
+
### Step 2 — Gather inputs (ask once, in one batch)
|
|
48
|
+
|
|
49
|
+
For `scaffold-app`, use `AskUserQuestion` to collect:
|
|
50
|
+
|
|
51
|
+
- **App name** (kebab-case for the directory; the productName in `tauri.conf.json` can be Title Case).
|
|
52
|
+
- **Bundle identifier** in reverse-DNS form (e.g. `com.example.myapp`) — **must come from the user**, never hardcoded. This goes in `tauri.conf.json` `identifier` and the macOS `Info.plist` `CFBundleIdentifier`.
|
|
53
|
+
- **Frontend flavor**: React + Tailwind, React (no Tailwind), vanilla TypeScript, or "I'll wire it up myself".
|
|
54
|
+
- **Plugins to wire**: opener, notification, dialog, fs, global-shortcut, single-instance, updater, process. Default-ON: `opener`, `dialog`, `fs`, `single-instance`. Updater is OFF by default — only wire it if the user has (or will have) a manifest URL.
|
|
55
|
+
- **Tray icon** (yes/no). If yes, generate a minimal tray with Show/Hide/Quit.
|
|
56
|
+
- **Encrypted-secrets module** (yes/no). If yes, generate an `encryption/` module using `aes-gcm` + `argon2` for at-rest encryption of API keys/tokens. The salt-derivation source (machine UUID, keychain, etc.) is platform-specific — generate stubs, but **do not** ship a hardcoded "pepper" string.
|
|
57
|
+
- **Updater endpoint** (optional). If provided, write it into `tauri.conf.json` under `plugins.updater.endpoints`. **Never** invent a URL or paste a public key from another project — ask the user to generate one with `tauri signer generate` and paste it in afterwards.
|
|
58
|
+
- **CI** (default ON): GitHub Actions matrix building on macOS/Windows/Ubuntu.
|
|
59
|
+
- **First command/module** (optional) — if provided, also run the matching mode after scaffold.
|
|
60
|
+
|
|
61
|
+
For `add-command`: command name (snake_case), the module it belongs in (`commands/<module>.rs`), inputs + return type, whether it's `async`, whether it needs `AppHandle` / `State<T>` / a Tauri plugin. If the command does blocking I/O, confirm it needs `tokio::task::spawn_blocking`.
|
|
62
|
+
|
|
63
|
+
For `add-module`: module name (snake_case), the data type(s) it owns, whether it persists to JSON in `app_data_dir`, what error variants it needs.
|
|
64
|
+
|
|
65
|
+
### Step 3 — Generate files
|
|
66
|
+
|
|
67
|
+
Use the **templates in [`references/templates/`](references/templates/)** as the source of truth. Apply these rules:
|
|
68
|
+
|
|
69
|
+
- Use `Write` for new files. Never use `Edit` on files you're creating fresh.
|
|
70
|
+
- Replace all `{{AppName}}`, `{{app_name}}`, `{{BundleId}}`, `{{Command}}`, `{{Module}}` placeholders consistently. `{{app_name}}` is snake_case for Rust crate / lib names; `{{AppName}}` is Title Case for product name; the directory is kebab-case.
|
|
71
|
+
- Create directories before files. On Windows shell use PowerShell `New-Item -ItemType Directory -Force`.
|
|
72
|
+
- Do **not** run `create-tauri-app` to bootstrap — write the files directly from templates so the layout matches the conventions in [`references/folder-layout.md`](references/folder-layout.md). Use `npm install` after `package.json` is written, then `cargo fetch` from `src-tauri/` to seed the Cargo cache.
|
|
73
|
+
- For icons, run `npx @tauri-apps/cli icon <path-to-source.png>` once a source image exists, or generate a 1024×1024 placeholder with `sharp` if the user wants. Do **not** commit a placeholder square as the final icon — surface it as a TODO.
|
|
74
|
+
|
|
75
|
+
### Step 4 — Verify and report
|
|
76
|
+
|
|
77
|
+
- Run `npm install` (or chosen PM).
|
|
78
|
+
- Run `cd src-tauri && cargo build` — must exit 0. (Don't run `tauri dev` in CI — it opens a window.)
|
|
79
|
+
- Run `cd src-tauri && cargo test` — must exit 0 if any tests were generated.
|
|
80
|
+
- Run `npm run build` (frontend) — must exit 0.
|
|
81
|
+
- Optionally run `npm run tauri build -- --debug` if the user wants a debug bundle; skip in CI by default since it's slow.
|
|
82
|
+
- Reply with a short summary: project path, versions chosen, plugins wired, bundle identifier, next steps (e.g. "fill in the macOS `Info.plist` usage descriptions for any permissions you'll request", "generate updater keys with `npx tauri signer generate`", "run `npm run tauri dev`").
|
|
83
|
+
|
|
84
|
+
## Project layout (canonical)
|
|
85
|
+
|
|
86
|
+
See [`references/folder-layout.md`](references/folder-layout.md) for the full tree, file-by-file purpose, and the rules that govern it.
|
|
87
|
+
|
|
88
|
+
Top-level shape:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
{{app-name}}/
|
|
92
|
+
package.json
|
|
93
|
+
tsconfig.json # strict: true, noUnusedLocals, noUnusedParameters
|
|
94
|
+
vite.config.ts # port 5173, ignores src-tauri, base: './'
|
|
95
|
+
index.html # anti-flash theme script if dark/light theming is in scope
|
|
96
|
+
.gitignore # MUST include src-tauri/target/, dist/, .env, *.backup, *.orig, *.temp
|
|
97
|
+
.env.example # NEVER .env — only .env.example committed
|
|
98
|
+
src/ # frontend (TS / React)
|
|
99
|
+
main.tsx # entry: createRoot + render
|
|
100
|
+
App.tsx
|
|
101
|
+
tauriReady.ts # isTauriReady() guard
|
|
102
|
+
hooks/
|
|
103
|
+
useTauriCommand.ts # generic typed invoke() wrapper with loading/error
|
|
104
|
+
use<Domain>.ts # per-domain hooks composing useTauriCommand
|
|
105
|
+
components/
|
|
106
|
+
styles/
|
|
107
|
+
src-tauri/
|
|
108
|
+
Cargo.toml # [lib] name = "{{app_name}}_lib", target-specific deps via [target.'cfg(...)']
|
|
109
|
+
build.rs # links macOS frameworks, Windows libs, Linux pthread/m as needed
|
|
110
|
+
tauri.conf.json # productName, identifier, bundle, plugins.updater (if enabled)
|
|
111
|
+
Info.plist # macOS bundle identifier + usage descriptions for permissions requested
|
|
112
|
+
entitlements.plist # macOS hardened-runtime entitlements (audio-input, apple-events, etc.)
|
|
113
|
+
capabilities/
|
|
114
|
+
default.json # main-window capability with granular permission scopes
|
|
115
|
+
icons/ # icon.png (1024), icon.ico, icon.icns, 32x32.png, 128x128.png, 128x128@2x.png
|
|
116
|
+
src/
|
|
117
|
+
main.rs # #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + embed_plist on macOS
|
|
118
|
+
lib.rs # pub mod ...; pub fn run() builds Tauri, registers plugins + invoke_handler
|
|
119
|
+
commands/ # ONE file per domain (recording.rs, settings.rs, system.rs, ...)
|
|
120
|
+
mod.rs # pub mod ...; pub use *;
|
|
121
|
+
system.rs # generic commands (write_file, dialogs, notifications)
|
|
122
|
+
<domain>.rs # per-domain command modules
|
|
123
|
+
state/ # pub struct AppState { ... } managed via tauri::Manager
|
|
124
|
+
mod.rs
|
|
125
|
+
storage/ # JSON-on-disk utilities: get_app_data_dir, load_json, save_json
|
|
126
|
+
mod.rs
|
|
127
|
+
settings/ # Settings struct (camelCase serde) + load/save + migrations
|
|
128
|
+
mod.rs
|
|
129
|
+
defaults.rs
|
|
130
|
+
storage.rs
|
|
131
|
+
commands.rs
|
|
132
|
+
platform/ # trait-based platform abstractions
|
|
133
|
+
mod.rs
|
|
134
|
+
traits.rs # PermissionChecker, FileOpener, TextInserter
|
|
135
|
+
macos.rs # #[cfg(target_os = "macos")]
|
|
136
|
+
windows.rs
|
|
137
|
+
linux.rs
|
|
138
|
+
wrappers.rs # PlatformXxx wrappers managed in Tauri State
|
|
139
|
+
error/ # into_string_err! macro + ResultExt trait
|
|
140
|
+
mod.rs
|
|
141
|
+
encryption/ # (optional) AES-GCM + Argon2id for secrets at rest
|
|
142
|
+
mod.rs
|
|
143
|
+
tray.rs # (optional) tray icon, menu, click handlers
|
|
144
|
+
tests/ # integration tests; one file per domain
|
|
145
|
+
common/
|
|
146
|
+
mod.rs
|
|
147
|
+
<domain>_test.rs
|
|
148
|
+
.github/
|
|
149
|
+
workflows/
|
|
150
|
+
unit-tests.yml # cross-platform matrix: cargo test on macos/windows/ubuntu
|
|
151
|
+
publish.yml # (optional) tag-triggered release pipeline; user fills in CDN secrets
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Dependency rules (enforced):**
|
|
155
|
+
|
|
156
|
+
- `commands/` files call into `state/`, `settings/`, `storage/`, `platform/`, `<domain>/` modules — never the other way.
|
|
157
|
+
- Domain modules (`audio`, `history`, `whisper`, …) may depend on `storage/` and `platform/traits`, but not on `commands/` or Tauri's runtime types directly (use `AppHandle` only when persisting via `storage::get_app_data_dir`).
|
|
158
|
+
- `platform/<os>.rs` files are gated with `#[cfg(target_os = "...")]` and **only** these files contain `cfg!(target_os)` checks. All other modules go through `platform::traits::*` and a `Platform<X>` wrapper managed in `State`.
|
|
159
|
+
- Frontend `hooks/use<Domain>.ts` files compose `useTauriCommand` — they never call `invoke()` directly inline in a component.
|
|
160
|
+
|
|
161
|
+
## Required code patterns
|
|
162
|
+
|
|
163
|
+
Full templates are in [`references/templates/`](references/templates/). The full Keep/Eliminate rationale is in [`references/good-patterns.md`](references/good-patterns.md) and [`references/anti-patterns.md`](references/anti-patterns.md).
|
|
164
|
+
|
|
165
|
+
### Keep
|
|
166
|
+
|
|
167
|
+
- **Thin frontend, rich Rust backend.** All file I/O, audio, system permissions, OS-specific behavior, and long-running work happen in Rust. The TS layer only invokes commands and renders state.
|
|
168
|
+
- **`#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]` in `main.rs`** to prevent a console window on Windows in release.
|
|
169
|
+
- **`embed_plist::embed_info_plist!("../Info.plist")`** in `main.rs` under `#[cfg(target_os = "macos")]` so the binary carries its plist when run standalone.
|
|
170
|
+
- **`tauri-plugin-single-instance` registered first** in the builder chain, with a callback that focuses the existing window when a second instance launches. See [`references/templates/lib-rs.md`](references/templates/lib-rs.md).
|
|
171
|
+
- **Modular `commands/`** — one file per domain, all re-exported from `commands/mod.rs`, registered in a single `tauri::generate_handler![...]` call in `lib.rs`.
|
|
172
|
+
- **`State<AppState>` for shared app state** managed via `app.manage(Mutex::new(AppState::new()))`. Inside async commands, hold the lock for the minimum time and drop it before any `.await`.
|
|
173
|
+
- **Trait-based platform abstractions** (`PermissionChecker`, `FileOpener`, `TextInserter`) in `platform/traits.rs`, with `#[cfg(target_os = "...")]` implementations in `platform/macos.rs`, `windows.rs`, `linux.rs`, and `Send + Sync` wrappers in `platform/wrappers.rs` managed in Tauri State. Tests assert `Send + Sync` on the wrappers.
|
|
174
|
+
- **`storage/mod.rs` shared utilities** — `get_app_data_dir`, `get_storage_path`, `load_json<T>`, `save_json<T>`, `generate_id`, `Timestamped` trait, `prune_entries_by_age` — used by `settings/`, `history/`, `error_log/`, and any other persistent JSON store.
|
|
175
|
+
- **Settings with `#[serde(rename_all = "camelCase", default)]`** so the on-disk JSON matches frontend naming, and missing fields fall back to `Default`. Add a migration pass in `deserialize_settings(json: &Value) -> Settings` when fields change shape.
|
|
176
|
+
- **`thiserror` enums at module boundaries**; `anyhow::Result` only inside private helpers. Commands return `Result<T, String>` with errors stringified at the `#[tauri::command]` boundary — use the `into_string_err!` macro / `ResultExt::into_string()` from `error/mod.rs` to keep this clean.
|
|
177
|
+
- **`tokio::task::spawn_blocking`** for any synchronous I/O or CPU-bound work inside an `async` command — file pickers, native dialogs, ML inference, encryption. Awaiting blocking work directly stalls Tauri's IPC runtime.
|
|
178
|
+
- **`tracing` + `tracing-subscriber`** for logs, initialized in `pub fn run()` before the builder. Use `EnvFilter::from_default_env().add_directive(Level::INFO.into())` so `RUST_LOG=debug` works.
|
|
179
|
+
- **Encrypted secrets at rest** (`encryption/mod.rs`): AES-256-GCM ciphertext + Argon2id key derivation + platform-specific hardware-bound salt (machine UUID on macOS via `ioreg IOPlatformExpertDevice`, Windows via `wmic csproduct get uuid`, Linux via `/etc/machine-id` or `machine-uid` crate). Store `EncryptedApiKey { ciphertext, nonce, salt }` in settings. Auto-migrate legacy plaintext keys on load.
|
|
180
|
+
- **`capabilities/default.json` with granular permissions** — only the scopes the app actually uses (e.g. `fs:allow-read-file`, `dialog:allow-open`), not blanket `"*"`. One capability file per window (e.g. `default.json` for `main`, `<window>.json` for any auxiliary windows).
|
|
181
|
+
- **macOS `Info.plist` usage descriptions** for every permission the app requests (`NSMicrophoneUsageDescription`, `NSAccessibilityUsageDescription`, etc.). The app will silently fail without them.
|
|
182
|
+
- **macOS `entitlements.plist` for hardened runtime** with only what's needed (`com.apple.security.cs.allow-jit`, `com.apple.security.device.audio-input`, `com.apple.security.automation.apple-events` — but only if used).
|
|
183
|
+
- **`build.rs` that links platform frameworks** explicitly (`Accelerate`, `Metal`, `CoreGraphics`, `Foundation`, `ApplicationServices` on macOS; `user32` on Windows; `pthread`, `m` on Linux). Use `CARGO_CFG_TARGET_OS` at build time, not host OS.
|
|
184
|
+
- **Target-specific Cargo deps** via `[target.'cfg(target_os = "macos")'.dependencies]` for platform crates (`security-framework`, `cocoa`, `winapi`, `windows-sys`, `machine-uid`). The default deps list stays portable.
|
|
185
|
+
- **Release profile in `Cargo.toml`**: `lto = "fat"`, `codegen-units = 1`, `opt-level = 3`, `strip = true`, `panic = "abort"`. Dev profile: `opt-level = 1` for tolerable rebuild times.
|
|
186
|
+
- **Vite config tuned for Tauri**: `base: './'` (relative paths so the bundled HTML loads from `tauri://localhost`), `server.port: 5173`, `server.strictPort: true`, `server.watch.ignored: ['**/src-tauri/**']`, `clearScreen: false` so Rust errors aren't hidden.
|
|
187
|
+
- **TypeScript `strict: true`** with `noUnusedLocals`, `noUnusedParameters`, `noFallthroughCasesInSwitch`, and `isolatedModules`.
|
|
188
|
+
- **Typed frontend `useTauriCommand<T>` hook** in `src/hooks/useTauriCommand.ts` that wraps `invoke<T>(name, args)` with `data`, `isLoading`, `error`, and `execute`. Per-domain hooks (`useSettings`, `useRecording`, …) compose it; components never call `invoke()` inline. See [`references/templates/use-tauri-command.md`](references/templates/use-tauri-command.md).
|
|
189
|
+
- **`isTauriReady()` guard** in `src/tauriReady.ts` — frontend code that runs before the Tauri IPC bridge is ready (e.g. early auto-load) must call it.
|
|
190
|
+
- **GitHub Actions cross-platform matrix** that runs `cargo test` on macOS, Windows, and Ubuntu. Ubuntu needs `libgtk-3-dev libayatana-appindicator3-dev pkg-config`.
|
|
191
|
+
- **`tauri.conf.json` `windows[].devtools: false`** (or omitted — Tauri defaults to off in release). DevTools should be opened from a build-time feature flag, not the production config.
|
|
192
|
+
- **Reset-state-on-startup pattern** — in `pub fn run()`'s `.setup(|app| { ... })`, clear any in-progress / stuck state from a previous run before showing the window. Crashes leave globals in odd places; treat each startup as recovering from "the app was force-quit".
|
|
193
|
+
- **Show window FIRST in `.setup()`** before any blocking initialization. Heavy work (ML model loading, large config parses) goes on a background thread that emits events back to the frontend.
|
|
194
|
+
|
|
195
|
+
### Eliminate (anti-patterns)
|
|
196
|
+
|
|
197
|
+
Every one of these is forbidden in generated code. Rationale for each is in [`references/anti-patterns.md`](references/anti-patterns.md).
|
|
198
|
+
|
|
199
|
+
- ❌ Committed `*.backup`, `*.orig`, `*.temp` files in `src-tauri/src/` (e.g. `lib.rs.backup`, `macos.rs.orig`). Add them to `.gitignore` and never commit. These are merge-conflict / WIP artefacts, not source of truth.
|
|
200
|
+
- ❌ Plaintext API keys / OAuth tokens in `settings.json`. If the app stores any secret, route it through `encryption/` and persist only `EncryptedApiKey { ciphertext, nonce, salt }`. Auto-migrate any plaintext encountered on load.
|
|
201
|
+
- ❌ Tokens in browser `localStorage` / `sessionStorage`. Anything sensitive lives in Rust-side encrypted storage; the frontend asks for it only when needed.
|
|
202
|
+
- ❌ `devtools: true` in the release `tauri.conf.json`. Gate dev tools behind a build-time feature or a separate `tauri.dev.conf.json`.
|
|
203
|
+
- ❌ `cfg!(target_os = "...")` checks scattered through command bodies. Platform-specific behavior goes through a `platform::traits::*` impl + `cfg(target_os = "...")` modules; commands stay portable.
|
|
204
|
+
- ❌ Hand-rolled date / leap-year / ISO-8601 parsing. Use `chrono::Utc::now()`, `DateTime::parse_from_rfc3339`, `Duration::days(n)`. The "hand-rolled time math" pattern is an anti-pattern even when it works.
|
|
205
|
+
- ❌ Raw `std::fs::read` / `std::fs::write` against user-chosen paths from inside a `#[tauri::command]`. Either go through `tauri-plugin-fs` (which respects capability scopes) or restrict raw I/O to paths derived from `app_handle.path().app_data_dir()`.
|
|
206
|
+
- ❌ Blocking I/O inside `async fn` commands without `tokio::task::spawn_blocking`. The Tauri IPC runtime is shared — a blocked task stalls all command throughput.
|
|
207
|
+
- ❌ `#[tauri::command] fn long_running(...) -> Result<...>` that holds a `std::sync::Mutex` lock across an `.await`. Use `tokio::sync::Mutex`, or drop the guard before awaiting.
|
|
208
|
+
- ❌ Missing `#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]` at the top of `main.rs`. Without it, a console window flashes behind the Tauri window on Windows in release.
|
|
209
|
+
- ❌ App built without `tauri-plugin-single-instance`. Double-clicking the app launches a second process, both fighting over global hotkeys, audio devices, sockets, and lock files.
|
|
210
|
+
- ❌ Builder calls in `lib.rs` that boot `init_*` before showing the window — model loading, audio device enumeration, network calls — block the user from seeing UI for seconds.
|
|
211
|
+
- ❌ Empty `catch {}` / `let _ = result;` swallowing errors silently. Either log via `tracing::warn!` / `error!` or propagate via `?`.
|
|
212
|
+
- ❌ Mutex-locking the AppState for the duration of a command. Read or clone what you need, drop the guard, then do the work.
|
|
213
|
+
- ❌ Hardcoded bundle identifiers, R2 / Cloudflare / S3 URLs, updater public keys, Apple Team IDs, Windows certificate thumbprints in any generated file. These are user-specific; ask, don't assume.
|
|
214
|
+
- ❌ Inventing a Tauri updater pubkey. The user generates one with `npx tauri signer generate -w ~/.tauri/myapp.key` and pastes the public key into `tauri.conf.json`. Never paste another project's pubkey.
|
|
215
|
+
- ❌ `String` errors at module boundaries (`fn foo() -> Result<T, String>`). Internal APIs use `thiserror` enums; only `#[tauri::command]` returns convert to `Result<T, String>` at the IPC boundary.
|
|
216
|
+
- ❌ Multiple `createRoot(...)` calls, or rendering React before the DOM root is wired. One entry point, `main.tsx`, renders into `#app` after `index.html` is parsed.
|
|
217
|
+
- ❌ Frontend invoking `@tauri-apps/api/core` `invoke()` inline in components instead of through a typed hook. Loses TS safety, scatters error handling, makes refactors painful.
|
|
218
|
+
- ❌ Capability files with blanket permissions (`"core:default"` only, no granular scopes; or `"fs:default"` without `allow-read-file` / `allow-write-file` actually being used). Capabilities should describe **exactly** what the app needs and nothing more.
|
|
219
|
+
- ❌ `tauri.conf.json` `app.security.csp: null` left in production without an explicit decision. If CSP is genuinely too restrictive for the app, add a CSP that whitelists `tauri://localhost` + your trusted origins, and document the choice.
|
|
220
|
+
- ❌ Bundling external binaries (FFmpeg, ML models) without listing them in `tauri.conf.json` `bundle.externalBin` / `bundle.resources`. They'll work in `cargo run` but be missing in the installer.
|
|
221
|
+
- ❌ macOS `Info.plist` missing `NSMicrophoneUsageDescription` / `NSAccessibilityUsageDescription` / `NSCameraUsageDescription` for permissions the app actually requests. The OS will refuse the prompt and the call returns "denied" silently.
|
|
222
|
+
- ❌ macOS `entitlements.plist` requesting entitlements the app doesn't use (`com.apple.security.cs.allow-unsigned-executable-memory`, `com.apple.security.cs.allow-jit`) — they break notarization or weaken the sandbox. Only enable what's required.
|
|
223
|
+
- ❌ Two binaries in `Cargo.toml` (`[[bin]]` × 2) without a clear sidecar reason. If the app has one main binary, keep one `[[bin]]`. Sidecars get their own crate inside a Cargo workspace, not a second `[[bin]]` in the same crate.
|
|
224
|
+
- ❌ `.env` committed to git. Only `.env.example` is tracked; `.env` is in `.gitignore`.
|
|
225
|
+
|
|
226
|
+
## Operating-mode playbooks
|
|
227
|
+
|
|
228
|
+
### Mode 1 — `scaffold-app`
|
|
229
|
+
|
|
230
|
+
1. Resolve versions per **Step 1**. Quote them.
|
|
231
|
+
2. Ask the inputs per **Step 2**. Wait for answers.
|
|
232
|
+
3. Generate, in this order:
|
|
233
|
+
1. Root: `package.json`, `tsconfig.json`, `tsconfig.node.json`, `vite.config.ts`, `index.html`, `.gitignore`, `.env.example`, `README.md`, `.github/workflows/unit-tests.yml`.
|
|
234
|
+
2. Frontend: `src/main.tsx` (or `main.ts`), `src/App.tsx`, `src/tauriReady.ts`, `src/hooks/useTauriCommand.ts`, `src/styles.css`. Tailwind config + `postcss.config.js` only if the user chose Tailwind.
|
|
235
|
+
3. `src-tauri/Cargo.toml` (with target-specific deps + release profile), `src-tauri/build.rs`, `src-tauri/tauri.conf.json`, `src-tauri/Info.plist`, `src-tauri/entitlements.plist`, `src-tauri/capabilities/default.json`.
|
|
236
|
+
4. `src-tauri/icons/` — placeholder 1024×1024 PNG **only** if the user hasn't supplied one. Surface as TODO; tell them to run `npx @tauri-apps/cli icon icons/icon.png`.
|
|
237
|
+
5. `src-tauri/src/main.rs`, `src-tauri/src/lib.rs`, `src-tauri/src/error/mod.rs`, `src-tauri/src/state/mod.rs`, `src-tauri/src/storage/mod.rs`, `src-tauri/src/commands/mod.rs` + `src-tauri/src/commands/system.rs`, `src-tauri/src/platform/{mod,traits,macos,windows,linux,wrappers}.rs`, `src-tauri/src/settings/{mod,defaults,storage,commands}.rs`.
|
|
238
|
+
6. Optional: `src-tauri/src/encryption/mod.rs`, `src-tauri/src/tray.rs`, `src-tauri/src/commands/<more>.rs` based on user answers.
|
|
239
|
+
7. `src-tauri/tests/common/mod.rs` + at least one integration test file (e.g. `system_test.rs`) so CI has something real to run.
|
|
240
|
+
4. `npm install`.
|
|
241
|
+
5. `cd src-tauri && cargo build && cargo test` — must succeed.
|
|
242
|
+
6. `npm run build` — must succeed (catches TS / Vite errors).
|
|
243
|
+
7. If a first command/module was requested, run **Mode 2** or **Mode 3** for it.
|
|
244
|
+
8. Report.
|
|
245
|
+
|
|
246
|
+
### Mode 2 — `add-command`
|
|
247
|
+
|
|
248
|
+
For command `{{command}}` (snake_case) in module `commands/{{domain}}.rs`:
|
|
249
|
+
|
|
250
|
+
1. **Rust**:
|
|
251
|
+
- Add (or extend) `src-tauri/src/commands/{{domain}}.rs` with a `#[tauri::command]` async fn.
|
|
252
|
+
- If the function does blocking work, wrap it in `tokio::task::spawn_blocking(move || { ... }).await.map_err(|e| e.to_string())?`.
|
|
253
|
+
- If it needs `AppHandle`, take it as a parameter (Tauri injects). If it needs shared state, take `State<'_, Mutex<AppState>>`.
|
|
254
|
+
- Errors: return `Result<T, String>`. Internally use `thiserror` + the `into_string_err!` macro from `error/mod.rs` to convert at the boundary.
|
|
255
|
+
- Re-export from `commands/mod.rs` (`pub use {{domain}}::*;`).
|
|
256
|
+
- Register in `lib.rs` inside `tauri::generate_handler![..., {{command}}]`.
|
|
257
|
+
2. **Capabilities**: if the command uses a plugin (notification, fs, dialog, global-shortcut), add the specific permission scope to `capabilities/default.json` (e.g. `"dialog:allow-open"`). Do **not** widen to `default` if a narrower scope works.
|
|
258
|
+
3. **Frontend**:
|
|
259
|
+
- Add a typed wrapper in `src/hooks/use{{Domain}}.ts` that composes `useTauriCommand<ReturnType>({ command: "{{command}}" })`.
|
|
260
|
+
- Export the hook. Do **not** call `invoke()` inline in a component.
|
|
261
|
+
4. **Test**: add an integration test in `src-tauri/tests/{{domain}}_test.rs` that exercises the command's underlying function (the `#[tauri::command]` itself is hard to call without a full Tauri context — test the inner function).
|
|
262
|
+
|
|
263
|
+
After generating: `cd src-tauri && cargo build && cargo test` then `npm run build` from the project root.
|
|
264
|
+
|
|
265
|
+
### Mode 3 — `add-module`
|
|
266
|
+
|
|
267
|
+
For module `{{module}}` (snake_case):
|
|
268
|
+
|
|
269
|
+
1. Create `src-tauri/src/{{module}}/mod.rs` (split into submodules if multiple concerns).
|
|
270
|
+
2. Define data types with `#[derive(Debug, Clone, Serialize, Deserialize)]` + `#[serde(rename_all = "camelCase")]` so JSON matches the frontend.
|
|
271
|
+
3. Define a `thiserror` error enum: `#[derive(Debug, thiserror::Error)] pub enum {{Module}}Error { ... }`.
|
|
272
|
+
4. If the module persists JSON to disk, use the shared `storage` module — `get_app_data_dir`, `load_json`, `save_json`. Don't roll your own file I/O.
|
|
273
|
+
5. Add `pub mod {{module}};` to `lib.rs`. Re-export the public types you want callable from commands (`pub use {{module}}::{Type, OtherType};`).
|
|
274
|
+
6. Add a `#[cfg(test)]` `mod tests { ... }` block at the bottom of `mod.rs` covering pure logic (no Tauri context required).
|
|
275
|
+
7. Add a `src-tauri/tests/{{module}}_test.rs` integration test if the module has cross-cutting behavior.
|
|
276
|
+
|
|
277
|
+
After generating: `cd src-tauri && cargo build && cargo test`.
|
|
278
|
+
|
|
279
|
+
## NuGet... wait, this is Tauri. Cargo + npm packages (resolve latest stable at scaffold time)
|
|
280
|
+
|
|
281
|
+
Look these up at scaffold time — do not hand-paste versions:
|
|
282
|
+
|
|
283
|
+
**Cargo (always)**
|
|
284
|
+
- `tauri` (2.x), `tauri-build` (2.x)
|
|
285
|
+
- `serde`, `serde_json`, `thiserror`, `anyhow`
|
|
286
|
+
- `tokio` (features: `sync`, `rt-multi-thread`, `macros`)
|
|
287
|
+
- `tracing`, `tracing-subscriber` (`env-filter`)
|
|
288
|
+
- `chrono`
|
|
289
|
+
- `dirs`
|
|
290
|
+
|
|
291
|
+
**Cargo (conditional, only if used)**
|
|
292
|
+
- `tauri-plugin-opener`, `tauri-plugin-notification`, `tauri-plugin-dialog`, `tauri-plugin-fs`
|
|
293
|
+
- `tauri-plugin-global-shortcut`, `tauri-plugin-single-instance`, `tauri-plugin-updater`, `tauri-plugin-process`
|
|
294
|
+
- `aes-gcm`, `argon2`, `base64`, `rand` (encryption module)
|
|
295
|
+
- `reqwest` (with `rustls-tls`, `json`) — only if the app makes HTTP calls
|
|
296
|
+
- `tokio-tungstenite` (with `rustls-tls-native-roots`) — only if WebSocket needed
|
|
297
|
+
- `regex` (with `default-features = false`, `features = ["std", "perf"]`)
|
|
298
|
+
- `embed_plist` (macOS only, gated in `main.rs`)
|
|
299
|
+
|
|
300
|
+
**Cargo (target-specific)**
|
|
301
|
+
- macOS: `security-framework`, `objc`, `cocoa`, `core-graphics`, `block`
|
|
302
|
+
- Windows: `winapi`, `windows-sys` (with the specific feature flags the code uses, not blanket)
|
|
303
|
+
- Linux: `machine-uid` (only if the encryption module needs a machine ID)
|
|
304
|
+
|
|
305
|
+
**Dev-dependencies**
|
|
306
|
+
- `mockall`, `mockito` (only if the code is structured for mocking)
|
|
307
|
+
- `tokio-test`, `tempfile`, `assert_matches`, `pretty_assertions`
|
|
308
|
+
- `proptest` (only if a domain has property-test-shaped invariants)
|
|
309
|
+
- `serde_yaml` (only if tests parse CI YAML)
|
|
310
|
+
|
|
311
|
+
**npm**
|
|
312
|
+
- `@tauri-apps/api` (2.x), `@tauri-apps/cli` (2.x)
|
|
313
|
+
- `@tauri-apps/plugin-*` matching the wired Rust plugins
|
|
314
|
+
- React stack only if chosen: `react`, `react-dom`, `@types/react`, `@types/react-dom`, `@vitejs/plugin-react`
|
|
315
|
+
- `vite`, `typescript`
|
|
316
|
+
- Tailwind only if chosen: `tailwindcss`, `postcss`, `autoprefixer`, `@fontsource/*` for any fonts used
|
|
317
|
+
- `rimraf` (clean scripts)
|
|
318
|
+
- `sharp` only if the user wants icon generation locally
|
|
319
|
+
|
|
320
|
+
## Verification checklist before reporting "done"
|
|
321
|
+
|
|
322
|
+
- [ ] `npm install` succeeds.
|
|
323
|
+
- [ ] `cd src-tauri && cargo build` exits 0.
|
|
324
|
+
- [ ] `cd src-tauri && cargo test` exits 0 (if tests were generated).
|
|
325
|
+
- [ ] `npm run build` succeeds (frontend bundles).
|
|
326
|
+
- [ ] `npm run tauri build -- --debug` succeeds if the user wants a smoke check on the bundler (optional, slow).
|
|
327
|
+
- [ ] `main.rs` contains `#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]`.
|
|
328
|
+
- [ ] `lib.rs` registers `tauri_plugin_single_instance::init` **first** in the builder chain when single-instance is wired.
|
|
329
|
+
- [ ] `tauri.conf.json` `identifier` is not empty and matches what the user provided — not a generic `com.example.*`.
|
|
330
|
+
- [ ] `tauri.conf.json` `plugins.updater.pubkey` is **empty / removed** unless the user pasted a real one. Never invent a pubkey.
|
|
331
|
+
- [ ] `tauri.conf.json` `app.windows[].devtools` is `false` (or omitted).
|
|
332
|
+
- [ ] `capabilities/default.json` has granular permission scopes — no blanket allows beyond `core:default`.
|
|
333
|
+
- [ ] `Info.plist` has a usage description for every permission the generated code requests.
|
|
334
|
+
- [ ] `.gitignore` includes `src-tauri/target/`, `dist/`, `node_modules/`, `.env`, `*.backup`, `*.orig`, `*.temp`.
|
|
335
|
+
- [ ] No `.backup` / `.orig` / `.temp` files exist anywhere under `src-tauri/src/`.
|
|
336
|
+
- [ ] No `cfg!(target_os = "...")` appears in `src-tauri/src/commands/**` or `src-tauri/src/<domain>/**` — only in `src-tauri/src/platform/**`.
|
|
337
|
+
- [ ] No `localStorage.setItem("token"` / `sessionStorage.setItem("token"` in `src/`.
|
|
338
|
+
- [ ] No raw `std::fs::write(&user_supplied_path, ...)` in command bodies (must go through plugin or `app_data_dir`).
|
|
339
|
+
- [ ] All `#[tauri::command]` async fns that do blocking work use `tokio::task::spawn_blocking`.
|
|
340
|
+
- [ ] No `cfg_if!` or `#[cfg]` guard around `embed_plist::embed_info_plist!` is missing on macOS.
|
|
341
|
+
- [ ] `Cargo.toml` `[profile.release]` has `lto`, `codegen-units`, `opt-level`, `strip`, `panic` set.
|
|
342
|
+
- [ ] GitHub Actions matrix workflow has macOS, Windows, **and** Ubuntu, and Ubuntu installs the GTK/AppIndicator system deps.
|
|
343
|
+
|
|
344
|
+
If any check fails, fix before reporting. Don't claim success with a known-broken scaffold.
|
|
345
|
+
|
|
346
|
+
## Examples
|
|
347
|
+
|
|
348
|
+
### Example 1: Fresh project
|
|
349
|
+
|
|
350
|
+
**User:** "Scaffold a new Tauri 2 desktop app called `acme-notes`. React frontend with Tailwind. Bundle identifier `com.acme.notes`. Wire single-instance, dialog, fs, opener. Skip updater for now."
|
|
351
|
+
|
|
352
|
+
**Claude:**
|
|
353
|
+
1. Runs `cargo --version`, `rustc --version`, `node --version`. Resolves latest stable Tauri 2.x, React, Vite, Tailwind versions via context7 / crates.io.
|
|
354
|
+
2. Reports chosen versions; waits for confirmation if anything looks off.
|
|
355
|
+
3. Asks the Step-2 questions for any inputs not already provided (tray? encryption? first command?).
|
|
356
|
+
4. Generates the full project skeleton per Mode 1.
|
|
357
|
+
5. Runs `npm install`, `cargo build`, `cargo test`, `npm run build` — all must pass.
|
|
358
|
+
6. Reports the tree, the bundle identifier set, and next steps ("create your icon source and run `npx @tauri-apps/cli icon icons/icon.png`", "run `npm run tauri dev`").
|
|
359
|
+
|
|
360
|
+
### Example 2: Add a command
|
|
361
|
+
|
|
362
|
+
**User:** "Add a `get_disk_usage` command to my Tauri app that returns the size of a directory."
|
|
363
|
+
|
|
364
|
+
**Claude:** Runs Mode 2. Adds `#[tauri::command] pub async fn get_disk_usage(path: String) -> Result<u64, String>` to `commands/system.rs` (or a new `commands/disk.rs` if it warrants its own module), wraps the recursive walk in `tokio::task::spawn_blocking`, registers it in `lib.rs`, adds a typed hook `useDiskUsage` to `src/hooks/`, and writes a unit test for the inner function. Builds and tests.
|
|
365
|
+
|
|
366
|
+
### Example 3: Add a module
|
|
367
|
+
|
|
368
|
+
**User:** "Add a `notes` module that stores user notes as JSON in the app data dir, with create/list/delete operations."
|
|
369
|
+
|
|
370
|
+
**Claude:** Runs Mode 3. Creates `src-tauri/src/notes/mod.rs` with a `Note { id, title, body, created_at, updated_at }` struct (using `chrono::DateTime<Utc>`, not hand-rolled timestamps), a `NotesError` `thiserror` enum, and `create_note`, `list_notes`, `delete_note` functions backed by `storage::load_json` / `save_json` (file: `notes.json` in `app_data_dir`). Adds `pub mod notes;` to `lib.rs`. Generates a `#[cfg(test)] mod tests` block covering create/list/delete on a `tempfile::TempDir`. Builds and tests.
|
|
371
|
+
|
|
372
|
+
## Notes
|
|
373
|
+
|
|
374
|
+
- **Don't over-engineer**. Don't add Redux/Zustand/MobX on the frontend, Diesel/SQLx on the backend, gRPC, OpenTelemetry, Sentry, or a state-machine library unless the user asks. The default scaffold is intentionally lean.
|
|
375
|
+
- **Don't rewrite the user's existing project** as part of this skill. This skill is for *new* scaffolds (and additive command/module modes), not migrations. Migrations are a different conversation.
|
|
376
|
+
- **Tauri version**: the skill targets Tauri 2.x. If the user's environment somehow pins 1.x (legacy `tauri.conf.json` shape, `@tauri-apps/api@1`), stop and ask whether they want to upgrade — don't silently downgrade the patterns.
|
|
377
|
+
- **Bundle identifier**: always reverse-DNS, always provided by the user, never hardcoded. It cannot change after the app is in distribution (it's the OS-level identity for permissions, keychain entries, settings, and update channels).
|
|
378
|
+
- **Updater**: only wire it if the user has, or commits to setting up, a JSON manifest at a known URL and has generated a signing key. Half-wired updaters silently fail in production.
|
|
379
|
+
- **Icons**: a single high-res source PNG (1024×1024+) generates every platform-specific format via `npx @tauri-apps/cli icon <path>`. Don't ship the auto-generated placeholder as the real icon.
|
|
380
|
+
- **Naming**: kebab-case for the project directory, Title Case for the `productName`, snake_case for the Rust crate (`{{app_name}}` in `Cargo.toml`), reverse-DNS for the bundle identifier. Mismatches in casing are a top source of "works locally, broken installer" bugs.
|
|
381
|
+
- **Versions**: always quote the resolved versions before writing `Cargo.toml` / `package.json`. The user explicitly asked not to hard-code them.
|