fix(packui): M113 r1 复审整改 —— 2 处「界面说假话」+ 7 条必修 P2

复审 r1(p1-2items / fix-then-merge)逐条整改。**P2-6(core 的本地化契约)按塔台指示本轮不动**,
已登记为独立 mission 的债。

## P1

1. **「Save to」是死输入框**:`ExportRequestView` 的唯一构造点硬编码 `target: None`,用户敲的路径
   从不进请求。现在构造收成纯函数 `packui::model::export::request_of`(预检与确认共用同一份),
   `target` 由字段去空白后给出(空 ⇒ `None`,交给 host 给缺省)。
2. **取消导入的 toast 撒谎**:下载页那条说「安装已取消、实例未创建」,而导入会在后台跑完并
   **登记实例**。现在 `cancel_pack()` 回报「刚才有没有导入在跑」,`dispatch` 据此改发
   `PackEvent::Cancelled`(不再发 `InstallCancelled`),界面与提示改成
   「导入会在后台继续跑完 —— 本次结果作废;已写下的文件会留下」,并把这句话**留在屏上**
   (`ImportState.cancelled` + 面板位)。

## P2 必修(7 条)

3. **`Dismiss` 误伤下载页安装**(跨功能污染,最高优先):pack 原来共用 `CoreHost::install_epoch`,
   关一次导入面板会把下载页正在跑的安装静默丢掉。现在 pack 有自己的
   `PACK_EPOCH`(模块级 `AtomicU64`),`handle` 的 pack 分支只比它,`Dismiss`/取消只动它。
4. **中文实例名塌成 `pack`**:新增 `convert::dir_name` —— 名字全 ASCII 安全字符时保持原样
   (`Better MC` → `Better-MC`),一旦折过东西(中文/空格/符号)就补 `-<hash8>`(FNV-1a),
   保证两个中文名得到不同目录。
5. **失败回滚后空目录挡住重试**:`target_of` 对「已存在但空」的目录**复用**(非空才拒),
   并在失败回滚后清掉自己建的空目录;`failed_report` 的 `target` 语义改正
   (以前把包路径塞进「目标实例目录」)。
6. **新实例流程下「以包为准」必然失败**:`can_overwrite` 追加 `page.instance.is_some()` ——
   新建实例那条路上不再显示一个必然失败的按钮。
7. **阶段词打架且文档说修好了**:`progress.rs` 的模块文档改成与屏幕一致(粗/细两层 + 已知重叠),
   并写清彻底解决要靠「安装面板提成共享组件」那条 finding。
8. **导入侧标题计数与行数对不上**:被拒 component 有了自己的标题(`The client does not support
   these: {count}`);冲突标题改成中性的 `Conflicts: {count}`(逐条自带成因与处置)。
9. **预检「没进包」列表没有有界滚动**:13 条是真实形态。列表改成有界滚动(110px),
   并且**把动作按钮提到摘要下面**(`Write the pack` / `Back`)—— 明细再长也顶不掉按钮。
10. **导入成功后不刷新实例列表**:`handle` 的 pack 分支在收到 `Report` 时调 `refresh_instances()`
    (与 `InstallDone` 同一条),新实例不用等下一次无关的 `RefreshAll`。

## 证据

- 新增测试(都跑真管线/真函数):`the_save_to_field_really_reaches_the_request`、
  `pack_epoch_is_its_own_counter`、`chinese_instance_names_get_distinct_directories`、
  `a_failed_new_instance_directory_can_be_reused_for_retry`、
  `overwrite_is_offered_only_when_importing_into_an_existing_instance`、
  `cancelled_state_is_visible_and_says_the_import_keeps_running`。
- 出图重出 12 张(6 态 × 浅深),新增 `06-cancelled-*`;`04-export-*` 用 13 条 skipped 的真实形态。

门禁:`cargo fmt --all --check` 通过;`cargo clippy -p unlucky-app-iced -p unlucky-presenter
-p unlucky-i18n --all-targets -- -D warnings` EXIT=0;`cargo test -p unlucky-app-iced --lib`
507 passed;`cargo test -p unlucky-presenter --lib` 184 passed;`cargo test -p unlucky-i18n --lib`
24 passed;全部改动文件 ≤400 行。
This commit is contained in:
AskaEth committed 2026-11-05 04:06:42 +08:00
1 parent 27bbc74eaa
commit 99538e45b9
23 files changed
+474 -74

No files matched your search

+7
View File
@@ -57,6 +57,9 @@ pub enum PackEvent {
Exported(Box<ExportDoneView>),
/// 导出/预检在 host 侧失败(实例找不到、目录派生失败……一句人读原因)
ExportFailed(String),
/// 用户取消了导入:面板收回,但后台那次导入会跑完(core 没有取消令牌),
/// 所以留一位给界面说清「还在跑、结果作废」。
Cancelled,
/// 关掉所有面板
Cleared,
}
@@ -105,6 +108,10 @@ impl PackState {
self.export.pending = false;
self.export.failed = Some(reason);
}
PackEvent::Cancelled => {
self.import.progress = None;
self.import.cancelled = true;
}
PackEvent::Cleared => {
self.import = ImportState::default();
self.export = ExportState::default();
+3
View File
@@ -331,6 +331,9 @@ pub struct ProbeView {
pub struct ImportState {
/// 正在跑的进度(`None` = 没在跑)。
pub progress: Option<ImportProgressView>,
/// 用户点了取消(M113 r1):**面板收回但后台还在跑**,所以这一位要留在界面上——
/// 否则用户会以为「没建成」,回头再导一次就撞「directory already exists」。
pub cancelled: bool,
/// 最近一次的结果(`None` = 还没导过)。
pub report: Option<ImportReportView>,
/// 探测失败的具名诊断(有它就让用户看清「像 X 缺 Y」,而不是一句「无法识别」)。