feat(content): 安装失败的两档分组与「只重试这些」(M191)

M186 把「可只重试失败项」算到了 core/presenter(DependencyPlan::retry_request()),
但 DTO 没字段、界面没按钮,用户拿不到。这一轮补最后一公里:

- DTO:ContentFailureKind 加 Incompatible 一档(不再借 Unsupported,与「缺密钥」分开);
  InstallProgressView 加 retry: Vec<InstallFailureView>(project + 档位 + core 原话),
  档位是专门的 InstallFailureKind(Incompatible / Unreachable,穷尽映射 core 的 FailureKind);
  ContentInstallRequest 加 retry: bool(重试路径唯一读的字段是 instance)。
- presenter:plan.rs 把上一轮失败的计划按 host+实例记住(与 addon/pack.rs 同一手法,
  host/mod.rs 不在 scope);retry_request()/retry_plan() 就是「只重试这些」——
  调 core 的 retry_request()、只请求失败的那几个项目,present 重新扫目录。
  install.rs 的重试入口没有记录时回一条失败,不退化成整单重装;成功则 forget + 清名单。
- 界面:内容库页插入按档分组的失败面板(两段文案、各自条目)+「只重试这 N 个」实心按钮;
  判据只有一条且在模型里(stage==Failed && 名单非空),全成功时连同按钮一起不渲染。
- i18n:新增 6 条中英文案(不兼容 / 取不到 / 只重试这 N 个 / 标题 / 提示 / 关闭),金表同步。

守门(每条都做过变异验证,见报告):
① 把 retry_request() 换成根项目请求 ⇒ retrying_asks_for_the_failed_projects_only 红(roots 变 ["sodium"]);
② 两档合并 ⇒ the_retry_list_keeps_the_two_tiers_apart / the_two_tiers_are_two_groups_with_two_labels 红;
③ 恒渲染 ⇒ a_fully_successful_install_has_no_retry_surface_at_all 红。

数据包(datapack)本轮仍未做:ContentKind 没有这一项,界面上也没有半成品入口。
This commit is contained in:
AskaEth committed 2026-11-09 05:06:04 +08:00
1 parent 5505ea0f91
commit 5a52f38f35
24 files changed
+1072 -24

No files matched your search

+10
View File
@@ -284,6 +284,16 @@ pub struct ContentInstallRequest {
pub game_version: String,
/// 目标实例的加载器(选文件用)
pub loader: LoaderKind,
/// **只重试上次失败的那些项目**(M191)。
///
/// `true` 时 host 不再解根项目:它拿上一次装入留下的依赖计划调
/// `DependencyPlan::retry_request()`,只重解失败项(`present` 重新扫一遍目录),于是
/// 「已装好的不重下、没失败的不重发」。这种模式下本结构里**只有 `instance` 会被读**——
/// 过滤条件与落盘目录都来自那份计划。
///
/// 没有可重试的计划时(换进程 / 那一次是别的实例)一律回一条失败,**不**退化成「整单重装」:
/// 那正是本字段要避免的事。
pub retry: bool,
}
/// 内容库页的状态(M109):**检索条件 + 最近一批结果 + 请求态**。