fix(presenter,app-iced): 「游戏在跑」挡在落 core 之前(M247 r2)

塔裁决:三个写操作在实例启动/运行时必须拒,判据不能只靠界面灰按钮 ——
Windows 上是 os error 32 兜底,**Linux 上删一个正在跑的世界会成功**,那是在
用户玩到一半时把存档删掉。

- host/dispatch.rs:`AppCommand::Saves` 与 `AppCommand::Mods` 同款,先查
  `CoreHost::in_flight(&id)` 再把 `running` 传下去
- host/saves.rs::dispatch 增 `running`:为真时三个写操作**不 spawn、不碰盘**,
  直接回一条 `SavesFailureKind::GameRunning` 的结构化失败;读路径与「打开文件夹」不受影响
- dto:新增 `GameRunning` 类别(不折进 WriteFailed —— 那不是"写失败",是"我们主动没写")
- i18n:「游戏正在运行(先退出再操作)」中英成对 + 金表同步
- 用例:在飞时三条写命令盘上指纹不变/世界还在/备份落点没建,且回 GameRunning;
  反向用例钉住闸门没有把写操作一并关死;UI 侧断言三种动词的主句都不出现「写盘失败」且不画重试
- 拆分(400 行):host/saves.rs → host/saves/write.rs;两处测试各拆出 write 子模块
- SavesState 模块文档补「生命周期」表:draft / pending / confirm 谁写谁清、何时必须清,
  并把 confirm「取走即清」(连点两下不删两个)写成安全不变量
- 出图:ULL_SAVES_FIXTURE=running 一张(失败那句话的样子)
This commit is contained in:
AskaEth committed 2026-11-11 13:29:56 +08:00
1 parent c921b9c185
commit 8c67e4eca6
13 files changed
+889 -448

No files matched your search

@@ -91,6 +91,7 @@ pub fn view(instance: &str) -> unlucky_presenter::dto::SavesListView {
/// | `rename` | 行内重命名进行中(输入框 + 改名/取消) |
/// | `confirm` | 删除的二次确认模态框 |
/// | `backed-up` | 备份成功的回执那句话(带落点路径与大小) |
/// | `running` | 「游戏正在运行,先退出再操作」那条写失败(M247 的在飞闸门) |
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
/// 默认:列表 + 行操作区。
@@ -101,6 +102,8 @@ pub enum Mode {
Confirm,
/// 备份成功的回执。
BackedUp,
/// 写操作被「游戏在运行」挡下。
Running,
}
fn mode() -> Mode {
@@ -111,6 +114,7 @@ fn mode() -> Mode {
"rename" => Mode::Rename,
"confirm" => Mode::Confirm,
"backed-up" => Mode::BackedUp,
"running" => Mode::Running,
_ => Mode::Plain,
}
}
@@ -136,6 +140,17 @@ pub fn seed(app: &mut crate::app::state::App, instance: &str) {
};
match mode {
Mode::Rename => app.state.saves.start_rename(&dir),
// 在飞闸门那条失败(`host::saves::dispatch` 在 `running` 时直接回它)。
Mode::Running => app
.state
.saves
.failed(unlucky_presenter::dto::SavesFailureView {
instance: instance.to_string(),
file: dir,
op: Some(unlucky_presenter::dto::SavesFailureOp::Rename),
kind: unlucky_presenter::dto::SavesFailureKind::GameRunning,
detail: String::new(),
}),
Mode::Confirm => {
app.state.saves.ask_delete(&dir);
let (title, body, primary, secondary) =