Files
ElyOffice/docs/BugList.md
T
ZacharyZhang-NYandClaude Opus 5.5 c5cb153f72 Page warnings log their line only, never words or source (M-11)
A document can forge both a console message and its source (KaTeX's
\errmessage, data: scripts, sourceURL, encoded paths), so the display
handler logs a line number and nothing the page wrote. The page's own
diagnostics reach the app over elyQuery: a missed reveal sends its
line; the visual failure and diagram lines already did. A test calls
the real handler and checks the captured log.

Round 5 found only the missing test; it is added and mutation-checked,
with no sixth review under the five-round cap. M-15 is new: the print
page runs a document's scripts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:02:06 -04:00

67 KiB
Raw Blame History

缺陷清单

2026-10-09 对首版六个应用(ElyDoc、ElyTable、ElyShow、ElyPDF、ElyMark、ElyTree)与其共用 crate 的全量静态审查。逐行读了 apps/ 六个应用、crates/ 全部源码(含 C 垫片与 ElyMark 网页脚本);Calc 与 Impress 的内核行为对照上游 LibreOffice libreoffice-26-2 分支源码核实(sc/source/ui/view/*.cxx、sd/source/ui/view/*.cxx、desktop/source/lib/init.cxx)。审查容器里没有引擎与组件库,没有运行与构建;凡标“已确认”都是沿调用链读到底的结论,“可能”是路径成立但依赖未能运行核实的条件。

编号前缀:T ElyTable,S ElyShow,P ElyPDF,D ElyDoc,M ElyMark,G ElyTree,K LOK 桥(crates/ely-lok),X 共用外壳(ely-office、ely-files、ely-ui、ely-platform、ely-ipc)。严重度:致命=崩溃、丢数据或功能不可用;高=常见路径结果错或功能坏;中=边缘路径坏或界面误导;低=小问题。

统计

节 致命 高 中 低 合计
T ElyTable 与 LOK 画布 1 1 7 6 15
S ElyShow 2 0 2 6 10
P ElyPDF 1 3 4 6 14
D ElyDoc 0 2 2 4 8
M ElyMark 0 1 5 8 14
G ElyTree 0 0 3 11 14
K LOK 桥 0 1 1 2 4
X 共用外壳与平台 1 2 4 7 14
合计 5 10 28 50 93

致命:T-01 内核的图块与命中按像素网格,Ely 的光标、选区、表头按文档 twips,越远越错位;S-01 切换幻灯片后主画布不重画;S-03 ElyShow 的缩放一两帧后弹回“适合幻灯片”,缩放功能不可用;P-01 文件系统的 PermissionDenied 被拼成不存在的文案键,应用崩溃;X-01 设置窗口在前时 Cmd+Q 直接退出,跳过未保存询问并抹掉恢复点。

高:T-02 单元格光标露出边缘即把视图滚到中央,点击与拖选时视图跳动;D-01 导出或打印对话框开着时关闭其标签即崩溃;D-02 邮件合并以当前标签为模板,预览后生成的每份都是同一条记录;P-02 每次编辑落地清掉全部图块与缩略图,页面空白、刚画的墨迹消失;P-03 “编辑文字和图片”先命中图片对象再命中文字;一点抖动就移动了背景图;P-04 TEXT_BLOCKS 对每个文字块重跑一遍整页内容过滤,编辑工具长时间无反应;M-01 键入期间外部以相同内容重写文件,丢掉上次停顿之后键入的全部文字;K-01 非 macOS 的 worker 不运行 VCL 主循环,内核的回调与异步输入永远不送达;X-02 共用 settings.json 读不出时 Doc、Table、Show 根本起不来;X-03 第二个实例启动即清掉运行中实例的工作目录与恢复点。

用户报告的三个问题

ElyTable:点一个单元格,光标跳到别处;拖不出多格选区

根因是 T-01:Ely 把 Calc 的“打印 twips”(文档坐标)与“图块 twips”(按内核像素网格对齐的坐标)当成同一套。内核画图块与命中测试都按它自己每行每列取整后的像素网格(ScViewData::ToPixel 截断),而单元格光标、选区、表头几何都以文档 twips 报回;越往右下,两套坐标相差越多,到第 300 行左右相差整整一行。点击落在内核的像素网格上(选中的是鼠标下画出的那格),但光标框与表头按文档 twips 画,出现在相邻的格上,看起来就是“跳到别处”。T-02 让部分可见的格一被点中就把视图滚半屏,拖选到边缘时内容在指针下移动,选区随之错位。T-03、T-04 是同一组问题的两个侧面。

ElyShow:换幻灯片后主视图不变,要拉一下窗口才刷新

根因是 S-01:Impress 在 SwitchPage 期间压掉一切图块作废通知(sd::Window::LogicInvalidate 里 IsInSwitchPage() 直接返回),只发 LOK_CALLBACK_SET_PART;LokView 收到 SET_PART 只加计数,不作废图块,Input::Part 落地时也不作废。旧幻灯片的图块一直显示,直到窗口尺寸变化让适合缩放变了、全部图块退役重画。

ElyPDF:编辑不工作、画不出

鼠标、命中与坐标换算本身是对的(pages.rs 按工具分派到 press/drag_to/release 或 sketch_*,page_at/from_view 与垫片 placed() 互逆)。失败来自状态与时序:P-02 每次编辑落地都清掉全部图块与缩略图,页面先变成空白纸再一块一块重画,刚画的墨迹要等所在图块重画后才出现,重的页面上像“什么也没画出来”;P-05 上一笔还在落地时结束的下一笔被拒并提示“文档已改动”,连续手绘会丢笔画;P-03 “编辑文字和图片”先命中图片再命中文字,整页背景图或扫描件上永远点不到文字,点击稍一抖动就成了移动背景图;P-04 该工具的层读取在每个文字块上重跑一遍整页内容过滤,密集页面要等很久,等待期间点击被静默忽略;P-06 字体默认“原字体”,嵌入 Type 1/Type 3 字体的文件一应用就报原始引擎错误。另有 P-01:写到无权限目录等任何 PermissionDenied 都被拼成不存在的文案键,直接崩溃。

T ElyTable 与 LOK 画布(Calc)

T-01 内核的图块与命中按像素网格,Ely 的光标、选区、表头按文档 twips,越远越错位

  • 严重度:致命 | 置信度:已确认(上游源码核实)
  • 位置:crates/ely-lok/src/view/scroll.rs:112(doc_point 线性换算)、crates/ely-lok/src/view/render.rs:275-330(图块按 TILE_TWIPS 线性排布)、apps/ely-table/src/grid.rs:96-121(表头按 place() 线性换算)、crates/ely-lok/src/view.rs:382-387(单元格光标按打印 twips 直接用)
  • 现象:滚到几百行之后点一格,名称框显示的地址是鼠标下画出的那格,但光标框画在相邻行列上,表头标号也对不上;拖选时选区高亮与格线错开;行高非默认(如 0.2 cm)的表十几行就错一行。
  • 原因:上游 ScGridWindow::PaintTile(sc/source/ui/view/gridwin4.cxx:1459-1600)把图块起点换成像素(fTilePosXPixel = nTilePosX * nOutputWidth / nTileWidth),再用 ScPositionHelper/ScBoundsProvider 在累加取整的像素网格里找首行首列,以该列的像素起点为原点画;ScModelObj::postMouseEvent(docuno.cxx:852)把 twips 乘 GetPPTX() 成像素后 GetPosFromPixel(viewdata.cxx:2858)逐行 ToPixel 累加命中。默认行高 256 twips 取整成 17 像素(真值 17.07),每行差 0.067 像素,约 255 行差一行;默认列宽 1280 twips 取整成 85 像素,约 256 列差一列。而 sc_print_twips_msgs 下 CELL_CURSOR、TEXT_SELECTION、SheetGeometryData 都是文档 twips,上游专门有 ScViewData::GetPrintTwipsPosFromTileTwips(viewdata.cxx:2637)做两套坐标的换算,Collabora Online 的客户端也以 SheetGeometry 在两者之间换算;Ely 没有这一步。
  • 复现:新建工作簿,键入 1 回车三百次(或打开任何几百行的 xlsx),滚到第 300 行,点 A300:光标框落在 A301 附近,左侧表头标的是 299;拖选 A300:C305,高亮与格线错开。
  • 修法:以 .uno:SheetGeometryData 为准在客户端建像素网格(按内核同样的 ToPixel 截断累加),把鼠标与可见区从文档 twips 换成图块 twips 再发给内核,把 CELL_CURSOR、选区、引用标记从文档 twips 换回图块 twips 再画;并调用 setClientZoom 让内核的 PPTX 与 Ely 的图块比例一致(见 T-04)。

T-02 单元格光标露出边缘即把视图滚到中央,点击与拖选时视图跳动

  • 严重度:高 | 置信度:已确认
  • 位置:crates/ely-lok/src/view/scroll.rs:45-64(show),调用处 crates/ely-lok/src/view.rs:372-387
  • 现象:点一个只露出一半的格(右缘或底缘),整张表横向跳半屏、纵向跳三分之二屏;拖选拖到边缘时内容在指针下滑走,选区落错位置。
  • 原因:show() 对任何不完全可见的矩形都重置滚动:scroll.0 = x - fx - vw / 2、scroll.1 = y - fy - vh / 3,不是最小滚动量;而 apply_batch 对每次光标或单元格光标变化都调它。
  • 复现:把窗口缩到只露出 A 到 H 列,点半露的 H 列;或从 A1 向右下拖选到视口边缘。
  • 修法:show() 只滚到刚好露出目标(scroll = x + w - vw 或 x),与一般表格软件一致;拖选期间不因选区变化滚动。

T-03 视口比工作表宽时网格先居中再左移一次

  • 严重度:中 | 置信度:已确认
  • 位置:crates/ely-lok/src/view/render.rs:243-253(spare 居中)、crates/ely-lok/src/view/scroll.rs:99-108(extent)
  • 现象:宽屏上打开工作簿,表格先出现在视口中央、两侧是空底色,内核按 setClientVisibleArea 扩大平铺区并报 DOCUMENT_SIZE_CHANGED 后整张表再跳到左边;窗口每次拉宽都重现一次。
  • 原因:prepare 把窄文档居中(slack(viewport.0, extent.0)),这对 Writer 页面合理,对 Calc 的网格不合理:Calc 的文档尺寸只是已用区加余量(上游 GetTiledRenderingArea),随可见区增长。
  • 修法:Calc 文档不居中(spare = (0, 0)),或以 reach 先把 extent 撑到至少一个视口宽。

T-04 从不调用 setClientZoom,内核命中与几何按默认缩放算

  • 严重度:中 | 置信度:已确认
  • 位置:crates/ely-lok/src/ffi/document.rs(无对应调用)、crates/ely-lok/src/view/zoom.rs:44-62(缩放只改本地 geom)
  • 现象:Ely 缩放到 125% 或 200% 后,内核的 PPTX 仍是 100% 的 1/15,getDocumentSize、GetPosFromPixel 的像素取整都按 100% 网格算;与 T-01 叠加,缩放越大错位越明显(图块按 Ely 的比例画,命中按内核的比例算,两套像素网格的取整不同)。
  • 原因:上游 ScModelObj::setClientZoom(docuno.cxx:1100)是客户端告知像素/twips 比例的唯一入口,Collabora Online 在载入与每次缩放时都调;Ely 的 Document 没有包装它。
  • 修法:载入与每次 zoom_at 后发 setClientZoom(tile_pixels, tile_pixels, TILE_TWIPS, TILE_TWIPS)。

T-05 文档尺寸按内核像素取整,表尾被裁

  • 严重度:低 | 置信度:已确认(上游源码核实)
  • 位置:crates/ely-lok/src/view.rs:360-368(doc_size 直接取 getDocumentSize)
  • 现象:getDocumentSize(上游 docuno.cxx:740-780)返回“像素累加后再除 PPTX”的 twips(85 像素×15=1275 twips 一列,而不是 1280),平铺区越大短得越多;滚动到最右最下时最后一列、最后几行露不全。
  • 修法:以几何(SheetGeometryData)算 Calc 的文档范围,不用 getDocumentSize。

T-06 滚轮向下被内核平铺区卡住

  • 严重度:低 | 置信度:已确认
  • 位置:crates/ely-lok/src/view/scroll.rs:20-28(clamp_scroll 以 extent 为界)、render.rs:264-268(可见区在绘制时才发)
  • 现象:快速向下滚动空白区时每次只能滚到当前文档底,内核收到可见区后再扩 25 行,下一次滚动才能继续;连续滚动一顿一顿。
  • 修法:Calc 下 extent 至少放到 scroll + 2 个视口,或滚动时提前发可见区。

T-07 表头点选、行高列宽拖动是“按编号的命令”,而每次点击与拖选都推进修订,紧接着的表头操作被当作过期拒绝

  • 严重度:中 | 置信度:已确认(时序窗口为一次后台读取)
  • 位置:crates/ely-lok/src/commands/sheet.rs:154-164(select.column/select.row 的 Col/Row 在 NUMBERED 内,commands.rs:63-72)、crates/ely-office/src/lok.rs:187-202(run_in 对编号命令取 read_at 发 EXPECT)、crates/ely-lok/src/worker/revision.rs:51-66(鼠标按下、松开、按键拖动都 bump)、apps/ely-table/src/grid.rs:204-211、:306-327、apps/ely-table/src/book.rs:199-246(reread 之后 read_at 才更新)
  • 现象:点一格(或拖选、键入)后马上点列头或行头、或拖完列边:命令带着上一次读取的修订发 EXPECT,内核修订已被鼠标事件推进,回 stale:,弹“文档在此间已改动,请重试”,整列没有选中。read_at 还没来得及由 reread 更新时(刚打开工作簿、worker 忙于画图块时)必现。
  • 原因:列号、行号不会被光标移动改变,EXPECT 的保护在这里是多余的;而所有鼠标按下与松开(含拖选的每一步移动)都计入修订。
  • 修法:select.column、select.row、columns.width、rows.height 不作编号命令(或 run_in 对它们不带 at),只保留对工作表、对象、参考线等真会被改动重排的编号做 EXPECT。

T-08 自动筛选的列菜单对光标所在的表而不是按钮所在的表筛选、排序

  • 严重度:中 | 置信度:可能(内核侧按补丁追到;取决于单元格光标在哪)
  • 位置:apps/ely-table/src/data.rs:412-437(column_asked 发 data.filter/data.sort)、:379-409(ask_column);内核 patches/libreoffice/0017-lok-calc-filter-validation-columns.patch 的 lcl_Query(rView.GetDBData())与 SID_SORT 路径(GetDBData())
  • 现象:单元格光标在筛选区之外(刚在别处键入)时点列头的筛选箭头、勾选值或“升序”:要么弹内核错误(Check::Undoable 失败),要么筛选、排序了光标所在的另一块数据。菜单本身能开,因为 .uno:AutoFilterValues?col=&row= 用的是按钮格。
  • 原因:内核自己的菜单对按钮格 GetDBAtCursor(nCol, nRow) 操作,补丁后的 LaunchAutoFilterMenu 不移动光标;Ely 的答复却经 GetDBData()(当前光标或选区)解析。
  • 修法:column_asked 先 cell.goto 到 address(column, row)(Dialog::Column 记下 row),或让补丁按按钮格取数据区。

T-09 导入文本文件时整文件的 UTF-8 校验在界面线程跑

  • 严重度:中 | 置信度:已确认
  • 位置:apps/ely-table/src/text.rs:241-245(in_window 里 Pending::new)、:58-63、:108-116(guess_encoding 对全文 from_utf8)
  • 现象:打开几百 MB 的 CSV,导入对话框出现前整窗冻结几百毫秒到数秒(树莓派更久)。
  • 原因:读字节在后台,Pending::new(编码与分隔符猜测)却在主线程闭包里。
  • 修法:猜测放进后台任务,in_window 只接现成的 Pending。

T-10 CSV 导入对话框开着时打开任何别的对话框,待导入的文件静默消失

  • 严重度:中 | 置信度:已确认
  • 位置:apps/ely-table/src/text.rs:254-262(next_import 把 Pending 移进 Dialog::Import);无条件 self.dialog = Some(..) 的:apps/ely-table/src/output.rs:218、rules.rs:146、:278、:341、analysis.rs:123、:149、page.rs:146、protect.rs:61、formulas.rs:119、:209、data.rs:231、:317、:397
  • 现象:访达双击 .csv 出现导入对话框,按 Cmd+E 再取消:Pending(已检查字节的唯一副本)被丢,文件再也不打开,无提示。
  • 修法:替换 Dialog::Import 时把它放回 imports 队头,或有对话框开着时拒绝再开。

T-11 对话框比其标签活得久:导出、定义名称、重命名不可见却“开着”,挡住导入;名称表的“转到”激活已关闭的标签

  • 严重度:低 | 置信度:可能
  • 位置:apps/ely-table/src/dialog.rs:644-648(导出 sheet_in(*tab)?)、:563-571、:542-545;apps/ely-table/src/text.rs:255-257(next_import 要 dialog.is_none());apps/ely-table/src/formulas.rs:255-258(core.active = tab 不核对)
  • 现象:Cmd+E 后 Cmd+W 关掉该标签:对话框不画了但 self.dialog 仍为 Some,无法关闭,之后打开 .csv 没反应,直到再开关一次别的对话框;名称表的“转到”把 core.active 指到已关标签,正文显示“没有打开的文档”。ElyDoc 的 D-01 是同一类问题(那里直接崩溃)。
  • 修法:Book 实现 Host::tab_closed 清掉指向该标签的 dialog 与 choices;“转到”先核对 tab(tab).is_some()。

T-12 打印区域是整列或整行时页面设置无法应用

  • 严重度:中 | 置信度:可能(内核以 $A:$D 格式化整列区域)
  • 位置:apps/ely-table/src/page.rs:75-83(cell_ok 要求字母加数字)、:86-97(areas)、:169-176(page_asked 以 table.page.bad_reference 拒绝)
  • 现象:Excel 文件的打印区域 =Sheet1!$A:$D 预填进对话框,只改方向或缩放再按应用也被拒“Write ranges like A1:D20…”;不重写区域就用不了页面设置。
  • 修法:areas() 接受 COL:COL 与 ROW:ROW(内核 ParseAny 本就接受),或内核给的原值免校验。

T-13 打印失败报成“导出失败”(非 macOS 必现)

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-table/src/output.rs:147-150(print 把 print_later 的失败交给 output_failed,文案 table.export.failed)、crates/ely-platform/src/print.rs:87-89
  • 修法:单独的 table.print.failed 文案;平台未实现时禁用或说明。

T-14 导入扫描把整个文件的每个字段都建成字符串

  • 严重度:低 | 置信度:已确认(影响随文件大小)
  • 位置:apps/ely-table/src/text.rs:78-106(scan)、:158-173(split 返回全部行的 Vec<Vec<String>>)
  • 现象:只需要 8 行预览与每列的类型标志,却为 200 MB 的 CSV 分配约 1–2 GB 堆,树莓派上可能耗尽内存。
  • 修法:split 改为迭代器,只留前 SHOWN 行与列标志。

T-15 非 UTF-8 文本的默认编码跟系统区域而不是界面语言

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-table/src/text.rs:236(i18n::resolve(&locale::system()))
  • 现象:英文系统上把应用语言设成简体中文,打开 GB18030 的 CSV 默认仍是 Windows-1252(反之亦然),与 agents.md 的“中文界面 GB18030、英文界面 Windows-1252”不符。
  • 修法:用 ely_ui::i18n 解析后的当前语言。

S ElyShow(Impress)

S-01 切换幻灯片后主画布不重画

  • 严重度:致命 | 置信度:已确认(上游源码核实)
  • 位置:crates/ely-lok/src/events.rs:274(SET_PART 只加 cell.sheets)、crates/ely-lok/src/view/input.rs:231-235(show_part 只排队 Input::Part)、crates/ely-lok/src/view/lane.rs:95-124(Input 落地不作废图块)、apps/ely-show/src/slides.rs:244-247、apps/ely-show/src/deck.rs:308-314(只在母版视图切换时 invalidate)
  • 现象:在左侧缩略图条点另一张幻灯片(或经批注栏、演示检查跳转),缩略图高亮变了,主画布仍是上一张;拖一下窗口边才变。
  • 原因:上游 DrawViewShell::SwitchPage(sd/source/ui/view/drviews1.cxx:1003-1012)只发 LOK_CALLBACK_SET_PART;切换期间 sd::Window::LogicInvalidate(sd/source/ui/view/sdwindow.cxx:1016-1020)因 IsInSwitchPage() 直接返回,不发 INVALIDATE_TILES。Collabora Online 的图块请求自带 part,切页即换缓存;Ely 的 paint_tile 不带 part,图块缓存按坐标键保留,所以旧页一直显示。窗口尺寸变化时 fit_zoom 改了缩放、retire_tiles 才重画。Calc 切表经 SetTabNo 走 PaintGrid 会发作废,所以 ElyTable 没有这个症状。
  • 复现:新建演示,“幻灯片›新建”,点第一张缩略图。
  • 修法:LokView 在 Input::Part 落地(或收到 SET_PART)时 retire_tiles() 并清 failed,或把 part 编进图块键。

S-02 deck.rs 超过 500 行

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-show/src/deck.rs(506 行)
  • 原因:违反规则 5(文件不超过 500 行);工作区内只有这一个文件超限。
  • 修法:把 reread 的读取闭包或 Extra 的图片管理拆到独立模块。

S-03 ElyShow 的缩放一两帧后弹回“适合幻灯片”,缩放功能不可用

  • 严重度:致命 | 置信度:已确认
  • 位置:apps/ely-show/src/deck.rs:139-150(Host::observed 每次都 view.fit_page(cx))、crates/ely-office/src/open.rs:253(每个 Reported 都调 observed)、crates/ely-lok/src/view/lane.rs:121 与 view.rs:413(任何输入落地、任何内核事件批次都发 Reported)、crates/ely-lok/src/view.rs:262-268(fit_page 置 fit = true)、crates/ely-lok/src/view/zoom.rs:51(zoom_at 置 fit = false)、render.rs:256、:286-289
  • 现象:Cmd+=、Cmd+-、Cmd+滚轮、捏合、状态栏缩放控件、“视图”菜单:幻灯片放大一帧,随即跳回适合页面,状态栏百分比也跳回。agents.md 写的“手动缩放即停止适合”不成立。
  • 原因:缩放后重绘改变了可见区,排队 Input::Visible,落地即 Reported → observed → 重新 fit_page;没有可见区变化时下一批内核事件也一样。
  • 复现:打开任一演示,Cmd+=。
  • 修法:只在标签首次观察时适合一次(Extra 里记标志),observed 不再调 fit_page;Cmd+0 已由 Zooming::zoom(Reset) 处理。

S-04 选屏对话框被别的对话框顶掉后,show_starting 卡住、放映副本泄漏

  • 严重度:中 | 置信度:可能(多屏才会到选屏对话框)
  • 位置:apps/ely-show/src/slideshow/start.rs:180-183(置 show_starting、开对话框)、:289-310(只有取消与开始两条路清理)、无条件覆盖 self.dialog 的:apps/ely-show/src/new.rs:298、apps/ely-show/src/table.rs:24-28、apps/ely-show/src/remarks.rs:80、apps/ely-show/src/chart.rs:453-454;守卫 start.rs:123-125 只记日志
  • 现象:两块屏,F5 出选屏对话框时按 Cmd+N(或插入›表格、批注、图表)顶掉它;此后 F5、Shift+F5、放映菜单都没反应也无提示,show-N.odp 副本留在内核与磁盘里直到下次启动。同样的覆盖也会丢掉打了一半的批注或图表数据。
  • 修法:任何 ask_* 替换对话框时(或 watch_dialogs 发现种类离开 Dialog::Show)跑与取消相同的清理;或有对话框开着时拒绝再开。

S-05 一条批注的时间恰为午夜或只有日期,整个批注栏不显示

  • 严重度:中 | 置信度:可能(上游 utl::toISO8601 时分秒全零时不写时间部分;jiff 的 civil::DateTime 要求有时间)
  • 位置:apps/ely-show/src/remarks.rs:28-36(逐条 parse::<jiff::civil::DateTime>()?)、:168-176(一条失败整栏换成错误文字);内核 sd/source/ui/unoidl/unomodel.cxx 以 utl::toISO8601 写 dateTime
  • 现象:含 00:00:00 批注(LibreOffice “去除个人信息”保存的文件、只写 dc:date 的工具)的演示,右栏只显示“a bad comment time 2025-03-01: …”,画布上的标记还在但栏内无法编辑删除。ElyDoc 的 D-04 是同一类问题。
  • 修法:无时间部分时按 civil::Date 解析,单条失败只影响该条。

S-06 导出或打印的“从–到”填 0 时导出全部幻灯片

  • 严重度:低 | 置信度:可能
  • 位置:apps/ely-show/src/output/dialog.rs:82(value.round() as u32,无下限)、apps/ely-show/src/output.rs:53-59(pages() 只排序)、crates/ely-lok/src/worker.rs:403-430(from == 0 表示全部)、apps/ely-show/src/output.rs:117-125(targets 的 number as usize - 1,0 时溢出:发行构建回绕、调试构建 panic)
  • 修法:set 里夹到 1..=last,pages() 再校验。

S-07 母版视图下演示检查的行按幻灯片序号跳转

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-show/src/check.rs:184-194(show_part(part) 用幻灯片序号)、:112(check_show 无母版视图守卫,slides.rs:93-99 有)
  • 现象:显示母版时点“第 3 张”,内核对母版列表 setPart(2),显示的是第 3 个母版或空白,对话框关闭。
  • 修法:先退出母版视图,或母版视图下禁用行。

S-08 刚编辑过幻灯片就进备注栏键入,备注可能丢

  • 严重度:低 | 置信度:可能
  • 位置:apps/ely-show/src/notes.rs(notes.at 在聚焦期间冻结在上次读完的修订;run_on(tab, &at, "notes.set", …) 是编号命令,EXPECT 拒绝过期修订;强制重读后内核旧备注覆盖输入框,而 shown 已置为键入文字)
  • 现象:编辑幻灯片后在后台重读完成前点进备注框键入、失焦:提示“文档在此间已改动,请重试”,键入的备注被旧备注换掉;再失焦不再发送(text == shown)。
  • 修法:聚焦时取视图的 read_at() 更新 notes.at;发送失败时让 shown 保持内核文字以便下次失焦重发。

S-09 改尺寸失败后 size_asked 一直留着

  • 严重度:低 | 置信度:可能
  • 位置:apps/ely-show/src/design.rs:62-64(命令前置 size_asked)、apps/ely-show/src/deck.rs:343-347(只在读回 0.5 mm 内才清);用它的 deck.rs:71-74、apps/ely-show/src/insert.rs:144、apps/ely-show/src/media.rs:116、apps/ely-show/src/guides.rs:200、:318
  • 现象:slide.size 失败(worker 错误、无撤销步)后宽高字段显示内核没接受的尺寸,新参考线、媒体按它定位裁剪。transition_asked 有落地判定,尺寸没有。
  • 修法:命令报错时清掉,或像切换那样按序号落定。

S-10 开始放映的两处拒绝无提示(规则 2)

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-show/src/slideshow/start.rs:194-198(标签或屏幕已不在 → 只记日志并 discard;选屏对话框开着时关掉标签再按开始可达)、:123-125(正在启动 → 只记日志)
  • 修法:两处都弹提示。

P ElyPDF(MuPDF)

P-01 文件系统的 PermissionDenied 被拼成不存在的文案键,应用崩溃

  • 严重度:致命 | 置信度:已确认(文案层缺键即失败)
  • 位置:apps/ely-pdf/src/view.rs:123-126(restricted 拼 pdf.may.{needed})、:228(fail 对一切 PermissionDenied 都走 restricted(&error.to_string()))、apps/ely-pdf/src/protect.rs:315、apps/ely-pdf/src/ask.rs:507-509
  • 现象:导出、提取、拆分、签名、保护、附件另存写到不可写文件夹,或任何 OS 权限错误,不显示错误而是崩溃。
  • 原因:restricted 假定错误文字是 worker 的 restricted:<名>(client.rs:297-302),但 ely_files::export→save_atomic 保留 OS 的错误类别,holder()→same_file→canonicalize 同样,annotate() 里的 user::name() 读设置也可能是 PermissionDenied;得到的键 pdf.may.Permission denied (os error 13) 不在任何表里。
  • 复现:文件›导出… 到只读文件夹。
  • 修法:只在文字是 may::NAMES 之一时走 restricted,否则走 pdf.failed/office.error.save;或给 worker 的拒绝单独的错误类型。

P-02 每次编辑落地清掉全部图块与缩略图,页面空白、刚画的墨迹消失

  • 严重度:高 | 置信度:已确认
  • 位置:apps/ely-pdf/src/edits.rs:102-111(refresh 清 tiles、thumbs)、apps/ely-pdf/src/pages.rs:272-279(每帧只请求一块)
  • 现象:任何编辑(一笔手绘、勾一个复选框、填一个字段)后可见页面变成空白纸,再以每帧一块 512 像素的速度填回;期间刚加的批注看不见;缩略图也全部重画。图多的页面上每次编辑要几秒,感觉“画了没反应”。
  • 原因:LOK 画布作废时旧图继续显示到新图到达(agents.md 已定),PDF 视图没有照做:retire_tiles() 直接清空 self.tiles,page_view 画 bg(paper) 加空的 tiles。
  • 修法:旧图块保留到替换到达时才退役(paint() 的 tiles.insert 处已这样做),缩略图同。

P-03 “编辑文字和图片”先命中图片对象再命中文字;一点抖动就移动了背景图

  • 严重度:高 | 置信度:已确认
  • 位置:apps/ely-pdf/src/content.rs:172-187(命中顺序)、:206(1 点阈值)
  • 现象:整页背景图、扫描件、包裹整页的表单 XObject 上,点任何文字选中的都是图片(面板显示替换/删除);按下与松开相差 1 个页面点(适合宽度下约 1 像素)即成“移动对象”编辑,把背景图挪走。图下的文字永远编辑不到。
  • 原因:layer.drawn.iter().rposition(|d| inside(..)) 先于文字块查找,不看目标大小;release() 的阈值是页面点不是窗口像素。
  • 修法:对象整页或包住文字块时优先文字块;移动阈值按窗口像素(如 3 像素)。

P-04 TEXT_BLOCKS 对每个文字块重跑一遍整页内容过滤,编辑工具长时间无反应

  • 严重度:高 | 置信度:可能(代价推断,未计时)
  • 位置:crates/ely-pdf-engine/shim/ely_text.c:227-245、:156-179、:133-145(每块一次 pdf_filter_page_contents);apps/ely-pdf/src/content.rs:129-156(逐页读)、:169-171(层未到即静默返回)
  • 现象:密集页面上开编辑工具很久不出框,点击被忽略,图块与缩略图也停住(worker 单线程被占满)。
  • 修法:每页一遍干跑记下全部字形原点与字符,再逐块比对;或只对点中的块判定可编辑。

P-05 连续快画或快点被拒:“文档在此间已改动,未执行”

  • 严重度:中 | 置信度:已确认
  • 位置:apps/ely-pdf/src/annotate.rs:189-203(settled_edit)、apps/ely-pdf/src/content.rs:209-214、apps/ely-pdf/src/writes.rs:176-190、apps/ely-pdf/src/edits.rs:56-93
  • 现象:上一笔还在落地(annotate+重读页数、页框、书签)时结束的下一笔、下一个矩形、下一个复选框点击被丢弃,并把原因说成“文档改动”;多笔手绘丢笔。
  • 原因:settled() 是队列为空且不在编辑中;按坐标的批注不会被在途编辑作废(按编号的才会,worker 已以 stale: 拒绝),却被一律拒绝而不是排队。
  • 修法:按坐标的批注进编辑队列;拒绝只留给按编号的编辑,并改正文案。

P-06 默认字体“原字体”在 Type 1/Type 3 字体的文件上失败,只给引擎原话

  • 严重度:中 | 置信度:可能(pdf_font_writing_supported 只认 TrueType/CFF/OpenType)
  • 位置:apps/ely-pdf/src/editing.rs:77、crates/ely-pdf-engine/shim/ely_write.c:61-67、apps/ely-pdf/src/writes.rs:115、:138-142
  • 现象:LaTeX 或旧 PDF 上“应用”报“previewing text failed: the document's font cannot write new text”,不提示换字体(缺字形的情形倒有 pdf.text.missing 提示)。
  • 修法:像 Wrote::Missing 一样在面板里提示换字体,或读块时预判可写性、默认 Helvetica。

P-07 含连字的文字块永远不可编辑

  • 严重度:中 | 置信度:可能
  • 位置:crates/ely-pdf-engine/shim/ely_text.c:91-109(block_targets 按 stext 字符计数)、:112-130(take_glyph 按字形计数)、:167
  • 现象:含 fi、fl、ffi 连字的段落不出编辑框、点击无效;真放过去 ely_remove_block 也会报“N of M glyphs were found”。
  • 修法:按同一原点的字符分组计字形,或每字形按 ucslen 多计命中。

P-08 页面读取失败后每帧重试、每帧弹提示

  • 严重度:中 | 置信度:已确认
  • 位置:apps/ely-pdf/src/annotate.rs:76-110(ask_notes)、:113-134、apps/ely-pdf/src/content.rs:129-156(ask_layers)、apps/ely-pdf/src/attach.rs:184-205、apps/ely-pdf/src/sign.rs:49-75
  • 现象:某页的批注、层、附件、名称或签名读一次失败后,fail 弹提示、notify 重绘、重绘再问、再失败,没完没了;worker 死掉后每个可见页每帧一条错误提示,直到按“重新打开”。
  • 原因:失败不记页(图块有 failed/thumbs_failed,这些没有)。
  • 修法:各类读取加按页的失败集合(refresh() 清),引擎已结束时不再问。

P-09 识别文字的写入被拒后仍弹“成功”

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-pdf/src/ocr.rs:510-513(toast(.., Success) 在 on_same 之后无条件执行)
  • 修法:成功提示移进 on_same 的闭包。

P-10 预览回复被丢弃时“应用”静默无事

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-pdf/src/writes.rs:72-86、apps/ely-pdf/src/view.rs:241-247(read 丢弃编辑前的回复而不调 then)
  • 修法:改用带版本核对的 off,不符即提示 pdf.object.changed。

P-11 撤销或重做在 MuPDF 侧失败后 worker 的步数不再同步

  • 严重度:低 | 置信度:已确认
  • 位置:crates/ely-pdf-engine/src/bin/ely-pdf-worker/main.rs:62-86、:161-165
  • 修法:失败时 doc.sync(&self.mupdf, None)。

P-12 unhook 中途抛出时泄漏保留的对象

  • 严重度:低 | 置信度:已确认
  • 位置:crates/ely-pdf-engine/shim/ely_insert.c:17-49、:352-356(n 只在返回时发布,fz_always 以 0 释放)
  • 修法:以 int *n 传入并逐个递增。

P-13 优化导出的“之前”大小取磁盘文件而非当前文档

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-pdf/src/output.rs:217-224
  • 修法:量 worker 的快照,或标签有改动时不写“之前”。

P-14 文字块含控制字符时 JSON 回复不可解析

  • 严重度:低 | 置信度:可能
  • 位置:crates/ely-pdf-engine/shim/ely_text.c:240(%q 不是 JSON 转义;别处用的是 ely_json_string)
  • 现象:ToUnicode 坏掉的文件文字里有 U+0001–U+001F,text_blocks 解析失败,编辑工具永远读不到该页,并落入 P-08 的循环。
  • 修法:text、font 用 ely_json_string 写。

D ElyDoc(Writer)

D-01 导出或打印对话框开着时关闭其标签即崩溃

  • 严重度:高 | 置信度:已确认
  • 位置:apps/ely-doc/src/actions.rs:84-86(self.tab(*tab).expect("a modal keeps its tab")),对话框由 apps/ely-doc/src/output.rs:38-48 打开;关闭路径 crates/ely-office/src/close.rs:38-42、:142-154、:191-221
  • 现象:开着“导出…”或“打印…”对话框按 Cmd+W、点窗口关闭钮或 Cmd+Q,标签被移除后下一帧渲染 expect 失败,进程崩溃;Cmd+Q 时先于其余脏标签的保存询问崩溃,这些标签只剩 10 秒一次的恢复点。
  • 原因:Dialog::Output 记着标签号,dialog_element 每帧无条件解包;没有任何地方在标签关闭时清 self.dialog(Docs 未实现 Host::tab_closed),release_tab 还把 output() 排队的 when_settled 续动作丢掉。其他对话框(Ask、Link、Reference、外壳的 Unsaved)都是防御性写法。
  • 复现:打开任一文档 → 文件›导出… → Cmd+W。
  • 修法:dialog_element 在 tab() 为 None 时清掉对话框并返回 None;Docs 实现 tab_closed 丢掉指向该标签的 Dialog::Output。

D-02 邮件合并以当前标签为模板,预览后生成的每份都是同一条记录

  • 严重度:高 | 置信度:已确认
  • 位置:apps/ely-doc/src/merge.rs:88-94(source() 返回 core.active)、:152-164(预览)、:178-195(生成);apps/ely-doc/src/merge/run.rs:351-371(预览经 load_into 开新标签)、crates/ely-office/src/open.rs:231(新标签成为当前)
  • 现象:选 CSV、插字段、点“预览”(填好的副本开成新标签并成为当前标签,合并面板仍在右侧)再点“生成”:模板成了预览副本,字段早已替换成第 N 条记录,N 个输出文件内容相同,文件名还带预览标签名;再点预览是预览的预览。无任何提示。
  • 原因:Merge 不记来源标签,每次点击时取 core.active;单测 a_merge_keeps_its_source_when_the_tab_changes 直接传 source,没覆盖界面路径。
  • 复现:任一含 «Name» 的文档 → 邮件合并 → 三行 CSV → 预览 → 生成。
  • 修法:选 CSV 或插字段时记下来源标签号(或在 Extra 标记预览标签),面板显示来源名,预览与生成都用它,当前标签是预览副本时拒绝。

D-03 界面线程同步持 worker 锁读内核(样式、字体、书签、公式、分节名)

  • 严重度:中 | 置信度:已确认
  • 位置:apps/ely-doc/src/insert.rs:318-324(insert_section)、apps/ely-doc/src/refs.rs:81-86(read,书签、交叉引用、公式对话框用)、apps/ely-doc/src/props.rs:116-122(read_fonts)、:133-137(read_styles,每次切标签与 Reported 都跑)
  • 现象:保存大文档、邮件合并几百条记录(每条载入、替换、另存、关闭都持锁)期间,切标签或打开“插入›书签…”整窗冻结到锁空出来为止。
  • 原因:违反 2026-10-05 决策“界面线程不碰 worker”,这四处直接 worker.lock() 而不走 read_in/read_off。
  • 修法:改为后台读取,回来时核对仍是同一标签与视图。

D-04 一条批注的时间读不出,全部批注与修订都不显示

  • 严重度:中 | 置信度:可能(内核对无日期批注写 0-00-00,utl::toISO8601 的空日期即如此)
  • 位置:apps/ely-doc/src/review.rs:164-167、:373-393(threads 遇第一个解析失败即整体 Err)、:179-181
  • 现象:打开含无日期批注的 DOCX(w:comment 无 w:date),批注与修订两栏都空,并弹引擎错误提示;每次审阅事件重读再失败再弹。
  • 原因:单条解析失败被提升为整表失败,and_then 连已解析的 changes 一起丢掉。
  • 修法:逐条解析时间,失败的那条用占位时间;changes 不因批注失败而丢。

D-05 邮件合并某条记录失败时内核文档泄漏

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-doc/src/merge/run.rs:464-470
  • 原因:fill/save_as 以 ? 提前返回,close_doc 不执行,Doc 无 Drop;每次重试多漏一份,worker 的 N documents open 计数漂移。
  • 修法:先取结果再一律 close_doc,最后传出首个错误。

D-06 导航栏大纲点击按当前列表取下标,后台重读后可能越界

  • 严重度:低 | 置信度:可能
  • 位置:apps/ely-doc/src/navigate.rs:277-283(navigator.outline[at])
  • 修法:用 get(at),或在渲染时把目标矩形放进行的闭包。

D-07 导航栏隐藏时仍随每次光标移动重读大纲与书签

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-doc/src/chrome.rs:236-245(left_view 不看 navigating 就 navigator.show(tab))、apps/ely-doc/src/navigate.rs:79、:91-123
  • 现象:关掉导航栏后每次按键仍排两次内核读取,与键入争 worker 锁(规则 9:空闲不做事)。
  • 修法:隐藏时 show(None),显示时重读一次。

D-08 Linux、Windows 上“打印…”可用,导出 PDF 后才报“此平台未构建”

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-doc/src/output.rs:28-30、:80-88;crates/ely-platform/src/print.rs:87-89
  • 现象:菜单与页码范围对话框都正常,最后一步弹出带临时路径的错误提示。各文档应用同(见 X-13)。
  • 修法:平台未实现时禁用菜单项或在对话框里写明;错误文字去掉路径。

M ElyMark(CEF)

M-01 键入期间外部以相同内容重写文件,丢掉上次停顿之后键入的全部文字

  • 严重度:高 | 置信度:已确认
  • 位置:apps/ely-mark/src/outside.rs:223-224((true, _) | (false, false) => take_disk)、:243-268(take_disk)、apps/ely-mark/src/view.rs:184-196(refresh 把 changed、reported 归零)、apps/ely-mark/web/src/main.js:52-59(全文停手 300 毫秒才送)、:179-187(refresh 清掉待送)
  • 现象:连续键入时外部进程以相同字节重写文件(touch、同内容的 git checkout/stash pop、云同步重盖、格式化器输出相同),编辑器以旧文字重建,上次停顿以来键入的字全部消失,标签还显示已保存;无提示。
  • 原因:页面只在停手 300 毫秒后送全文,键入中 Rust 侧 doc.current 是停顿前的文字;disk.text == current 的分支不看 dirty(它含 edit_pending),直接 take_disk。与 agents.md “有未保存内容(含页面还没送回的编辑)即不动磁盘与编辑器”相悖。
  • 复现:打开 a.md 连续键入几秒,期间在终端 touch a.md。
  • 修法:disk.text == current 而视图未落定时不 take_disk:等 when_settled 后重判,或当作 Outside::Changed;落定且相同时只换指纹不动编辑器。

M-02 refresh 归零版本计数却不换代,在途的一键让标签一直“未落定”

  • 严重度:中 | 置信度:可能
  • 位置:apps/ely-mark/src/view.rs:193、:264-272(Loaded 路径有换代)、apps/ely-mark/web/src/main.js:179-187、crates/ely-web-content/src/engine.rs:127-143;受影响 crates/ely-office/src/close.rs:96-99、save.rs:325-326、apps/ely-mark/src/compare.rs:381-382
  • 现象:干净标签被外部改动刷新后,Cmd+S、Cmd+W、Cmd+Q、比较与自动保存都静静等 when_settled,直到再键入一个字才一起执行。
  • 原因:刷新前排队的一键在渲染进程里先产生 changed = N+1,再执行 refresh 丢掉待送全文;Rust 已归零又收到 changed,没有 text 跟上,settled() 为假;refresh 不像 Loaded 那样换代,旧回报没被丢。
  • 修法:refresh 自己换一代(self.load += 1 传给 ely.refresh(text, load))。

M-03 干净标签接过磁盘文字后关闭仍问“是否保存”

  • 严重度:中 | 置信度:已确认
  • 位置:apps/ely-mark/src/outside.rs:243-268(take_disk 不更新 tab.mark)、apps/ely-mark/src/view.rs:192、apps/ely-mark/src/engine.rs:133-136、crates/ely-office/src/close.rs:107-128
  • 现象:文件在别处被改、ElyMark 静默重载后,Cmd+W 对没动过的标签弹未保存询问;比较对话框选“用磁盘上的”后同。
  • 原因:request_close 比较 Engine::history(磁盘文字散列)与 tab.mark(载入或上次保存时的散列),不等即问。
  • 修法:take_disk(与合并采用后)同步 tab.mark = mark(&disk.text)。

M-04 第二次打印或导出 PDF 悄悄取消第一次,没有文件也没有错误

  • 严重度:中 | 置信度:已确认
  • 位置:apps/ely-mark/src/export.rs:93-97、:276(打印与导出共用 print-{id}.html)、:315-334(self.printing = Some(..) 覆盖)
  • 原因:Desk.printing 只有一个槽,赋新值即丢掉上一个的隐藏 WebView 与订阅,第一份的 Event::Printed 无人接。
  • 修法:按作业编号保存多份,或一份未完时拒绝第二份并提示。

M-05 点页面里的链接什么也不发生

  • 严重度:中 | 置信度:已确认
  • 位置:apps/ely-mark/src/view.rs:274(Event::Leave(url) => log::info!(..))、crates/ely-web-content/src/fence.rs:319-324
  • 现象:预览与可视模式里点 http(s)/mailto 链接:不跳转、不开浏览器、不提示。PRD 允许用户主动打开外部链接,agents.md 说“交给应用”,应用没处理。
  • 修法:Leave 时 http、https、mailto 交系统打开,其他协议提示拒绝(同 ElyPDF 的链接策略)。

M-06 on_app_quit 无条件删恢复点与崩溃锁,AppKit 直接终止时丢未保存内容

  • 严重度:中 | 置信度:可能(依赖 GPUI 对非 Quit 动作的终止也跑 on_app_quit)
  • 位置:apps/ely-mark/src/desk.rs:244-248、apps/ely-mark/src/restore.rs:145-155(Keeping::close)
  • 现象:注销、关机或 Dock 的“退出”经 terminate: 结束进程,proceed(After::Quit) 的未保存流程不跑,退出观察者却删掉 session.lock 与 recovery/;下次启动读到“干净结束”,什么也不回来。外壳的 Recovering::end 有同样假设(见 X-01)。
  • 修法:退出观察者只在无标签(或全部干净)时清理,否则留下锁与恢复点。

M-07 崩溃恢复的未命名副本按“第一个显示的未命名标签”配对,内容可能落错标签

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-mark/src/restore.rs:195-201、apps/ely-mark/src/desk.rs:226-228
  • 修法:Waiting 条目记下为它新建的标签号,restore_shown 按号配对。

M-08 页面重载后悬着的渲染回调永不完成,导出既不成功也不报错

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-mark/src/view.rs:264-272(Loaded 不清 renders)、:210-220、apps/ely-mark/src/export.rs:165-185
  • 修法:Loaded、Failed 时把 renders 全部以错误结束。

M-09 导出的保存面板出错被当作取消

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-mark/src/export.rs:129-131(let Ok(Ok(Some(path))) = chosen.await else { return log::info!(..) })
  • 修法:照 Desk::open_picked 分三种情形,错误走 panel_failed。

M-10 关闭查找框只清当前标签的高亮

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-mark/src/finding.rs:116-124(close_find 只对 active_view 发 unfind)、:60-69、apps/ely-mark/web/src/main.js:132、:270-273
  • 修法:对每个标签的视图发 unfind。

M-11 页面把搜索词写进应用日志

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-mark/web/src/main.js:246(console.warn 带 place.query)、crates/ely-web-content/src/handlers.rs:272-275(控制台警告进 log::warn!)
  • 原因:违反规则 4(日志不含正文)。
  • 修法:只记行号与是否标题。

M-12 浏览器创建完成前 WebView 已释放,浏览器泄漏

  • 严重度:低 | 置信度:已确认
  • 位置:crates/ely-web-content/src/view.rs:582-587(Drop 时还没有浏览器)、crates/ely-web-content/src/handlers.rs:349-354(on_after_created 无条件存入)、crates/ely-web-content/src/engine.rs:165-170
  • 现象:开了标签在约 100 毫秒内关掉,渲染进程留到退出。
  • 修法:on_after_created 发现 surface.closed 即 close_browser(true)。

M-13 编辑器构建失败时页面空白且不上报

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-mark/web/src/main.js:117-139(show 无 try/catch)、apps/ely-mark/web/src/visual.js:106-109
  • 现象:Milkdown create() 或 newTable 抛出时没有 shown 也没有 Failed,#document 空着,MarkView.shown 永为假(跳转、外观等一直等)。
  • 修法:build 包 try/catch,可视失败退回源码并 tell({kind:'failed'})。

M-14 render_pdf 在界面线程内嵌图片并写文件

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-mark/src/export.rs:276-282(渲染回调里 inline、read_to_string、fs::write);对照 :172-176 的 write_output 已在后台
  • 修法:暂存步骤移到 background_spawn。

M-15 打印与导出 PDF 的暂存页执行文档里的脚本

  • 严重度:中 | 置信度:已确认(M-11 第 2、3 轮审查在 Windows 与 WSL 的 CEF 上复现)
  • 位置:apps/ely-mark/web/src/render.js(allowDangerousHtml 原样保留 HTML 块)、apps/ely-mark/src/export.rs(render_pdf、打印把同一 HTML 暂存后交不显示的 CEF 视图)
  • 现象:Markdown 里的 <script src="data:..."> 在打印与导出 PDF 时运行,可调 console、elyQuery,可读暂存页。
  • 修法:暂存页不运行脚本。

G ElyTree(Git)

G-01 打开“文件历史”或“逐行追溯”后,中间区不再响应任何别的选择

  • 严重度:中 | 置信度:已确认
  • 位置:apps/ely-tree/src/view.rs:185-187(body 先于 match self.side 返回 look_pane);入口 apps/ely-tree/src/hist_pane.rs:43-66、apps/ely-tree/src/pane.rs:483
  • 现象:按下差异标题栏的“文件历史”或“逐行追溯”后,点改动列表里另一个文件、历史里另一个提交、分支或储藏,中间区都不变;只有历史面板的小叉能退出。
  • 原因:show()(pick.rs:302)、pick_commit、pick_place、set_side 都不清 self.look,只有 close_look 与 blame 的 show_commit 清。
  • 修法:任何新选择都清 self.look,或只在 side == Changes 且所选文件不变时画它。

G-02 进行中操作读取失败被吞掉;git am 冲突无提示条无错误

  • 严重度:中 | 置信度:已确认(git am 细节为可能)
  • 位置:apps/ely-tree/src/ops.rs:81-98(doing)、apps/ely-tree/src/moves.rs:74、apps/ely-tree/src/conflict_pane.rs:62、:106-109
  • 现象:doing() 出错时 opened.doing = Some(Err(..)),没有任何显示或日志;冲突文件列出来了,却没有“继续/跳过/中止”提示,冲突栏两侧退回泛称。
  • 原因:rebase-apply 一律当变基读 onto,git am 的 rebase-apply/ 没有 onto,整个读取 NotFound;describe() 失败(如 MERGE_HEAD 指向已被清理的对象)同样整体失败。违反规则 2。
  • 复现:终端里 git am 停在冲突,在 ElyTree 打开该仓库。
  • 修法:Some(Err) 以提示条显示 Git 原话并记日志;无 onto 的 rebase-apply 视为 git am 进行中。

G-03 新建的空仓库在“仓库”页显示两段 Git 致命错误(引用日志、LFS)

  • 严重度:中 | 置信度:可能
  • 位置:apps/ely-tree/src/recover.rs:50-69(reflog … HEAD --)、apps/ely-tree/src/structure.rs:137-138(git lfs ls-files)、apps/ely-tree/src/places.rs:40-50、apps/ely-tree/src/side_places.rs:261-264、:279-281
  • 现象:“新建仓库…”后仓库页出现 “fatal: bad revision 'HEAD'” 与 “Git can't resolve ref: HEAD” 两块红字;仓库没有问题,空列表才是正确状态。
  • 原因:HEAD 未诞生时显式传 HEAD 的 reflog show 以 128 退出,run_or_one 不接受;git lfs ls-files 同样先解析 HEAD。
  • 修法:status.head.oid 为空时两项直接给空列表。

G-04 停止取回/拉取/推送失败的提示写成“克隆没有停下”

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-tree/src/network.rs:126(stop_remote 复用 tree.error.stop)、crates/ely-ui/src/i18n/tree.rs:53
  • 修法:加一条不限动词的文案。

G-05 合并结果保存失败报成“Git 失败”,tree.error.save 是死文案

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-tree/src/work.rs:424((_, Err(error)) => failed("tree.error.write", …))、crates/ely-ui/src/i18n/tree_ops.rs:127、:255(定义却无人用,规则 6)
  • 修法:written() 加 (Write::Save, Err) 分支用 tree.error.save。

G-06 首次提交后紧接着取消暂存,按过期的“已有提交”判定跑成 git rm --cached

  • 严重度:低 | 置信度:可能(竞争窗口为一次状态重读)
  • 位置:apps/ely-tree/src/work.rs:352(born 取自缓存的 opened.status)、:426-427(read 异步、next_write 同步)、:85-93、:103-112
  • 现象:新仓库里暂存、提交,列表刷新前按“全部取消暂存”:第二条写操作仍按 oid: None 跑 rm --cached -f -r -q -- .,把刚提交的全部文件标为删除(可恢复,但吓人)。
  • 修法:写操作执行前在后台 rev-parse --verify -q HEAD 判定,不用缓存状态。

G-07 任一引用没有创建日期,整个分支页读不出

  • 严重度:低 | 置信度:可能
  • 位置:apps/ely-tree/src/branches.rs:33(%(creatordate:unix))、:106(空串解析失败整体 Err)、apps/ely-tree/src/side_refs.rs:227-235
  • 复现:git tag blobtag HEAD:README.md,打开分支页。
  • 修法:日期可缺(显示“—”),或跳过非提交对象的引用。

G-08 清理预览后文件被外部删除,对话框只给裸 OS 错误

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-tree/src/work.rs:270(symlink_metadata(&at)?)、:415-422
  • 现象:标题“Git did not do it”,正文 “No such file or directory (os error 2)”,没有路径,Git 并未运行。
  • 修法:映射成带路径的“自预览后已变”文案。

G-09 后台读取回来的对话框覆盖用户同时打开的对话框

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-tree/src/network.rs:186(推送选远程)、:237(强制推送)、apps/ely-tree/src/recovering.rs:116(移动分支)
  • 现象:按“推送”(无上游)后立刻按“储藏改动…”并开始键入,git remote 回来时储藏对话框连同输入被换掉。
  • 修法:只在 tree.ask 仍为空(或仍是本次请求)时赋值。

G-10 选中的分支、标签、位置是窗口级的,跨仓库标签串台

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-tree/src/tree.rs:107-108(picked_ref、picked_place 在 Tree 上)、apps/ely-tree/src/refs_pane.rs:25-33、apps/ely-tree/src/side_refs.rs:238-245
  • 现象:仓库 A 选中 main,切到仓库 B,B 的分支页显示它自己的 main 为已选并带动作按钮。
  • 修法:移入 Opened,或 activate() 时清掉。

G-11 已选边的冲突块再选另一边,提示“结果已手改”

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-tree/src/conflict.rs:219-224
  • 修法:给“该块已选定”单独文案,或支持改选。

G-12 符号链接父目录下,当前打开的工作树被当成别的工作树而可“移除”

  • 严重度:低 | 置信度:可能
  • 位置:apps/ely-tree/src/places_pane.rs:253-255、:265-271、apps/ely-tree/src/structure.rs:245-247
  • 原因:here 用 git worktree list 记录的原始路径比对已规范化的 opened.repo.root(macOS /tmp → /private/tmp)。
  • 修法:比对前 canonicalize。

G-13 非 macOS 平台的死入口

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-tree/src/running.rs:167-173(停止远程命令非 Unix 报未构建)、apps/ely-tree/src/tools.rs:109-115(“在终端中打开”非 macOS 报未构建)、crates/ely-ui/src/i18n/tree_places.rs:147、:281(“在访达中显示”文案在 Linux、Windows 照样显示)
  • 修法:按 cfg 禁用或隐藏,reveal 换平台中性文案。

G-14 repo.rs 的孤立文档注释

  • 严重度:低 | 置信度:已确认
  • 位置:apps/ely-tree/src/repo.rs:10(“How many repositories the recent list keeps.” 的常量早已移到 ely_files::recent,注释挂到了 struct Repo 上)
  • 修法:删掉。

K LOK 桥(crates/ely-lok 的 worker、客户端、事件)

T-01、T-04、T-07 与 S-01 的根源都在这一层,这里只列此外的问题。

K-01 非 macOS 的 worker 不运行 VCL 主循环,内核的回调与异步输入永远不送达

  • 严重度:高 | 置信度:可能(代码已读;Linux、Windows 未运行)
  • 位置:crates/ely-lok/src/worker.rs:149-164(非 macOS 的 serve 只是阻塞的 read_frame 循环)、patches/libreoffice/0001-lok-macos-tile-painting.patch(CallbackFlushHandler::scheduleFlush 非 MACOSX 分支仍 Application::PostUserEvent)
  • 现象:Linux、Windows 上 worker 能答同步请求(载入、画图块、保存),但作废、光标、状态、对话框关闭等回调一条也推不出来,键盘与鼠标(内核经 SfxLokHelper::post*EventAsync 以用户事件派发)也到不了文档——编辑器是一张静止的图。树莓派是明确目标。
  • 原因:用户事件只在 Application::Yield/lo_runLoop 里派发;只有 macOS 路径以 GCD 加 run_loop 跑循环。
  • 修法:各平台都以 unipoll 的 runLoop 加轮询回调驱动请求队列,照 macOS 的做法。

K-02 图块的代际在画完之后才取,画到一半的作废被盖掉

  • 严重度:中 | 置信度:可能
  • 位置:crates/ely-lok/src/worker/input.rs:76-81(painted:先 paint,再 events::generation(id))、crates/ely-lok/src/view.rs:466(tile.generation >= self.generation 即接受)
  • 现象:paintTile 期间内核发出作废(Writer 画时懒排版、Impress 空闲任务),返回的像素是作废前的,却带着新代际;视图接受并不再重画,旧内容留到下一次无关的作废。
  • 修法:paint() 之前先读代际并盖到图块上。

K-03 rect_of 宽容解析把读不出的载荷悄悄吞掉

  • 严重度:低 | 置信度:可能
  • 位置:crates/ely-lok/src/events/parse.rs:25-34,用于 events.rs:212、:217-218、:221、:248
  • 现象:不是 “x, y, w, h…” 的载荷(如 Impress/Writer 表格多选时 GRAPHIC_SELECTION 的 JSON)要么被静默丢掉(选区框与手柄消失、不报 fault),要么凑出四个数字就被误读。与规则 2 相悖。
  • 修法:严格解析开头的数字字段,读不出像 found_of 一样进 faults。

K-04 Sheets.current 用 as u64 转有符号的 part

  • 严重度:低 | 置信度:可能
  • 位置:crates/ely-lok/src/worker.rs:377(doc.part() as u64、count as u64)、crates/ely-lok/src/client/doc.rs:40
  • 现象:getPart() 返回 -1(没有视图外壳,关闭或重排期间)时 current 成了 2^64-1;今天的应用用 .get 不会 panic,但列表与标签页会错。
  • 修法:try_from,为负即报错。

X 共用外壳与平台(ely-office、ely-files、ely-ui、ely-platform、ely-ipc)

X-01 设置窗口在前时 Cmd+Q 直接退出,跳过未保存询问并抹掉恢复点

  • 严重度:致命 | 置信度:已确认
  • 位置:crates/ely-ui/src/run.rs:77(应用级 on_action(Quit → cx.quit()))、crates/ely-ui/src/commands.rs:170(secondary-q 不带键位上下文)、:150(菜单项)、crates/ely-ui/src/settings/window.rs(设置窗口根上没有 Quit 处理)、crates/ely-office/src/recover.rs:59-71、crates/ely-files/src/crash.rs:29-46
  • 现象:文档应用开着设置窗口(Cmd+,)时按 Cmd+Q 或选应用菜单“退出”,进程立即结束:未保存文档没有询问;on_app_quit 跑 Keeping::end() 把这次运行标为干净结束,下次启动 Crash::check 读到干净、prune 删光恢复点,内容也找不回。
  • 原因:动作先沿聚焦窗口的树派发,没人处理才落到应用级监听;文档宿主只在自己窗口根上截 Quit(走 Closing::proceed(After::Quit)),设置窗口的 Shell 不截,于是直接 cx.quit()。
  • 复现:ElyDoc 新建文档键入几字,Cmd+, 打开设置,Cmd+Q。
  • 修法:全局 Quit 改为让每个文档窗口走自己的退出流程(遍历 cx.windows() 派发),或设置窗口把 Quit 转发给主窗口宿主;Keeping::end 只在全部标签干净时删恢复点。

X-02 共用 settings.json 读不出时 Doc、Table、Show 根本起不来

  • 严重度:高 | 置信度:已确认
  • 位置:crates/ely-office/src/lok.rs:135(启动任务里 set_author(&user::name()?))、:82(restart 同)、crates/ely-platform/src/user.rs:7(Shared::load()?)
  • 现象:<数据目录>/ElyOffice/settings.json 不能解析、version 大于 1 或某字段类型不对时,三个 LOK 应用都以“the document engine did not start: …settings.json…”的致命对话框退出(码 1);引擎崩溃后的“重新打开文档”同样失败。agents.md 规定设置读不出“即用默认值、记下原因”,settings::install 也确实这样做,唯独这条路径把它变成致命错误。
  • 复现:往 settings.json 写 {"version": 2},启动 ElyDoc。
  • 修法:start/restart 里设置读不出时退回 user::account() 并记日志(设置窗口已有 Banner 说明);或让 user::name() 用 ely_ui::settings::current 已读好的值。

X-03 第二个实例启动即清掉运行中实例的工作目录与恢复点

  • 严重度:高 | 置信度:机制已确认;频率为可能(Linux 桌面每个文件起一个进程;macOS open -n 或开发构建)
  • 位置:crates/ely-office/src/lib.rs:219-226(fresh_work 无条件 remove_dir_all),调用 crates/ely-office/src/lok.rs:128、apps/ely-pdf/src/viewer.rs:102、apps/ely-mark/src/desk.rs:162;crates/ely-office/src/recover.rs:59-71 与 crates/ely-files/src/crash.rs:40-46(锁里 pid 活着=干净结束)
  • 现象:实例 B 启动时 (a) 删掉 <data>/work——A 的每个 tab.work——此后 A 的每次保存都在消失的目录里写 save.{format} 失败(files.rs:48-50),状态变“保存失败”、自动保存停止;(b) Crash::check 见 A 的 pid 活着判为干净,prune 删掉 A 的全部恢复点与 index.json;(c) Crash::open 把 session.lock 改成 B 的 pid,A 正常退出时删掉 B 的锁,B 之后崩溃被当作干净结束。
  • 复现:Linux 从文件管理器连开两个 .docx;macOS open -n -a ElyDoc;然后在第一个实例 Cmd+S。
  • 修法:数据目录加单实例锁(flock),已有实例在时把文件交给它;或工作目录按进程分(work/<pid>)、恢复点按锁 pid 存活判定不清理。

X-04 属性栏字号菜单的 expect("sizes are numbers") 是脆弱点,但当前不可达

  • 严重度:低 | 置信度:已确认(上游源码核实)
  • 位置:crates/ely-office/src/panel.rs:220-228、:283-285
  • 说明:上游 FontHeightPayload(sfx2/source/control/unoctitm.cxx:960-966)只发 =<数字>(禁用时为 0),不发 disabled;混合选区经补丁 0003 的 JSON 走 MIXED 被过滤。所以这里不会 panic。保留为脆弱点:任何将来改动内核状态格式都会让整个属性栏崩溃,points 应改为可失败。

X-05 恢复点写不成时每 10 秒弹一次提示

  • 严重度:中 | 置信度:已确认
  • 位置:crates/ely-office/src/recover.rs:227-231、crates/ely-files/src/recovery.rs:105-116
  • 现象:快照超过恢复点容量上限(默认 64 MB,可设到 32 MB;带视频的 PPTX、大 XLSX),或磁盘满、数据目录只读,只要标签还脏,每个计时周期都弹“office.recovery.failed”。
  • 原因:失败时 slot.written 不变、也没有像 keeping.behind 那样记住上次失败,下个周期 due 又含该标签。
  • 修法:按槽记上次失败文字,变了或成功才再提示;超容量时写 written = Some(edited) 并解释一次。

X-06 session.lock 空或损坏即永久关闭恢复功能

  • 严重度:中 | 置信度:已确认
  • 位置:crates/ely-files/src/crash.rs:21-25(非原子写)、:29-50(其他内容返回 InvalidData)、crates/ely-office/src/recover.rs:59、:134-141
  • 现象:断电截断 session.lock 后,每次启动都显示“office.recovery.off: bad session lock”、不写恢复点;没有任何代码重写该文件,用户得手删。
  • 修法:解析不出当作 Marker::Crashed(给出恢复点并重写锁),锁文件原子写。

X-07 加密文档的恢复点每次写入都解密全部已有点,且持 worker 锁

  • 严重度:中 | 置信度:可能
  • 位置:crates/ely-files/src/recovery.rs:120-121、:164-190、:193-199、:225-237;调用 crates/ely-office/src/recover.rs:201-214(在 read_in 内)
  • 现象:带密码的 PDF 每 10 秒写点时读并 AES-GCM 解密每个 .enc(至多 20 份×文档大小)只为列出;RecoveryKey::load()(钥匙串加 1 秒锁循环)也每次都跑;PDF 视图排队的编辑与读取随之停顿(树莓派上更久)。
  • 修法:按文件名列点,恢复时才解密;密钥每次运行读一次。

X-08 另存为在等待关闭或退出时被拒绝,关闭链悬空,ElyMark 的会话清单冻结

  • 严重度:中 | 置信度:已确认
  • 位置:crates/ely-office/src/save.rs:219-246(identity 出错、office.error.format、office.error.taken、office.error.convert 四处提前返回不调 close_cancelled),对照 :191-194、:419-424 有调;apps/ely-mark/src/desk.rs:85-88、apps/ely-mark/src/restore.rs:282-285、:341-343、:362-366
  • 现象:退出或关窗途中对未命名标签答“保存”、选了不可保存的扩展名或别的标签正打开的文件:弹一条提示,退出静静停住;ElyMark 的 keeping.leaving 留在 true,keep() 不再写 session.json,到下次尝试退出前打开、关闭、改名的标签都不进清单,崩溃后恢复缺它们。
  • 复现:ElyMark 新建未命名文档,Cmd+Q → 保存 → 选 notes.txt。
  • 修法:write_as 的每个提前返回在 then.is_some() 时调 close_cancelled。

X-09 保存期间落地的编辑可能让自动保存停摆

  • 严重度:低 | 置信度:可能(与事件次序有关)
  • 位置:crates/ely-office/src/open.rs:241-251(只在 ModifiedStatus 假→真时 session.edit())、crates/ely-office/src/save.rs:68-70、:131-132、crates/ely-files/src/session.rs(票据按写入时的修订)
  • 现象:save_owned 清掉内核 modified、随后导出期间键入的 =true 若在导出续动作调 ely_files::save 之前处理,reported 看到 (true, true) 不调 edit(),finish_save 置 Saved;tab.dirty() 为真而 state != Dirty,自动保存不再触发直到手动保存。
  • 修法:导出开始时取票据,或每次 Engine::edited 递增都 session.edit()。

X-10 别的标签的文件所在卷消失时,本标签的另存为失败并被标为保存失败

  • 严重度:低 | 置信度:已确认
  • 位置:crates/ely-office/src/save.rs:32-41(holder 对每个标签 same_file(open, target)?)、:231-234、:429-450(report → save_failed())
  • 修法:holder 里解析不出的别的标签路径视为“不是同一文件”并记日志;写入前的拒绝不调 save_failed()。

X-11 worker 的 stderr 中继遇到第一行非 UTF-8 即永久停止

  • 严重度:低 | 置信度:已确认
  • 位置:crates/ely-ipc/src/relay.rs:31-34(lines() 出错即 return)
  • 现象:LibreOffice 或 MuPDF 打出一行非 UTF-8(Linux 上 Latin-1 文件名)后,之后的结构化日志、崩溃栈与 Relay::last_error 全丢。
  • 修法:read_until(b'\n') 加 from_utf8_lossy,出错继续。

X-12 恢复路径在界面线程做文件 I/O

  • 严重度:低 | 置信度:已确认
  • 位置:crates/ely-office/src/recover.rs:89-98(flush_index 的暂存写加 sync_all,keep_tab/forget_tab 调)、:287、:316(restore 同步调钥匙串与拷贝)
  • 现象:每次开关标签都在界面线程 fsync index.json;崩溃后首启在界面线程查钥匙串(最多等 1 秒锁)并逐个拷贝恢复点。慢介质(树莓派、SD 卡)上可见卡顿(规则 9)。
  • 修法:移到后台执行器,in_window 续。

X-13 非 macOS 构建里仍提供只会报“未构建”的入口

  • 严重度:低 | 置信度:已确认
  • 位置:crates/ely-platform/src/print.rs:87-89(打印,各文档应用“文件›打印…”与 Cmd+P 照常)、crates/ely-platform/src/trust.rs:23(PDF 签名对话框)、crates/ely-platform/src/media.rs:52(ElyShow 插入媒体)、crates/ely-files/src/rename.rs:63(改名)
  • 修法:按 cfg 或能力标志禁用菜单项,或补齐平台实现。

X-14 agents.md 引用的 docs/engineering-risks.md 不存在

  • 严重度:低 | 置信度:已确认
  • 位置:agents.md(三处引用“证据见 docs/engineering-risks.md R-01”等);docs/ 下无此文件
  • 修法:补回文件或改引用。