From 5c345b6f7cc03fd0112977bb133b434624669e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 9 Oct 2026 20:27:51 -0400 Subject: [PATCH] ElyShow: an unreadable comment time no longer empties the comments column (S-05) --- apps/ely-show/src/remarks.rs | 62 ++++++++++++++++++++++++++-------- crates/ely-ui/src/i18n/show.rs | 2 ++ tasks/BUGS.md | 2 +- tasks/reviews/S-05-r1.md | 3 ++ tasks/reviews/S-05-r2.md | 1 + 5 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 tasks/reviews/S-05-r1.md create mode 100644 tasks/reviews/S-05-r2.md diff --git a/apps/ely-show/src/remarks.rs b/apps/ely-show/src/remarks.rs index 38ba8ee..bf8d5af 100644 --- a/apps/ely-show/src/remarks.rs +++ b/apps/ely-show/src/remarks.rs @@ -19,20 +19,21 @@ use gpui::{ use crate::deck::Deck; use crate::dialog::Dialog; -/// Each comment as a thread quoted with its slide. -fn threads(remarks: &[Remark], cx: &App) -> Result, String> { +/// Threads quoted with their slides; unreadable comments apart. +fn threads(remarks: &[Remark], cx: &App) -> (Vec, Vec<(String, String)>) { let authors: Vec<&str> = remarks .iter() .map(|remark| remark.author.as_str()) .collect(); - remarks + let (good, bad): (Vec<_>, Vec<_>) = remarks .iter() .map(|remark| { let at = remark .at .parse::() .and_then(|at| at.to_zoned(jiff::tz::TimeZone::system())) - .map_err(|error| format!("a bad comment time {}: {error}", remark.at))?; + .map_err(|error| (remark.at.clone(), error.to_string()))? + .timestamp(); let hue = authors .iter() .position(|name| *name == remark.author) @@ -43,14 +44,16 @@ fn threads(remarks: &[Remark], cx: &App) -> Result, String> { quote: Some(Deck::text(cx, "show.remark.slide", &[("n", &slide)])), comments: vec![Comment { author: Peer::new(remark.author.clone(), remark.author.clone(), hue), - at: at.timestamp(), + at, body: remark.text.clone().into(), reactions: Vec::new(), }], resolved: false, }) }) - .collect() + .partition(Result::is_ok); + let bad = bad.into_iter().filter_map(Result::err).collect(); + (good.into_iter().filter_map(Result::ok).collect(), bad) } impl Deck { @@ -166,14 +169,15 @@ impl Deck { .active_tab() .map(|tab| tab.extra.remarks.clone()) .unwrap_or_default(); - let threads = match threads(&remarks, cx) { - Ok(threads) => threads, - Err(error) => { - log::error!("show: {error}"); - let message = InlineMessage::new(Severity::Danger, error); - return div().p_2().child(message).into_any_element(); - } - }; + let (threads, bad) = threads(&remarks, cx); + for (at, error) in &bad { + log::error!("show: a bad comment time {at}: {error}"); + } + // One line however many: the comments below stay in view. + let bad = bad.first().map(|(at, _)| { + let n = bad.len().to_string(); + InlineMessage::new(Severity::Danger, Deck::text(cx, "show.remark.bad", &[("n", &n), ("at", at)])) + }); let active = self.shown_remark(); let act = |id: &'static str, key: &str, @@ -227,6 +231,7 @@ impl Deck { .gap_2() .p_2() .child(head) + .children(bad) .child(div().flex_1().min_h_0().child(sidebar)) .into_any_element() } @@ -289,3 +294,32 @@ impl Deck { (!pins.is_empty()).then(|| div().absolute().inset_0().children(pins).into_any_element()) } } + +#[cfg(test)] +mod tests { + use ely_lok::show::Remark; + use gpui::TestAppContext; + + use super::threads; + + #[test] + fn one_unreadable_time_spares_the_other_comments() { + let cx = TestAppContext::single(); + cx.update(|cx| ely_ui::i18n::install_as("en-US", cx)); + let remark = |id: &str, at: &str| Remark { + id: id.into(), + author: "A".into(), + text: "t".into(), + at: at.into(), + part: 0, + anchor: (0, 0, 0, 0), + }; + // The kernel writes an empty date as 0-00-00. + let remarks = [remark("1", "2025-03-01"), remark("2", "0-00-00"), remark("3", "2025-03-01T10:15:00")]; + let (good, bad) = cx.update(|cx| threads(&remarks, cx)); + let keys: Vec<_> = good.iter().map(|thread| thread.key.to_string()).collect(); + assert_eq!(keys, ["1", "3"]); + assert_eq!(bad.len(), 1); + assert_eq!(bad[0].0, "0-00-00"); + } +} diff --git a/crates/ely-ui/src/i18n/show.rs b/crates/ely-ui/src/i18n/show.rs index 85b049d..2d7c421 100644 --- a/crates/ely-ui/src/i18n/show.rs +++ b/crates/ely-ui/src/i18n/show.rs @@ -174,6 +174,7 @@ pub(super) const EN: &[(&str, &str)] = &[ "These notes are formatted; Ely shows them read-only to keep it.", ), ("show.remark.slide", "Slide {n}"), + ("show.remark.bad", "Comments with unreadable times: {n}, first {at}"), ("show.remark.text", "Comment"), ("show.remark.new_title", "New Comment"), ("show.remark.edit_title", "Edit Comment"), @@ -353,6 +354,7 @@ pub(super) const ZH_CN: &[(&str, &str)] = &[ ("show.notes.none", "这张幻灯片没有备注框。"), ("show.notes.rich", "备注带格式,为保留格式在此只读。"), ("show.remark.slide", "第 {n} 张幻灯片"), + ("show.remark.bad", "时间读不出的批注:{n} 条,第一条为 {at}"), ("show.remark.text", "批注"), ("show.remark.new_title", "新建批注"), ("show.remark.edit_title", "编辑批注"), diff --git a/tasks/BUGS.md b/tasks/BUGS.md index c145fbd..11e03f8 100644 --- a/tasks/BUGS.md +++ b/tasks/BUGS.md @@ -27,7 +27,7 @@ - [x] S-02 `deck.rs` 超过 500 行 — 随 S-03 把 Extra 移到 extra.rs,deck.rs 463 行 - [x] S-03 ElyShow 的缩放一两帧后弹回“适合幻灯片”,缩放功能不可用 — 只在标签首次观察时适合一次;R1 PASS - [x] S-04 选屏对话框被别的对话框顶掉后,`show_starting` 卡住、放映副本泄漏 — 有对话框开着时不再顶掉它,提示先完成;选屏对话框被拒即丢副本;R2 PASS -- [ ] S-05 一条批注的时间恰为午夜或只有日期,整个批注栏不显示 +- [x] S-05 一条批注的时间恰为午夜或只有日期,整个批注栏不显示 — 读不出时间的批注只占一行汇总,其余照常显示(jiff 本可读只有日期的时间);R2 PASS - [ ] S-06 导出或打印的“从–到”填 0 时导出全部幻灯片 - [ ] S-07 母版视图下演示检查的行按幻灯片序号跳转 - [ ] S-08 刚编辑过幻灯片就进备注栏键入,备注可能丢 diff --git a/tasks/reviews/S-05-r1.md b/tasks/reviews/S-05-r1.md new file mode 100644 index 0000000..7472f2c --- /dev/null +++ b/tasks/reviews/S-05-r1.md @@ -0,0 +1,3 @@ +FAIL + +apps/ely-show/src/remarks.rs:229 — Unscrollable error rows can still hide every readable comment (SHOW-08). The danger messages occupy the outer column above the sidebar, while scrolling belongs exclusively to the sidebar below them. Reproduction: render the actual remarks pane at 280×600 with one comment dated `2025-03-01` and twelve comments dated `0-00-00`. Native macOS rendering shows only errors; the readable comment and later errors lie below the viewport. Three downward scroll gestures at different heights leave the screenshot pixel-identical, preventing selection, editing and navigation to the readable comment. A layout probe also fails with eight bad dates, placing the readable row at y=740 in a 600-high pane. The supplied unit test passes because it checks only `threads()`. Put the errors into a scrollable layout that preserves access to readable comments, and cover multiple invalid timestamps with a rendered-pane scrolling regression test. Evidence: `/tmp/ely-s05-r1-layout.log` and `/tmp/ely-s05-r1-native.log`. diff --git a/tasks/reviews/S-05-r2.md b/tasks/reviews/S-05-r2.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/S-05-r2.md @@ -0,0 +1 @@ +PASS