fix(presenter): 自动发现实例按盘上痕迹选运行目录 + 空态用例补上分支/传参(M273 r1)

r1 复审的两条 P2。

## P2-1:外部实例不再一律 isolated

`versions/<id>/` 里出现 `mods` / `saves` / `config` / `options.txt` 任一 ⇒ `isolated`
(gameDir = `versions/<id>`);否则 ⇒ `shared`(gameDir = 游戏根 `.minecraft`)。
判据只 `exists()`,只读,不改盘;写盘仍然只在用户第一次改设置时(`ensure_profile`)。

- 判据收在 `host/game/discovered.rs::base_mode()`,签名 `(env, id, version_id)`:
  **有档案的实例返回全局 `instances.mode`**(D20 原口径,一字不变),自动发现的按痕迹判。
- 派生运行目录的五处(启动 / mods / saves / addon install / addon pack)从传
  `global_mode` 改成传 `base_mode` ⇒ 「读到的存档/模组在哪」与磁贴上的 ISOLATION 同一份判据。
- `isolation_of(env, id, version_id)` 多收一个版本 id:实例文件优先,缺省 `base_mode`。
  于是懒创建档案(只写 launch 段、isolation 仍为空)之后,磁贴不会从「共用目录」翻回「独立目录」。
- 磁贴/详情:自动发现的实例多一句「运行目录由盘上已有的文件决定」(i18n 中英);
  按启动时再弹一条说清**这一次**用哪一边(`pages::instances::discovered_run_toast`,
  同文案在屏不重复叠加)。
- `dto::DISCOVERED_ID_PREFIX` / `is_discovered_instance` 成为「自动发现」的公开判据
  (不给 `InstanceSummary` 加字段:那要改 24 处结构体字面量,跨 agent / cli)。

## P2-2:空态用例的覆盖面

r1 复审实测:把 `!block.rows.is_empty()` 改成 `block.rows.is_empty()`(0 实例时反而没按钮),
当时三条用例全绿。现在:

- `detail_empty` 拆成 `empty_not_selected` / `empty_no_instances` 两个函数,分支各自可断言;
- `the_cta_lives_only_in_the_no_instances_branch`:CTA 只在「一个都没有」那一支、
  「没选中」那一支必须是 `Vec::new()`,且分派必须写成 `if has_instances {`(取反即红);
- `the_page_asks_the_empty_state_with_the_negated_row_count`:两条调用点(宽窗 + 窄窗)
  都必须传 `!block.rows.is_empty()`,条数也钉住(多一条即红)。
- 两个变异已自证:传参取反 ⇒ 后一条红;CTA 挪支 ⇒ 前一条红(红原文见交付报告)。

## P3

- `java::target_major()` 改读并集快照:只有自动发现实例的用户原来会拿到默认 JDK17 而不是
  该版本要求的 JDK(`profiles.json` 是空的)。有档案时顺序与旧写法一致。
- `instances()` 全量扫仍在 UI 线程——本轮不动,刷新时机与代价写进报告(塔另开一条)。

## 结构

`host/game.rs` 394 → 286 行:自动发现那一簇(痕迹判据 / 扫出来的版本 / 合成视图)搬到
`host/game/discovered.rs`(138 行),只为守 400 行硬线;`game::base_mode` 仍是同一条路径。

## 门禁

app-iced lib 973 passed / presenter all-targets 386 passed / i18n 36 passed /
fmt 0 / clippy -D warnings 0。
This commit is contained in:
AskaEth committed 2026-11-12 14:21:30 +08:00
1 parent 5479f25068
commit c5009442c5
21 files changed
+494 -157

No files matched your search

+15
View File
@@ -26,3 +26,18 @@ pub use super::launch_failure::{LaunchFailureCause, LaunchFailureStage, LaunchFa
/// 实例标识:core::game 生成的稳定 id,app 视为不透明字符串。
pub type InstanceId = String;
/// **自动发现**实例的 id 前缀(M273):`versions/<id>` 里扫出来、没有档案的版本用
/// `ext-<hash>` 作实例 id。
///
/// 为什么让它是公开常量而不给 [`InstanceSummary`] 加字段:加字段要改 24 处结构体字面量
/// (横跨 presenter / agent / cli / app-iced),而「这个实例是不是自动发现的」在界面上的
/// 用途只有一处——详情页那句「为什么它按独立目录 / 共用目录跑」。前缀是**稳定契约**,
/// 由 `host::game` 的 `external_instance_id` 唯一生产(M274 多根时前缀不变,哈希里加根)。
pub const DISCOVERED_ID_PREFIX: &str = "ext-";
/// 这个实例是不是「从盘上自动发现」的(没有用户 / 向导建过的档案)。
#[must_use]
pub fn is_discovered_instance(id: &str) -> bool {
id.starts_with(DISCOVERED_ID_PREFIX)
}
+3 -2
View File
@@ -61,8 +61,9 @@ pub use install::{
InstallStage,
};
pub use instances::{
CreateRequest, InstanceId, InstanceRuntime, InstanceSummary, LaunchFailureCause,
LaunchFailureStage, LaunchFailureView, LaunchStage, LoaderInfo, LoaderKind, Progress,
is_discovered_instance, CreateRequest, InstanceId, InstanceRuntime, InstanceSummary,
LaunchFailureCause, LaunchFailureStage, LaunchFailureView, LaunchStage, LoaderInfo, LoaderKind,
Progress, DISCOVERED_ID_PREFIX,
};
pub use java::{JavaChainOutcome, JavaChainStep, JavaPageView, JavaRuntimeView, JavaVendorView};
pub use launch_args::{LaunchArgsPatch, LaunchArgsView};