fix(app-iced): M95 r3 摘要页脚占比 4:2 + 演示数据自洽 + 样例解析不再 panic
视觉复审 r2 的 P2-A 与两个顺手的 p3。
**P2-A 摘要页脚占比过大(3:2 → 4:2)**:1180×760 下诊断卡 338px,摘要页脚固定吃掉 ~150px
(标签+按钮 40 + 摘要折 5 行 80 + 间距/分割 30),滚动视口只剩 141px,而一条完整结论
(分组标题 → 建议动作 → 置信度)要 165px ⇒ 首屏看不到「建议动作」,那是任务清单 T1/T3 要求的
可见项。改成 `DIAGNOSIS_SHARE = 4`(`LOGS_SHARE` 仍 2)。**数值实测**(`r3-crash-light.png`
逐像素量,非估算):诊断卡 376px、滚动视口 **178px**(y 201–378,两侧 1px 分割线的位置)、
一条完整结论块 **165px**、底部余量 **13px** —— 建议动作与 `Confidence 85%` 整块在首屏可见。
**p3 演示数据自相矛盾**:`live` 状态页头写「Finished normally」却配崩溃日志尾行
(`启动失败:exit code 1`)。新增 `sample_live.log`(一场正常跑完的会话:加载 → 进世界 →
停服),`State::Live` 用它,不再复用崩溃样例。
**p3 `demo::parse` 对非 ASCII 首字符 panic**:`raw.split_at(1)` 会切在多字节序列中间。
改成按字符取前缀(`chars().next()` + `chars.as_str()`);顺带把它提成 `pub(super)` 以便测。
新增 `the_fixture_parser_survives_a_non_ascii_first_char`:非 ASCII 首字符不 panic(静默跳过),
并钉住 `I/W/E` 前缀与注释/空行的解析。
测试:**278 passed / 0 failed**(lib)+ 6(`no_hardcoded_tokens`)+ 23(i18n)全绿;
`fmt --check` 通过;`clippy -p unlucky-app-iced -p unlucky-i18n --all-targets -D warnings` 干净。
出图:`C:\Temp\ull-shots-m95\r3-{crash-light,crash-dark,live-light,empty-light}.png`(1180×760)。
This commit is contained in:
1 parent
62d4af150e
commit
5ed2bf3a7d
5 files changed
+65
-12
No files matched your search
@@ -51,7 +51,11 @@ mod panel;
|
||||
mod tests;
|
||||
|
||||
/// 诊断卡在正文里的高度份额(见 [`panel`] 的模块文档:两块必须都是定高 + 内部滚动)。
|
||||
const DIAGNOSIS_SHARE: u16 = 3;
|
||||
///
|
||||
/// r2 是 3:2 —— 1180×760 下诊断卡的滚动视口只剩 141px,而一条完整结论(分组标题 → 建议动作 →
|
||||
/// 置信度)要 177px,于是**首屏看不到任何一条完整结论**(视觉复审 r2 的 P2-A:任务要求的
|
||||
/// 「建议动作」在验收尺寸下不可见)。改成 4:2:视口 ~184px,一条结论整块可见。
|
||||
const DIAGNOSIS_SHARE: u16 = 4;
|
||||
/// 日志卡的高度份额。
|
||||
const LOGS_SHARE: u16 = 2;
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@ pub const STATE_ENV: &str = "ULL_LOGS_FIXTURE";
|
||||
/// 内嵌的崩溃日志片段(见模块文档的出处)。
|
||||
const SAMPLE: &str = include_str!("sample_crash.log");
|
||||
|
||||
/// 内嵌的**正常跑完**一场的日志片段(`live` 状态用;见 `sample_live.log` 的文件头)。
|
||||
const LIVE_SAMPLE: &str = include_str!("sample_live.log");
|
||||
|
||||
/// 演示状态。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum State {
|
||||
@@ -61,10 +64,14 @@ impl State {
|
||||
pub fn build(self, lang: Lang) -> Logs {
|
||||
match self {
|
||||
Self::Empty => Logs::default().with_status(lang),
|
||||
// 正常跑完的一场用**自己的**日志样例:页头写「正常结束」却配崩溃日志尾行是自己打自己
|
||||
// (视觉复审 r2 的 p3)。
|
||||
Self::Live => Logs {
|
||||
instance: Some("开发测试".to_owned()),
|
||||
when: Some("11-02 19:22".to_owned()),
|
||||
outcome: Some(Outcome::Ok),
|
||||
findings: Vec::new(),
|
||||
..crash()
|
||||
lines: parse(LIVE_SAMPLE),
|
||||
..Logs::default()
|
||||
}
|
||||
.with_status(lang),
|
||||
Self::Crash => crash().with_status(lang),
|
||||
@@ -94,7 +101,7 @@ pub fn crash() -> Logs {
|
||||
}
|
||||
|
||||
/// 解析内嵌样例:`#` 注释与空行跳过,首字符是等级前缀。
|
||||
fn parse(text: &str) -> Vec<Line> {
|
||||
pub(super) fn parse(text: &str) -> Vec<Line> {
|
||||
text.lines()
|
||||
.filter_map(|raw| {
|
||||
let raw = raw.trim_end();
|
||||
@@ -102,17 +109,19 @@ fn parse(text: &str) -> Vec<Line> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (prefix, rest) = raw.split_at(1);
|
||||
let level = match prefix {
|
||||
"D" => Level::Debug,
|
||||
"I" => Level::Info,
|
||||
"W" => Level::Warn,
|
||||
"E" => Level::Error,
|
||||
// 按**字符**取等级前缀,不 `split_at(1)`:后者在非 ASCII 首字符上会切到
|
||||
// 多字节序列中间直接 panic(视觉复审 r2 的 p3)。
|
||||
let mut chars = raw.chars();
|
||||
let level = match chars.next() {
|
||||
Some('D') => Level::Debug,
|
||||
Some('I') => Level::Info,
|
||||
Some('W') => Level::Warn,
|
||||
Some('E') => Level::Error,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(Line {
|
||||
text: rest.trim_start().to_owned(),
|
||||
text: chars.as_str().trim_start().to_owned(),
|
||||
level,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
//!
|
||||
//! | 块 | 高度 | 为什么 |
|
||||
//! |---|---|---|
|
||||
//! | 诊断 | `FillPortion(3)` | 崩溃时先读结论;结论通常比日志短,给 3 份 |
|
||||
//! | 诊断 | `FillPortion(4)` | 崩溃时先读结论;份额要够 177px 的滚动视口,才放得下**一条完整结论**(含建议动作与置信度) |
|
||||
//! | 日志 | `FillPortion(2)` | 日志是长流水,滚就是了 |
|
||||
//!
|
||||
//! 两者都是**兄弟**滚动区(不是嵌套):滚轮归属清楚,不存在「滚动条套滚动条」。
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# M95 演示数据:一场**正常跑完**的会话(与 sample_crash.log 成对)。
|
||||
#
|
||||
# 为什么单独一份(视觉复审 r2 的 p3):`live` 状态的页头写着「Finished normally」,
|
||||
# 却配着崩溃日志的尾行(`启动失败:exit code 1`),演示数据自相矛盾。
|
||||
#
|
||||
# 每行格式:`<等级> D|I|W|E <原文>`;`#` 开头是注释。
|
||||
|
||||
I [19:22:01] [main/INFO] 正在启动 1.20.4,实例「开发测试」,加载器 NeoForge 21.1.72
|
||||
I [19:22:02] [main/INFO] Java 21.0.5 (Microsoft build 21.0.5+11-LTS), 内存 4096 MiB, JVM 参数 -Xmx4G -XX:+UseG1GC -Dfile.encoding=UTF-8 -Djava.library.path=C:\Users\Player\AppData\Roaming\.unlucky\instances\dev-test\natives
|
||||
W [19:22:03] [Render thread/WARN] GLFW 报告 OpenGL 3.2,低于推荐值 4.5;部分光影可能不可用
|
||||
I [19:22:05] [Render thread/INFO] OpenGL 3.2.0 NVIDIA 552.44,渲染器 NVIDIA GeForce GTX 1060 6GB
|
||||
I [19:22:08] [modloading-worker-0/INFO] 已加载 148 个模组,其中 3 个标记为客户端专用:iris-1.7.4, sodium-0.5.11, indium-1.0.32
|
||||
I [19:22:14] [Worker-Main-2/INFO] 正在准备生成区域,种子 20261102,生物群系 47 种,结构与地物共 113 项已注册
|
||||
I [19:22:19] [main/INFO] 世界「生存世界」已加载,游玩时间 3 小时 12 分
|
||||
I [19:22:20] [main/INFO] 本地服务器已启动,端口 25565
|
||||
I [19:22:41] [Render thread/INFO] 区块渲染完成,平均帧率 61 fps(垂直同步开)
|
||||
W [19:31:07] [Render thread/WARN] 一个资源包(Soartex Fanver 1.20)缺少 pack.png,已跳过图标
|
||||
I [19:40:12] [main/INFO] 正在停止服务器,保存世界数据
|
||||
I [19:40:13] [main/INFO] Stopping!
|
||||
@@ -215,6 +215,27 @@ fn empty_state_is_driven_by_both_lists_being_empty() {
|
||||
assert!(live.findings.is_empty(), "live 状态没有诊断");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_fixture_parser_survives_a_non_ascii_first_char() {
|
||||
// 抓得住:`parse` 用 `split_at(1)` 切多字节序列会直接 panic(视觉复审 r2 的 p3 实测过)。
|
||||
// 抓不住:样例文件本身写坏(比如等级前缀写成小写 `e`)——那会被静默跳过,只体现为行数变少。
|
||||
assert!(!demo::parse("日本語の行")
|
||||
.iter()
|
||||
.any(|line| line.level == Level::Info));
|
||||
|
||||
let parsed = demo::parse(
|
||||
"I 正常一行
|
||||
W 警告一行
|
||||
# 注释
|
||||
|
||||
E 错误一行",
|
||||
);
|
||||
assert_eq!(parsed.len(), 3);
|
||||
assert_eq!(parsed[0].text, "正常一行");
|
||||
assert_eq!(parsed[1].level, Level::Warn);
|
||||
assert_eq!(parsed[2].level, Level::Error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn demo_state_comes_from_the_fixture_variable() {
|
||||
// 抓得住:环境变量取值拼错(截图会静默退成 crash,出图的人以为拍到了空态)。
|
||||
|
||||
Reference in new issue
Block a user