chore(presenter,app-iced): 复审 r2 三项(文档对齐 / 拆 dialog.rs / 收窄关闭路径)

1. 文档自相矛盾(复审 r2 第 1 项):`SavesState::apply` 的文档还写着「无论成败都清『在飞』与
   『确认中』」——改完之后这是假的,而且与模块文档「安全判据第 3 条」互相打脸。现在逐支写清:
   `Ok` 清 draft/pending、`confirm` 只在被确认的世界已不在快照里时清并登记 `closed_confirm`;
   `Err` 只清 pending。生命周期表里的 `dismissed` 改成真字段名 `closed_confirm`(消费点是
   `app/state/refresh.rs::settle_saves_confirm`)。

2. 拆 `app/update/dialog.rs`(复审裁决):397 行里 262 行是内联 `mod tests`,而它装的是
   主按钮 / Esc / 次按钮三条不可逆分支。照仓内先例(`app/dialog.rs` + `app/dialog/tests.rs`)
   把测试体搬到 `app/update/dialog/tests.rs`,**纯搬移**:`dialog.rs` 397 → 138,
   `dialog/tests.rs` 262;从 `6c87e2a` 的 `dialog.rs` 里抽出 `mod tests` 去一层缩进后与
   新文件**逐行比对 0 处差异**(262 = 262)。

3. 收窄关闭路径(复审 r2 加固):`SavesState::close_confirm(current)` 只在 `current` 等于登记的
   那个框时才清;`clear_confirm()` 保持无条件(换实例/复位用)。`escape()` 与 `Secondary|Dismiss`
   改成传当前框 id。新用例 `closing_another_dialog_leaves_our_target_alone`(别人关框不清我们的
   目标;自己关框才清)。
This commit is contained in:
AskaEth committed 2026-11-11 14:25:26 +08:00
1 parent 6c87e2ad8a
commit 32384b952e
5 files changed
+316 -271

No files matched your search

+9 -2
View File
@@ -17,7 +17,7 @@
//! |---|---|---|---|
//! | `draft` | `start_rename`(预填当前磁盘名)、`edit_rename`(每敲一个字) | `cancel_rename` / `take_rename`(提交时取走)/ `mark_pending`(请求已发出,草稿没用了)/ `bind` / `changed` | **换实例**、**请求发出**、**快照回来**:三者之后草稿都属于"上一次对话",留着会让「改名」按钮改到一个已经在别的实例上的名字 |
//! | `pending` | `mark_pending`(发请求之前,一跳都不等) | `changed`(成功)/ `failed`(失败也要清——**按钮不能永远灰着**,用户要能直接再点一次)/ `bind` | **任何一次后台往返收尾**、**换实例** |
//! | `confirm` | `ask_delete(dialog, dir)`(app 层先 `dialogs.push` 拿到 id 再登记) | [`SavesState::take_confirm(current)`](SavesState::take_confirm)(**只核销同一个框、取走即清**)/ [`SavesState::clear_confirm`](次按钮 / 遮罩 / `Esc` / 换实例)/ [`SavesState::changed`](**只在被确认的世界已从快照里消失时**) | **点主按钮那一刻**、**取消/关闭模态框**、**换实例**、**那个世界没了**(`dismissed` 会一并收框) |
//! | `confirm` | `ask_delete(dialog, dir)`(app 层先 `dialogs.push` 拿到 id 再登记) | [`SavesState::take_confirm(current)`](SavesState::take_confirm)(**只核销同一个框、取走即清**)/ [`SavesState::clear_confirm`](次按钮 / 遮罩 / `Esc` / 换实例)/ [`SavesState::changed`](**只在被确认的世界已从快照里消失时**) | **点主按钮那一刻**、**取消/关闭模态框**、**换实例**、**那个世界没了**(`closed_confirm` 会一并收框) |
//! | `closed_confirm` | `changed`(世界从快照里消失时登记作废的那个框) | app 层 `refresh_view` → `settle_saves_confirm`(收框后取走)/ `ask_delete` / `clear_confirm` / `bind` | **同一拍或下一拍**:留着会让"世界已经不在了"的确认框一直挡在屏幕中央 |
//!
//! ### `confirm` 是**绑定到具体对话框**的一条安全不变量
@@ -145,7 +145,14 @@ impl SavesState {
/// 一条结果落地(`Ok` = 快照整体替换,`Err` = 记一条具名失败;迟到/串台的丢掉)。
///
/// **无论成败都清「在飞」与「确认中」**:这是一次后台往返的收尾,操作已经不在飞了。
/// **成败对三样交互态的处理不一样**(复审 r1 P2-1;与模块文档的安全判据第 3 条同源):
///
/// | 支 | `draft` / `pending` | `confirm` |
/// |---|---|---|
/// | `Ok`(快照) | 清(这一拍写操作收尾了) | **只在「被确认的那个世界已不在快照里」时**清,并登记 [`SavesState::closed_confirm`] 让 app 收框 |
/// | `Err`(失败) | 只清 `pending`(按钮不能永远灰着) | **不动**:一条读盘失败不该把用户刚点出来的确认框变成"点了没反应" |
///
/// 换言之「无论成败都清确认中」是**旧的**说法,已经不成立:无条件清会让主按钮静默空操作。
pub fn apply(&mut self, result: Result<SavesListView, SavesFailureView>) {
match result {
Ok(view) => self.changed(view),
@@ -60,7 +60,17 @@ impl SavesState {
}
}
/// 确认框被取消/关掉(次按钮、遮罩、`Esc` 三条路径都走它):清掉目标。
/// **某个**确认框被取消/关掉(次按钮、遮罩、`Esc` 三条路径都走它):**只清它自己那个目标**。
///
/// 为什么不无条件清(复审 r2 的加固):`clear_confirm` 不认框,理论上够得着"清掉别人的删除目标"
/// ——今天三条关闭路径关的都是当前框,够不着,但把判据写死在这里比依赖调用顺序稳。
pub fn close_confirm(&mut self, current: DialogId) {
if self.confirm_dialog() == Some(current) {
self.clear_confirm();
}
}
/// 清掉目标(**无条件**):换实例 / 复位时用——那时"哪个框"已经不重要了。
pub fn clear_confirm(&mut self) {
self.confirm = None;
self.closed_confirm = None;
+25
View File
@@ -138,6 +138,31 @@ fn the_delete_confirmation_hands_out_once() {
assert!(saves.take_confirm(8).is_none(), "取消之后还留着目标");
}
/// **复审 r2 加固**:别人那个框被关掉时,不该清掉我们的删除目标。
///
/// `close_confirm(current)` 只在 `current` 等于登记的那个框时才清;`clear_confirm()` 仍然无条件
/// (换实例复位时用——那时"哪个框"已经不重要了)。
#[test]
fn closing_another_dialog_leaves_our_target_alone() {
let mut saves = SavesState::default();
saves.bind(Some("a"));
saves.ask_delete(7, "Doomed");
saves.close_confirm(99);
assert_eq!(
saves.take_confirm(7).as_deref(),
Some("Doomed"),
"别的框关掉时把我们的目标清掉了"
);
saves.ask_delete(8, "Other");
saves.close_confirm(8);
assert!(
saves.take_confirm(8).is_none(),
"自己的框关掉了,目标还在(下一次点确定会删上一个)"
);
}
/// **P1-1 的核心**:目标只属于登记它的那个框。别的框点主按钮**拿不走**它。
///
/// 事故形态:`Esc` 关掉删除确认框(某条关闭路径漏清)→ 崩溃卡片弹出来 →