| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 0 | 11 | 9 | 0% |
| Commands | 0 | 8 | 1 | 0% |
| Section tags | 1 | 3 | 3 | 14% |
What each file covers
Sections
0 shared · 11 only in A · 9 only in B- − Project Identity
- − Project Name & Purpose
- − Tech Stack
- − Frontend
- − Backend
- − External Binaries
- − Development Environment
- − Directory Structure
- − Package Rules
- − Dev Commands
- − Window Config
- + Backend Rules
- + Tauri Commands
- + State Management
- + Frontend Communication
- + Logging
- + External Processes
- + Compile-time Config
- + HTTP Client
- + Clippy & Code Quality
Commands
0 shared · 8 only in A · 1 only in B- − yarn dev
- − yarn tauri dev
- − yarn build
- − yarn tauri build
- − yarn rustcheck
- − npm install
- − pnpm add
- − yarn
- + cargo build
Section tags
1 shared · 3 only in A · 3 only in B- − setup
- − architecture
- − types
- + build
- + lint-format
- + code-style
- do-not
Line diff
VaillerTeeter/HoshimiNest · .clinerules/project-identity.md
@@ −1 @@
1---
2description: "HoshimiNest project overview — tech stack, directory structure, package manager, dev commands, Rust dependencies. Always apply."
3globs: ""
4alwaysApply: true
5---
6
7# Project Identity
8
9<!-- HoshimiNest 项目身份与架构,AI 每次对话自动加载 -->
10
11## Project Name & Purpose
12
13HoshimiNest is a desktop anime tracking & download manager.
14
15## Tech Stack
16
17### Frontend
18
19- **React 19 + TypeScript** — functional components + hooks, strict mode
20- **Vite 7** — bundler, dev port `1520`
21- **Zustand** — state management (`src/store/`)
22- **Plain CSS** — NO Tailwind, NO CSS-in-JS
23- **Yarn** — the ONLY package manager
24
25### Backend
26
27- **Tauri v2** — Rust desktop shell
28- **Key Rust crates:**
29 - `reqwest` (HTTP client, rustls-tls)
30 - `serde` + `serde_json` (serialization)
31 - `tokio` (async runtime: time, sync, macros, rt)
32 - `tauri-plugin-shell` / `tauri-plugin-opener` / `tauri-plugin-dialog`
33 - `log` + `env_logger`
34
35### External Binaries
36
37Bundled with the app via `externalBin`:
38
39- `aria2c` — download engine
40- `mkvmerge` — media remuxing
41
42## Development Environment
43
44- **OS**: Windows only — NEVER assume macOS/Linux/WSL paths or tools
45- **Shell**: PowerShell (`.ps1` scripts) — NEVER write `.sh` or bash scripts
46- **Terminal commands**: always use PowerShell syntax; NEVER use Unix-only flags or pipes
47
48## Directory Structure
49
50```text
51src/ # React frontend source
52 ├── App.css # global layout & component styles (colors reference theme.css vars)
53 ├── App.tsx # root component (topbar + sidebar nav + download settings modal)
54 ├── assets/
55 │ └── fonts/ # bundled font files
56 ├── main.tsx # React entry point
57 ├── pages/ # page-level components (one per nav item)
58 │ ├── BacklogPage.tsx # backlog / plan-to-watch
59 │ ├── DownloadPage.tsx # download manager
60 │ ├── FinishedPage.tsx # completed anime (main file, sub-logic in finished/)
61 │ ├── QueryPage.tsx # seasonal query (main page)
62 │ ├── QueryPage/ # seasonal query sub-components
63 │ ├── SearchPage.tsx # resource search (main page)
64 │ ├── SearchPage/ # resource search sub-components
65 │ ├── TracksPage.tsx # track workshop (main file, sub-logic in tracks/)
66 │ ├── WatchingPage.tsx # currently watching
67 │ ├── WatchListPage.tsx # watchlist base (main file, sub-logic in watchlist/)
68 │ ├── finished/ # completed anime sub-components
69 │ ├── tracks/ # track workshop sub-components
70 │ └── watchlist/ # watchlist sub-components
71 ├── store/ # global state
72 │ ├── downloadStore.tsx # download task context (state machine + aria2 events + localStorage)
73 │ └── watchStore.ts # watchlist localStorage helpers
74 ├── styles/
75 │ ├── fonts.css # @font-face declarations
76 │ └── theme.css # theme CSS variables
77 └── vite-env.d.ts # Vite type declarations
78src-tauri/ # Tauri/Rust backend
79 ├── app-config.json # compile-time config (BT trackers, ports, cache)
80 ├── build.rs # Tauri build script
81 ├── capabilities/
82 │ └── default.json # Tauri ACL capability config
83 ├── icons/ # app icons
84 ├── src/
85 │ ├── lib.rs # Tauri commands + aria2 control + mkvmerge track merge
86 │ └── main.rs # Rust entry point
87 └── tauri.conf.json # Tauri app config (window/bundle/permissions)
88scripts/ # dev & setup scripts (.ps1)
89```
90
91## Package Rules
92
93NEVER run `npm install` or `pnpm add`. Always use `yarn`.
94
95NEVER suggest re-installing these — already in `package.json`:
96
97- `@tauri-apps/api`, `@tauri-apps/plugin-dialog`, `@tauri-apps/plugin-opener`
98- `animal-island-ui`, `bangumi-api-client`
99- `react`, `react-dom`, `zustand`
100
101## Dev Commands
102
103```bash
104yarn dev # Vite dev server (frontend only, port 1520)
105yarn tauri dev # full Tauri app in dev mode
106yarn build # TypeScript check + Vite build
107yarn tauri build # production Tauri bundle
108yarn rustcheck # cargo check (Rust compilation check only)
109```
110
111## Window Config
112
113- Window: 1360×820, non-resizable, non-maximizable, frameless (custom titlebar)
114- App identifier: `HoshimiNest`
115
VaillerTeeter/HoshimiNest · .clinerules/backend-rules.md
@@ +1 @@
1---
2description: "Use when: writing or modifying Rust code in src-tauri/. Covers Tauri command structure, state management, external process handling, logging, and compile-time config."
3globs: "src-tauri/**"
4alwaysApply: false
5---
6
7# Backend Rules
8
9<!-- Tauri v2 / Rust 后端规范,操作 src-tauri/ 目录时自动加载 -->
10
11## Tauri Commands
12
13Always declare Tauri commands in `src-tauri/src/lib.rs` and register them in `run()` via `.invoke_handler(tauri::generate_handler![...])`.
14
15Always use `#[tauri::command]` on async functions. NEVER use sync commands for I/O-bound work.
16
17```rust
18#[tauri::command]
19async fn my_command(app: tauri::AppHandle, ...) -> Result<T, String> { ... }
20```
21
22Always receive `app: tauri::AppHandle` as the first parameter. Use `app.state::<MyState>()` to access managed state.
23
24Prefer `Result<T, String>` as the return type for all commands. Use the `?` operator with `.map_err(|e| e.to_string())` or `.map_err(|e| format!("context: {e}"))` for error propagation.
25
26Use `tauri::async_runtime::spawn()` for long-running background tasks spawned from commands (e.g., polling loops, sidecar process monitors). NEVER use `std::thread::spawn` or `tokio::spawn` directly.
27
28## State Management
29
30Always use Tauri managed state (`app.manage(...)` in `run()`) for shared data. Declare state structs with thread-safe wrappers:
31
32```rust
33/// task_id → aria2 gid
34struct GidMap(Mutex<HashMap<String, String>>);
35
36/// task_id → oneshot sender to stop the polling loop
37struct PollStops(Mutex<HashMap<String, oneshot::Sender<()>>>);
38
39/// Flag to break re-entry loops
40struct Closing(AtomicBool);
41```
42
43Never use global `static mut` or `lazy_static!` for mutable state — always go through `app.state::<T>()`.
44
45## Frontend Communication
46
47Always emit events to the frontend via `app.emit("event-name", payload)`. Define payload types with `#[derive(serde::Serialize, Clone)]`.
48
49```rust
50#[derive(serde::Serialize, Clone)]
51struct MyEventPayload {
52 id: String,
53 value: u8,
54}
55
56let _ = app.emit("my-event", MyEventPayload { id, value });
57```
58
59Never call `app.emit()` from a synchronous context that holds a lock — always clone the `AppHandle` and emit from an async context or after releasing the lock.
60
61## Logging
62
63Always use the `log` crate macros: `debug!`, `info!`, `warn!`, `error!`.
64
65Prefix log messages with a tag for filtering:
66
67```rust
68info!("[add_magnet] task={task_id}");
69debug!("[{task_id}] gid handoff: {current_gid} → {new_gid}");
70error!("[merge][{}] 合并失败: {}", job.id, msg.trim());
71```
72
73Use `debug!` for detailed diagnostics, `info!` for lifecycle events, `warn!` for recoverable issues, `error!` for failures.
74
75Register `env_logger` in `run()` before the Tauri builder. Do NOT add additional logging frameworks.
76
77## External Processes
78
79Always use `tauri-plugin-shell` for spawning external binaries (aria2c, mkvmerge, etc.). NEVER use `std::process::Command` directly.
80
81```rust
82use tauri_plugin_shell::{process::CommandEvent, ShellExt};
83
84let sidecar_cmd = app.shell().sidecar("mkvmerge").map_err(|e| format!("sidecar error: {e}"))?;
85let (mut rx, _child) = sidecar_cmd.args(&args).spawn().map_err(|e| format!("spawn error: {e}"))?;
86```
87
88External binaries (`aria2c`, `mkvmerge`) are bundled via `externalBin` in `tauri.conf.json`. Reference them by name via `app.shell().sidecar()`.
89
90Always handle `CommandEvent::Stdout`, `Stderr`, `Terminated`, and the `None` case in the event loop.
91
92## Compile-time Config
93
94Compile-time configuration lives in `src-tauri/app-config.json`. Embed it at compile time with `include_str!`:
95
96```rust
97fn load_app_config() -> Result<AppConfig, String> {
98 const RAW: &str = include_str!("../app-config.json");
99 serde_json::from_str(RAW).map_err(|e| format!("app-config.json 格式错误: {e}"))
100}
101```
102
103Define a `#[derive(serde::Deserialize)]` struct matching the JSON shape. Changes to `app-config.json` take effect on the next `cargo build`.
104
105## HTTP Client
106
107Always use `reqwest` for outbound HTTP. Include `use reqwest::Client;` and build clients with `reqwest::Client::builder()`.
108
109Include reasonable `User-Agent` headers for external scraping. Use `.timeout(Duration::from_secs(N))` on requests to aria2c and external services.
110
111```rust
112let client = reqwest::Client::builder()
113 .user_agent("Mozilla/5.0 ...")
114 .build()
115 .map_err(|e| e.to_string())?;
116```
117
118## Clippy & Code Quality
119
120Always respect Clippy's `disallowed_macros` and `too-many-arguments` rules. If a lint is explicitly suppressed, add `#[allow(...)]` with a comment explaining why.
121
122Keep argument counts low. Use helper structs or `app.state()` to pull dependencies into the function body rather than adding more parameters.
123
124Avoid `unwrap()` on locks in async contexts — use `unwrap_or_else(|e| e.into_inner())` to handle poisoned mutexes gracefully.
125
@@ −1 +1 @@
11 ---
2−description: "HoshimiNest project overview — tech stack, directory structure, package manager, dev commands, Rust dependencies. Always apply."
3−globs: ""
4−alwaysApply: true
2+description: "Use when: writing or modifying Rust code in src-tauri/. Covers Tauri command structure, state management, external process handling, logging, and compile-time config."
3+globs: "src-tauri/**"
4+alwaysApply: false
55 ---
66
7−# Project Identity
7+# Backend Rules
88
9−<!-- HoshimiNest 项目身份与架构,AI 每次对话自动加载 -->
9+<!-- Tauri v2 / Rust 后端规范,操作 src-tauri/ 目录时自动加载 -->
1010
11−## Project Name & Purpose
11+## Tauri Commands
1212
13−HoshimiNest is a desktop anime tracking & download manager.
13+Always declare Tauri commands in `src-tauri/src/lib.rs` and register them in `run()` via `.invoke_handler(tauri::generate_handler![...])`.
1414
15−## Tech Stack
15+Always use `#[tauri::command]` on async functions. NEVER use sync commands for I/O-bound work.
1616
17−### Frontend
17+```rust
18+#[tauri::command]
19+async fn my_command(app: tauri::AppHandle, ...) -> Result<T, String> { ... }
20+```
1821
19−- **React 19 + TypeScript** — functional components + hooks, strict mode
20−- **Vite 7** — bundler, dev port `1520`
21−- **Zustand** — state management (`src/store/`)
22−- **Plain CSS** — NO Tailwind, NO CSS-in-JS
23−- **Yarn** — the ONLY package manager
22+Always receive `app: tauri::AppHandle` as the first parameter. Use `app.state::<MyState>()` to access managed state.
2423
25−### Backend
24+Prefer `Result<T, String>` as the return type for all commands. Use the `?` operator with `.map_err(|e| e.to_string())` or `.map_err(|e| format!("context: {e}"))` for error propagation.
2625
27−- **Tauri v2** — Rust desktop shell
28−- **Key Rust crates:**
29− - `reqwest` (HTTP client, rustls-tls)
30− - `serde` + `serde_json` (serialization)
31− - `tokio` (async runtime: time, sync, macros, rt)
32− - `tauri-plugin-shell` / `tauri-plugin-opener` / `tauri-plugin-dialog`
33− - `log` + `env_logger`
26+Use `tauri::async_runtime::spawn()` for long-running background tasks spawned from commands (e.g., polling loops, sidecar process monitors). NEVER use `std::thread::spawn` or `tokio::spawn` directly.
3427
35−### External Binaries
28+## State Management
3629
37−Bundled with the app via `externalBin`:
30+Always use Tauri managed state (`app.manage(...)` in `run()`) for shared data. Declare state structs with thread-safe wrappers:
3831
39−- `aria2c` — download engine
40−- `mkvmerge` — media remuxing
32+```rust
33+/// task_id → aria2 gid
34+struct GidMap(Mutex<HashMap<String, String>>);
4135
42−## Development Environment
36+/// task_id → oneshot sender to stop the polling loop
37+struct PollStops(Mutex<HashMap<String, oneshot::Sender<()>>>);
4338
44−- **OS**: Windows only — NEVER assume macOS/Linux/WSL paths or tools
45−- **Shell**: PowerShell (`.ps1` scripts) — NEVER write `.sh` or bash scripts
46−- **Terminal commands**: always use PowerShell syntax; NEVER use Unix-only flags or pipes
39+/// Flag to break re-entry loops
40+struct Closing(AtomicBool);
41+```
4742
48−## Directory Structure
43+Never use global `static mut` or `lazy_static!` for mutable state — always go through `app.state::<T>()`.
4944
50−```text
51−src/ # React frontend source
52− ├── App.css # global layout & component styles (colors reference theme.css vars)
53− ├── App.tsx # root component (topbar + sidebar nav + download settings modal)
54− ├── assets/
55− │ └── fonts/ # bundled font files
56− ├── main.tsx # React entry point
57− ├── pages/ # page-level components (one per nav item)
58− │ ├── BacklogPage.tsx # backlog / plan-to-watch
59− │ ├── DownloadPage.tsx # download manager
60− │ ├── FinishedPage.tsx # completed anime (main file, sub-logic in finished/)
61− │ ├── QueryPage.tsx # seasonal query (main page)
62− │ ├── QueryPage/ # seasonal query sub-components
63− │ ├── SearchPage.tsx # resource search (main page)
64− │ ├── SearchPage/ # resource search sub-components
65− │ ├── TracksPage.tsx # track workshop (main file, sub-logic in tracks/)
66− │ ├── WatchingPage.tsx # currently watching
67− │ ├── WatchListPage.tsx # watchlist base (main file, sub-logic in watchlist/)
68− │ ├── finished/ # completed anime sub-components
69− │ ├── tracks/ # track workshop sub-components
70− │ └── watchlist/ # watchlist sub-components
71− ├── store/ # global state
72− │ ├── downloadStore.tsx # download task context (state machine + aria2 events + localStorage)
73− │ └── watchStore.ts # watchlist localStorage helpers
74− ├── styles/
75− │ ├── fonts.css # @font-face declarations
76− │ └── theme.css # theme CSS variables
77− └── vite-env.d.ts # Vite type declarations
78−src-tauri/ # Tauri/Rust backend
79− ├── app-config.json # compile-time config (BT trackers, ports, cache)
80− ├── build.rs # Tauri build script
81− ├── capabilities/
82− │ └── default.json # Tauri ACL capability config
83− ├── icons/ # app icons
84− ├── src/
85− │ ├── lib.rs # Tauri commands + aria2 control + mkvmerge track merge
86− │ └── main.rs # Rust entry point
87− └── tauri.conf.json # Tauri app config (window/bundle/permissions)
88−scripts/ # dev & setup scripts (.ps1)
45+## Frontend Communication
46+
47+Always emit events to the frontend via `app.emit("event-name", payload)`. Define payload types with `#[derive(serde::Serialize, Clone)]`.
48+
49+```rust
50+#[derive(serde::Serialize, Clone)]
51+struct MyEventPayload {
52+ id: String,
53+ value: u8,
54+}
55+
56+let _ = app.emit("my-event", MyEventPayload { id, value });
8957 ```
9058
91−## Package Rules
59+Never call `app.emit()` from a synchronous context that holds a lock — always clone the `AppHandle` and emit from an async context or after releasing the lock.
9260
93−NEVER run `npm install` or `pnpm add`. Always use `yarn`.
61+## Logging
9462
95−NEVER suggest re-installing these — already in `package.json`:
63+Always use the `log` crate macros: `debug!`, `info!`, `warn!`, `error!`.
9664
97−- `@tauri-apps/api`, `@tauri-apps/plugin-dialog`, `@tauri-apps/plugin-opener`
98−- `animal-island-ui`, `bangumi-api-client`
99−- `react`, `react-dom`, `zustand`
65+Prefix log messages with a tag for filtering:
10066
101−## Dev Commands
67+```rust
68+info!("[add_magnet] task={task_id}");
69+debug!("[{task_id}] gid handoff: {current_gid} → {new_gid}");
70+error!("[merge][{}] 合并失败: {}", job.id, msg.trim());
71+```
10272
103−```bash
104−yarn dev # Vite dev server (frontend only, port 1520)
105−yarn tauri dev # full Tauri app in dev mode
106−yarn build # TypeScript check + Vite build
107−yarn tauri build # production Tauri bundle
108−yarn rustcheck # cargo check (Rust compilation check only)
73+Use `debug!` for detailed diagnostics, `info!` for lifecycle events, `warn!` for recoverable issues, `error!` for failures.
74+
75+Register `env_logger` in `run()` before the Tauri builder. Do NOT add additional logging frameworks.
76+
77+## External Processes
78+
79+Always use `tauri-plugin-shell` for spawning external binaries (aria2c, mkvmerge, etc.). NEVER use `std::process::Command` directly.
80+
81+```rust
82+use tauri_plugin_shell::{process::CommandEvent, ShellExt};
83+
84+let sidecar_cmd = app.shell().sidecar("mkvmerge").map_err(|e| format!("sidecar error: {e}"))?;
85+let (mut rx, _child) = sidecar_cmd.args(&args).spawn().map_err(|e| format!("spawn error: {e}"))?;
10986 ```
11087
111−## Window Config
88+External binaries (`aria2c`, `mkvmerge`) are bundled via `externalBin` in `tauri.conf.json`. Reference them by name via `app.shell().sidecar()`.
11289
113−- Window: 1360×820, non-resizable, non-maximizable, frameless (custom titlebar)
114−- App identifier: `HoshimiNest`
90+Always handle `CommandEvent::Stdout`, `Stderr`, `Terminated`, and the `None` case in the event loop.
91+
92+## Compile-time Config
93+
94+Compile-time configuration lives in `src-tauri/app-config.json`. Embed it at compile time with `include_str!`:
95+
96+```rust
97+fn load_app_config() -> Result<AppConfig, String> {
98+ const RAW: &str = include_str!("../app-config.json");
99+ serde_json::from_str(RAW).map_err(|e| format!("app-config.json 格式错误: {e}"))
100+}
101+```
102+
103+Define a `#[derive(serde::Deserialize)]` struct matching the JSON shape. Changes to `app-config.json` take effect on the next `cargo build`.
104+
105+## HTTP Client
106+
107+Always use `reqwest` for outbound HTTP. Include `use reqwest::Client;` and build clients with `reqwest::Client::builder()`.
108+
109+Include reasonable `User-Agent` headers for external scraping. Use `.timeout(Duration::from_secs(N))` on requests to aria2c and external services.
110+
111+```rust
112+let client = reqwest::Client::builder()
113+ .user_agent("Mozilla/5.0 ...")
114+ .build()
115+ .map_err(|e| e.to_string())?;
116+```
117+
118+## Clippy & Code Quality
119+
120+Always respect Clippy's `disallowed_macros` and `too-many-arguments` rules. If a lint is explicitly suppressed, add `#[allow(...)]` with a comment explaining why.
121+
122+Keep argument counts low. Use helper structs or `app.state()` to pull dependencies into the function body rather than adding more parameters.
123+
124+Avoid `unwrap()` on locks in async contexts — use `unwrap_or_else(|e| e.into_inner())` to handle poisoned mutexes gracefully.
115125
