diff --git a/crates/ely-lok/src/client/tests/rules.rs b/crates/ely-lok/src/client/tests/rules.rs index 24e010e..5142284 100644 --- a/crates/ely-lok/src/client/tests/rules.rs +++ b/crates/ely-lok/src/client/tests/rules.rs @@ -485,29 +485,3 @@ fn enter_and_tab_move_the_cell_cursor() { }); assert_eq!(right.cell.address, "G2", "Tab moves right"); } - -#[test] -fn a_click_far_down_selects_the_cell_under_it() { - if skip() { - return; - } - let (_dir, mut worker, doc, seen) = open(); - // One painted tile fixes the kernel's scale. - let (side, twips) = (512u32, 3840); - let ppt = f64::from(side) / f64::from(twips); - doc.paint_tile(&mut worker, (0, 0, twips, twips), (side, side)).expect("a tile"); - let geometry = sheet::geometry(&worker.values(&doc, sheet::GEOMETRY).expect("geometry")).expect("parsed"); - worker.run(&doc, "cell.goto", &["B4998"]).expect("B4998"); - let at = poll_until(&mut worker, &doc, &seen, SETTLE, |seen| seen.cell.address == "B4998"); - let (x, y, w, h) = at.cell.cursor.expect("the cursor"); - worker.run(&doc, "cell.goto", &["A1"]).expect("A1"); - let home = poll_until(&mut worker, &doc, &at, SETTLE, |seen| seen.cell.address == "A1"); - // The cursor is in print twips; clicks take tile twips. - let (x, y) = (geometry.columns.tile(x + w / 2, ppt), geometry.rows.tile(y + h / 2, ppt)); - for kind in [0, 1] { - let click = crate::Mouse { kind, x, y, count: 1, buttons: 1, modifiers: 0 }; - worker.post_mouse(&doc, click).expect("clicks"); - } - let after = poll_until(&mut worker, &doc, &home, SETTLE, |seen| seen.cell.address != "A1"); - assert_eq!(after.cell.address, "B4998"); -} diff --git a/crates/ely-lok/src/view/render.rs b/crates/ely-lok/src/view/render.rs index ea61a72..03e76fd 100644 --- a/crates/ely-lok/src/view/render.rs +++ b/crates/ely-lok/src/view/render.rs @@ -40,7 +40,7 @@ impl Render for LokView { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let dpi = window.scale_factor(); if dpi != self.geom.dpi { - self.geom.dpi = dpi; + self.keep_scroll(|view| view.geom.dpi = dpi); self.retire_tiles(); } self.follow_theme(cx.theme().is_dark()); @@ -210,7 +210,7 @@ impl LokView { let down = self.viewport.1 * TWIPS_PER_PIXEL / h as f32; let zoom = (across.min(down) * MARGIN).min(4.0); if (zoom - self.geom.zoom).abs() > 0.001 { - self.geom.zoom = zoom; + self.keep_scroll(|view| view.geom.zoom = zoom); self.retire_tiles(); self.failed.clear(); } diff --git a/crates/ely-lok/src/view/scroll.rs b/crates/ely-lok/src/view/scroll.rs index e5a47ed..2fd10c5 100644 --- a/crates/ely-lok/src/view/scroll.rs +++ b/crates/ely-lok/src/view/scroll.rs @@ -49,11 +49,9 @@ impl LokView { self.geom.twips_of(self.viewport.0) - fx, self.geom.twips_of(self.viewport.1) - fy, ); - // Room past the target, so the clamp lets it show. - self.reach = ( - self.reach.0.max(x + w + vw + fx), - self.reach.1.max(y + h + vh + fy), - ); + // Room past the target, in print twips for later zooms. + let need = self.print_of((x + w + vw + fx, y + h + vh + fy)); + self.reach = (self.reach.0.max(need.0), self.reach.1.max(need.1)); if x >= fx && (x < fx + self.scroll.0 || x + w > fx + self.scroll.0 + vw) { self.scroll.0 = x - fx - vw / 2; } @@ -104,10 +102,10 @@ impl LokView { if self.fit { return self.doc_size; } - ( + self.tile_of(( self.doc_size.0.max(self.reach.0), self.doc_size.1.max(self.reach.1), - ) + )) } /// The print-twips document point under a view position (LOK-04). diff --git a/crates/ely-lok/src/view/sheet.rs b/crates/ely-lok/src/view/sheet.rs index 44100ce..f6ca18e 100644 --- a/crates/ely-lok/src/view/sheet.rs +++ b/crates/ely-lok/src/view/sheet.rs @@ -48,6 +48,14 @@ impl LokView { self.tile_of(self.frozen) } + /// Rescales, keeping the scrolling pane's start in place. + pub(super) fn keep_scroll(&mut self, change: impl FnOnce(&mut Self)) { + let start = self.scrolled_start(); + change(self); + let ((x, y), (fx, fy)) = (self.tile_of(start), self.frozen_tiles()); + self.scroll = (x - fx, y - fy); + } + /// Where the scrolling pane starts, in print twips. pub fn scrolled_start(&self) -> (i32, i32) { let (fx, fy) = self.frozen_tiles(); diff --git a/crates/ely-lok/src/view/tests/large.rs b/crates/ely-lok/src/view/tests/large.rs index ac1843b..dfa1f12 100644 --- a/crates/ely-lok/src/view/tests/large.rs +++ b/crates/ely-lok/src/view/tests/large.rs @@ -73,6 +73,44 @@ const SHORT_ROWS: &str = r#" "#; +/// A short-row sheet at 2× DPI, scrolled to `cell`'s row. +fn far_down(cx: &mut gpui::TestAppContext, install: &std::path::Path) -> (Entity, crate::sheet::Geometry) { + let (view, worker) = cx.update(|app| live_view_of(app, install, "short.fods", SHORT_ROWS)); + let json = cx.update(|app| { + let read = view.read(app); + worker.lock().expect("the worker lock").values(&read.doc, crate::sheet::GEOMETRY).expect("geometry") + }); + let geometry = crate::sheet::geometry(&json).expect("parsed"); + let row = cell(&geometry, 1, 4997); + cx.update(|app| { + view.update(app, |view, cx| { + view.geom.dpi = 2.0; + view.set_geometry(geometry.clone(), cx); + view.prepare(Bounds { origin: point(px(0.0), px(0.0)), size: size(px(800.0), px(600.0)) }); + view.show(row, cx); + }) + }); + (view, geometry) +} + +/// Column `x`, row `y` in print twips. +fn cell(geometry: &crate::sheet::Geometry, x: u32, y: u32) -> crate::Rect { + (geometry.columns.start(x), geometry.rows.start(y), geometry.columns.size(x), geometry.rows.size(y)) +} + +/// Waits until the kernel reports `want` as the selection. +fn selected(view: &Entity, cx: &mut gpui::TestAppContext, want: &str) -> String { + let mut address = String::new(); + for _ in 0..200 { + cx.executor().tick(); + address = cx.update(|app| view.read(app).cell().address.clone()); + if address == want { + break; + } + } + address +} + #[test] fn a_click_far_down_a_sheet_selects_the_cell_under_it() { let Some(install) = crate::client::tests::install() else { @@ -80,38 +118,59 @@ fn a_click_far_down_a_sheet_selects_the_cell_under_it() { return; }; let mut cx = gpui::TestAppContext::single(); - let (view, worker) = cx.update(|app| live_view_of(app, &install, "short.fods", SHORT_ROWS)); - let json = cx.update(|app| { - let read = view.read(app); - worker.lock().expect("the worker lock").values(&read.doc, crate::sheet::GEOMETRY).expect("geometry") - }); - let geometry = crate::sheet::geometry(&json).expect("parsed"); - let (row, column) = (4997, 1); - let cell = ( - geometry.columns.start(column), - geometry.rows.start(row), - geometry.columns.size(column), - geometry.rows.size(row), - ); + let (view, geometry) = far_down(&mut cx, &install); + let (x, y, w, h) = cell(&geometry, 1, 4997); cx.update(|app| { - view.update(app, |view, cx| { - view.geom.dpi = 2.0; - view.set_geometry(geometry.clone(), cx); - view.prepare(Bounds { origin: point(px(0.0), px(0.0)), size: size(px(800.0), px(600.0)) }); - view.show(cell, cx); - let at = view.window_point((cell.0 + cell.2 / 2, cell.1 + cell.3 / 2)); + view.update(app, |view, _| { + let at = view.window_point((x + w / 2, y + h / 2)); for kind in [0, 1] { view.mouse(kind, at, 1, 1, &gpui::Modifiers::default()); } }) }); - let mut address = String::new(); - for _ in 0..200 { - cx.executor().tick(); - address = cx.update(|app| view.read(app).cell().address.clone()); - if address == "B4998" { - break; - } - } - assert_eq!(address, "B4998"); + assert_eq!(selected(&view, &mut cx, "B4998"), "B4998"); +} + +#[test] +fn a_drag_far_down_a_sheet_selects_the_range_under_it() { + let Some(install) = crate::client::tests::install() else { + eprintln!("SKIP: no pinned LOK build"); + return; + }; + let mut cx = gpui::TestAppContext::single(); + let (view, geometry) = far_down(&mut cx, &install); + let middle = |(x, y, w, h): crate::Rect| (x + w / 2, y + h / 2); + let (from, to) = (middle(cell(&geometry, 1, 4997)), middle(cell(&geometry, 3, 5000))); + cx.update(|app| { + view.update(app, |view, _| { + let modifiers = gpui::Modifiers::default(); + view.mouse(0, view.window_point(from), 1, 1, &modifiers); + view.mouse(2, view.window_point(((from.0 + to.0) / 2, (from.1 + to.1) / 2)), 1, 1, &modifiers); + view.mouse(2, view.window_point(to), 1, 1, &modifiers); + view.mouse(1, view.window_point(to), 1, 1, &modifiers); + }) + }); + assert_eq!(selected(&view, &mut cx, "B4998:D5001"), "B4998:D5001"); +} + +#[test] +fn zooming_far_down_a_sheet_keeps_the_rows_in_view() { + let Some(install) = crate::client::tests::install() else { + eprintln!("SKIP: no pinned LOK build"); + return; + }; + let mut cx = gpui::TestAppContext::single(); + let (view, geometry) = far_down(&mut cx, &install); + let middle = |cx: &mut gpui::TestAppContext| { + cx.update(|app| { + let read = view.read(app); + let (w, h) = read.viewport(); + let at = read.origin() + point(px(w / 2.0), px(h / 2.0)); + geometry.rows.at(read.doc_point(at).1) + }) + }; + let before = middle(&mut cx); + cx.update(|app| view.update(app, |view, cx| view.set_zoom(0.8, cx))); + let after = middle(&mut cx); + assert!(before.abs_diff(after) <= 1, "row {before} became {after}"); } diff --git a/crates/ely-lok/src/view/zoom.rs b/crates/ely-lok/src/view/zoom.rs index bf506af..fee4b9d 100644 --- a/crates/ely-lok/src/view/zoom.rs +++ b/crates/ely-lok/src/view/zoom.rs @@ -47,13 +47,15 @@ impl LokView { Some(at) => (f32::from(at.x - self.origin.x), f32::from(at.y - self.origin.y)), None => (self.viewport.0 / 2.0, self.viewport.1 / 2.0), }; - let under = (self.scroll.0 + self.geom.twips_of(ax), self.scroll.1 + self.geom.twips_of(ay)); + // Anchored in print twips: tile twips change with the zoom. + let under = self.print_of((self.scroll.0 + self.geom.twips_of(ax), self.scroll.1 + self.geom.twips_of(ay))); self.fit = false; if (zoom - self.geom.zoom).abs() > 0.0001 { self.geom.zoom = zoom; self.retire_tiles(); self.failed.clear(); } + let under = self.tile_of(under); self.scroll = (under.0 - self.geom.twips_of(ax), under.1 - self.geom.twips_of(ay)); self.clamp_scroll(); log::debug!("lok: zoom {:.0}%", zoom * 100.0); diff --git a/tasks/reviews/R2.1-r1.md b/tasks/reviews/R2.1-r1.md new file mode 100644 index 0000000..16075d3 --- /dev/null +++ b/tasks/reviews/R2.1-r1.md @@ -0,0 +1,5 @@ +FAIL + +site/e2e/night.spec.ts:69:对比度计算只取颜色的前三个分量,丢掉 alpha,随后直接与页面背景比较,因此低透明度链接能通过 4.5:1 断言。复现:在临时副本中将浅色链接设为 `rgba(42, 106, 208, 0.1)`、深色链接设为 `rgba(120, 166, 238, 0.1)`;运行 `pnpm e2e e2e/night.spec.ts --grep '4.5'`,两项均通过。浏览器读取下载链接的实际颜色并与背景合成后,对比度分别为 1.1425:1、1.1874:1。修复方向:按实际背景合成颜色及有效透明度后计算对比度,覆盖低透明度链接的回归场景。证据:`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-r21-evidence-7s8j6kd8/mutation-alpha.log`。 + +site/e2e/text.spec.ts:27:语言检查只遍历仍存在的 `[data-t]` 元素,并从生产代码的 `TEXT` 取得预期值;缺少 R2.1 打开步骤的存在性与内容断言。复现:在临时副本中从 `main.ts` 的 footer 移除 `said("p", "unsigned")`,页面中英两种语言均失去打开步骤,`pnpm e2e e2e/text.spec.ts` 的 7 项仍全部通过。修复方向:在中文首次加载、英文首次加载和语言切换后,明确断言下载区说明可见,并独立核对“系统设置 / System Settings”“隐私与安全性 / Privacy & Security”“仍要打开 / Open Anyway”。证据:`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-r21-evidence-7s8j6kd8/mutation-unsigned.log`。原始实现已独立通过构建、13 项单元测试、34 项端到端测试及注释检查;临时源码副本与构建产物已删除,原始源码哈希核对通过。 diff --git a/tasks/reviews/R2.1-r2.md b/tasks/reviews/R2.1-r2.md new file mode 100644 index 0000000..6a316c3 --- /dev/null +++ b/tasks/reviews/R2.1-r2.md @@ -0,0 +1,3 @@ +FAIL + +site/e2e/night.spec.ts:82:对比度断言只合成文字颜色的 alpha,遗漏元素及祖先的 CSS opacity,上轮要求的有效透明度校验仍有缺口。具体复现:在临时副本中添加 `#download a { opacity: 0.1; }`,运行 `pnpm e2e e2e/night.spec.ts --grep '4.5'`,浅色与深色两项断言均通过;独立读取源码下载链接的颜色、背景及有效透明度,合成后的实际对比度分别为 1.1425:1、1.1874:1,违反 R2.1 与 PRD §20.3 的 4.5:1 回归要求。修复方向:计算文字的有效透明度时纳入元素与祖先的 opacity,并用该低透明度场景验证两种主题的断言能捕获退化。证据:`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-r21-r2-fh521a5u/mutation-opacity.log`。上轮原始 alpha 变异已使两项对比度断言失败,删除打开步骤已使新增存在性断言失败;原始代码独立通过构建、13 项单元测试、35 项端到端测试(含外部下载链接检查)及注释检查。临时源码副本和构建目录已删除,原始源码哈希核对通过。 diff --git a/tasks/reviews/R2.1-r3.md b/tasks/reviews/R2.1-r3.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/R2.1-r3.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/R2.15-r1.md b/tasks/reviews/R2.15-r1.md new file mode 100644 index 0000000..fd1bfb9 --- /dev/null +++ b/tasks/reviews/R2.15-r1.md @@ -0,0 +1,9 @@ +FAIL + +scripts/inert-lo.sh:14 — 注销操作缺少幂等性,阻断后续构建。复现:对真实构建包的副本先执行 `lsregister -f`,再连续执行两次 `scripts/inert-lo.sh`;第一次退出 0,第二次退出 1,报 `failed to scan ...: -10814`。尚未登记的新副本同样退出 1。`scripts/build-libreoffice.sh:87` 每次构建都调用该脚本,已注销状态因此使正常重建失败。修复方向:明确识别并接受登记记录已经不存在的状态,继续暴露其他注销错误,覆盖首次执行与重复执行。 + +tests/scripts/lo-unregistered.sh:23 — `|| true` 吞掉 Swift 查询失败,并将空输出当作验收成功。复现:在 PATH 前置一个向 stderr 输出错误并退出 42 的 `swift`,运行该检查仍退出 0,打印 `ok: no LibreOffice in Open With`,整个 NSWorkspace 查询均未执行。修复方向:单独检查 Swift 的退出状态,成功取得查询结果后再筛选 LibreOffice;查询失败必须使验收失败。 + +tests/scripts/lo-unregistered.sh:10 — PlistBuddy 的读取错误被当作声明键缺失。复现:在隔离副本中创建预期的 `LibreOffice.app/Contents/Info.plist`,内容写入 `this is invalid plist data`,运行检查仍退出 0 并打印成功。损坏的构建包配置因此通过结构验收。修复方向:先要求 plist 存在、可读且解析成功,再检查各声明键是否缺失;为损坏或缺失 plist 添加失败用例。 + +crates/ely-lok/src/client/tests.rs:294 — 官方内核负向测试接受任意启动错误,无法证明提取出的内核实际经过版本与图块检查。复现:官方内核目录存在时,设置 `ELY_LOK_WORKER=/usr/bin/false`,单独运行 `client::tests::an_unpatched_build_is_rejected`,测试仍在 0.00 秒内通过,LibreOffice 完全未加载。独立实测提取出的官方内核能返回固定 BuildId,图块自检改写字节数为 0。修复方向:要求负向用例确实抵达该未打补丁的图块拒绝,并核对对应错误类型与原因;worker 启动失败、依赖缺失和版本错误应使该用例失败。 diff --git a/tasks/reviews/R2.15-r2.md b/tasks/reviews/R2.15-r2.md new file mode 100644 index 0000000..80a74fb --- /dev/null +++ b/tasks/reviews/R2.15-r2.md @@ -0,0 +1,3 @@ +FAIL + +scripts/inert-lo.sh:9 — plist 解析错误被吞掉,新增的 -10814 容错使损坏的构建包也报告成功。复现:在隔离副本的 LibreOffice.app/Contents/Info.plist 写入 `this is invalid plist data`,执行该脚本退出 0,打印 `claims no files and is unregistered`;同一副本运行 `tests/scripts/lo-unregistered.sh` 退出 1,报告 plist 解析失败。四个 PlistBuddy 查询的错误均被当成键缺失,lsregister 对损坏包返回的 -10814 又被第 16 行接受;调用此脚本的构建流程因此把无效包配置当成成功。修复方向:删除声明键和注销之前,先验证 Info.plist 存在、可读且解析成功,并覆盖损坏 plist 的失败用例。 diff --git a/tasks/reviews/R2.15-r3.md b/tasks/reviews/R2.15-r3.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/R2.15-r3.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/R2.16-r1.md b/tasks/reviews/R2.16-r1.md new file mode 100644 index 0000000..923bb8a --- /dev/null +++ b/tasks/reviews/R2.16-r1.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-lok/src/view/sheet.rs:26 — 打印坐标按当前 ppt 转成图块坐标,滚动锚点却继续沿用旧比例的图块 twips;每行截断产生的累计偏移随缩放改变,导致视口跳到另一批单元格,违反 LOK-04。实测使用 large.rs 的 SHORT_ROWS 样本、2 倍 DPI、1100×720 窗口,在 100% 下通过名称框跳到 B4998,可见行是 4988–5017;点击缩小到 80% 后,可见行变为 4923–4958,名称框仍为 B4998,选中格已移出视口。方向:缩放时先按旧比例把锚点还原为打印坐标,再按新比例换回图块坐标并重算滚动位置;增加真实工作表的大行号缩放锚点断言。证据:/tmp/r216-review-evidence.log。 + +crates/ely-lok/src/view/tests/large.rs:103 — R2.16 的拖选故障缺少回归保护;这里和 client/tests/rules.rs:507 的新增用例都只发送按下、抬起,并只断言单格地址。实测用临时 worker 代理把手势的移动及抬起坐标固定在按下处,两项 a_click_far_down 测试仍全部通过;同一代理下,真实窗口从 B4999 拖到 D5002,名称框仅得到 B4999,原版得到 B4999:D5002。方向:增加经过实际鼠标拖动路径的大行号区域选择用例,断言完整起止范围,并确认这种拖选失效变异会使测试失败。独立构建通过,原版 ely-lok 的 221 项测试和 ely-table 的 43 项测试通过;变异证据:/tmp/r216-drag-mutation.log、/tmp/r216-drag-native-mutation.log。 + +crates/ely-lok/src/client/tests/rules.rs:501 — 新增点击用例使文件从 487 行增长到 513 行,违反 agents.md 的每文件最多 500 行规则;wc -l 可直接复现。方向:按测试主题拆分用例,使各文件保持在 500 行以内。 diff --git a/tasks/reviews/R2.18-r1.md b/tasks/reviews/R2.18-r1.md new file mode 100644 index 0000000..c3cf999 --- /dev/null +++ b/tasks/reviews/R2.18-r1.md @@ -0,0 +1,3 @@ +FAIL + +site/src/text.ts:94 — 截图翻译循环及第 20、57 行的 `shot` 文案已成为死代码,违反 agents.md 第 6 条。具体场景:打开首屏,依次进入六个应用锚点并切换中英文;浏览器探针在全部位置均得到 `[data-shot]` 数量 0、`[data-t=shot]` 数量 0,同时存在六个视频元素。搜索 `site/src`,`data-shot` 与 `dataset.shot` 仅在此循环出现,现有页面生成逻辑没有对应元素生成点。修复方向:删除过期的截图翻译循环及其两份文案。 diff --git a/tasks/reviews/R2.18-r2.md b/tasks/reviews/R2.18-r2.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/R2.18-r2.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/R2.2-r1.md b/tasks/reviews/R2.2-r1.md new file mode 100644 index 0000000..a45cb9d --- /dev/null +++ b/tasks/reviews/R2.2-r1.md @@ -0,0 +1,7 @@ +FAIL + +site/src/tour.ts:341:ResizeObserver 只观察 pin 与 hero,模型自身尺寸变化无法触发 moved,违背 R2.2 的模型尺寸变化后重新定位要求。复现:1280×1000、减少动效,聚焦 ElyPDF,将 .model .stage 的内联 width 与 height 同比缩至 80%,等待 300ms;建筑中心已变为 x=736.979,标签仍在 x=877.313,偏移约 140px。手动向 .model 派发 moved 后标签回到 x=736.969。修复方向:观察实际模型的尺寸变化,并用变化后的几何更新可见标签;补充独立模型缩放的回归断言。 + +site/src/touch.ts:37:添加 lifted 后立即测量,此时读取的是 translate 过渡开始时的几何;过渡结束没有再次定位,标签停留在旧位置。复现:1280×1000、普通动效,等待初始生长完成后聚焦 ElyDoc,再等待 300ms;标签底边 y=368.281,与抬升后的建筑顶边 y=368.282 几乎重合,按 place 的当前几何计算应为 y=328.566,误差约 39.7px。连续三次复现,手动派发 moved 后底边变为 y=328.563。修复方向:在建筑交互过渡期间及结束后同步标签位置,并覆盖普通动效下的聚焦与按压路径。 + +site/e2e/touch.spec.ts:160:新增定位断言只检查横向范围,缺少纵向位置与当前建筑顶边的关系;整组 reducedMotion 设置也跳过了上条缺陷涉及的过渡。复现:在临时测试副本的 beforeEach 中注入 .tour .caption { top: 0px !important; },标签固定在模型顶部,7 项 touch 测试仍全部通过,其中包括窗口变窄的新增测试。修复方向:断言标签的横向中心与纵向底边均对应当前几何,覆盖模型尺寸单独变化及普通动效结束,并要求该定位破坏探针使测试失败。 diff --git a/tasks/reviews/R2.2-r2.md b/tasks/reviews/R2.2-r2.md new file mode 100644 index 0000000..775f243 --- /dev/null +++ b/tasks/reviews/R2.2-r2.md @@ -0,0 +1,5 @@ +FAIL + +site/src/touch.ts:94:用 SVG 元素宽度除以 viewBox 宽度计算一格的屏幕高度,忽略 preserveAspectRatio 按高度限制缩放的情况,模型尺寸变化后标签位置仍会偏离当前建筑几何。复现:1280×1000、减少动效,聚焦 ElyPDF,仅把 .model .stage 的内联 height 缩至原值的 80%,保持 width;等待 300ms,建筑顶边为 y=580.363,标签底边为 y=532.703,间距为 47.660px;getScreenCTM() 给出的当前一格高度为 31.772px,标签向上偏离 15.888px,连续三次结果相同。上一轮宽高同比缩放与普通动效结束后的场景已通过。修复方向:从 SVG 实际坐标变换计算纵向一格的屏幕尺寸,覆盖宽度、独立高度及同比尺寸变化。 + +site/e2e/touch.spec.ts:152:drift 重复实现中的宽度换算和静止命中层公式,断言与实际建筑顶边的关系缺少独立验证,会接受上条定位错误。复现:隔离副本中仅将第 172 行的尺寸操作改为 svg.style.height = `${svg.getBoundingClientRect().height * 0.8}px`,9 项 touch 测试全部通过,此时独立读取建筑轮廓与标签边界可见 15.888px 偏差。原版构建、13 项单测、38 项端到端测试均通过;上一轮 top: 0px !important 破坏探针现已使两项尺寸定位测试失败。修复方向:以实际渲染的建筑边界及独立坐标变换核对双轴位置,加入独立高度变化的回归,并要求上述偏差使断言失败。 diff --git a/tasks/reviews/R2.2-r3.md b/tasks/reviews/R2.2-r3.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/R2.2-r3.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/R2.4-r1.md b/tasks/reviews/R2.4-r1.md new file mode 100644 index 0000000..22d2d9b --- /dev/null +++ b/tasks/reviews/R2.4-r1.md @@ -0,0 +1,3 @@ +FAIL + +crates/ely-lok/src/view/tests/input.rs:308:切页回归断言将 `entry.stale` 当作成功;旧图块标为过期后仍会被 `prepare` 绘制,因此该测试允许画布持续显示上一页。测试只塞入人工图块、调用 `show_part` 与 `flush`,全程没有驱动重绘或核对第二页内容。具体复现:在隔离副本的 `lane.rs:105` 后加入 `self.fetching.extend(self.tiles.keys().copied());`,使切页后的新图块请求被视为正在读取,`switching_slides_repaints_the_shown_tiles` 仍通过;实际驱动 `prepare` 并比对第二页像素的探针报 `new page tiles must finish repainting`。同一像素探针在原实现通过,且覆盖连续切页;原实现的 worker、ElyShow 构建及 43 项视图测试通过,真窗口点击也显示第二页。修复方向:用实际第一张幻灯片图块建立基线,切页后驱动绘制并等待新图块完成,核对其像素属于目标页,并让上述刷新受阻变异触发失败。复现日志:`/tmp/ely-r2.4-mutant-tests.log`;原实现像素验证:`/tmp/ely-r2.4-pixels.log`。探针源码副本与独立构建目录已删除。 diff --git a/tasks/reviews/R2.4-r2.md b/tasks/reviews/R2.4-r2.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/R2.4-r2.md @@ -0,0 +1 @@ +PASS