From f01150be059ac62a5d59fd085c4ab6efa8cff0b5 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 21:23:25 -0400 Subject: [PATCH] ElyShow: a slide size the kernel did not take stops showing (S-09) --- apps/ely-show/src/deck.rs | 8 ++---- apps/ely-show/src/design.rs | 15 ++++++++--- apps/ely-show/src/extra.rs | 22 +++++++++++++--- apps/ely-show/src/tests/slides.rs | 44 +++++++++++++++++++++++++++++++ tasks/BUGS.md | 2 +- tasks/reviews/S-09-r1.md | 3 +++ tasks/reviews/S-09-r2.md | 1 + 7 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 tasks/reviews/S-09-r1.md create mode 100644 tasks/reviews/S-09-r2.md diff --git a/apps/ely-show/src/deck.rs b/apps/ely-show/src/deck.rs index 1940ed4..b136993 100644 --- a/apps/ely-show/src/deck.rs +++ b/apps/ely-show/src/deck.rs @@ -220,6 +220,7 @@ impl Deck { } (tab.extra.reading, tab.extra.read) = (true, Some(seen)); let landed = tab.extra.transition_landed(view.read(cx)); + let sized = tab.extra.size_landed(view.read(cx)); let doc = tab.doc.clone(); ely_lok::read_off( &self.core.worker, @@ -296,12 +297,6 @@ impl Deck { { tab.extra.stale = tab.extra.thumbs.keys().cloned().collect(); } - let read = crate::design::mm100(size); - if tab.extra.size_asked.is_some_and(|(w, h)| { - (w - read.0).abs() <= 50 && (h - read.1).abs() <= 50 - }) { - tab.extra.size_asked = None; - } (tab.extra.slides, tab.extra.design) = (slides, Some(design)); tab.extra.objects = Some(objects); (tab.extra.notes, tab.extra.remarks) = (Some(notes), remarks); @@ -309,6 +304,7 @@ impl Deck { tab.extra.settle_transition(landed); tab.extra.reads += 1; tab.extra.size = size; + tab.extra.settle_size(sized); tab.extra.failed.clear(); deck.paint_next(id, cx); deck.read_fonts(id, cx); diff --git a/apps/ely-show/src/design.rs b/apps/ely-show/src/design.rs index 0ce28ab..c824fc8 100644 --- a/apps/ely-show/src/design.rs +++ b/apps/ely-show/src/design.rs @@ -59,9 +59,18 @@ impl Deck { log::info!("show: the slides already measure {w} x {h}"); return; } - tab.extra.size_asked = Some((w, h)); - let (w, h) = (w.to_string(), h.to_string()); - self.design_command("slide.size", &[&w, &h, "true"], cx); + let (id, view) = (tab.id, tab.view.clone()); + let before = view.read(cx).sent(); + tab.extra.size_asked = Some(((w, h), before + 1)); + let (sw, sh) = (w.to_string(), h.to_string()); + self.run_in(Some(id), "slide.size", &[&sw, &sh, "true"], cx); + // Refused before sending: run_in said so already. + if view.read(cx).sent() == before + && let Some(tab) = self.tab_mut(id) + { + tab.extra.size_asked = None; + } + self.force_reread(id, cx); } /// Enters or leaves the master view. diff --git a/apps/ely-show/src/extra.rs b/apps/ely-show/src/extra.rs index 1836c7b..0921607 100644 --- a/apps/ely-show/src/extra.rs +++ b/apps/ely-show/src/extra.rs @@ -37,8 +37,8 @@ pub struct Extra { pub size: (i64, i64), /// Undo and redo lists at the last read. pub history: Option<(Vec, Vec)>, - /// A size sent, in 1/100 mm, until read back. - pub size_asked: Option<(i64, i64)>, + /// A size sent (1/100 mm) and its input, until read. + pub size_asked: Option<((i64, i64), u64)>, /// A transition sent for a slide, until read back. pub transition_asked: Option<(String, ely_lok::show::Transition, u64)>, /// Pictures by slide name; stale ones show until repainted. @@ -60,6 +60,22 @@ impl Extra { /// The size to show, in 1/100 mm: asked, else read. pub fn shown_size(&self) -> (i64, i64) { self.size_asked - .unwrap_or_else(|| crate::design::mm100(self.size)) + .map_or_else(|| crate::design::mm100(self.size), |(size, _)| size) + } + + /// The size input that landed before this read began. + pub fn size_landed(&self, view: &ely_lok::LokView) -> Option { + let (_, seq) = self.size_asked?; + view.reached(seq).then_some(seq) + } + + /// Forgets the asked size once read back, taken or refused. + pub fn settle_size(&mut self, landed: Option) { + let read = crate::design::mm100(self.size); + if self.size_asked.is_some_and(|((w, h), seq)| { + landed == Some(seq) || (w - read.0).abs() <= 50 && (h - read.1).abs() <= 50 + }) { + self.size_asked = None; + } } } diff --git a/apps/ely-show/src/tests/slides.rs b/apps/ely-show/src/tests/slides.rs index 24ef8ed..59162fc 100644 --- a/apps/ely-show/src/tests/slides.rs +++ b/apps/ely-show/src/tests/slides.rs @@ -417,3 +417,47 @@ fn a_check_row_does_not_jump_among_masters() { }); assert!(cx.update(|app| deck.read(app).dialog.is_some()), "the check stays open"); } + +#[test] +fn a_refused_size_does_not_stay_shown() { + let dir = tempfile::tempdir().expect("a temp dir"); + let mut cx = TestAppContext::single(); + let Some(deck) = blank(&mut cx, dir.path()) else { + eprintln!("SKIP: no LOK build"); + return; + }; + let before = cx.update(|app| deck.read(app).active_tab().expect("a tab").extra.size); + update(&mut cx, &deck, |deck, cx| deck.resize((-500, -500), cx)); + settle(&cx, |app| { + deck.read(app).active_tab().is_some_and(|tab| tab.extra.size_asked.is_none()) + }); + let extra = cx.update(|app| { + let tab = deck.read(app).active_tab().expect("a tab"); + (tab.extra.size, tab.extra.shown_size()) + }); + assert_eq!(extra.0, before, "the kernel kept its size"); + assert_eq!(extra.1, crate::design::mm100(before), "the fields show it"); +} + +#[test] +fn a_size_the_kernel_does_not_take_clears_after_its_read() { + let dir = tempfile::tempdir().expect("a temp dir"); + let mut cx = TestAppContext::single(); + let Some(deck) = blank(&mut cx, dir.path()) else { + eprintln!("SKIP: no LOK build"); + return; + }; + update(&mut cx, &deck, |deck, cx| { + deck.resize((30_000, 19_050), cx); + // As if the kernel refused: it keeps another size. + let id = deck.core.active.expect("a tab"); + let tab = deck.tab_mut(id).expect("a tab"); + let (_, seq) = tab.extra.size_asked.expect("asked"); + tab.extra.size_asked = Some(((40_000, 19_050), seq)); + }); + settle(&cx, |app| { + deck.read(app).active_tab().is_some_and(|tab| tab.extra.size_asked.is_none()) + }); + let read = cx.update(|app| crate::design::mm100(deck.read(app).active_tab().expect("a tab").extra.size)); + assert!((read.0 - 30_000).abs() <= 50, "the kernel's own size shows: {read:?}"); +} diff --git a/tasks/BUGS.md b/tasks/BUGS.md index 433b923..6f1bdd2 100644 --- a/tasks/BUGS.md +++ b/tasks/BUGS.md @@ -31,7 +31,7 @@ - [x] S-06 导出或打印的“从–到”填 0 时导出全部幻灯片 — 键入的页码夹到 1..=末页;R1 PASS - [x] S-07 母版视图下演示检查的行按幻灯片序号跳转 — 母版视图下点行提示先关母版,不跳;R1 PASS - [x] S-08 刚编辑过幻灯片就进备注栏键入,备注可能丢 — 发出的备注待读回确认,键入中读回只移基线不动草稿;R2 PASS -- [ ] S-09 改尺寸失败后 `size_asked` 一直留着 +- [x] S-09 改尺寸失败后 `size_asked` 一直留着 — 所要尺寸带输入序号,发送前先记、未入队即清,命令落地后的读回即清;R2 PASS - [ ] S-10 开始放映的两处拒绝无提示(规则 2) - [x] P-01 文件系统的 `PermissionDenied` 被拼成不存在的文案键,应用崩溃 — worker 的拒绝改为自有错误类型 Refused;R1 测试不跨平台、不看提示;组件库 f834f55 加 Toaster::shown;R2 PASS - [x] P-02 每次编辑落地清掉全部图块与缩略图,页面空白、刚画的墨迹消失 — 图块随 R2.6;缩略图在编辑后留到重画;R1、R2 测试未证实际绘制;R3 PASS diff --git a/tasks/reviews/S-09-r1.md b/tasks/reviews/S-09-r1.md new file mode 100644 index 0000000..d932ff1 --- /dev/null +++ b/tasks/reviews/S-09-r1.md @@ -0,0 +1,3 @@ +FAIL + +apps/ely-show/src/tests/slides.rs:430:回归覆盖遗漏了已排队尺寸命令失败后的清理。`(-500, -500)` 在参数校验阶段即被拒绝,只验证 `design.rs` 的立即清理。具体复现:在隔离副本中移除 `extra.rs:76` 的 `landed == Some(seq) ||`,现有四项尺寸测试仍全部通过;通过临时 IPC 代理对已排队的合法尺寸 `(30000, 19050)` 返回 ERR,原实现通过,变异实现因 `size_asked` 持续存在而超时,重现 S-09。修复方向:补充已排队命令被拒绝的回归测试,断言命令后的回读清除待定尺寸、保留内核尺寸,并让上述变异触发测试失败。证据:`/tmp/S-09-r1-size-tests.log`、`/tmp/S-09-r1-mutated-size-tests.log`、`/tmp/S-09-r1-queued-baseline.log`、`/tmp/S-09-r1-queued-mutated.log`。 diff --git a/tasks/reviews/S-09-r2.md b/tasks/reviews/S-09-r2.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/S-09-r2.md @@ -0,0 +1 @@ +PASS