# 工具链 / 工程
## 概览
Cargo workspace 的模块组织、feature 门控、构建/发布、平台坑。`Cargo.toml`(根)、`crates/*/Cargo.toml`、`lefthook.yaml`、`.github/workflows`、`release.sh`。
## 模块组织
### 统一用 mod.rs 风格
全 workspace(含主程序 `src/` 与子 crate)统一用 **`foo/mod.rs` 目录风格**,不用 `foo.rs` + `foo/` 并列风格。重构 `parse-book-source` 时把扁平的 16 个文件按功能域收进目录:`source/`、`eval/`、`fetch/`(含 `browser/`)、`host/`、`engine/`,每个目录一个 `mod.rs`。
**保持外部路径稳定**:`lib.rs` 用 re-export 把内部新路径映射回旧的对外路径,例如 `pub use fetch::cookie;`、`pub use host::state;`——外部 crate(主程序)的 `use parse_book_source::cookie::...` 不受目录重构影响。
**相关文件**:`crates/parse-book-source/src/lib.rs`、各域 `mod.rs`
### include_str! 路径随文件深度变
把 `source.rs` 移到 `source/mod.rs` 后多了一层目录,`include_str!("../book-source.schema.json")` 要改成 `../../`。移动含 `include_str!`/`include_bytes!` 的文件时记得同步相对路径。
**相关文件**:`crates/parse-book-source/src/source/mod.rs`(schema_sync 测试)
## 依赖钉版(勿随意升级)
### ort / kokoro-tts 钉死,勿升
- `ort` 钉死 `2.0.0-rc.10`(onnxruntime 绑定)。
- `kokoro-tts` 钉死 `0.3.1`——**`rc.12` 砍掉了 Intel Mac 支持,不要升**。
- onnxruntime 等 C 依赖本就**静态链入** trnovel,"零 C 依赖"从来不是本项目约束。
**相关文件**:`crates/novel-tts/Cargo.toml`、根 `Cargo.toml`
## 构建 / 发布
### 平台坑
- **Edition 2024 + std 文件锁**:需 Rust ≥ 1.89(stable,无需 nightly)。`parse-book-source` 的 browser-pool 用 `std::fs::File::try_lock` 做跨进程启动临界区锁;该 API 稳定于 1.89。
- **Linux 构建依赖**:`libasound2-dev`(rodio)、`libssl-dev`、`pkg-config`,CI 用 apt 装。
- **Windows**:根 `Cargo.toml` 的 `[workspace.metadata.dist]` 里 `msvc-crt-static = false` **必须保留**——ort/onnxruntime 是动态 CRT,静态链接会 `__imp_tolower` 等 unresolved-symbol LNK 错。
### pre-commit / 发布链
`lefthook.yaml` pre-commit:test → `clippy --fix --allow-dirty`(自动 stage 修复)→ `cargo fmt` → `cargo doc`。发布走 `./release.sh`(cargo-release + git-cliff,tag `<crate>-v<version>`)+ cargo-dist(`trnovel-v*` tag 触发)。
### VHS 录制要显式清 NO_COLOR
Codex / CI shell 可能带 `TERM=dumb` 或 `NO_COLOR=1`,会让 ratatui/crossterm 抑制样式码,录出来的 GIF 接近黑白。所有 `docs/tapes/*.tape` 都要在 `Env TERM "xterm-256color"` 与 `Env COLORTERM "truecolor"` 后补 `Env NO_COLOR ""`,并用 VHS 内置 `Screenshot` 验证关键帧颜色。
**相关文件**:`docs/tapes/README.md`、`docs/tapes/*.tape`
### 改引擎公开 API 后要单独 cargo build 验 Send
子 crate 的 lib test **测不出** `tokio::spawn` 上下文的 `Send` 约束。改了会被主程序 spawn 调用的引擎公开 API(如 `Engine::explore`/`search`)后,CI 四件套之外还要 `cargo build` 主程序 trnovel——曾因 `explore`/`search` 的 Future 变 `!Send`(async closure 参数)导致主程序编译失败、`cargo run` 跑不起来。
**相关文件**:`crates/parse-book-source/src/engine/mod.rs`
### `ort` 是「假死依赖」——版本钉死,代码零引用但不可删
`crates/novel-tts/Cargo.toml` 的 `ort = "=2.0.0-rc.10"` 在 novel-tts src 里**零 `use`/`ort::` 引用**——它不是直接用的,而是**钉死 kokoro-tts 传递依赖 ort 的版本**(kokoro-tts 声明宽松预发布范围,rc.12 砍 Intel Mac/要 glibc 2.38+)。`cargo-machete` 等死依赖工具会把它误报为 unused 并建议删除,**删了会让 ort 解析到坏版本、发布炸**。审计死依赖时这类「纯版本钉死 dep」要人工豁免。
**相关文件**:`crates/novel-tts/Cargo.toml`(注释已说明)
### 死依赖清理(change upgrade-ratatui-kit-07 随手)
手动审计(grep 每个直接依赖的下划线名在对应 src)清掉:trnovel 的 `tui-scrollview`(框架自带 ScrollView,项目从未直接 use)、`rodio`/`tokio-util`(仅 novel-tts 用,trnovel 自己声明的是死的);novel-tts 的 `futures`(零引用)。`tui-scrollview`/`tui-big-text` 同时升到最新(0.6.7/0.8.7);**`tui-big-text` 0.8.7 补了 `&BigText: Widget`**,免去为 `widget(big_txt)` 自写 WidgetRef 适配层。
### ratatui-kit 0.7.1 的两处自修(已发版、已切回 crates.io)
迁移期在框架仓修了两个 0.7 的坑并**已发布到 crates.io**(`ratatui-kit-macros` 0.6.1 + `ratatui-kit` 0.7.1,均带 trybuild 测试 `tests/ui/pass/{macro_call_child,widget_by_value}.rs`);TRNovel 依赖已从临时 path 切回 `ratatui-kit = { version = "0.7.1", features = ["full"] }`:
1. **element! 子节点位接纳宏调用**(`ratatui-kit-macros/src/element.rs::parse_children` 加 `is_macro_call`):`element!(...)`/`vec![...]` 在 children 块 / 一等控制流分支体内当 `{expr}` embed 解析,原误报 `expected identifier`(`Comp(..){ element!(..) }` 把 `{}` 当 children 时最坑)。
2. **widget() 收按值 widget**(`adapter/widget.rs` 约束 `for<'a> &'a T: Widget` → `T: Widget + Clone` 按值克隆渲染;`text.rs` 的 `TextParagraph` 同步改按值):`BigText` 等只实现按值 `Widget` 的部件可直接 `widget(...)`。**取舍**:只实现按引用 `impl Widget for &T` 的部件(如 ratatui-widgets 的 `Shadow`)在 0.7.1 起不再被 `widget()` 接纳。