fix(design): M228 复审 r1 三条(字段底角色对齐、四处小疵、产品默认口径)

P2-1 字段底角色不对称(塔裁决方案 a):Silver 的 #dcddde 从 surface-container-high 改判到
surface-container-highest,与 Ink 的 #262b25 同位——component_map 的 TextField 口径就是最高
容器层,单一角色接法才不会一边掉层。Silver 的 high 改取推导值 #e4e4e5(tone 90),阶梯
94.12 → 90.61 → 88.07 保持单调、步长 ≥1.74 L*。实测:Error on 新 highest = 4.686(原按单一
角色接到 high 时的 4.379 失守已消除)、OnSurfaceVariant = 6.383、OnSurface = 12.576。
测试同步:Silver 三条字段用法 pin 从 high 改 highest(与 Ink 对称),并新增
`field_backdrop_is_the_same_role_on_both_boards` 把这个不对称钉住(顺带断言 high 比 highest
更亮、highest 就是规格卡的 #dcddde)。

P2-2 四处小疵:
① inkcat/size.rs 扩展 FAB 起始内距 16 → **28**(规格卡画的数):框 x=292 → 图标盒 320(`+`
   font-size 24 居中于 x=332),28 才是图上的距离;16 是 M3 官方口径,已在 doc 里登记为偏离,
  并说明「要回 M3 的 16 得同时把图标中心挪回 316,那是要出截图的视觉改动」。测试补一条自洽
   断言:292 + 28 + 24/2 = 332。
② inkcat/metrics.rs 的 SWITCH_THUMB_INSET 注释重写(原句断成碎片且读起来像算错):写明
   轨道 y=1060..1092 / 拇指 y=1064..1088 ⇒ 上下各 4、右端 116−112=4,以及行程
   52−24−2×4 = 20 的推导。
③ Silver「状态容器 / 容器层」数字统一成实测口径:boards/mod.rs 与 palette/visibility.rs 都写
   「最低 1.02」(Ink 1.54),并点名最差的一组是 tertiary-container 压 surface-container-high
   = 1.016;visibility 表的 Silver 阶梯步长改写 −1.7 ~ −4.1。
④ inkcat/type_scale.rs 正文清单只列 11 个**文字**字号(去掉 FAB 字形 20/26/34),并注明那三个
   归 FabSize::icon_px、SpecText 没有 20 这一档。

P2-3 产品默认口径统一:palettes.rs 的「两个入口」表与 Palettes::baseline 文档、color/mod.rs
的「出厂默认色」小节、Scheme::baseline 文档统一写明「M228 起出厂外观入口是
DesignTokens::brand,本入口是 D38 取色链中性锚点」,与 lib.rs 同口径;顺带把 BASELINE_SEED /
DEFAULT_SEED / DEFAULT_NEUTRAL_CHROMA 的「产品默认」措辞改成取色链口径。

另:tests/ink_cat_boards.rs 已 400 行贴线,把 4 条容器层可视性测试拆到新文件
tests/ink_cat_visibility.rs(纯搬移 + 补 import);palettes.rs 因新增口径说明到 403 行,按
「合并相邻段落、不删信息」压回 399 行;size.rs 现在 323 行。

五步(design 口径):fmt --all --check=0;check -p unlucky-design --all-targets=0(零 warning);
test -p unlucky-design --all-targets=0(267 passed);clippy -p unlucky-design --all-targets
-- -D warnings=0;400 行扫描只剩 README.md(豁免)、无 <2 字节 .rs。
This commit is contained in:
AskaEth committed 2026-11-10 22:34:05 +08:00
1 parent 0169d4c8fb
commit b8003177d7
11 files changed
+203 -118

No files matched your search

+6 -90
View File
@@ -1,14 +1,12 @@
//! Ink Cat 双主题的**消费方视角**门禁(M228):色板完整性、双主题切换、对比度、容器层可视性。
//! Ink Cat 双主题的**消费方视角**门禁(M228):色板完整性、双主题切换、对比度。
//!
//! 只用公开 API(`unlucky_design::...`),所以它同时是「iced 适配层能拿到什么」的样本:
//! 换肤时只需要 `BrandBoard` → `Scheme` → `Role`,一个色值字面量都不用抄。
//! 规格卡的几何与字级校核在同目录的 `ink_cat_geometry.rs`(单文件 ≤400 行)。
//! 几何与字级校核在 `ink_cat_geometry.rs`,容器层可视性在 `ink_cat_visibility.rs`
//! (拆开是单文件 ≤400 行的硬约束)。
use unlucky_design::color::BrandBoard::{Ink, Silver};
use unlucky_design::color::{BrandBoard, Color, Role, SchemeFlavor};
use unlucky_design::palette::visibility::{
check_surface_hierarchy, LAYER_ROLES, STATE_CONTAINERS, STATE_CONTAINER_MIN,
};
use unlucky_design::palette::wcag::{check, AA_TEXT};
use unlucky_design::DesignTokens;
@@ -236,19 +234,19 @@ const SPEC_TEXT_ON_SURFACE: [(BrandBoard, Role, Role, &str); 25] = [
(
Silver,
Role::OnSurfaceVariant,
Role::SurfaceContainerHigh,
Role::SurfaceContainerHighest,
"字段标签",
),
(
Silver,
Role::OnSurface,
Role::SurfaceContainerHigh,
Role::SurfaceContainerHighest,
"字段内容",
),
(
Silver,
Role::Error,
Role::SurfaceContainerHigh,
Role::SurfaceContainerHighest,
"错误字段标签",
),
(
@@ -285,85 +283,3 @@ fn spec_text_pairs_meet_aa() {
failures.join("\n")
);
}
#[test]
fn state_containers_clear_the_visibility_floor() {
// 债务台账「SecondaryContainer 对 Surface 仅 1.06」的令牌层裁决:状态容器 ≥1.20:1。
for board in BrandBoard::ALL {
let report = check_surface_hierarchy(&board.scheme());
assert!(
report.state_failures().is_empty(),
"{} 的状态容器未达 {STATE_CONTAINER_MIN}:1:{:?}",
board.name(),
report.state_failures()
);
let (role, ratio) = report.worst_state();
assert!(
ratio >= STATE_CONTAINER_MIN,
"{} 最弱的状态容器 {} 只有 {ratio:.3}",
board.name(),
role.name()
);
// 旧 baseline 的现场值 1.057 必须被显著甩开,否则「修了但没修好」。
assert!(
ratio > 1.20,
"{} 只是刚好卡线({ratio:.3}),等于没修",
board.name()
);
}
// 两套板覆盖 4 个状态容器角色,一个不落。
assert_eq!(STATE_CONTAINERS.len(), 4);
}
#[test]
fn surface_container_ladder_is_monotone_with_visible_steps() {
for board in BrandBoard::ALL {
let report = check_surface_hierarchy(&board.scheme());
assert!(
report.layer_violations().is_empty(),
"{} 的容器阶梯有断层或方向错:{:?}",
board.name(),
report.layer_violations()
);
assert!(report.is_clean(), "{} 的容器层体检没过", board.name());
// 方向:深色板越靠上越亮、浅色板越靠上越暗。
let steps = report.layer_steps();
for step in steps {
let expect_positive = board.flavor() == SchemeFlavor::Dark;
assert_eq!(
step.delta_lstar > 0.0,
expect_positive,
"{} 的 {} 阶梯方向错",
board.name(),
step.role.name()
);
}
assert_eq!(LAYER_ROLES.len(), 5);
}
}
// ---------------------------------------------------------------- 规则:容器层不当状态用
#[test]
fn container_layers_are_not_state_indicators() {
// 裁决第 3 条的可测化一半:把「状态容器压在最亮容器层上」的实际数字钉住,
// 提醒后来者别把它当唯一指示(浅色板这里必然贴到 1.05~1.24)。
let silver = BrandBoard::Silver.scheme();
let worst = Role::ALL
.iter()
.filter(|role| STATE_CONTAINERS.contains(role))
.map(|role| silver.contrast(*role, Role::SurfaceContainerHigh))
.fold(f32::INFINITY, f32::min);
assert!(
(1.0..1.5).contains(&worst),
"Silver 的状态容器对 surface-container-high = {worst:.3},与「浅色必然贴近」的登记不符"
);
// Ink 相反:深色板的状态容器与容器层拉得开,所以深色可以靠底色差做弱指示。
let ink = BrandBoard::Ink.scheme();
let ink_worst = Role::ALL
.iter()
.filter(|role| STATE_CONTAINERS.contains(role))
.map(|role| ink.contrast(*role, Role::SurfaceContainerHighest))
.fold(f32::INFINITY, f32::min);
assert!(ink_worst >= 1.5, "Ink 最弱的状态容器 = {ink_worst:.3}");
}