test(app-iced): M102 r2 护栏改成「抓得住页面换摆法」——块偏移/行距/散文接线全部有源码结构断言

r1 复审的两条 P1 都是假护栏:把修复原样回滚(A:frame::body 换回居中 424;
C:内缩 X2→X1;D:行距加回 4px)后测试仍全绿,因为断言只喂 frame_metrics() 与常量重算,
既不观察 frame::body,也不引用页面常量。本轮把「摆法」做成可断言的东西:

- 新增 `pages/layout/block.rs`:body_width(pane 宽封顶 1440)、body_offset(恒 0 = 贴左)、
  prose_width(= metrics.column ≤424)三个纯量 + 单测。frame::body / frame::prose 从它们建盒,
  frame::body_width 改成 pub use(不另算一份)。
- 新增 `pages/layout/source.rs`(cfg(test)):把某个函数的实现文本切出来。iced 的 Element
  没有几何内省,量不到「块摆在哪」,只能断言「照哪个数建的」;仓里已有 no_hardcoded_tokens
  扫源码、download/tests 用 include_str! 扫 key 的先例。
- 新护栏(每条都做过变异实测,见报告):
  · frame_body_is_built_from_the_block_quantities(A 变异红)
  · frame_prose_is_built_from_the_prose_width(E 变异红)
  · list_column_spaces_rows_only_from_the_shared_constant(D 变异红)
  · instances 几何断言改用页面真源 LIST_BOTTOM_INSET.dp() / ROW_SPACING(C 变异红)
  · the_detail_note_goes_through_the_prose_box(散文接线)
- P2:wrap::strip 的「会删原文自带 U+200B」写成限界 + 一条断言;docs 口径从 layout.rs
  挪到 layout/block.rs(layout.rs 399 → 389)。

零像素变化:改后 9 张图与 r1 的 m102-after 逐张 cmp 完全相同;2560×760 新出三张证明块宽封顶
1440(112..1551)且仍贴左。
This commit is contained in:
AskaEth committed 2026-11-04 19:53:41 +08:00
1 parent 89d98184c1
commit 824bb36f8e
11 files changed
+469 -67

No files matched your search

+19 -18
View File
@@ -12,13 +12,17 @@
//! 页头带与内容区共用同一个 `gutter`(`Padding::from([0.0, metrics.gutter])` / `padding(gutter)`),
//! 所以页头标题的左缘 = `rail 88 + gutter 24 = 112`。正文块必须从**同一条线**起:
//!
//! - 单列页用 [`body`](贴左 + 吃满 `layout::available`);
//! - 成段散文再套一层 [`prose`](宽度 ≤424,保证一行 ≤72 字符);
//! - 单列页用 [`body`](贴左 + 宽 `layout::block::body_width` = pane 宽,封顶 1440);
//! - 成段散文再套一层 [`prose`](宽 `layout::block::prose_width` ≤424,保证一行 ≤72 字符);
//! - 两列页的正文在 [`panes`] 的主列里,左缘 = `112 + 列表列 336 + 间隙 24 = 472`(有据的分栏,不是错位)。
//!
//! 旧 [`single_column`] 把 424 的块**居中**摆进 1044,于是左缘 422、标题 112——视觉复审的 ① 就是这个。
//! 它保留给设置页(`pages/settings/**`,几何归设置页线)。
//!
//! **这三个量都在 [`super::layout::block`](纯函数 + 单测)**;[`body`] / [`prose`] 只是把
//! 「从哪个数建盒」写死在一处,`pages/tests.rs` 有两条**源码结构断言**盯着这两个盒子
//! (iced 的 Element 没有几何内省,量不到摆法,只能断言「照哪个数建的」)。
//!
//! ## 判据落点(`research/12b`)
//!
//! | 部件 | 参考 | 落点 |
@@ -50,7 +54,7 @@ use unlucky_design::typography::TypeRole;
use crate::theme::Ui;
use super::layout::{self, FrameMetrics, PageLayout};
use super::layout::{self, block, FrameMetrics};
use super::Message;
/// 页头操作区之间的间隔(`Space::X2`)。
@@ -162,22 +166,18 @@ pub fn single_column<'a>(
centered(metrics.column, content)
}
/// **页面块宽度**:单列版面 = 整条可用宽度;两列版面 = 主列 pane 宽度。
/// **页面块宽度**:pane 宽(单列 = 可用宽度 / 两列 = 主列 pane)封顶 1440。
///
/// 这是「表格 / 卡片 / 列表能有多宽」的答案,与 [`FrameMetrics::column`](一句话能有多宽,
/// ≤424)是**两个量**。M102 的根因就是把后者当成了前者:424 的块在 1044 里居中,
/// 于是正文左缘比页头标题右移了 310px(1180 窗宽下)。
pub fn body_width(metrics: FrameMetrics) -> f32 {
match metrics.layout {
PageLayout::SingleColumn => metrics.available,
PageLayout::SupportingPane | PageLayout::ListDetail => metrics.main_pane,
}
}
/// 真源在 [`super::layout::block::body_width`](纯量 + 单测);这里只是转发,**不另算一份**。
/// 与 [`FrameMetrics::column`](一句话多宽,≤424)是两个量。
pub use super::layout::block::body_width;
/// **正文块**:从页边距起(= 页头标题那条左缘)、铺满 [`body_width`]。
/// **正文块**:从页边距起(= 页头标题那条左缘)、宽 [`body_width`]、左内边距 [`body_offset`]。
///
/// 与旧 `single_column` 的差别只有两点,但都是判据本身:**贴左**而不是居中,
/// 宽度取 pane 而不是行宽上限。散文仍要自己套 [`prose`],别把整页块拉回 424。
/// 盒子**从纯量建**:宽度取 [`body_width`]、左内边距取 [`super::layout::block::body_offset`]
/// (恒 0)、对齐 `Start`。这三件事的顺序就是契约,`pages/tests.rs` 的
/// `frame_body_is_built_from_the_block_quantities` 逐条扫这段源码(iced 的 Element 没有几何
/// 内省,量不到「摆在哪」,只能断言「照哪个数建的」)。
pub fn body<'a>(
metrics: FrameMetrics,
content: impl Into<Element<'a, Message>>,
@@ -187,10 +187,11 @@ pub fn body<'a>(
.height(Length::Fill)
.align_x(Alignment::Start)
.align_y(Alignment::Start)
.padding(Padding::default().left(block::body_offset(metrics)))
.into()
}
/// **散文块**:宽度锁在 [`FrameMetrics::column`](≤424 = 逐行 ≤72 字符)。
/// **散文块**:宽度锁在 [`super::layout::block::prose_width`](= `metrics.column`,≤424 ⇒ 一行 ≤72 字符)。
///
/// 只包「成段读的文字」(实例详情正文那样的 prose);卡片、列表、Tabs 吃的是 [`body_width`],
/// 它们的宽度不承担行宽承诺(列宽只影响每行放几个字段,不影响读一句话的舒适度)。
@@ -199,7 +200,7 @@ pub fn prose<'a>(
content: impl Into<Element<'a, Message>>,
) -> Element<'a, Message> {
container(content)
.width(Length::Fixed(metrics.column))
.width(Length::Fixed(block::prose_width(metrics)))
.into()
}