docs(app-iced): 把「裸 button 不消费 forced」这句假话改掉 + 用法表补 --force-state(M285 r1 p2-1)

复审原话:harness.rs:43-45 那段「设置页外观区的滑块 / 文本框 / 裸 iced::button 都不消费
forced」**正是 M282/M284 放弃像素验收的原始理由**——M285 之后后半句已经是假的,留着它下一
个 agent 会再推一遍同一个死胡同。

- pages/harness.rs:参数清单补 --force-state=rest|hover|pressed;那段改成现状(裸 button 已
  经消费:外观 11 处 choice + 语言/主题 chip + 日志 action_button 走 settings::resolve_state;
  components::buttons::Button 三组走 forced_button;仍不消费的只剩滑块 / 文本框 / 色块),
  并明确「别把它改回去」。
- examples/pages_demo.rs:用法表补 --force-state / --appearance / --passthrough(此前
  --force-state=bogus 先报约束、却打出一张没有该参数的用法表)。
  实测:`--force-state=bogus` → exit 2,用法表里含 [--force-state=rest|hover|pressed]。

顺带 r1 p3-1(机制说法订正):forced_button 的文档写清真实优先级 —— Button::forced() 是
`self.state.or(没有消息 ⇒ Disabled)`,**state 在前**,所以 disabled 也能被 .state() 盖掉;
并加一条单测钉住实际结果(forced_button(Button::disabled(..), Some(Hovered)) ⇒ state:
Some(Hovered))。今天 7 个站点都写在启用分支里所以安全,但**别**把它套到 if..else 外面。

行数:settings/mod.rs 396 → 397(先算过余量,未破 400);harness.rs 321 → 330;
pages_demo.rs 37 → 39。判据 1 复验:重出 5 张 rest 图,md5 与基线逐张相同。
This commit is contained in:
AskaEth committed 2026-11-12 23:12:20 +08:00
1 parent e15bd741cd
commit 3255579bba
3 files changed
+31 -19

No files matched your search

+13 -4
View File
@@ -3,7 +3,8 @@
//! **刻意不接 `cli.rs`**:等 `cli.rs` 具备 `--screen` / `--window-width` 之后,这个 harness 应当
//! 删掉,改由正式启动参数出图。参数:`--screen=instances|download|settings|logs`、`--width`、
//! `--height`、`--theme` / `--screenshot-theme`(后者优先)、`--screenshot`、`--renderer`、
//! `--software-renderer`、`--scale-factor`、`--appearance=real|demo`、`--passthrough=on|off`。
//! `--software-renderer`、`--scale-factor`、`--appearance=real|demo`、`--passthrough=on|off`、
//! `--force-state=rest|hover|pressed`(M285:把控件钉到那一态出图,`rest` / 不给时截图通道默认就是它)。
//! 解析与默认值在 [`request`](M100 拆出去:本文件当时正好 400 行、零余量);没给 `--screenshot`
//! 就只是开窗看。
//!
@@ -40,9 +41,17 @@
//! 改成 900×600,落盘的就是 900×600 那张)。现在闸门在 [`should_request_capture`],日志里的
//! `落盘次数=` 必须恒为 1。
//!
//! **为什么不是给每个控件灌 forced**:设置页「外观」区的滑块 / 文本框 / 裸 `iced::button` 都不
//! 消费 forced,那样做要改十几个组件文件——为取证去动产品组件,代价与风险都不对,而且新产品
//! 组件随时会漏。
//! **forced 能到哪儿(M285 订正,原句已过时)**:`--force-state` 只改**画出来的**态,不改状态机。
//! 它经 `pages::Model.forced` → `settings::view(.., forced)` → `Group.body` 递到各控件:
//! **裸 `iced::button` 已经消费**——外观区 11 处 `choice` + 语言 / 主题两类 chip + 日志两颗
//! `action_button`,一律走 `settings::resolve_state`;`components::buttons::Button` 那三组
//! (update / ai / java)走 `settings::forced_button` 的 `.state(..)`。**仍然不消费的**:外观区的
//! 滑块 / 文本框 / 8 颗预设色块(`accent::swatch` 的描边闭包没有 forced 入口)——那几处要接,
//! 得先给对应的组件开口,不在本通道的账上。
//!
//! ⚠️ 上一段在本 mission 之前写的是「裸 `iced::button` 都不消费 forced」——**那句正是 M282/M284
//! 放弃像素验收的理由**,已经不成立:这四类的 hover / pressed 现在出得来像素证据(判据见
//! M285 的 `DevDocs/artifacts/forcedwire/`)。别把它再改回去。
//!
//! ```text
//! cargo run -p unlucky-app-iced --example pages_demo -- --screen=instances --width=1180 \
+15 -14
View File
@@ -178,11 +178,11 @@ pub(super) fn resolve_state(forced: Option<Interaction>, status: button::Status)
}
/// 把一个 `components::buttons::Button` 钉到 `forced` 那一态(`pages/instances.rs` 的 `forced_state`
/// 在设置页的这一份)。
/// 在设置页的这一份):`Some` ⇒ `.state(..)`,`None` ⇒ 原样返回。
///
/// **这是第二份,不是抽象**(M285):实例页的控件本身就是 `components::buttons::Button`(有
/// `state(Interaction)`),设置页的 `choice` / `chip` / `action_button` 是裸 `iced::button`(走
/// [`resolve_state`])。三次法则现在到 2;第三次真出现时落点是 `components/interaction/`,不是这里。
/// ⚠️ **优先级是 `state` 赢**(`Button::forced()` = `self.state.or(没有消息 ⇒ Disabled)`,`state` 在前)
/// ⇒ `forced_button(Button::disabled(..), Some(Hovered))` 会把禁用按钮**画成 hover**。今天 7 个站点都
/// 写在**启用分支里**所以安全,**别**套到 `if..else` 外面。第二份不是抽象:三次法则到 2。
pub(super) fn forced_button<'a, M>(
button: Button<'a, M>,
forced: Option<Interaction>,
@@ -352,16 +352,10 @@ fn section_columns<'a>(
mod resolve_tests {
use super::*;
/// `forced` 必须**覆盖**观测到的 status;不给 `forced` 时必须**退回**观测值(M285)。
///
/// 抓得住:把 `Some` 分支改成忽略 forced(直接 `Interaction::from_status(status)`)——第一条立刻红,
/// 而那正好等于「`--force-state` 在四个控件上没接线」。
/// 抓不住:forced 真的画进了 PNG(那是 `pages_demo --force-state=hover|pressed` 的像素判据)。
/// `forced_button` 只做一件事:`Some` 钉态、`None` 原样(M285)。
///
/// `Button::state` 是私有字段、没有 getter,所以断 **derived Debug** 里的那一格。
/// 抓得住:`Some` 分支被改成恒 `None`(第一条红);`None` 分支也顺手 `.state(..)`(第二条红)。
/// 抓不住:按钮真画成那色(那是出图的事)。
/// 两条语义各钉一次(M285):`forced_button` 的 `Some` 钉态 / `None` 原样(**禁用态也会被 `state`
/// 盖掉**——`Button::forced()` 里 `state` 在 disabled 默认之前,钉住实际行为,别让站点踩)、
/// `resolve_state` 的 `Some` 覆盖观测 status / `None` 退回观测值。`Button::state` 没有 getter,
/// 所以断 derived Debug 的那一格。抓不住:按钮真画成那色(出图负责)、站点传错 forced(只有像素)。
#[test]
fn forced_button_forces_only_when_asked() {
let plain = Button::new("x", ());
@@ -378,6 +372,13 @@ mod resolve_tests {
untouched.contains("state: None"),
"没给 forced 时必须原样返回:{untouched}"
);
// 禁用按钮同样会被钉态(`state` 覆盖 disabled 默认,类型层不挡):
let disabled: Button<'_, ()> =
forced_button(Button::disabled("x"), Some(Interaction::Hovered));
assert!(
format!("{disabled:?}").contains("state: Some(Hovered)"),
"state 在 disabled 默认之前 ⇒ 禁用按钮也会被画成 hover/pressed"
);
}
#[test]