fix(settings): 改口逐实例覆盖的事实陈述,并把第三条守卫换成行为断言

复审 r1 的两条 P2:

P2-1(`settings/mod.rs` 的 `form` 文档):原先写「『逐实例覆盖』既没有数据源也没有
消息落点」——这是错的。逐条核过:真控件在实例详情页的「启动参数」区(M157
`pages/instances/launch_args.rs`,由 `pages/instances/detail.rs` 渲染),落盘走
`app/update/instances.rs` 的 `AppCommand::set_launch_args` → presenter
`host/launch_args.rs` 的 `resolved`/`apply`(写回 `config/instances/<id>.json`),
回落与落位在 core 的 `game/launch_overrides.rs` / `launch/overrides.rs`。真正无人产出
的只有 UI 变体 `Action::AddOverride`。改口后的事实:它是**放错了地方**,不是没接。

P2-2(守卫):删掉按名字扫源码的两条(`empty_state`/`overrides` 子串、`instance` 子串)——
前者的假警报会挡住「某组确实需要自己的空态」,后者抓的根本不是它声明的不变量(真正让实例
选择进不来的是 `settings::view` 的签名),且日后一句正当英文注释就能把它弄红。换成:
- 判据 ①(保留)守**收尾**:`form()` 里 `stack = stack.push(` 恰好一处、且以
  `frame::scroll(ui, stack)` 收尾(整段重写让计数变 0 也红);
- 判据 ②(新增,行为断言)守**生产**:把每组一列的循环抽成 `section_columns()`,
  测试真调它数返回值——窄窗 == `GROUPS.len()`,单选各组 == 1,换任何名字都躲不掉。
文档里的「抓不住」段也不再指向静态图(静态图只能证明「现在没有」)。

`form` 的拼装形状没变(循环 + `frame::scroll` 收尾),分组表、分发顺序、正文内容一字未动。
This commit is contained in:
AskaEth committed 2026-11-08 19:54:38 +08:00
1 parent 7fdebba961
commit e5a4b9bbd1
2 files changed
+117 -56

No files matched your search

+36 -14
View File
@@ -304,20 +304,19 @@ pub fn view<'a>(
// --------------------------------------------------------------- 内容分发
/// 表单列(正文列宽 ≤424)。`only = Some(i)` 只渲染一组(supporting-pane),
/// `None` 渲染全部(窄窗:分组导航退化成按组堆叠)。
/// 表单列(正文列宽 ≤424)。`only = Some(i)` 只渲染一组(supporting-pane),`None` 渲染全部(窄窗按组堆叠)。
///
/// ## M196:正文到最后一组就结束,尾部**什么**都不许挂
///
/// 这里原先在循环之后无条件叠了一张「没有逐实例覆盖」的空态卡(M84 骨架期的占位,M99 纯搬移时
/// 原样留着),于是**每一组**下面都长出同一张卡:用户在 M174 的助手页与 M175 的 Java 页都看到了它
/// (两张图 1180×1100、卡片位置完全一致、说明被窗口下沿截断)。设置页永远有内容(总有一组被选中),
/// 「逐实例覆盖」既没有数据源也没有消息落点(全仓没有它的渲染点与处理器),所以它不是本页的空态:
/// **删掉,不挪给任何一组**。守门见 `frame.rs` 末尾的三条用例。
/// 这里原先在循环之后无条件叠了一张「没有逐实例覆盖」的空态卡(M84 骨架期的占位,M99 纯搬移时原样
/// 留着),于是**每一组**下面都长出同一张卡(用户在 M174 的助手页与 M175 的 Java 页都看到了它:两张图
/// 1180×1100、位置一致、说明被窗口下沿截断)。**删掉,不挪给任何一组。**
///
/// **分发点**:一组的正文由它自己文件里的 `GROUP.body` 决定——`Some(f)` 让 `f` 自己去
/// [`States`] 里取它要的数据(`None` 返回值 ⇒ 退回演示行),`None` 直接画演示行
/// ([`frame::demo_card`])。这里不出现任何一个分组名,也不比较下标,更不认识任何一组的字段。
/// 它不是「还没接的活」,是**放错了地方**:逐实例覆盖(内存 / Java 运行时 / JVM 参数)的真控件在
/// **实例详情页**的「启动参数」区(M157 `pages/instances/launch_args.rs` → `AppCommand::set_launch_args`
/// → presenter `host/launch_args.rs` 的 `resolved`/`apply`,写回 `config/instances/<id>.json`;回落与
/// 落位在 core 的 `game/launch_overrides.rs` / `launch/overrides.rs`)。设置页拿不到那份快照,也不该
/// 重复一份([`view`] 的形参里没有 `Model`);真正无人产出的是 UI 变体 `Action::AddOverride`(死消息)。
fn form<'a>(
ui: &'a Ui,
only: Option<usize>,
@@ -325,9 +324,31 @@ fn form<'a>(
language: Language,
update: &'a Update,
) -> Element<'a, Message> {
let catalog = language.effective.catalog();
let mut stack = column![].spacing(ui.space(Space::X6)).width(Length::Fill);
for section in section_columns(ui, only, states, language, update) {
stack = stack.push(section);
}
frame::scroll(ui, stack)
}
/// 正文里**每组一件**的列(该组标题 + 正文),顺序 = [`GROUPS`] 顺序,只含被选中的那些。
///
/// **分发点**:正文由 `GROUP.body` 决定——`Some(f)` 自己去 [`States`] 取数(`None` 退回演示行);
/// 这里不出现任何分组名、不比较下标、也不认识任何一组的字段。抽成独立函数是为了能被**行为断言**
/// 量到(`frame.rs` 的 `the_form_holds_exactly_one_column_per_group` 数它的列数,换名字也红);
/// 收尾留在 [`form`],由 push 计数守住——两半合起来 =「正文恰好这些列,没有尾巴」。
fn section_columns<'a>(
ui: &'a Ui,
only: Option<usize>,
states: States<'a>,
language: Language,
update: &'a Update,
) -> Vec<Element<'a, Message>> {
let catalog = language.effective.catalog();
let mut columns = Vec::new();
for (index, group) in GROUPS.iter().enumerate() {
if only.is_some_and(|selected| selected != index) {
continue;
@@ -354,7 +375,7 @@ fn form<'a>(
.unwrap_or_else(|| frame::demo_card(ui, group.rows))
};
stack = stack.push(
columns.push(
column![
frame::text_role(
ui,
@@ -369,9 +390,10 @@ fn form<'a>(
card,
]
.spacing(ui.space(Space::X2))
.width(Length::Fill),
.width(Length::Fill)
.into(),
);
}
frame::scroll(ui, stack)
columns
}