docs(app-iced): M101 r3 —— 补「页外值 / 首帧取数」共享点,守门收窄,文档口径统一

r2 复审 P1:报告与代码文档的「仍然共享」清单只写了 3 项,漏了「读页外真值 / 首帧取数」这半边。
本轮按复审给的第一条路(文档/契约层面诚实交代,不动结构):

- `settings/mod.rs` 的「仍然共享」扩到 6 条,新增:
  4. 读页外真值 —— `States` 只有 appearance + groups,General 要显示 `Model.lang` 就得改
     `settings/state.rs` + `pages/mod.rs`(2 个共享文件)或在 `app/state.rs` 用真值初始化
     `Groups.language_appearance`(1 个);给出建议形状:一次性给 `States` 加 `model: &'a Model`。
  5. 首帧取数 / 异步发起 —— `Body` 返回不了 `Task`,组自己发不了首帧探测;两条路(`Boot` 分支派发
     1 个共享文件;或把 `Body` 返回类型加一条 `Task` 旁路,1 个文件一次性)与「不改的代价」都写明。
  6. 隐性代价:`Groups: Sync + Default`(`demo_groups` 的 `static LazyLock` 撑 harness),
     非 `Sync` 字段会让编译红在共享的 `settings/state.rs`;给了改法。
- `general.rs` 文档改成正确指引(消息加在本线的 `language_appearance.rs`,落点
  `app/update/settings/language_appearance.rs`,**不要**改 `pages/mod.rs` / `pages/message.rs` /
  `app/update/page_messages.rs`),并指向上面第 4、5 条。
- `tests/mod.rs` 的「接真控件改 3 个文件」订正为 2 个,并删掉对**已被删除**的
  `only_the_appearance_group_carries_a_real_control_body` 的点名;`appearance_section.rs` 的
  模块文档与断言消息同步订正(`body = Some(appearance::body)`)。
- 守门 `the_group_dispatch_has_no_catch_all_arm` 判据**结构化收窄**:用大括号深度计数只看
  `match message {` 本层(深度 1)的臂,嵌套 match 整段跳过、`|` 折行 or-pattern 续行放行;
  只有「本层里含 `=>` 且不以该组变体路径开头」才算兜底。新增两条守门自测:
  `the_guard_ignores_nested_matches_and_folded_or_patterns`(复审 P2-3 的假红探针)与
  `the_guard_still_catches_both_catch_all_shapes`(`_ =>` 与 `other =>` 仍要红);
  误伤类别如实写进测试 doc 的「抓不住什么」。
- P3:`java::Message::Ping` 的文档改成指向真实存在的绊线(`tests/java_section.rs` 的
  `the_java_group_is_still_demo_rows`);`demo_groups()` 文档写明 `Sync + Default` 的隐性要求。
This commit is contained in:
AskaEth committed 2026-11-03 22:48:05 +08:00
1 parent 029ece3f17
commit c166944c8e
7 files changed
+206 -55

No files matched your search

@@ -1,8 +1,13 @@
//! 设置页「General」分组:语言 / 主题 / 启动页 / 关闭按钮(演示行)。
//!
//! **这一组归 language+appearance 线**:语言那一行将来换真下拉(`pages::Message` 侧还需一条
//! 「设置项改动」的消息,见 `pages/mod.rs` 的 `Message`),改这里 + 那一处消息即可,
//! `settings/mod.rs` 的注册表不用动。
//! **这一组归 language+appearance 线**:语言那一行将来换真下拉时,消息加在本线的消息口
//! `settings/language_appearance.rs` 的 `Message` 里,落点是
//! `app/update/settings/language_appearance.rs`——**不要**去改 `pages/mod.rs` / `pages/message.rs` /
//! `app/update/page_messages.rs`(那三个正是 M101 收掉的共享锚点,改了就把本 mission 的目的掀掉);
//! `settings/mod.rs` 的注册表也不用动。
//!
//! 要显示**页外真值**(例如「当前语言」的唯一来源是 `pages::Model.lang`)或首帧就要取数时,
//! 那两条仍有代价——见 `settings/mod.rs` 文档「仍然共享」小节里的第 **4、5** 条。
//!
//! 分组元数据只有一份(这里的 [`GROUP`]):`settings/mod.rs` 的 `GROUPS` 直接引用它。
+2 -1
View File
@@ -37,7 +37,8 @@ pub struct State {}
pub enum Message {
/// **只有测试构造它**:M101 r2 留的「活的端到端证据」——外壳消息 →
/// `on_page` → `on_settings` → `on_settings_java` 这条路在运行时真的跑通,不是只靠编译期穷尽。
/// java 线接第一个真控件时把这一支换成真变体(`tests/java_section.rs` 会提醒)。
/// java 线接第一个真控件时把这一支换成真变体:`tests/java_section.rs` 的
/// `the_java_group_is_still_demo_rows` 就是那条会提醒你的绊线(它断言 `body.is_none()`)。
#[cfg(test)]
Ping,
}
+22 -1
View File
@@ -81,7 +81,7 @@
//! `Message::Page(pages::Message::Settings(GroupMessage::<线>(…)))` 回来——`Page` 这条既有通道就是
//! 异步结果的入口,`app::Message` / `app/update.rs` 都不用动。
//!
//! ### 仍然共享、本 mission 无法消除的点(四条线都可能碰到,只能靠 tower 串行合并)
//! ### 仍然共享的点(四条线都可能碰到,只能靠 tower 串行合并,或者由专人一次性做掉)
//!
//! 1. `crates/i18n/src/catalog/golden.rs` —— 每加一条文案 key 都要按声明顺序往金表里插一行
//! (`split_keeps_every_translation` 逐条比 key + 英/中译文)。**注意**:`catalog.rs` 的 `Key`
@@ -90,6 +90,27 @@
//! 2. 本文件的 [`GROUPS`] —— 只有 ai+about 线**上第 5 组**时加一行(页面上多一组本来就是整页的变化)。
//! 3. `tests/mod.rs` —— ai+about 线新建 `tests/ai_section.rs` 时要加一行 `mod ai_section;`
//! (测试文件不挂 `mod` 就不编译),以及「左列顺序」那条字面量断言会连带红,需要人确认。
//! 4. **读页外真值**(r2 复审 P1 补的,上一版漏了)—— [`States`] 只有 `appearance` + `groups` 两格,
//! 组正文读不到 `pages::Model`,也读不到 presenter 的设置快照。最典型的是 language+appearance 线:
//! 「当前语言」的唯一来源是 `Model.lang`(`pages/model.rs`),General 分组要显示真值就得让值进
//! `States`,那要改 `settings/state.rs` + `pages/mod.rs`(`render` 现在只拿 `model` 算 metrics,
//! **2 个共享文件**);或者反过来在 `app/state.rs` 用真值初始化 `Groups.language_appearance`
//! (**1 个共享文件**)。建议形状:**一次性**给 `States` 加一格 `model: &'a Model`(并让
//! `render` 把 `model` 传下去),此后四条线都不必再碰这两个文件——这是「谁先撞谁付一次钱」的
//! 那类代价,不是永久性的四路撞车。
//! 5. **首帧取数 / 异步发起**(同上,r2 复审 P1 补)—— [`Body`] 是
//! `fn(&Ui, States) -> Option<Element>`,**返回不了 `Task`**,而 `view` 是纯函数:一组想在自己
//! 的首帧就发起探测(java 的「探测本机 JVM」、download 的「测源可用性」)有两条路:
//! ①在 `app/update.rs` 的 `Message::Boot` 分支里补一次首帧派发(**1 个共享文件**);
//! ②把 `Body` 的返回类型改成 `Option<(Element, Task<Message>)>` 或 `Option<Task>` 旁路
//! (改本文件 + `form`,**1 个共享文件**,一次性)。不改的代价是:首屏显示演示值,等用户点一次
//! 才变真值——四条线都能接受,就不必动。
//! 6. **隐性代价:`Groups` 必须是 `Sync + Default`**(r2 复审 P3 指出)——
//! [`demo_groups`] 用 `static LazyLock<Groups>` 撑着 harness 路径,所以某个组的 `State` 里一旦
//! 放了 `Rc` 这类非 `Sync` 字段,编译会在**本模块**(`settings/state.rs`)红。选择 `LazyLock`
//! 是为了让 `page()` / `page_with_appearance()` 的返回 `Element<'a>` 借得到一个 `'static` 的默认
//! 状态;如果哪天必须放非 `Sync` 状态,改法是把默认状态改成「每个调用点自己造一个 `Groups`」
//! (harness 侧改两处,同样是**一次性**代价)。
// 四条填页线的文件要能被**各自那条落点**(`app/update/settings/<线>.rs`)与状态架子
// (`state::Groups`)命名,所以是 `pub(crate)`:最小面,不是 crate 公开 API。
@@ -73,6 +73,11 @@ pub struct States<'a> {
///
/// `'static` 是有意的:那两条入口不持有任何组状态,而返回的 `Element<'a>` 会借走这份 `Groups`;
/// 让它活得比调用方长,借用就不会把返回值钉在函数栈上。
///
/// **隐性代价(r2 复审 P3)**:`static` 要求 `Groups: Sync + Default`,于是某个组的 `State` 里一旦
/// 放了 `Rc` 这类非 `Sync` 字段,编译会红在**本文件**(共享)而不是那一组自己的文件。真需要
/// 非 `Sync` 状态时的改法:去掉这个 `static`,改成每个 harness 调用点自己造一份 `Groups`
/// (`pages/mod.rs` 的两个入口各一处,一次性代价),见 [`super`] 文档「仍然共享」第 6 条。
pub fn demo_groups() -> &'static Groups {
static DEMO: LazyLock<Groups> = LazyLock::new(Groups::default);
&DEMO
@@ -3,7 +3,8 @@
//!
//! **归 language+appearance 线**:这是设置页唯一接了真控件的一组,真控件实现全在
//! `pages/appearance/section.rs`;本文件只守「注册表这一侧的接线」。
//! 本组已经把 `body` 接上了,所以 `tests/mod.rs` 那条「只有一组有真控件」的绊线不用再动。
//! 本组已经把 `body` 接上了;`tests/mod.rs` 那条「只有一组有真控件」的**聚合绞线已在 M101 r2 删除**
//! (被四条 per-group 断言完全蕴含——代价是「唯一性」从此没人守,那段说明写在 `tests/mod.rs` 同处)。
use super::super::appearance;
use unlucky_design::icons::IconKind;
@@ -45,6 +46,7 @@ fn the_appearance_group_keeps_its_icon() {
fn the_appearance_group_keeps_a_real_control_body() {
assert!(
appearance::GROUP.body.is_some(),
"外观分组必须是挂了真控件的那一组(`body = Some(crate::pages::appearance::section)`)"
"外观分组必须是挂了真控件的那一组(`settings/appearance.rs` 的 `body: Some(appearance::body)`;\
具体渲染器由那一行源码 + 截图担保,函数指针相等性断言不了)"
);
}
@@ -12,10 +12,11 @@
//! 判据(M100 报告里正面回答的那条):**给 java 加一条新断言,改几个文件?** —— 1 个
//! (`tests/java_section.rs`)。本文件不动。
//!
//! 但「java 线**接真控件**那天改几个文件」是另一回事,答案是 **3 个**(M100 r1 复审 p3-3 订正,
//! 我最初只写成 2 个):`settings/java.rs`(写 body)+ `tests/java_section.rs` 的
//! `the_java_group_is_still_demo_rows`(它断言 `body.is_none()`,接上就红)+ 本文件末尾那条
//! `only_the_appearance_group_carries_a_real_control_body`。download / general 同型。
//! 但「java 线**接真控件**那天改几个文件」是另一回事,答案是 **2 个**(M101 r2 订正:
//! M100 之后曾是 3 个,第 3 处就是本文件末尾那条聚合绊线
//! `only_the_appearance_group_carries_a_real_control_body`,它被四条 per-group 断言完全蕴含、
//! 已在 M101 r2 删除):`settings/java.rs`(写 body)+ `tests/java_section.rs` 的
//! `the_java_group_is_still_demo_rows`(它断言 `body.is_none()`,接上就红)。download / general 同型。
//!
//! ## ai+about 线要额外改本文件的两处(M100 r1 复审 p3-4)
//!