fix(content): 不兼容档在失败路径上取项目名 + 重试轮不再贴错根的标题 (M191 r2)

复审 r2 两条 p2,都修了,并按复审给的口径验过红。

**P2-1(续):不兼容档不再只有裸 id**
`FailureKind::Incompatible` 的项目**确实存在**(站点只是没有匹配目标版本/加载器的条目),
所以失败路径上值得现取一次名字——`plan::names_for_incompatible` 按失败项去重、一项一次 GET
(`Modrinth::project`),`fetch_title` 把任何失败都折成 `None`:
**拿名字失败不许影响安装结果**,取不到 / 拿到空标题就退回 id。只有这一档发请求;
「取不到」档 404 那部分项目根本不存在,再发一次只会再吃 404。

**P2-2(新):重试轮把根的标题贴到了前置头上**
根因是判「这条是不是根项目自己失败」用了 `plan.request.roots.first()`,而重试那一轮的 plan
来自 `retry_request()`,它的 `roots` 就是上一轮的失败名单——第一个是前置。
现在判据改成 **id 比对**(`Root{id,title}`,id 由 `remember` 一起记下来):
`label_of` 只在这条失败的 id 真的等于原始根 id 时才用根的标题。复审给的那个形状
(roots=["P7dR8mSH","1eAoo2KR"] + root_title="Sodium")现在画 `P7dR8mSH`,不再是
`Sodium (P7dR8mSH)`。

**守门(每条都真跑红过,且在拆完模块的最终代码上复跑)**
- 去掉不兼容档的名字来源 ⇒ `an_incompatible_item_shows_the_project_name_once_it_is_known` 红
  (`left: "P7dR8mSH"` / `right: "Entity Culling (P7dR8mSH)"`)。
- `label_of` 换回 `roots.first()` ⇒ `the_retry_round_does_not_paste_the_root_title_onto_a_dependency` 红
  (`left: ["Sodium (P7dR8mSH)","1eAoo2KR"]`,正是复审实测的那一行)。
- 取名字不降级(`expect`)⇒ `a_failed_name_lookup_falls_back_to_the_id_without_breaking_anything` 红。

**行数**:`plan.rs` 加完这套到 414 行、`install.rs` 到 403 行,都越过 400 硬线 ⇒
把「名字从哪来」整块(`Root`/`Names`/`names_for_incompatible`/`label_of`/`fetch_title`)
纯搬移到 `plan/labels.rs`(111 行):`plan.rs` 336、`install.rs` 388。
This commit is contained in:
AskaEth committed 2026-11-09 05:06:06 +08:00
1 parent 961ff453a0
commit 26e7b7333e
7 files changed
+313 -77

No files matched your search

+25 -12
View File
@@ -161,14 +161,14 @@ fn run(
// 「只重试这些」:输入是上一轮那份计划,**不**重新发起根项目安装。
// 没有记录时(换了实例 / 进程重启过)回一条失败,而不是退化成「整单重装」。
if request.retry {
let Some((target_dir, title, previous)) = plan::remembered(env, &request.instance) else {
let Some(saved) = plan::remembered(env, &request.instance) else {
return Err(local(RETRY_LOST));
};
// 面板头继续显示那次的显示名(不是项目 id:r1 复审 N5)
progress.version = title.clone();
let present = plan::present_files(&target_dir);
progress.version = saved.title.clone();
let present = plan::present_files(&saved.dir);
let core_engine = super::super::engine::engine(env);
let dependency_plan = plan::retry_plan(&previous, present, |retry| {
let dependency_plan = plan::retry_plan(&saved.plan, present, |retry| {
let transport = EngineTransport::new(&core_engine);
let client = Modrinth::new(&transport);
block_on(resolve(&client, retry))
@@ -178,8 +178,11 @@ fn run(
request,
dependency_plan,
Placement {
dir: &target_dir,
title: &title,
dir: &saved.dir,
root: plan::Root {
id: &saved.root,
title: &saved.title,
},
},
cancel,
progress,
@@ -221,7 +224,10 @@ fn run(
dependency_plan,
Placement {
dir: &target_dir,
title: &request.title,
root: plan::Root {
id: &request.project,
title: &request.title,
},
},
cancel,
progress,
@@ -230,12 +236,15 @@ fn run(
)
}
/// 「装到哪、那次叫什么」——这两样总是一起出现,捆成一个参数(否则 `collect` 顶破 8 个)。
/// 「装到哪 + 那次装入的根是谁」——总是一起出现,捆成一个参数(否则 `collect` 顶破 8 个)。
struct Placement<'a> {
/// 落盘目录(`mods/` / `resourcepacks/` / `shaderpacks/`)
dir: &'a Path,
/// 那次装入的显示名(失败名单里根项目的可读名就是它)
title: &'a str,
/// 当初那次装入的根项目(id + 显示名);重试轮里由 `plan::remembered` 提供
///
/// **必须**带 id:重试那一轮 `plan.request.roots` 是上一轮的失败名单,不能拿它判「是不是根」
/// (r2 复审 P2-2 就是这条:第一前置被贴上了根的标题)。
root: plan::Root<'a>,
}
/// 解析之后**两条入口共用**的尾巴:进度 → 逐个下载 → 收口。
@@ -293,12 +302,16 @@ fn collect(
// 解析与下载两段的失败在这里一起收口:两档分成两句话(不合并成「下载失败」),
// 并列出「可只重试这 N 个」。
if let Some(failure) = plan::failure_of_plan(&dependency_plan, installed.len()) {
progress.retry = plan::retry_items(&dependency_plan, placement.title);
// 「不兼容」那一档项目**存在**(站点只是没有匹配目标版本/加载器的条目),所以在这里
// 现取一次名字——只在失败路径上发、一个项目一次 GET,取不到就退回 id(见 `names_for_incompatible`)。
let names = plan::names_for_incompatible(&dependency_plan, |id| plan::fetch_title(env, id));
progress.retry = plan::retry_items(&dependency_plan, placement.root, &names);
plan::remember(
env,
&request.instance,
placement.dir,
placement.title,
placement.root.id,
placement.root.title,
&dependency_plan,
);
return Err(failure);