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:
1 parent
27bbc74eaa
commit
99538e45b9
23 files changed
+474
-74
No files matched your search
@@ -32,7 +32,11 @@
|
||||
//! 2. 这一轮之后回来的事件因代次不符被 `handle` 丢掉;
|
||||
//! 3. **后台那次导入会继续跑到结束**——core 的 `ImportRequest` 没有取消令牌,进度回调也拿不到
|
||||
//! 控制流。所以「取消」的真实含义是「不再显示、结果作废」,不是「把它停手」。要真停必须给
|
||||
//! core 加 cancel token(已登记 finding,报告里同样划出来)。
|
||||
//! core 加 cancel token(已登记 finding,报告里同样划出来);
|
||||
//! 4. **提示语不能借用安装那条**(复审 r1 P1-2):下载页的取消 toast 写着「实例未创建」,
|
||||
//! 而导入会把实例**登记上**。所以 `cancel_pack()` 回一个「刚才有没有导入在跑」,
|
||||
//! `dispatch` 据此改发 [`crate::dto::PackEvent::Cancelled`],界面上换成
|
||||
//! 「导入会在后台继续跑完 —— 本次结果作废」这句。
|
||||
|
||||
mod convert;
|
||||
mod diagnose;
|
||||
@@ -43,7 +47,7 @@ mod import;
|
||||
mod tests;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use unlucky_core::addon::export::{read_instance_snapshot, InstanceSnapshot};
|
||||
@@ -59,6 +63,27 @@ use crate::dto::{
|
||||
use super::super::env::HostEnv;
|
||||
use super::super::job::{HostTask, JobSender};
|
||||
|
||||
/// **pack 自己的**任务代次(与安装那条计数器完全分开)。
|
||||
///
|
||||
/// 为什么必须分开(复审 r1 P2-7):`HostTask::Install` / `InstallDone` 按 `CoreHost::install_epoch`
|
||||
/// 丢包。如果 pack 也共用那个数,用户在内容页关一次导入面板就会把代次 +1 —— 下载页**正在装版本**
|
||||
/// 的进度与收尾会被静默丢弃(面板卡住、不刷实例、没有失败提示)。所以 pack 用自己的一条,
|
||||
/// `CoreHost` 的字段不在本 mission 的 scope,这条计数器就落在模块里。
|
||||
static PACK_EPOCH: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// 当前 pack 代次(`handle` 用它判断「这条事件是不是已经被取消/关面板作废了」)。
|
||||
pub(in crate::host) fn pack_epoch() -> u64 {
|
||||
PACK_EPOCH.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// 作废在飞的一切 pack 事件(取消 / 关面板都走它)。
|
||||
pub(in crate::host) fn bump_pack_epoch() -> u64 {
|
||||
PACK_EPOCH.fetch_add(1, Ordering::SeqCst).wrapping_add(1)
|
||||
}
|
||||
|
||||
/// 有一次导入正在跑吗(取消时要据此决定弹哪条提示)。
|
||||
static PACK_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// 正在跑的那次导入的取消标记(模块级槽:`CoreHost` 的字段不在本 mission 的 scope)。
|
||||
///
|
||||
/// 语义见模块文档「取消是界面级的」——它挡的是**事件**,不是管线。
|
||||
@@ -67,16 +92,28 @@ use super::super::job::{HostTask, JobSender};
|
||||
static PACK_CANCEL: Mutex<Option<Arc<AtomicBool>>> = Mutex::new(None);
|
||||
|
||||
/// 取消正在跑的导入(`CancelInstall` 与「关面板」都走它;没在跑时是 no-op)。
|
||||
pub(in crate::host) fn cancel_pack() {
|
||||
///
|
||||
/// 返回值 = 「刚才确实有一次导入在跑」。调用方据此决定提示语:
|
||||
/// **导入的取消不是 install 的取消**(后台会跑完并登记实例,见模块文档),所以不能复用
|
||||
/// 下载页那条「安装已取消、实例未创建」的提示。
|
||||
pub(in crate::host) fn cancel_pack() -> bool {
|
||||
if let Ok(slot) = PACK_CANCEL.lock() {
|
||||
if let Some(cancel) = slot.as_ref() {
|
||||
cancel.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
cancel.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
bump_pack_epoch();
|
||||
PACK_RUNNING.swap(false, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// 这次导入跑完了(正常收尾也走它):`PACK_RUNNING` 归零,之后 `cancel_pack()` 会回 false。
|
||||
pub(super) fn finished() {
|
||||
PACK_RUNNING.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// 新一次导入的取消标记(同时把它挂进模块级槽,供 [`cancel_pack`] 用)。
|
||||
fn fresh_cancel() -> Arc<AtomicBool> {
|
||||
PACK_RUNNING.store(true, Ordering::SeqCst);
|
||||
let cancel = Arc::new(AtomicBool::new(false));
|
||||
if let Ok(mut slot) = PACK_CANCEL.lock() {
|
||||
*slot = Some(Arc::clone(&cancel));
|
||||
@@ -87,7 +124,7 @@ fn fresh_cancel() -> Arc<AtomicBool> {
|
||||
/// `AppCommand::Pack` 的落点(M113)。
|
||||
impl super::super::CoreHost {
|
||||
pub(crate) fn pack(&mut self, command: PackCommand) {
|
||||
let epoch = self.install_epoch;
|
||||
let epoch = pack_epoch();
|
||||
match command {
|
||||
PackCommand::Import(request) => {
|
||||
let cancel = fresh_cancel();
|
||||
@@ -109,9 +146,8 @@ impl super::super::CoreHost {
|
||||
export::spawn_write(self.env.clone(), self.tx.clone(), *request, epoch);
|
||||
}
|
||||
PackCommand::Dismiss => {
|
||||
// 只作废 pack 自己的代次:**不碰** `install_epoch`(那是下载页安装的命门)
|
||||
cancel_pack();
|
||||
// 代次 +1:这次之后回来的旧事件一律作废
|
||||
self.install_epoch = self.install_epoch.wrapping_add(1);
|
||||
self.send(CoreEvent::Pack(Box::new(PackEvent::Cleared)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user