trnovel 0.17.1

Terminal reader for novel
# 工具链 / 工程

## 概览

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()` 接纳。

<!-- 随开发补充:新 feature 门控约定、CI matrix 变更等 -->

### ARM64 musl release artifact

`local-artifacts-jobs = ["./musl"]` extends cargo-dist without hand-editing the generated workflow. The reusable musl workflow builds both application binaries natively in an ARM64 Alpine 3.23 Rust container and uploads `artifacts-build-musl`; the generated host job includes these files in the GitHub Release. It runs on PRs separately because the main dist workflow normally only plans PR releases.

The pinned ort rc.10 requires ONNX Runtime API 1.22. Its GNU prebuilt libraries are incompatible with musl, so the musl build uses Alpine's system ONNX Runtime with `ORT_LIB_LOCATION`, `ORT_PREFER_DYNAMIC_LINK=1`, `ORT_SKIP_DOWNLOAD=1`, and `-C target-feature=-crt-static`. This preserves TTS but requires runtime shared libraries. Do not add this target to dist's ordinary matrix until its build and installer dependency handling supports this setup. The custom archive is currently a manual download rather than an installer-selected platform.

**相关文件**:`.github/workflows/musl.yml`、`.github/scripts/build-musl.sh`、`.github/scripts/smoke-musl.sh`、`Cargo.toml`。