diff --git a/crates/ely-lok/src/client.rs b/crates/ely-lok/src/client.rs index 06af277..78d8f46 100644 --- a/crates/ely-lok/src/client.rs +++ b/crates/ely-lok/src/client.rs @@ -73,9 +73,16 @@ pub struct Doc { id: u64, /// The document's revision when this handle was taken. pub(crate) rev: u64, + /// LOK_DOCTYPE: 0 text, 1 spreadsheet, 2 presentation. + kind: u64, } impl Doc { + /// Whether this is a spreadsheet. + pub fn is_sheet(&self) -> bool { + self.kind == 1 + } + /// Whether this handle saw a later state than `other`. pub fn newer_than(&self, other: &Doc) -> bool { self.rev > other.rev @@ -217,9 +224,10 @@ impl Worker { pack.u64(at(COLUMNS.iter().position(|c| c == column))) }); let payload = self.request(tag::IMPORT, &pack.bytes())?; - let id = Unpack::new(&payload).u64()?; + let mut reply = Unpack::new(&payload); + let (id, kind) = (reply.u64()?, reply.u64()?); self.feed.opened(id); - Ok(Doc { worker: self.session, id, rev: 0 }) + Ok(Doc { worker: self.session, id, rev: 0, kind }) } /// Loads `url` with its password; a locked one is PermissionDenied. @@ -232,9 +240,10 @@ impl Worker { error } })?; - let id = Unpack::new(&payload).u64()?; + let mut reply = Unpack::new(&payload); + let (id, kind) = (reply.u64()?, reply.u64()?); self.feed.opened(id); - Ok(Doc { worker: self.session, id, rev: 0 }) + Ok(Doc { worker: self.session, id, rev: 0, kind }) } /// Runs a macro by its URL (LOK-11 probe). diff --git a/crates/ely-lok/src/client/feed.rs b/crates/ely-lok/src/client/feed.rs index d715d77..b0748c2 100644 --- a/crates/ely-lok/src/client/feed.rs +++ b/crates/ely-lok/src/client/feed.rs @@ -181,17 +181,17 @@ mod tests { }; feed.push(1, first); feed.push(1, second); - let batch = feed.wait(&Doc { worker: 0, id: 1, rev: 0 }, 0, 0).expect("a batch"); + let batch = feed.wait(&Doc { worker: 0, id: 1, rev: 0, kind: 0 }, 0, 0).expect("a batch"); assert_eq!((batch.seq, batch.all, batch.rects.len()), (2, true, 1)); assert_eq!( batch.states, [("b".to_string(), "1".to_string()), ("a".into(), "2".into())] ); - let idle = feed.wait(&Doc { worker: 0, id: 1, rev: 0 }, 2, 0).expect("the snapshot"); + let idle = feed.wait(&Doc { worker: 0, id: 1, rev: 0, kind: 0 }, 2, 0).expect("the snapshot"); assert!(idle.rects.is_empty() && idle.states.is_empty() && idle.seq == 2); feed.closed(1); assert!( - feed.wait(&Doc { worker: 0, id: 1, rev: 0 }, 2, 0).is_err(), + feed.wait(&Doc { worker: 0, id: 1, rev: 0, kind: 0 }, 2, 0).is_err(), "a closed document" ); } diff --git a/crates/ely-lok/src/view/render.rs b/crates/ely-lok/src/view/render.rs index efc8558..e9ee386 100644 --- a/crates/ely-lok/src/view/render.rs +++ b/crates/ely-lok/src/view/render.rs @@ -251,6 +251,10 @@ impl LokView { /// Room beside a narrow document, which sits centred. pub(super) fn slack(&self) -> (f32, f32) { + // A sheet's grid grows with the view; it starts left. + if self.doc.is_sheet() { + return (0.0, 0.0); + } let extent = self.extent(); let slack = |view: f32, doc: i32| (view - self.geom.px_of(doc)).max(0.0) / 2.0; // A fitted page sits centred both ways. diff --git a/crates/ely-lok/src/view/tests/large.rs b/crates/ely-lok/src/view/tests/large.rs index ad91428..22aeb31 100644 --- a/crates/ely-lok/src/view/tests/large.rs +++ b/crates/ely-lok/src/view/tests/large.rs @@ -186,7 +186,12 @@ fn a_sheet_brings_far_cut_and_frozen_cells_into_view() { let (view, worker) = live_view_of(app, &install, "short.fods", SHORT_ROWS); let json = worker.lock().expect("the worker lock").values(&view.read(app).doc, crate::sheet::GEOMETRY).expect("geometry"); view.update(app, |view, cx| { + let wide = Bounds { origin: point(px(0.0), px(0.0)), size: size(px(6000.0), px(200.0)) }; + view.prepare(wide); + assert_eq!(view.spare, (0.0, 0.0), "before its geometry a sheet starts left"); view.set_geometry(crate::sheet::geometry(&json).expect("parsed"), cx); + view.prepare(wide); + assert_eq!(view.spare, (0.0, 0.0), "and stays left after it"); view.viewport = (800.0, 200.0); let (width, height) = (view.geom.twips_of(view.viewport.0), view.geom.twips_of(view.viewport.1)); let shows = |view: &LokView, rect: crate::Rect| { diff --git a/crates/ely-lok/src/worker.rs b/crates/ely-lok/src/worker.rs index 481342e..4b39fe9 100644 --- a/crates/ely-lok/src/worker.rs +++ b/crates/ely-lok/src/worker.rs @@ -187,8 +187,8 @@ fn answer(lok: &Lok, tag: u8, id: u64, payload: &[u8]) { .str() .and_then(|url| Ok((url, unpack.str()?))) .and_then(|(url, password)| load(lok, &url, password, "")) - .map(|id| Pack::new().u64(id).bytes()), - tag::IMPORT => text::import(lok, unpack).map(|id| Pack::new().u64(id).bytes()), + .and_then(loaded), + tag::IMPORT => text::import(lok, unpack).and_then(loaded), tag::EXPORT_TEXT => text::export(unpack).map(|()| Vec::new()), tag::DOC_SIZE => unpack .u64() @@ -339,6 +339,12 @@ fn load(lok: &Lok, url: &str, password: String, filter: &str) -> io::Result Ok(id) } +/// The reply to a load: the number and LOK_DOCTYPE. +fn loaded(id: u64) -> io::Result> { + let kind = with_doc(id, |doc| doc.kind())?; + Ok(Pack::new().u64(id).u64(kind as u64).bytes()) +} + /// Stops watching and frees document `id`. fn close(id: u64) { let mut docs = docs().lock().expect("the docs lock"); diff --git a/tasks/BUGS.md b/tasks/BUGS.md index f142100..d6b9170 100644 --- a/tasks/BUGS.md +++ b/tasks/BUGS.md @@ -4,7 +4,7 @@ - [x] T-01 内核的图块与命中按像素网格,Ely 的光标、选区、表头按文档 twips,越远越错位 — R2.16 已修(图块 twips 与打印 twips 互换) - [x] T-02 单元格光标露出边缘即把视图滚到中央,点击与拖选时视图跳动 — R1 拖选中仍滚、超宽格跳回起点;R2 PASS -- [ ] T-03 视口比工作表宽时网格先居中再左移一次 +- [x] T-03 视口比工作表宽时网格先居中再左移一次 — 载入回复带文档种类,表格不居中;R1 测试漏几何到达前的首帧;R2 PASS - [x] T-04 从不调用 `setClientZoom`,内核命中与几何按默认缩放算 — R2.16 已修(worker 每次报可见区前 setClientZoom) - [ ] T-05 文档尺寸按内核像素取整,表尾被裁 - [ ] T-06 滚轮向下被内核平铺区卡住 @@ -95,3 +95,8 @@ - [ ] X-12 恢复路径在界面线程做文件 I/O - [ ] X-13 非 macOS 构建里仍提供只会报“未构建”的入口 - [ ] X-14 `agents.md` 引用的 `docs/engineering-risks.md` 不存在 + +## 0.1.2 发布 + +- [ ] REL-1 dmg 里有指向“应用程序”的快捷方式,可直接拖入安装(用户 2026-10-09 报) +- [ ] REL-2 修完上列各条后打包、发布 0.1.2,官网指向新发布页 diff --git a/tasks/reviews/T-03-r1.md b/tasks/reviews/T-03-r1.md new file mode 100644 index 0000000..7469b16 --- /dev/null +++ b/tasks/reviews/T-03-r1.md @@ -0,0 +1,3 @@ +FAIL + +crates/ely-lok/src/view/tests/large.rs:189 — T-03 回归测试遗漏几何数据到达前的首次绘制。测试先调用 set_geometry,再准备宽视口,因此允许依赖几何就绪的实现通过。具体复现:在隔离副本中将 render.rs:255 的 self.doc.is_sheet() 改成 self.sheet.is_some(),现有 a_sheet_brings_far_cut_and_frozen_cells_into_view 仍通过;对同一 short.fods 在 set_geometry 前绘制 6000×200 视口,首块原点从应有的 (10,20) 变成 (2123.5667,20),横向偏移 2113.5667 像素。ElyTable 的几何由 book.rs 的 reread 异步读取,这个首帧窗口对应实际加载路径。恢复原实现后,同一首帧探针通过。修复方向:在 set_geometry 前加入宽视口首帧断言,核对首块的实际绘制位置和命中坐标;几何到达后再次核对位置稳定。证据:/tmp/ely-t03-mutant-late.log、/tmp/ely-t03-firstframe-mutant.log、/tmp/ely-t03-firstframe-baseline.log。 diff --git a/tasks/reviews/T-03-r2.md b/tasks/reviews/T-03-r2.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/T-03-r2.md @@ -0,0 +1 @@ +PASS