193 KiB
193 KiB
Ely Office
单一事实来源。技术栈或产品方向变更时,同步改本文件。需求以 PRD.md 为准。进度以 tasks/TASKS.md 为准。
产品
八个桌面应用:ElyDoc、ElyTable、ElyShow、ElyPDF、ElyMail、ElyTree、ElyMark、ElyAgent。离线优先,文件在用户目录。
技术栈
| 层 | 选型 |
|---|---|
| 界面 | Rust + GPUI + Ely-GPUI-Components |
| Doc / Table / Show | LibreOfficeKit,独立工作进程 |
| Show 媒体 | 各平台原生:macOS AVFoundation,Windows Media Foundation,Linux GStreamer(LOK 不放媒体) |
| MuPDF(C 接口),Tesseract OCR,独立工作进程 | |
| MailCore 2 + SQLite;正文用 CEF 离屏内容区 | |
| Tree | Git CLI,参数数组启动 |
| Mark | Milkdown + CodeMirror 6,CEF 离屏内容区 |
| Agent | Kigi CLI 的 Rust 代码直接嵌入(不用 ACP),沿用其提供商登录与模型配置 |
目录
apps/ 八个应用,各自一个二进制;ely-mark/web 是其网页源码(锁定版本,esbuild 构建)
crates/ ely-ui ely-platform ely-ipc(应用与引擎 worker 之间的帧);ely-files ely-lok ely-office(文档应用共用的外壳:标签、会话、保存、关闭询问、冲突、密码;引擎为 LOK 或 MuPDF)ely-pdf-engine(MuPDF worker 与客户端)ely-web-content(CEF 离屏页面,ElyMark 与 ElyMail 共用);ely-mail-engine 随其任务建
reference/ 上游源码,只读,不属于本项目,不审查
engines/ 下载的二进制与引擎构建产物,不入库,不审查;mupdf-src 是 reference/mupdf 加 patches/mupdf 的构建副本;content/<应用> 是网页内容的构建产物
patches/ 对上游的补丁,随源码发行(libreoffice、mupdf)
packaging/ 三端打包脚本,apps.tsv 是八个应用的二进制名、名字、桌面分类与发布批次;icons/ 由图标母版渲染各端格式;libreoffice/ely.xcd 是内核的默认配置层
assets/ icons/ 是八个应用的图标母版(SVG)与渲染出的 icns、png、ico,规则见其 README
dist/ 打包产物,不入库
docs/ 工程风险与已知限制
scripts/ fetch-references.sh 取上游 doctor.sh 查工具 review.sh 开分栏审查 build-*.sh 构建引擎
tests/ scripts 与 engines 下是脚本测试;engines 缺二进制时报 SKIP;fixtures 是测试共用的样本(media/clip.mp4:1 秒 H.264/AAC;pdf/aes128.pdf:AES-128 加密,打开密码 user、所有者密码 owner;pdf/signer.p12 与 signer.der:自签名测试证书,密码 ely)
tasks/ TASKS.md 进度真相
site/ 官网(Vite 8、pnpm、TypeScript,无框架;等轴 SVG 积木;中英两种文案在 src/text.ts);site/video 是发布视频的分镜、配乐、录屏与合成脚本,产物在 site/video/out(不入库)
组件映射
依赖 ely-gpui-component(pin 69265b4,gpui 为 crates.io 的 gpui-pre =0.3.8)。名字取自 reference/Ely-GPUI-Components/src。gpui 用法规则见该仓库 AGENTS.md。tests/scripts/agents_md.py 核对下表每个名字都是组件库公开项。
| 需要 | 用 |
|---|---|
| 窗口外壳 | shell::{TitleBar, TabBar, StatusBar, Toolbar, ToolbarGroup, WindowSwitcher, ZoomControl} layout::{AppShell, SplitPane, Sidebar, Dock} |
| 命令与菜单 | menus::{MenuBar, ContextMenu, DropdownMenu} navigation::{CommandPalette, QuickOpen} |
| 导航 | navigation::{EditorTabs, Tabs, Breadcrumb, TableOfContents} lists::{Tree, Outline, FileTree, SelectableList} |
| 属性与表单 | data_display::{PropertyGrid, DescriptionList} forms::{Select, FontPicker, ColorPicker, NumberInput, ScrubInput, Switch, SearchInput, TextInput} buttons::{Button, IconButton, ToggleButton, SegmentedControl} |
| 对话框 | overlays::{Dialog, ConfirmDialog, AlertDialog, PromptDialog, Popover} |
| 反馈 | feedback::{Toaster, ToastViewport, SaveState, SavingIndicator, SyncStatus, Alert, Banner, EmptyState, ErrorView} shell::{OfflineIndicator, CrashReporter, UpdateBanner, UpdateDialog} |
| 设置 | settings::{SettingsLayout, SettingsSection, SettingsRow, ThemeSelector, KeyboardShortcutsList, StorageSettings, PrivacySettings} |
| Doc | documents::{PrintPreview, TemplatePicker, WordCount, PageThumbnailList} editor::{FindWidget} collab::{CommentSidebar, Thread, Comment, Peer} lists::{List, ListItem} |
| Table | tables::{PivotTable, FilterBuilder, SortBuilder} misc::{CsvImporter, ExportDialog} editor::{CompletionMenu, SignatureHelp, FindWidget} forms::{CheckboxGroup, ColorPalette} |
mail::{MailboxList, MailList, MailThreadView, MailReader, MailComposer, SignatureEditor, ScheduleSend} |
|
| Tree | git::{ChangesList, CommitInput, CommitList, lanes, DiffViewer, ConflictResolver, BlameView, BranchList, TagList, StashList, FileHistory} |
| Mark | editor::{CodeEditor, FindWidget} documents::{MarkdownRenderer, ZenMode} |
| Agent | agent::{ToolCallCard, PermissionPrompt, ToolApprovalDialog, AgentStepList, FileChangeCard, MultiFileDiffReview, ArtifactPanel, CostBreakdown} chat::{MessageList, PromptInput, StreamingMarkdown, ConversationList} |
| 动效 | motion::{duration, Flip, Reorder, Transition, Stagger, ProgressBar, Spinner, Skeleton} |
| 主题 | theme::{Theme, Mode};Omarchy 色板由 ely-ui 映射成组件库的调色板 |
缺口
组件库没有的,在本项目内建:
| 缺口 | 位置 |
|---|---|
| LOK 图块画布与输入桥 | crates/ely-lok |
| 富剪贴板:gpui 剪贴板只有文本与图片,无 HTML(富文本、表格) | ely_platform::clipboard,用 arboard(三端 HTML、PNG) |
gpui 无障碍只有 aria_value 等,无文本选区与插入点:LOK 文档的光标与选区到不了辅助技术 |
LokView 只桥接当前段落文本(Role::MultilineTextInput)。上游待办:gpui 补文本选区 AX |
| 选中对象的框与手柄:LOK 不画进图块 | LokView 按图形选区画框与 8 个手柄;按住手柄拖动经 setGraphicSelection 交内核(缩放;裁剪模式下裁剪) |
虚拟化 PDF 页面栈(DocumentViewer 一次铺全部页,不合 PERF-05) |
crates/ely-pdf-engine、apps/ely-pdf |
| CEF 离屏内容视图 | crates/ely-web-content |
| CEF 页面的无障碍:离屏渲染的页面不进系统 AX 树(CEF 另给 AX 事件,GPUI 无从挂接) | 未桥接;页面文字在辅助技术里不可见。上游待办:gpui 可挂外部 AX 子树 |
组件库的大纲项(lists::Outline)与提示(feedback::Alert)无无障碍角色:屏幕阅读器读不到 |
上游待办;ElyMark 的搜索失败改为结果列表里的行。feedback::Banner 已修(fac20ab:危险与警告为 alert,其余为 status,名为其文字) |
documents::ZenMode 把正文夹进 Sm 宽的列,与 ElyMark 的正文宽度设置冲突 |
ElyMark 专注模式自己收起标签条、左栏与状态栏,正文宽度由页面设置 |
CEF 页面的弹出层(select 下拉、日期框)在离屏模式另画一层 |
不画;Ely 的页面不用原生 select,用自绘菜单 |
| macOS 的 CEF 离屏输入不带 AppKit 编辑命令:Blink 只凭它们处理 Cmd+方向键与 Cmd+Backspace | ely-web-content 的 edit.rs:页面没处理这些键时照 AppKit 标准键位执行 |
表格公式栏与名称框(网格是 LOK 图块,tables::Spreadsheet 不用) |
apps/ely-table |
Doc/Table/Show 工具栏(documents::FixedFormatToolbar 绑 TextInput,不接 LOK) |
各 app,用 shell::Toolbar 组合 |
CSV 导入的编码、分隔符、引号、列类型(misc::CsvImporter 只读逗号 CSV、映射到属主字段,文案写死英文) |
apps/ely-table 的 text.rs,内核按选项导入 |
导出格式与说明(misc::ExportDialog 无 TSV,文案写死英文,不能附说明) |
各 app,用 overlays::Dialog 组合 |
幻灯片缩略图条(documents::PageThumbnailList 只收图片路径,无隐藏标记、右键菜单与重排) |
apps/ely-show 的 slides.rs,内核按页绘制到内存图 |
| PDF 页面缩略图(同上,且一次铺全部页,不合 PERF-05) | apps/ely-pdf 的 side.rs,uniform_list 只画可见行,worker 按需渲染 |
| 演示的参考线:LOK 不把参考线画进图块,也无参考线接口 | 补丁 0027 读写参考线;apps/ely-show 的 guides.rs 画在画布上,拖动移动,拖出幻灯片即删除 |
A11yChecker 的 audit 与对照表是私有的;Omarchy 色板要照同一套规则 |
ely-ui 的 omarchy/palette.rs 抄了对照表。上游待办:公开 audit |
| 组件无 role/aria 语义:菜单、列表等控件在 AX 树仍不可见 | 上游已修核心控件(7380afb:Button、TextInput、Tabs、面板行、对话框等);其余上游待办。项目自绘控件已自带 |
gpui 无 aria-disabled API(AriaProperties 无 disabled 字段) |
组件 7380afb 起以本地化 aria_description 表达;上游待办:gpui 补 API |
组件内部文案未接 i18n(面板族之外:欢迎页标题、菜单项、SortBuilder 与 FilterBuilder 等) |
面板族文案已接 i18n(7380afb,内建 en/zh-CN),CommentSidebar 与 RelativeTime 同(408f2b0);其余上游待办 |
LOK 的 .uno:StyleApply 状态给界面名(Default Paragraph Style),样式列表给程序名(Standard),两者对不上 |
ElyDoc 属性栏:列表外的当前样式显示为占位文字;更新样式用状态里的界面名。上游待办:状态带程序名 |
gpui 规则
取自组件库的 AGENTS.md、src/lib.rs 与 examples/docs/hello.rs,应用必须遵守。
- 启动:
gpui_platform::application().with_assets(Assets).run(..),闭包里先调ely_gpui_component::init(cx)。没接Assets会 panic。 - 首帧用
Theme::set_mode_now。之后Theme::set_mode渐变。 - 根元素包进
FocusScope,调.root(),聚焦它的句柄。 - 渲染之外改了状态,调
cx.notify()。绘制中改的,再调window.request_animation_frame()。不用request_animation_frame轮询布局。 - 颜色、控件尺寸、圆角、阴影取自
cx.theme()。间距用 gpui 的 rem 阶梯。边框用border_1。 - 盖在页面上的东西走
primitives::raise。对话框走overlays::Dialog。每个窗口一个Toaster,一个ToastViewport。 - 可按压的元素都是 Tab 停靠点:
tab_index(0)加focus_ring。hover()需要元素 id。 - 动效时长取
motion::duration(),它遵守减少动效。重复动画在减少动效下静止。动画只包住移动的部分:动画 id 传给子孙,换 id 会重建它们的焦点与拖动状态。 - 文本框是
forms::TextInput实体。文案走i18n::I18n,缺键即失败。 - 可复用组件内的 id 带上属主的 id。
uniform_list的行设w_full。盖在内容上的层按需用block_mouse_except_scroll。
规则
- 界面优先用 Ely-GPUI-Components。通用交互回组件库,不在应用里复制。
- 出错就暴露。不吞错,不写兜底,不用默认值遮挡缺失数据。
- 修根因。不打补丁式小修。
- 关键路径有日志(
log,与 gpui 一致)。日志不含正文、附件、密钥、提示词。 - 文件不超过 500 行。注释一行,十字以内,只在必要时写。
- 无 dead code,无 v1/v2 接口,无过期代码。
- 禁止大框套小框。禁止无用说明文字和小标题。
- 不过度设计。不加预留扩展,不加无第二个使用者的抽象。
- 动效只在交互时运行,每段不超过 220 毫秒,遵守系统“减少动效”。空闲时不重绘。目标设备含树莓派。
- 未验证的能力不写成已完成。
命令
scripts/doctor.sh 工具链版本
scripts/fetch-references.sh 取上游源码到 reference/
scripts/fetch-tessdata.sh 取 OCR 语言数据到 engines/tessdata(钉提交,核对散列)
scripts/fetch-cef.sh 取 CEF minimal 包到 engines/cef(随 cef-rs 钉的版本,核对 SHA-1)
scripts/build-git.sh 构建钉住的 Git 到 engines/git(ElyTree 随包)
scripts/fetch-git-lfs.sh 取 git-lfs 放进 engines/git(核对散列)
scripts/review.sh ID 轮次 路径 审查一条任务
scripts/apply-lo-patches.sh engines/lo-src 找出源码已到补丁序列第几个,补上其余(构建脚本调用)
tests/scripts/lo-patches.sh 补丁序列从中途接着打、再打一次为空操作
tests/scripts/media-targets.sh 媒体播放接口在 Linux、Windows 目标可编译(缺目标时 SKIP)
cargo test -p ely-show --test compose 切换半程帧经 Metal 取像素(主线程,仅 macOS)
tests/scripts/*.sh *.py 脚本测试(缺工具时报 SKIP)
packaging/macos/bundle.sh ely-doc debug 出 dist/ElyDoc.app(带图标、文档类型与该应用的引擎)
packaging/macos/release.sh [批次] 该批各应用的 release 包与 dmg;ELY_SIGN_IDENTITY 加 ELY_NOTARY_PROFILE 即公证
packaging/icons/masters.py 写出 assets/icons 的全部 SVG 母版(图标设计只改这里)
packaging/icons/build.sh 由 assets/icons 的母版渲染 icns、png、ico(只在 macOS)
tests/scripts/icons.sh 八个图标齐全、未过期,macOS 为 squircle,其余无底形
tests/scripts/release-macos.sh dist 里首批六个 .app:以访达的方式打开文件(启动与运行中),引擎都取自包内,带齐许可,不含系统会登记的内嵌包,每个二进制要求的系统不高于应用所写;源码包齐全
tests/scripts/hardened-macos.sh ELY_SIGN_IDENTITY 签名(hardened runtime)的 ElyMark 照样画出页面
tests/engines/*.sh 引擎探测(LOK、MuPDF、libetpan);LOK 探测本构建的内核,不挂载官方镜像
scripts/inert-lo.sh 路径.app 构建出的 LibreOffice.app 去掉文档类型、URL 类型、快速查看与聚焦插件并注销(build-libreoffice.sh 调用)
scripts/extract-official-lo.sh 用 7zz 把官方镜像的 Frameworks 与 Resources 解到 engines/lo-official(不挂载,无包,“未打补丁即拒绝”的单测用)
tests/scripts/lo-unregistered.sh “打开方式”里没有 LibreOffice,构建出的包不声明任何文件类型
tests/scripts/lokview.sh 真窗口里的 LokView:输入法、键入、点击、拖动,AX 树里的段落文本;ELY_CLIPBOARD_TEST=1 再测复制粘贴(会覆盖系统剪贴板)
tests/scripts/doc-shell.sh 真窗口 ElyDoc:空白标签进 AX 树,启动后 Cmd+N 打开新建对话框(需辅助功能权限)
tests/scripts/doc-props.sh 真窗口 ElyDoc 属性栏:段前段后连续键入、回车、失焦,读 AX 值与命令数(需辅助功能权限)
tests/scripts/theme.sh 真窗口 ElyTable 以深、浅两种外观启动,截窗口量网格亮度(需屏幕录制权限)
tests/scripts/lag.sh ely-doc 真窗口键入,从调试日志算输入落地、图块、按键到新图块的分位数(需辅助功能权限);ELY_LAG_LOG=路径 留下日志
tests/scripts/pdf-idle.sh 真窗口 ElyPDF 无文件启动时无 worker 子进程
scripts/build-mark-content.sh 按锁文件构建 ElyMark 页面到 engines/content/ely-mark(不跑安装脚本)
tests/scripts/mark-content.sh 真窗口:构建的页面离线渲染各包(可视、预览、源码)
tests/scripts/mark-session.sh 真窗口 ElyMark:不改保存逐字节相同,键入一字只改该块(需辅助功能权限)
tests/scripts/mark-modes.sh 真窗口 ElyMark:文档旁图片、三种模式往返、预览只读、跨模式撤销、改名、新建(需辅助功能与屏幕录制权限)
tests/scripts/mark-places.sh 真窗口 ElyMark:文件夹树、大纲跳转、文件夹搜索、最近列表(需辅助功能权限)
tests/scripts/mark-output.sh 真窗口 ElyMark:专注模式与外观持久、四种导出、打印、Git 状态与打开 ElyTree、大文件(需辅助功能权限与 pdftotext;ELY_CLIPBOARD_TEST=1 再测富文本复制)
tests/scripts/mark-outside.sh 真窗口 ElyMark:外部替换、改名、删除、冲突比较、自动保存、退出与崩溃后的恢复(需辅助功能权限)
tests/scripts/mark-tools.sh 真窗口 ElyMark:查找替换、正则、Tab、列表、表格、拖放与粘贴图片(需辅助功能权限;ELY_CLIPBOARD_TEST=1 再测粘贴)
tests/scripts/comments-rules.sh comments.py 对长注释与跨行注释报错
tests/scripts/settings.sh 真窗口:ElyPDF 设置里选深色,ElyTree 即刻变深;作者名写入、不丢外观;ElyTree 拒绝非 Git 程序;设置文件读不出时提示且不覆盖;主窗口重开回到原处(需辅助功能与屏幕录制权限)
tests/scripts/privacy.sh 真窗口 ElyDoc:键入的文字不进日志;worker 的日志进应用日志;设置里的诊断先列出所含内容,导出的文件去掉家目录与姓名、不含正文(需辅助功能权限)
tests/scripts/panels.sh 真窗口 ElyDoc:左侧栏默认不宽于 230、拖动内侧边缘变宽、宽度存入设置、重启后照旧(需辅助功能权限)
tests/scripts/storage.sh 真窗口 ElyMark:设置里的存储列出各类大小,日志当场清空,缓存下次启动前清掉,恢复点数写入设置(需辅助功能权限)
tests/scripts/recovery.sh 真窗口 ElyDoc:恢复点、崩溃后副本、连续两次崩溃、只杀引擎后重新打开(需辅助功能权限)
tests/scripts/pdf-recovery.sh 真窗口 ElyPDF:加锁文件的封存恢复点、空闲引擎退出后重开时再问密码(需辅助功能权限)
tests/scripts/autosave.sh 真窗口 ElyDoc:自动保存开关、停手即写、外部改动不覆盖(需辅助功能权限)
tests/scripts/print.sh 真窗口 ElyDoc:打印用文档自己的纸张,取消不动文档(需辅助功能权限)
tests/scripts/types.sh packaging/types.tsv 的文件类型在 Linux 桌面项、Windows 安装程序与 macOS 文档类型里一致,从不设为默认
tests/scripts/layout.sh 真窗口:六个应用在 640、800、1100 宽下无元素越出窗口左右,留截图(需辅助功能与屏幕录制权限)
tests/scripts/ooxml.py <用例> <文件> 不经 LibreOffice 读 DOCX、XLSX、PPTX 并按自算的预期核对(往返用例调用)
tests/scripts/webview.sh 真窗口 CEF 页面:输入法组字与提交、键入、焦点、主题变量、缩放、不可离开所在文件夹
cd site && pnpm deploy 官网构建并经 wrangler 发到 Cloudflare Workers(只有静态资源),地址 https://ely-office.zhangyanghaha0407.workers.dev
cd site && pnpm test && pnpm e2e 官网单测(几何、道路连通)与 Playwright 端到端(悬停、聚焦、按压、命中归属)
cargo check --workspace
cargo test --workspace
rm -rf target/debug/incremental 每次构建或测试后都删(用户定:增量缓存会占满磁盘)
cargo build -p ely-lok --bins LOK 单测前先建 worker;单测不会自动重建它
cargo build -p ely-pdf-engine --bins ElyPDF 的 worker(链接 engines/mupdf-build,ELY_MUPDF_DIR 可改;运行时 ELY_PDF_WORKER 可指定)
cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely-lok --bins,LOK 取 ELY_LOK_DIR 或 engines/ 下的构建);ELY_LOG 设日志级别;ELY_SMOKE=1 开窗口、画一帧即退出,ELY_SMOKE=hold 开着不抢焦点;ELY_NO_DIALOG=1 启动失败不弹对话框
决策
- 2026-10-01:上游版本以
scripts/fetch-references.sh为准。LibreOffice 26.2.6(8221e31)、MuPDF 1.28.5(8ad45e9)、gpui 为gpui-pre=0.3.8 与gpui-pre-platform=0.3.8(crates.io,2026-10-07 起,随组件库 K01)、组件库 69265b4(2026-10-08 起)。 - 2026-10-02:组件库 7380afb 起:对话框与面板的 Enter 提交改走字段的 key-down 动作(macOS IME 天然守卫,不再提前提交);面板族文案经组件
i18n::text(内建 en/zh-CN,应用目录可覆盖);核心控件带 role/aria;对话框带角色、名称与说明,Cancel/Close 与禁用说明本地化。同仓库~/Documents/GitHub/Ely-GPUI-Components修复并经 SSH 推送(本机 HTTPS keychain 令牌失效)。2026-10-04 组件库 b6cb94c:NumberInput::on_commit只在 Enter、失焦、步进、拖动时触发,键入不触发;改文档的数值框用它,否则每个键入数字都成一条内核命令与一步撤销。同批带入上游的 Checkbox、Switch、Select 等 role/aria 与 RenderSurface。c0f17ee:ToggleButton::disabled(不可按、无 Tab 停靠、说明不可用,提示可写原因)。967d1e4:ContextMenu::manual(按属主编号的请求在某点打开,右键不开),供行在按下之后才到的菜单,如内核给的拼写建议。 - 2026-10-06:审查者为探针复制的源码树须用自己的
CARGO_TARGET_DIR(scripts/review.sh写明);写完结论前删掉这些副本与其构建目录(曾积到 /tmp 近 900 GB,磁盘写满、链接失败):曾编进本仓库target/,ely-lok 里烘焙的源码路径指向已删的副本,单测找不到 worker。 - 2026-10-03:每条任务由
scripts/review.sh在 herdr 分栏里交给 codex(gpt-6.1-sol,effort high;本机codex别名已带--yolo,重复传参会报错)审查,最多三轮。此前各轮用 gpt-6-astra xhigh。 - 2026-10-01:macOS 的 LOK 用自建 LibreOffice 26.2.6.3 加
patches/libreoffice/0001-lok-macos-tile-painting.patch(已验证出图块)。2026-10-03 加0002-lok-dispatch-to-document-frame.patch:postUnoCommand原发往桌面的活动框架,另一文档关闭后命令落空或落到别的文档;改发本文档的框架。2026-10-04 加0003-lok-char-style-and-spacing-states.patch:LOK 原不发当前字符样式与段前段后,属性栏无从同步;补发.uno:CharStyle(界面名,无字符样式为空)与.uno:ULSpacing(上,下,1/100 mm);混合选区的状态发{"commandName","dontcare":true},ely_lok::MIXED表示,段落样式跨段不一致也算混合(原样报首段),字符样式带来的字体、字号、粗斜体等在选区内不一致也算混合(原样只看直接格式)。.uno:ULSpacing缺的成员归零,故两值同发;属性栏记住已发未回显的两值状态(段距、页边距),连续编辑以它为底。同日加0004-lok-panel-states.patch:补发页面尺寸、页面项(横向、版式、页码格式)、页边距、分栏、页眉页脚开关与版式、四种锚点与六种环绕(真假或禁用)、标题行重复;页面项缺的成员同样归零,故转向时页面项与尺寸都发。0005-jsdialog-skip-disposed-dialogs.patch:对话框守卫关闭对话框后,JSDialog 空闲任务仍为它生成整窗更新,读已释放窗口崩溃(实测崩溃报告),补丁让已释放的对话框不再生成消息。0006-page-size-takes-the-page-item.patch:.uno:AttributePageSize也收页面项,转向是一次改动、一步撤销。0007-lok-aqua-anyinput-without-peeking.patch:worker 的请求经主 GCD 队列派发,而 Aqua 的AnyInput用nextEventMatchingMask窥探时会转动 run loop、在内核空闲任务中途执行请求(实测:表格删列与排版空闲任务交错,SwLayIdle::DoIdleJob_段错误,约三成全量测试复现);补丁让 LOK 下的AnyInput改问客户端,worker 以registerAnyInputCallback报告是否有待处理请求,请求只在顶层让出时执行(补丁后连续十次全绿)。0008-lok-toc-caption-formula-without-dialogs.patch:目录、题注、公式在内核只有对话框入口;补丁给.uno:InsertMultiIndex加Title(默认目录)、.uno:InsertCaptionDialog加Category(程序名)与Text(照内核题注对话框的选项)、.uno:InsertObjectStarMath加Formula(文字转公式,选中公式时整体替换,一步撤销),并加getCommandValues(".uno:Formula")读选中公式。插入目录后光标落在目录内(内核原样)。0009-lok-word-count-values.patch:getCommandValues(".uno:WordCount")给文档与选区的字数、字符数;内核的.uno:StateWordCount状态是英文串,只作重读的信号。构建加--with-myspell-dicts,随包词典取自 LibreOffice 的 dictionaries 子模块(core 钉的 9925407,来源与许可见各目录 README 与 license),装在Resources/extensions。构建后只留dict-en(25 MB,英语拼写、断词、同义词):界面语言是英文与简体中文,中文无 Hunspell 词典;全部 65 个(473 MB)时 LOK 首启 5.4 秒、再启 2.3 秒,只留英文为 1.1 秒与 0.7 秒(PERF-01 要求 3 秒内可操作)。词典语种已由用户定:英文。0010-lok-outline-values.patch:getCommandValues(".uno:Outline")给各标题的文字、级别、位置(twips)与是否当前(光标所在处按文档顺序之前最近的标题,GetOutlinePos;分栏时位置比不出先后),.uno:Bookmarks的每项加位置(版面不显示的书签无位置);导航栏的跳转都按位置。0011-lok-font-lists-present-when-a-member-is-used.patch:LOK 把以;连写的字体列表整串当作字体名,列表中有字体已用上也报缺失(实测报出界面字体的回退列表);补丁让列表中任一成员用上即不算缺失,文档里写的列表全缺时照报。0012-lok-calc-cursor-references-merge.patch:Calc 在回调注册后补发单元格光标;.uno:SheetReferences?sheet=N给引用某表的公式格数、命名区域数、第一个公式格,以及用 INDIRECT 的公式与名称数(这类引用运行时才知指向,删除说明里单独列出);合并带MoveContents时不弹对话框,并修上游“多个数据格的 API 合并什么也不做”。worker 以SAL_LOK_OPTIONS=sc_print_twips_msgs(macOS 另加unipoll)运行,Calc 的光标、选区与.uno:SheetGeometryData同为文档 twips;视图把可见区经setClientVisibleArea报给内核。0013-lok-author-option.patch:lo_setOption("author", 名)设内核用户全名,新批注与修订以此署名。0014-lok-calc-formula-entry-and-names.patch:公式语法错误不弹询问、补全不切走编辑、提交清引用标记、名称的可撤销定义删除与列表判定、自动计算状态、xlsx/xls 的手动计算往返。0015-aqua-no-wait-with-a-due-timer.patch:Aqua 在直接超时待处理时不阻塞等原生事件(请求块里起的 0 毫秒定时器原会让调度器停到下一个请求)。0005另加存活标记:发送消息的客户端关掉对话框后不再读已释放成员。worker 记下每个文档载入时的内核视图,每次请求先切到该视图(Calc 按当前视图读写,多工作簿时否则会错表)。切表经视图的有序输入队列。视图记录跳转所需的最远点,滚动与图块以内核文档尺寸和它的较大者为界(Calc 空白区随可见区扩展)。视图按内核报的冻结行列(.uno:FreezePanesColumn/Row)分格:冻结部分不随滚动,其余照滚,图块按格裁剪,命中与表头同一换算(view/panes.rs)。文档占用 Tab 与 Shift-Tab(在 LokView 键位上下文里优先于焦点切换):Writer 插制表符或跳表格,Calc 跳单元格。worker 崩溃时打印本机栈(worker/fatal.rs),客户端报出退出信号。官方 macOS 包的paintTile不出图,上游 master 的修复也不够。Skia 必须开。工作进程在主线程以 unipoll 模式运行,请求经主 GCD 队列派发,环境设SAL_FORCE_HIDPI_SCALING=1与SAL_SKIA=raster。证据见docs/engineering-risks.mdR-01。 - 2026-10-04:LOK 命令与对话框。界面只经
ely_lok::commands::COMMANDS发 UNO 命令,worker 拒绝表外命令;内核弹出的对话框(LOK_CALLBACK_WINDOWcreated 或 JSDialog)在回调里即刻关闭并经LokError报给应用,需要的对话框由 Ely 自建。起因:Calc 的 Backspace 弹同步模态“删除内容”,曾卡死 worker。 - 2026-10-04:ElyDoc 查找替换、拼写、比较、邮件合并(DOC-12 至 14)。查找经
.uno:ExecuteSearch:查全部计数并高亮,下一处按全部的矩形定当前序号;替换一处后重新计数,无当前项时“替换”先选下一处。拼写建议来自右键时内核给的上下文菜单,只取拼写部分,ContextMenu::manual在插入点下打开。比较把当前文档存成工作副本、开成新标签、对它发.uno:CompareDocuments,两个源文件都不动。邮件合并的字段在正文里写成«列名»(同 Word 的显示,已定),预览与生成都是对模板副本整体替换;生成逐行写 DOCX 或 PDF 到所选文件夹,任一目标已存在即整体拒绝,从不发邮件。 - 2026-10-04:ElyDoc 输出与导航(DOC-15、16)。导出 PDF 经 worker 的
EXPORT_PDF请求,滤镜选项为 JSON(页码范围、批注印在页边、标出修订);打印先导出同一 PDF,macOS 用 PDFKit 的NSPrintOperation弹系统打印面板(ely_platform::print),其他平台报错,未实现。加密文档经 LOK 的DOCUMENT_PASSWORD回调询问密码,错的密码再问一次,取消即不打开。左栏导航默认打开(大纲、页面、书签),跳转前记下所在处,“返回”逐级回到原处;上一跳的输入未落到内核、或落下后光标尚未移动时,以其目标为所在处,连续跳转不丢位置;落下之后即按实际光标。当前标题取内核的标记,当前页按光标所在页。导出 PDF 与另存为的目标按文件身份(符号链接与硬链接)比对打开着的文档:导出遇之即拒绝,另存为遇本文件时只许原格式。缺字体时页面上方给出提示条。QA-10 与 Microsoft Office 互开未验证:本机无 Office。 - 2026-10-04:ElyDoc 的标签、会话、打开、保存、另存为(按文件身份比对)、关闭询问、冲突、密码与标签条移入
crates/ely-office:应用实现Host(格式表Kind、每标签附加数据、内核回报钩子),共用逻辑以扩展 trait 提供(use ely_office::prelude::*)。ElyTable 用同一外壳。共用文案在office.*。 - 2026-10-04:ElyTable(C09.1)。新建为空白 XLSX(首个工作表名随界面语言),保存保留 xlsx/ods/xls/fods,另存为 XLSX(默认)或 ODS,xlsm/xltm 进风险模式。上方名称框与公式栏(回车分别发
.uno:GoToCell与.uno:EnterString,Esc 交回网格),中间行列表头按内核几何自绘(点选整行整列,拖边改尺寸),下方工作表标签(右键与“工作表”菜单:插入、重命名、复制、左右移、隐藏、显示、删除;删除前列出引用该表的公式数与命名区域数)与状态栏(保存状态、内核给的选区统计)。“单元格”菜单:插删行列与单元格、合并拆分(多个数据格时先问移入或保留隐藏)、行高列宽(厘米)与最适、隐藏显示行列、冻结。填充柄拖到目标格发.uno:AutoFill。选区统计文字由内核给出,当前为英文(内核界面语言未随 Ely 切换),待处理。 - 2026-10-04:ElyTable 公式、计算、名称(C09.2,TABLE-03 至 06)。公式语法为 Excel A1(用户定):
Sheet2!A1、逗号分隔参数,由安装层packaging/libreoffice/ely.xcd设定;载入选项Language=en-US,DeviceFormFactor=desktop,内核界面与数字区域都是 en-US(小数点、月/日/年输入)。单元格内编辑由内核完成:键入时内核给出匹配的函数(editor::CompletionMenu显示在单元格下,上下键选,Tab 补全,经completeFunction入有序队列),参数提示(editor::SignatureHelp,取内核的用法文字),点选单元格插入引用并按内核颜色画框;内核只为点选的引用与再次编辑(F2)的已有公式画框,新键入的引用不画(同桌面版)。第一下 Esc 关提示,第二下取消(同内核)。语法错误的公式照原样输入,状态栏按界面语言解释当前格的错误(内核英文错误串换成界面文案,同统计);循环引用同此提示。“公式”菜单:自动计算开关(关时状态栏显示手动计算)、重新计算、全部重新计算、定义名称(名称、引用位置默认为所选区域的绝对引用、范围为工作簿或某表;名称先经内核判定)、名称列表(改引用、转到所命名的绝对区域、删除,失效引用标出;内核给出每个名称的区域,.uno:SelectedRange给所选区域)、追踪错误、追踪引用与从属单元格、移去箭头。名称框可直接输入名称跳转,跳转后显示该名称。手动计算在 ods、xlsx、xls、fods 存后重开都保持。 - 2026-10-04:ElyTable 格式、数据整理、规则(C09.3,TABLE-07 至 09)。补丁
0016-lok-calc-formats-and-conditions.patch(数字格式、条件格式、两处 sdi 参数)、0017-lok-calc-filter-validation-columns.patch(筛选、验证、分列)、0018-lok-calc-reads-requests-search.patch(读值、请求、查找全部):数字格式按代码设(.uno:NumberFormat的FormatCode),.uno:NumberFormatCode读当前格代码与类别(常规按区域块首判定)或校验代码;标准筛选收 JSON 条件(.uno:DataFilterStandardFilter的Query,含按列取值),.uno:AutoFilterValues照内核自己的筛选菜单给出条目、他列隐藏与本列勾选;数据验证收 JSON 规则(.uno:Validation的Rule),.uno:Validation读当前格规则,.uno:DataSelectValues给序列;条件格式收 JSON(单元格规则配内核单元格样式、色阶、数据条、按键删除,cellsh.sdi的同名槽也加参数),.uno:ConditionalFormats列出本表;分列收Options(ScAsciiOptions 串);.uno:SelectedRange另给所选区域内数据区的四角、首行文字与内核的标题判定。LOK 下内核不再弹自己的筛选菜单与序列下拉,改发LOK_CALLBACK_TOOLTIP(autofilter、datalist,带列行),无效输入发validityerror(标题、内容、样式、是否接受;接受与否取内核对话框的默认:信息接受,警告与停止拒绝),Ely 以ely_lok::Request接收后自建菜单与提示。查找全部按格报矩形(内核原把相邻命中并成一块,计数偏少)。查找替换移入ely-office(Finding,ElyDoc 与 ElyTable 共用,Host::search_leaves供 ElyDoc 记返回位置)。“粘贴为值/公式”只在系统剪贴板仍是本视图上次的复制时进行(内核自己的单元格剪贴板;Ely 复制的 HTML 末尾带一个进程内唯一的标记注释,以标记而不是文字识别,同样文字的外来复制不算);其他内容(含别的工作簿复制的单元格)明确拒绝并提示改用粘贴:内核无法从外来内容单取值或公式。排序最多三个关键字(内核限制)。右侧格式面板(数字、对齐、边框、填充)、“数据”菜单(排序、筛选、删除重复项、分列、查找替换、粘贴为值或公式、数据验证)、“格式”菜单(格式面板开关、条件格式)。组件库SortBuilder、FilterBuilder的文案未接 i18n,中文界面下仍为英文(上游待办)。 - 2026-10-04:右侧属性栏宽取
theme.sheet_size()(380),左栏仍是sidebar_width。PropertyGrid名称列固定 120,240 宽时编辑器只剩约 76。颜色经色块弹出ColorPalette,一次点击一条命令。 - 2026-10-01:MailCore 2 的子模块 libetpan 过旧,改钉 libetpan master
4d47862。 - 2026-10-01:每个应用的全部数据在
<本机数据目录>/ElyOffice/<slug>/,路径由ely_platform::data_dir给出。卸载只动这里,且先问。用户文档永不碰。 - 2026-10-04(用户定):macOS 包标识前缀
ely.zacharyzhang.office(packaging/macos/bundle.sh);发布者 ZacharyZhang,网址 zacharyzhang.com(macOSNSHumanReadableCopyright,Windows 卸载项Publisher与URLInfoAbout;Linux 桌面文件无此字段)。界面语言英文与简体中文,以后再加。拼写词典只随英文:LibreOffice 的词典库没有中文(中文不用 Hunspell 拼写)。内核界面语言固定英文,函数名照英文;ElyTable 把内核英文的选区统计(Sum、Average 等七个固定名)换成界面语言,数值照内核。批注与修订的作者取各用户的账户全名(无全名用登录名,与内核自身次序一致),由补丁 0013 的setOption("author")在 worker 启动时设定;作者设置页随设置任务接入。邮件合并字段写法«列名»由我定(用户交我决定)。 - 2026-10-01:更新清单是 JSON:
{"version", "assets": {"<os>-<arch>": {"url", "sha256"}}},只走 https(本机回环除外)。检查、下载、校验在ely_platform::update,从不静默安装。包管理器安装的 Linux 版本不自更新。清单地址由设置给出,尚未配置。 - 2026-10-05(用户定):ElyAgent 的后端与内置模型的登录配置以 Kigi CLI(https://github.com/ZacharyZhang-NY/Kigi-CLI,Rust)为基础,以 Rust 依赖直接嵌入,不用 ACP;沿用其提供商登录(订阅账号或 API 密钥)与模型配置。
- 2026-10-05(用户定):八个应用都不许卡顿:键入、选择、滚动、文档渲染都要即时(GPUI 的 120 FPS 手感),树莓派 3 上也要流畅。输入延迟与帧时间是验收项,要实测;界面线程不等内核、不持 worker 锁做同步请求,空闲不做事。
- 2026-10-05:LOK 的输入与事件路径(实测后定)。原样:视图的事件监听长轮询 worker(持 worker 锁最多 50 毫秒,worker 读线程也随之阻塞最多 250 毫秒),按键排在锁后,键入锁等待中位 290 毫秒、最长 9 秒;输入队列在界面执行器上逐条跳回界面线程,每键约一帧,连续键入积压到 1 至 2 秒。现在:worker 有新事件即主动推送(
tag::EVENTS,推送线程在输出锁下取批,批次即发送时状态),客户端读线程把回复与事件分开,事件存入client::Feed,视图以异步方式等它(Feed::next,有新批次时唤醒,不占线程、不碰 worker 锁;原先每个打开的文档阻塞一个后台线程最多一秒,文档多于核数时会饿死输入与图块;监听任务归视图所有,视图释放即停);每个视图一条后台通道(view/lane.rs),输入优先、图块其次,连续执行,界面线程只收落地结果;文档尺寸与页框只在内核报尺寸变化时在后台读;批次弄脏的已显示图块即刻重绘,不等下一帧;视图的Reported事件只在内核回报或输入落地时发出,滚动与图块到达不触发应用的观察钩子;编辑与光标移动后应用要读的内核列表(ElyDoc 导航、字数、审阅,ElyTable 工作表与几何、单元格规则、格式面板)经ely_lok::read_off在后台读,一次一个,结果按发起者核对。实测(macOS,tests/scripts/lag.sh):按打字速度键入时输入落地 0 至 1 毫秒,图块 1 至 3 毫秒,按键到新图块 ElyDoc 中位 19 毫秒、ElyTable 3 毫秒;连续快速键入(脚本一次送出近百字)时,各次运行在 3 至 225 毫秒的中位之间摆动:新文档首次键入时内核有一次 150 至 230 毫秒的冷启动(首块图块与拼写等初始化),同一批字排在它之后;原为中位 1 秒、p95 1.9 秒。树莓派 3 未实测(本机无设备)。界面线程不碰 worker:应用对内核的读取、保存与另存为的导出、导出 PDF、比较、邮件合并都经Tabs::read_in(或ely_lok::read_off)在后台执行,回到窗口时按发起的标签与读取时的状态核对;保存导出期间标签状态为“正在保存”,同一标签的后续保存排队到它结束;另存为在写入前按文件身份再查一次目标是否已被别的标签持有;导出期间又有编辑时,“保存后关闭”不关闭,回到关闭询问;关闭标签时的撤销历史也在后台读;邮件合并预览与比较在发起时就保留自己的标签号与工作目录;读取失败不立即重试,等下一次状态变化;载入时一并在后台读版面与撤销历史(ely_lok::layout,LokView::open接收版面)。窗口关闭回调先不放行,在后台逐个读标签的撤销历史并关闭(有改动的照常询问),最后一个标签关闭后移除窗口;内核文档的关闭也在后台(2026-10-06 起,原先同步读历史、在界面线程持 worker 锁关闭)。 - 2026-10-04(用户定):八个应用的内容区一律随系统深浅色:Doc 页面、Table 网格、Show 幻灯片、PDF 页面、邮件正文、Mark 编辑区、Agent 输出。自动颜色随之翻转,文件里写明的颜色照原样;打印与导出 PDF 仍为浅色。各引擎(LOK、MuPDF、CEF)接入应用主题,不留引擎默认色。LOK:
ely.xcd定义COLOR_SCHEME_ELY_DARK与COLOR_SCHEME_ELY_LIGHT(文档底色、自动文字色、网格、链接等取 Ely 默认色板;内核自带的深色方案颜色留空、按应用外观取默认值,传方案名不改外观,所以不用它),LokView在主题深浅变化时经有序队列发.uno:ChangeTheme(不算编辑),页面底色深色取surface、浅色取paper;Omarchy 色板与这两套固定色可能略有差别。导出 PDF 实测仍为白底。图块作废时旧图仍显示,新图到达后替换,编辑与主题切换都不闪。 - 2026-10-01:主题。
Appearance为系统、浅色、深色。系统档在有 Omarchy 时跟随 Omarchy(ThemeSettings::omarchy_sync)。读 Omarchy 的$HOME/.local/state/omarchy/current/theme/colors.toml(Omarchy 写死此路径,不随XDG_STATE_HOME),目录由ELY_OMARCHY_DIR可改。读法与回退链照reference/omarchy/bin/omarchy-theme-color:松散引号、colorN、purple、selection_background、theme_type、light.mode、亮度和 382 比。缺的色相用 Ely 自己的色并警告。色板照组件库A11yChecker的规则收口:正文、状态色、代码色在各自的表面上 4.5:1,焦点与图表色在页面上 3:1;先拉近前景再拉到纯黑白,最后逐项核对,核对不过的主题整套忽略。切换时 Omarchy 先暂存next-theme再整体替换theme,用文件监视跟随,不用它的钩子。新主题无colors.toml时退回 Ely 色板。ELY_APPEARANCE、ELY_OMARCHY_SYNC、ELY_REDUCE_MOTION是覆盖开关,设置页接入前先用环境变量。 - 2026-10-05:组件库 8658999(组件库重写历史后的 main,加本项目三个提交):属主不经
Close移除的浮层(一起移除的嵌套浮层同样,回到最外层打开前之处)(对话框动作只清自身状态、异步校验通过后才关)照样把焦点交回打开前之处;原先焦点落在根上,键入无处可去,须先点一下网格。同批带入上游的 ColumnShell、输入法组字时光标重新闪烁、Icon::from_path、MIT 许可。旧 pin 2f02c82 已不在 main 上,只能按完整哈希取得。Select::label与TextInput::label给控件无障碍名称而不绘制;对话框里描述列表旁的控件以此命名,开关用自身的label。7186179 加NumberInput::label(经内部文本框的TextInput::set_label),同批带入上游的 rustfmt 修正。80c46b1:TemplatePicker的列表是一个 Tab 停靠点,上下、Home、End 移动,回车使用,行带列表项语义,搜索提示、全部、使用、无匹配的文案进内建中英目录。7df3045:SelectableList在 AX 树里是列表(label命名),ListItem是列表项(标题为名,带说明、选中、在可选列表中的序号与总数);原先行无角色,不在 AX 树里。af54877:NumberInput::blank显示占位文字而非数字(混合选区),空着回车或失焦不提交,键入后照常。408f2b0:CommentSidebar与RelativeTime的文案进内建中英目录;属主不处理“解决”时(无on_resolve)不分未解决与已解决,一列显示全部。bcf0378:CommentSidebar的批注列是列表,每条是 Tab 停靠点(列表项语义,名为作者与正文,带选中、序号与总数),回车或空格选中。71bfc93:init_for_tests(test-support下)装主题与键位、不要资源,应用单测可渲染 Ely 组件。617a8d6:FindWidget在查询框里按 Esc 即关闭(调on_close)。2ce3368:ContextMenu可定宿主大小。3d08744:DescriptionList的值占满所在列,其中的文本框随之全宽;同批带入上游的图表、分页改动。a6b1bb0:SignaturePad的“在此签名”与“清除”进内建中英目录;同批带入上游 P01 至 P15(各组件的越界与失效状态记日志而不 panic)。490b022:ChangesList分列冲突、已暂存、改动、未跟踪、已忽略(各有其动作:冲突与已忽略只可打开),标题与提示进内建中英目录;同批带入上游 P16 至 P20。7bb53f4(ElyTree 用):ChangesList行数可缺、按区报按下、当前行;DiffViewer外算的段、块按钮、可选的变动行、逐行读给辅助技术;CommitList、FileHistory、BlameView、ThreeWayMerge只画可见行,CommitList近尾加载;DescriptionList::text;SegmentedControl各段为单选;BranchList、TagList可选中、只画属主处理的行内动作;marked、conflicts、resolve按标记宽度(MARKER为默认 7),a284c71 起只按 LF 断行;c5799cc:StashList文案进内建中英目录,行可选(selected、on_pick),只画属主处理的行内动作;7fab072 至 3741d5d:分支、标签、储藏的行分两行(上行名称与行内动作,下行说明与行尾文字),窄时名称与说明先让;行内动作一律显示(原先只在悬停时显示,键盘停在其上时看不见),改动列表同;各 git 行带透明的 1 像素边框,焦点框才画得出(原先focus_ring无边框宽度,键盘焦点不可见);同批带入上游 P21 至 P29。4f8f3bd:Changed::lines可缺(二进制文件、文件夹不计行数,不显示);37c5187 缩短其注释。19b0b29:DiffViewer::from_stretches(别处算好的差异,如 Git 自己的)、hunk_action(每块标题上一个按钮)、selected与on_line(改动行可按,带 Shift),diff::mark_words给外来的行标出改动的词,“行内”“并排”“N 行未改动”进内建中英目录;30fea9e:有块按钮时各行同高,底色相接。edd2c12:ChangesList每次按下带分区(ChangeSection),行是 Tab 停靠点(列表项语义),selected标出当前文件。2e080b8:SegmentedControl每段是单选按钮(名为其文字,带选中状态),原先不在 AX 树里。e226d79:差异查看器的行模型与配色移入git/rows.rs(文件不超过 500 行);7b807a9 缩短其注释。51d2aea:CommitList、FileHistory、BlameView只画视口内的行(uniform_list,填满所在的框;CommitList收Rc的提交与lanes算好的图,on_end在接近末尾时要下一页),各行是 Tab 停靠点(列表项语义,选中状态;Blame 每段首行可按,名为作者、时间与摘要),FileHistory的行数可缺;CommitInput的“提交到”“修改最近提交”进内建中英目录。e6220ad 与 3fc5a46:提交行窄时作者、提交号与时间先让,摘要至少 6rem。d9cbe99:DescriptionList::text(标签与文字值,行的无障碍名为“标签: 值”),提交行的无障碍名带上引用。fb7ee42 与 fcc45cc:DiffViewer只读时每行也是标签(“行号 + 文字”),块头为标题,折叠行有名;BlameView每行为“行号 文字”,段首按钮的名略去空的部分。3d0a3de 至 949d776:差异与内建目录的注释缩短。829ef68:ThreeWayMerge只画视口内的行(填满所在的框;每行三侧各一行,冲突处一行带三个选择按钮,各行同高;不再附结果与统计,结果由属主显示),文案进内建中英目录,每行与冲突行在 AX 树里有文字;merging::conflicts认 diff3 的|||||||段,resolve取当前侧时不带基底段;BranchList与TagList的行可选(selected、on_pick,Tab 停靠点、列表项语义)。63d4a45 至 922cd53:BranchList只画属主处理的行内动作;merging::marked按标记文字里 Git 的分组得出各段(diff3 段为基底);窄时行尾文字先让,行内动作不被挤出。 2ce3368:ContextMenu实现Styled,样式定其宿主大小,其中的虚拟列表才能撑满(原宿主无尺寸,uniform_list高度为零)。 - 2026-10-05:ElyTable 透视表与图表(C09.4,TABLE-10、11)。补丁
0019-lok-calc-pivot-tables.patch与0020-lok-calc-charts.patch:.uno:DataPilotExec收Pivot(JSON:源区域、行、列、筛选、值字段与函数 sum/count/average/max/min),光标在透视表上即原地修改,否则从源区域在新工作表创建;行、列、筛选里的空名表示内核的数据布局字段(多个值字段横排或竖排的位置),读出与写回都保留,已在表中的行、列、筛选字段保留各自的小计函数,读出的布局原样写回不改表;照内核布局对话框的步骤(ConvertOrientation,保留各字段的成员、排序与版式),新表的筛选按钮、钻取、展开收起都关(同对话框初值);放到新工作表时插表与透视表是两步撤销(同桌面版)。刷新.uno:RecalcPivotTable在内核里本就不进撤销(它改缓存)。.uno:InsertObjectChart收Chart(JSON:类型 column/bar/line/scatter/pie、标题、图例、X/Y 轴标题;other保持原类型),LOK 下不开图表向导、插入为一步撤销、插入后图表选中;给出的区域每段都要解析得了、所指工作表都要存在,否则插入与修改都拒绝、文档不动(内核原会改用示例数据,修改时ChartModel::setArguments抛出未捕获的异常使 worker 退出);.uno:ChangeChartData也收Chart,区域与外观合成一步撤销(撤销保存整个图表的图示与主标题的副本,堆积等外观以外的设置一并恢复)(列表动作带当前视图号,否则 LOK 拒绝撤销别的视图的动作)。读取:.uno:Chart(选中图表的对象名、区域、表头、外观)、.uno:PivotTable(光标处透视表的名称、位置、源区域、字段与布局),.uno:SelectedRange加data(选区内数据的绝对区域)。界面在“插入”菜单:数据透视表、刷新数据透视表、图表;对话框在光标处有透视表、或有选中图表时为修改,否则以选中的数据(单格扩到数据区)新建,源与图表区域取data,不含空行。为对话框发起的读取(Tabs::ask_in)回来时,发起的工作簿已不是当前标签即丢弃,不为隐藏的工作簿开对话框(ElyTable 全部对话框、合并询问)。没有数据时恢复原光标(select.data会把单格标成区域,回车随之只在格内打转)。公式语法是 Excel A1,区域写Sheet1!$A$1:$C$5。 - 2026-10-05:ElyTable 文本文件、导出、打印、保护、大表(C09.5,TABLE-12 至 16,§9.3)。补丁
0021-lok-calc-page-setup-protection.patch:.uno:PageFormatDialog收Page(JSON:横向,百分比、宽高页数或总页数之一),一步撤销;.uno:Protect收Protection(JSON:带密码与八项许可保护,或凭密码取消,密码错不改动);.uno:CellProtection带值时按值设锁定(原样只切换);读.uno:PrintLayout(打印区域、重复行列、方向、缩放、共用页面样式的工作表、光标处手动分页)与.uno:SheetProtection;修上游:缩放改动后InvalidateTextWidth按整表列数遍历未分配的列,越界崩溃。0022-lok-calc-text-files-and-links.patch:LOK 下 CSV 带选项载入不再弹导入对话框(原样照弹,模态挂死 worker);saveAs的过滤器选项原样交给过滤器,只去掉 LOK 自己的关键字(原样丢空项、插空格,CSV 选项错位);读.uno:ExternalLinks(外部引用、区域链接、表链接、DDE、WEBSERVICE 的来源)。LOK 载入选项可带FilterName(原样只按扩展名猜,.txt落进 Writer)。worker 的 CSV 导入与导出只收类型化字段(IMPORT、EXPORT_TEXT),选项串与 Calc 文本过滤器名在 worker 内生成,载入后不是表格即关闭并报错。CSV 规则由我定(用户未定):打开 csv、tsv、tab、txt 先出导入对话框,选编码(UTF-8、UTF-16、GB18030、Big5、Shift_JIS、EUC-KR、Windows-1252、Windows-1251、ISO-8859-1;有 BOM 按 BOM,合法 UTF-8 即 UTF-8,否则中文界面 GB18030、英文界面 Windows-1252)、分隔符(tsv、tab 为制表符,否则取首行最多的逗号、分号、制表符、竖线,都没有为逗号)、引号、逐列类型(常规、文本、三种日期顺序、跳过),预览与类型推断按 Calc 文本导入的规则拆字段(impex.cxx:引号前的空格跳过、引号后的文字并入、双写引号转义、空行算一行),后台读整个文件,严格解码(有字节读不通即提示并禁用打开,不以替换字符静默改写),预览前 8 行、每格 200 字,每列都有类型选择;打开时导入的就是检查过的那份字节(对话框期间源文件再变也不读);整个文件里出现前导零、加号开头、超过 15 位数字(可带负号)的列默认为文本;数字按 en-US 读,日期只在标为日期的列按所选顺序读,不识别特殊数字,=开头的文字不作公式。导入得到未命名的新工作簿(标签为原文件名),保存走另存为 XLSX,原文件不动。导出(Cmd+E):PDF(全部工作表,按页面设置)、CSV、TSV;文本导出只写当前工作表的显示值(UTF-8,无 BOM),对话框写明导出哪张表、公式格式与其他表不保留;内核“只存当前表”的警告框由ely.xcd关掉。打印(Cmd+P)先导出同一 PDF 再交系统打印面板。“页面”菜单:页面设置(打印区域写作 A1:D20,多个用逗号;重复行 1:2;重复列 A:B;方向;缩放),插入、删除行列分页符(光标处没有即提示);页面样式由多张表共用时对话框写明作用于哪些表。“工作表保护…”在工作表菜单,“锁定单元格”“解锁单元格”在格式菜单。§9.3:ely.xcd设 XLSX 与非 LibreOffice 生成的 ODF 载入时总是重算(原样信任文件缓存,实测 Excel 留下的=1+1过期缓存 7 与未知函数缓存 42 都当结果显示);不支持的函数显示#NAME?,原公式保留(函数名存成小写),状态栏解释错误;其缓存值不保留,另存写成错误值(已知限制)。外部链接照内核“按需更新”不刷新,显示保存时的值,工作簿有链接时上方提示条列出来源(每次编辑后在后台重读)。NOW、TODAY 用系统时区;1904 日期系统随文件,未专门验证。视图只请求屏上的图块,离屏超过一屏的图块丢弃(原样只增不减,滚动长表内存随之涨);行列表头只画可见的。 - 2026-10-05:ElyShow 应用壳、幻灯片管理、模板与母版(C10.1,SHOW-01、02)。补丁
0023-lok-impress-slide-design.patch:.uno:PageSize改走页面对话框的SetPageSizeAndBorder(原样走SetupPage,不进撤销);.uno:PresentationLayout收Master(母版名,套到当前幻灯片,一步撤销;不存在即拒绝);.uno:ThemeDialog收ColorSet(内核配色名,套到当前母版,一步撤销);读.uno:SlideDesign(母版列表、当前母版、是否在母版视图、当前版式、配色列表与当前配色)。内核参数类型:SfxUInt32Item取long(传unsigned long时参数被丢弃,命令仍跑出一步错误的撤销)。缩略图经 worker 的PAINT_PART(paintPartTile,第二参是编辑模式:0 幻灯片,1 母版),按名缓存,编辑时只重画当前页,结构、尺寸、配色变动时全部重画,内核换深浅色方案后全部重画(首帧先于方案落地)。母版视图的进出内核不发重绘,按设计读出的编辑模式判定,进出后整屏作废重画(LokView::invalidate)。演示的画布按视口缩放整页并居中(LokView::fit_page)。新建:内核自己的空白演示(private:factory/simpress,16:9,标题版式)每次运行写一次到工作目录,再按副本打开;或内核随带的 Impress 模板(template/common/presnt)。版式取内核版式面板的 12 种;尺寸预设宽屏 16:9、16:9、4:3、A4、Letter,另可输入厘米。章节(用户定为外部阻塞):内核无幻灯片章节,PPTX 导入丢弃p14:sectionLst,ODF 无此结构。补丁0024-lok-math-tiles-keep-the-painted-tile.patch:图块收尾时公式对象助手以默认擦除重设设备尺寸,文字编辑中的 Impress 图块整块被擦(macOS 上为黑);改为不擦,同图表助手。离开文字编辑(内核收起插入点,LokView::caret)时重画当前页缩略图,编辑中画的缩略图带编辑框。0025-lok-impress-paints-the-current-view.patch:Impress 的GetViewShell原取文档外壳所连的视图,外壳栈重排(ViewShellManager::MoveToTop)时内核先断开再接回,其间到达的绘制得到空白图块(macOS 上为黑),按键丢失;LOK 下改取当前视图的主外壳。0026-lok-impress-settles-panes-before-commands.patch:Impress 的窗格重配排在用户事件里,先到的命令找不到幻灯片排序外壳,隐藏、显示等槽被丢弃;ITiledRenderable::settleViews在postUnoCommand派发前执行,Impress 在此同步处理排队的配置。两者在八路并行下实测(各 96 次全绿,修前约四十次一败)。缩略图失效按撤销与重做列表的条目判定(ely_lok::history::steps):撤销列表有条目消失即撤销,重做列表的条目进了撤销列表即重做,撤销后立即编辑也能识别。 - 2026-10-05:ElyShow 文字、图形、图片、对象操作(C10.2,SHOW-03 至 06)。补丁
0027-lok-impress-objects-guides.patch:读.uno:SlideObjects(当前页各对象的种类、外框、占位符角色、锁定、是否选中,选区的种类、填充、轮廓、透明度、阴影、自动适配、外框、旋转与中心、锁定、能否组合与取消组合,网格与参考线吸附,参考线);.uno:SelectObject收Index、Add(按层次选中);.uno:CapturePoint收Kind、X、Y添加参考线,收Index加坐标或Delete移动或删除(LOK 无参考线对话框);.uno:TransformDialog收ProtectPos、ProtectSize;.uno:TextAutoFitToSize收Fit(off、shrink、grow,对所有选中对象一步撤销,横向增长一律关,撤销连同文字恢复;原样是只对单个对象的开关);.uno:ChangePicture收FileName原位替换;LOK 下插入图片放在幻灯片中央(原用窗口可见区中心,LOK 下无意义)。LOK 把TransformDialog的位置、尺寸与旋转中心当 twips 读入,读出是 1/100 mm,面板发送前换算;线宽与参考线不换算。插入菜单:文本框、矩形、椭圆、三角形、直线、箭头、连接线(内核直接在幻灯片中央创建)、图片;视图菜单:吸附到网格、吸附到参考线(勾选随内核)、添加水平或垂直参考线。右侧面板选中对象时为对象属性(位置、大小、旋转、锁定;填充、透明度、阴影、轮廓与线宽;文字的字体、字号、样式、颜色、对齐、列表、自动适配;图片的替换与裁剪;层次、对齐、分布、组合),否则为设计;两者下方是对象列表(可多选,按层次选中)。数值框在回车与随后的失焦各提交一次时只发一次:按字段记住已发值与发出时读到的值,到下一次读取(按标签与读取次数)为止;混合值的数值框为空并显示“混合”(NumberInput::blank),空着回车或失焦不发送,不作去重的依据。色块的清除按钮发各自的命令(无填充、无轮廓)。直线、箭头、连接线与图片只显示轮廓与线宽。对象列表取消全部选中即清除内核选区(.uno:SelectObject只带Add时不选任何对象)。补丁按序列打:scripts/apply-lo-patches.sh找出源码已等于“原版加前 N 个补丁”的 N,再打其余;原先逐个反向检测,补丁重叠后重跑构建失败。面板与菜单的命令不经视图输入,缩略图改按撤销列表的变化判定当前页作废。ElyDoc 属性栏的内核控件(开关、按钮、色块、字体、字号)移入ely_office::panel,ElyShow 共用。连接线接到图形靠在画布上拖动端点,未单独验证粘附。 - 2026-10-05(用户定):ElyShow 的音视频由各平台原生播放:macOS AVFoundation,Windows Media Foundation,Linux GStreamer(同 LibreOffice 自己的 avmedia 后端),H.264/AAC 解码与许可随系统;Ely 一个接口,三份实现。LOK 不放媒体。
- 2026-10-05:ElyShow 动画、切换、媒体(C10.4,SHOW-09、10)。补丁
0033-lok-impress-slide-layers-on-macos.patch:放映图层渲染(SlideshowLayerRenderer)把内核画进客户缓冲区,Aqua 设备画不进,macOS 上每层都空;改为画进自带透明度的设备再拷出(同图块)。0034-lok-impress-transitions-and-animations.patch:.uno:SetSlideTransition(JSON:预设、时长、单击或自动及秒数、全部幻灯片)、.uno:AddAnimation(给选中对象加预设)、.uno:ChangeAnimation(开始方式、时长、延迟、前后移、删除),各一步撤销;类型不合或内核无此预设即不改;读.uno:SlideTransition与.uno:SlideAnimations(每个效果的目标按.uno:SlideObjects的序号,并给放映图层的散列,同anim::convertTarget)。0035-lok-impress-media.patch:.uno:InsertAVMedia收Width、Height(1/100 mm)时不弹媒体对话框、不经内核播放器量尺寸,放在幻灯片中央;读.uno:SlideMedia(序号、URL、是否链接、实际播放的文件:链接即原文件,嵌入即内核的临时副本、文件是否缺失)。Ely 只给基础一组(切换:无、淡化、经黑色淡化、向左推入、向上推入、向左覆盖、向左揭开、向右擦除、向外分割、溶解;动画:出现、淡入、飞入、擦入、放大进入、浮入、放大缩小、闪烁及对应的退出),文案由 Ely 给出;文件自带的其他效果显示内核名称。预览不阻塞编辑:worker 的SLIDE_LAYERS在后台取图层(静止层、动画对象各一层、母版字段占位),Ely 按效果排时(单击接在前面全部之后,与上一同时,在上一之后)在画布上合成:淡入淡出改透明度,飞入飞出与浮入浮出移位,擦除裁剪,放大缩小与强调缩放,闪烁隐显;预览里不画日期、页脚、页码字段(内核留给客户端画)。切换预览用两页的图层:新页按初始可见,旧页对象全显示(只读当前页的效果,旧页的退出不知道);首页从黑开始;无填充的页铺页面底色(ely_lok::paper,同编辑区)。媒体(用户定各平台原生):ely_platform::media::Player,macOS 以 AVFoundation(AVPlayer加AVPlayerItemVideoOutput,帧为 420f 的CVPixelBuffer,经 GPUI 的surface画在对象上),状态有载入、可播(时长、画面尺寸)、失败(系统给的原因);Windows 与 Linux 尚未构建,打开即报“此平台未构建”。插入:选文件后先由系统播放器打开,可播才插入(视频按 96 dpi 换算并缩进幻灯片八成之内,声音 2×2 厘米),不能播即提示原因、不插入;默认嵌入,随文档保存。选中媒体对象时属性栏给播放、暂停与时间;链接文件缺失或系统不能播放时写明。正在播放的媒体按标签、幻灯片名与媒体 URL 认定,不符即停;播完再播从头开始。预览按标签、幻灯片名与撤销列表认定,读取回来、每帧绘制时不符即弃;停止令在途读取作废。向左揭开时旧页叠在新页上。面板的秒数字段经object::commit去重(回车与随后的失焦只发一次);切换的各字段以已发未读回的切换为底(Extra::transition_asked,带命令的输入序号;命令落地后开始的读取回来或换页即清,撤销也随之生效);秒数去重的键带幻灯片,动画的还带效果序号与图层散列(motion::effect_key)。预览另按视图的已落地与待落地编辑认定。播放实例在文档每次通知时核对(observe_self),标签关闭即停。 - 2026-10-05:ElyShow 表格与图表、备注与审阅(C10.3,SHOW-07、08)。补丁
0028-svx-chart-look-helpers.patch与0029-svx-chart-data-helpers.patch:图表的外观与数据读写移入 svx 的ChartHelper(Calc 与 Impress 共用;JSON 经 boost.json 保留类型,类型不对即拒绝,改动之前判定);0030-sc-chart-looks-from-svx.patch:Calc 改用它,原代码删去;0031-lok-impress-charts-notes-comments.patch:.uno:InsertObjectChart收Chart(外观与数据的 JSON),不开图表界面,落在幻灯片中央,选中一个图表时就地修改(复制后替换,一步撤销),JSON 不合即拒绝;先设外观再设数据(数据按图表类型写);读.uno:Chart。缺失的点读写为 null(图表自己的非数标记),对话框里是空格;散点图的类别即 X 值:须为数字,按数字写入(原样写成文字,PPTX 导出为文字引用,再打开 X 变成 1、2、3);PPTX 打开的散点图按系列角色读出,各系列共用一组 X 时以 X 为类别,各系列 X 不同时不是一张表,读出不带data,对话框只改外观并说明。0032-oox-scatter-x-values-as-numbers.patch:散点图的 X 全是数字时导出 PPTX 为数字引用。只在图表变成散点图时设为只画点,原是散点图的保留连接线;应用对散点图先查 X 是否数字,指明哪一格。原有的attachData去掉:各写入已自行生效,它反把数字类别写回文字;新命令.uno:SlideNotes以纯文字设Part页的备注,一步撤销(备注随所属页提交,切页排在失焦提交之前也不错页);读出当前页序号、文字与是否“带格式”。PowerPoint 给每段文字都写全字符格式,所以“带格式”指段落格式或字符格式在文中不一致:一致时新文字沿用它,不一致时拒绝(纯文字会丢格式,界面只读并说明)。批注带所在幻灯片序号;视图无作者时新批注取用户名(原样留空,补丁 0013 设的用户名到不了 Impress)。0027 另加:.uno:SlideObjects不列批注(26.2 的批注是绘图对象),.uno:SelectObject的序号同样不数批注。共用:ely_office::chart(图表对话框的外观字段,ElyTable 与 ElyShow),ely_office::panel(内核状态驱动的面板控件,ElyDoc 属性栏与 ElyShow 对象面板)。界面:“插入”菜单的表格(列 1–20、行 1–50)、图表(外观与图表自带的数据表,类别乘系列,可增删;非数字的格拒绝并指明哪一格;选中图表时为修改)、批注;表格对话框的行列数键入即生效(不等失焦);读选中图表时在同一次 worker 调用里读出当时选中的对象(页、在列表中的序号、外框),与发起时不符即丢弃,建对话框前再按界面所见核对一次;对象面板的表格组(行列插删、合并、删除表格,可否由内核状态定,光标进单元格后可用);画布下方备注栏(失焦提交);“视图”菜单的批注栏(collab::CommentSidebar:选中即转到该页并点亮画布上的标记,编辑、删除;只在所在页显示为选中,离开后可再点回)。不做回复:Impress 无回复结构,内核的回复把原批注改写成英文引语加回答;PRD 未要求。备注不上观众屏随放映(C10.5)。 - 2026-10-06:ElyShow 放映与演讲者视图(C10.5,SHOW-11、12)。补丁
0036-lok-impress-slide-show.patch:读.uno:SlideShow(每张幻灯片的隐藏、备注文字、切换与动画,同.uno:SlideTransition、.uno:SlideAnimations的形状,媒体带外框);.uno:SlideMedia每项加外框(1/100 mm)。放映用内核的放映图层(SLIDE_LAYERS),不是截图轮播:母版的日期、页脚、页码由内核另画成文字层,ely_lok::show::layers把它放到母版占位处的层次上(该页不显示的字段无文字层,即不画)。“放映”菜单:从头开始(F5,从第一张未隐藏的)、从当前幻灯片开始(Shift+F5,隐藏的也从它开始);开始时等该标签已发的输入落地,在同一次 worker 调用里读当前页、把文档另存为工作目录里的 ODP 副本(每次放映一个文件名)、载入副本并按应用深浅色设方案、读全部幻灯片;放映的图层、动画与嵌入媒体都来自副本,编辑不进入本次放映,放映结束时关闭并删除副本。启动中与放映中再按开始不响应。单击组从此前全部已触发动画与入页切换都结束后才开始。单击、右、下、空格、PageDown、N 下一步(先放本页的单击动画组,再到下一张未隐藏的幻灯片,最后是黑色结束屏,再一下退出);左、上、PageUp、Backspace、P 回到上一张并显示为全部动画已完成;数字加回车跳到第几张(含隐藏的);Home、End;B 或句号黑屏;L 激光笔;Esc 退出(有已键入的数字时先清掉)。与“上一个”同时、在其后开始的动画并入前一单击组,第一组在进入本页(切换结束)时开始;自动换片在本页动画全部完成后再等所设秒数。切换按进入方式:下一步有切换,跳转(含跳到本页)与后退无;离开的页按离开那一刻的动画状态画在切换里。媒体载入或播放失败即停,演讲者视图(单屏时观众屏)写明原因,放映继续。后退显示该页全部动画已完成。本页图层到达后才开始入页切换与动画计时;切换结束即释放离开页的图层。Player::tick判定循环重播与失败,Player::ended判定播完(人按的暂停不算播完;编辑区与放映共用);定位为精确定位(容差为零)。单击媒体对象播放、暂停或播完重播,离开本页即停;视频帧画在对象上。多屏:屏幕多于一块时先问观众屏与是否开演讲者视图(演讲者视图开在另一块屏上),两窗口都全屏;演讲者视图有当前页(鼠标在其上即为观众屏的激光点)、下一页(初始状态)、备注、计时(自开始起)、时钟、页码、上一步、下一步、黑屏、激光笔、结束放映。备注只在演讲者视图,观众屏从不画备注。图层宽度取观众屏上幻灯片的两倍像素,不超过 1920;只缓存当前页与下一页。GPUI 无隐藏光标的样式:单屏放映开激光笔时系统光标仍在。应用单测以组件库init_for_tests装主题,可渲染放映窗口。 - 2026-10-06:ElyShow 输出与演示检查(C10.6,SHOW-13、14,QA-12)。补丁
0037-pdf-impress-handouts.patch:PDF 导出的ExportHandouts(每页张数 1、2、3、4、6、9)照打印的讲义版式(SdPage::CalculateHandoutAreas,3 张带笔记线)把PageRange内未隐藏的幻灯片排到讲义页上;其他张数不出页,导出失败。讲义版式会改文档的讲义页,所以讲义与图片都在另存的 ODP 副本上做(output::on_copy,做完关闭并删除)。Pdf::handouts经 worker 的EXPORT_PDF传入。导出对话框:内容 PDF、图片、讲义,范围全部、当前、从–到,讲义每页张数(默认 3);打印对话框:幻灯片或讲义,生成同一 PDF 后交系统打印面板。图片每张未隐藏的幻灯片一个 PNG,宽 1920,副本设浅色方案,文件名名称-序号.png,所选文件夹里任一目标已存在即整体不写。PPTX/ODP 由保存与另存为负责。补丁0038-lok-impress-slide-check.patch:读.uno:SlideCheck(每张幻灯片与母版:文字所用字体族、链接图片与 OLE 链接及是否缺失、内核不能显示的对象:读不出的图片、无数据的 OLE;缺失的链接图片只作为链接报一次)。演示检查以内核报的缺失字体(FONTS_MISSING)为准,按所用字体定位到幻灯片或母版;媒体缺失与媒体链接取自.uno:SlideShow;点一行转到所在第一张幻灯片。系统打印面板只经ely_office::print_later打开:它在前台任务里、GPUI 放开应用之后运行(面板的模态循环会回调 GPUI,在实体更新里打开即借用冲突崩溃),三个应用共用。图片输出先以create_new占下全部目标(不跟随符号链接),任一冲突或失败都删去本次占下的文件。输出与检查的对话框由后台读取打开,回来时须仍是最近一次请求(Deck::ask_dialog、dialog_due)且没有别的对话框;对话框种类或当前标签一有变化,此前的请求即作废(watch_dialogs,随文档通知)。图片的张数与隐藏取自写出的副本本身;PNG 写完显式 flush。读不出的图片按载入后的状态判定(NONE或解码失败的Default占位);OLE 链接检查源文件是否在。 - 2026-10-06(用户定):ElyPDF 签名与权限。验证签名时证书信任取操作系统的信任库(macOS 钥匙串的信任设置;Windows、Linux 随其构建)。可视签名三种来源:手写、键入姓名、图片文件;可视签名不是数字签名。只凭用户密码打开、带所有者限制的文件照限制办(打印、复制、编辑等被拦并说明原因),输入所有者密码可解除;改保护须所有者权限。
- 2026-10-06(用户定):OCR 随包语言数据用 tessdata_fast,英文、简体中文、繁体中文(eng、chi_sim、chi_tra)。
- 2026-10-06(用户定):ElyMail 在八个应用里最后做:任务表中 C12 移到 C15 之后、C16 之前;CEF 内容宿主随 ElyMark 先建(C14.0),邮件编号依次前移。
- 2026-10-06(用户定):
ely-office的外壳不再只服务 LOK:Host带关联类型Engine(ely_office::Engine:后台载入、视图、撤销历史的标记、写出、关闭,视图的落定、编辑计数、待落地、焦点与事件),Tab与Core按引擎取文档、视图与 worker 类型。LOK 的实现在ely_office::lok(Lok、start、只对 LOK 的Commands即run_active/run_in;查找替换、面板、图表仍只限 LOK);MuPDF 的在apps/ely-pdf/src/engine.rs(Pdf;worker 首次载入才起,Mupdf::get)。Kind::macros为None表示该格式无宏,不扫文件。ElyPDF 每个标签一个PdfView实体(页面、图块、文字、选择、找到的词都在里面,自己绘制),Viewer只管标签条、书签栏、查找框与状态栏;打开、密码、关闭询问与保存走外壳。外壳的密码框:对话框开着时后到的加锁文件排队(Core::locked),对话框关闭(含取消、提交)后下一个再问;每次询问一个身份(按输入框实体),连续询问的 Esc 各归各的;提交时对话框先关、再载入。ElyDoc 的加锁测试原先清的是应用自己的对话框字段,改为清外壳的。 - 2026-10-07:本地 Web 内容宿主(C14.0,§17.3)。
crates/ely-web-content:CEF 154.0.32(Chromium 154.0.8037.58,cef-rs a712ad9,default-features = false),scripts/fetch-cef.sh取 minimal 包到engines/cef,.cargo/config.toml的CEF_PATH让构建用它。进程:应用的main先调ely_web_content::subprocess()(参数里有--type=即为 CEF 子进程,载入框架后execute_process,不碰 GPUI);开发构建子进程即应用本身的二进制;macOS 包里是五个辅助应用(<二进制名> Helper、(GPU)、(Renderer)、(Plugin)、(Alerts),Contents/Frameworks,LSUIElement,可执行文件为ely-web-helper),框架在Contents/Frameworks,packaging/macos/bundle.sh对依赖 ely-web-content 的应用自动放入并先签内层;查找次序ELY_CEF、包内、辅助应用所在、构建时的engines/cef。启动:start在 GPUI 里只做准备(载入框架、给 NSApp 的类补上 Chromium 要的isHandlingSendEvent、setHandlingSendEvent:与包住sendEvent:,并加CrAppProtocol、CrAppControlProtocol),initialize与建浏览器都在消息泵任务里、GPUI 的更新之外进行(实测:在更新里同步调 CEF,CEF 转动 AppKit 的循环,GPUI 报 RefCell 已借用、整窗不画);浏览器异步建(on_after_created交回)。凡在 GPUI 里对 CEF 的调用(输入、焦点、尺寸、脚本、缩放、重新载入、关闭)都排进主线程的队列,由泵在下一轮、更新之外依次执行(实测:在实体释放时同步关浏览器,CEF 转动 AppKit 的循环去校验菜单,GPUI 的菜单回调遇已借用的应用状态而中止,约三成复现)。泵:外部消息泵,按on_schedule_message_pump_work的时间,另照 cefclient 每轮后以 1/30 秒兜底(实测:只按请求时第一轮后 CEF 再不请求,页面不载入);空闲时 CPU 约 1.5%。离屏渲染须用 Alloy 风格(runtime_style;Chrome 风格不出画面)。同一缓存目录只允许一个实例,第二个启动时报出占用它的进程号。画面:on_paint的 BGRA 直接成 GPUI 图像,上一帧在下一次绘制时从图集释放(drop_image);光标随页面。输入:鼠标点击、移动、滚轮(行按 40 点),键照 cefclient 的 macOS 做法:键盘按下带字符与系统键码发KEYDOWN再发CHAR(退格、回车、方向等用 AppKit 的字符),可打出字的键等 GPUI 的输入法回调后以所打的字发同样两个事件,多字或组字经ime_commit_text/ime_set_composition(无替换范围时用 CEF 的“无范围”,两端为最大值;传空指针 CEF 不收);松开带同一字符(不带时 Chromium 当作又按一次,实测退格删两个字);拷贝、剪切、粘贴、全选走框架命令。焦点随 GPUI 的焦点给页面。页面与应用之间:CEF 的消息路由,页面调window.elyQuery({request, onSuccess, onFailure})(onSuccess必须有,缺了路由不发),应用收到Event::Query(请求, 回复);应用以run_js调页面。主题:每次载入与主题变化时把调色板、代码色、字体与字号比例、深浅写成:root的--ely-*变量与color-scheme。缩放set_zoom(倍数)。页面只许载入所在内容文件夹里的文件:按真实路径判定(..、百分号转义、指向外面的符号链接都算外面,文件须存在),导航与每个资源请求(脚本、图片、样式、请求等)都过这道门,资源另许data:与本页的blob:,其余一律取消;另在每个 CEF 进程以--proxy-server=127.0.0.1:9与--proxy-bypass-list=<-loopback>把一切网络(含本机回环与 WebSocket,后者不经资源回调)送往不通的代理;WebRTC 走 UDP 不经代理,故另以--force-webrtc-ip-handling-policy=disable_non_proxied_udp只许它经代理,并在每个页面上下文建立时、页面脚本之前删去RTCPeerConnection等构造(渲染进程的on_context_created);页面的地址由真实路径逐字节转义生成(文件名含#、?照样载入);用户点出的链接以Event::Leave交给应用;页面的警告与错误写入日志。载入失败(主框架,停止不算)、渲染进程退出、CEF 起不来都报Event::Failed,页面区改显示组件库的错误视图与“重新载入”(浏览器在即重载,否则重建;CEF 起不来时只说明),载入成功才报Event::Loaded。浏览器关闭时断开它与表面的互相持有,页面的内存随之释放(释放时写日志)。真窗口(macOS,开发构建与dist/ElyMark.app):页面以应用深色主题画出,点选、键入中英文、回车、退格、方向键、拷贝粘贴、elyQuery往返;tests/scripts/webview.sh以输入法回调组“你好”、键入、读回焦点、主题变量与缩放 1.25,核对页面跳不出所在文件夹、外面的脚本不执行、本机 HTTP 收不到请求、#名的页面载入、载入前被删的页面报失败、开关 20 个页面都释放内存、杀掉渲染进程后页面区不再显示旧画面、经辅助功能按下“Reload”后页面重新画出(截窗口量标记色),WebRTC 不存在且本机 UDP 收不到 STUN,并截窗口量页面画出的标记色(去掉绘制、去掉断开持有、去掉代理或不显示故障时该脚本都失败);需屏幕录制与辅助功能权限,缺即报失败。未验证:Windows 与 Linux;辅助技术(见缺口);树莓派(R-06)。 - 2026-10-07:ElyTree 恢复与清理、外部工具、操作规则(C13.6,TREE-13、14,§13.3,QA-19、20)。仓库页第二行:在终端中打开(macOS
open -a Terminal,其他平台报未构建)、在访达中显示(reveal_path)、清理未跟踪的文件…;页底“引用日志”列出 HEAD 最近 200 次移动(reflog show -z,位置、Git 写的原因、时间),选中即中间显示该提交(同提交详情,Whose::Reflog),上方“在此新建分支…”(找回删掉的分支)与“把当前分支移到此…”:有已暂存的改动即拒绝(reset --keep会丢掉它们,请先提交或取消暂存);否则在后台算出离开该分支的提交,对话框写明分支、从哪到哪、离开的提交(引用日志仍可找回)与“工作区改动保留”,确认后reset --keep(Git 在改动冲突时拒绝;没有任何按钮做硬重置),执行前在写队列里核对 HEAD 仍在预览时的分支(完整引用名,或仍游离)与提交、仍无暂存,任一变了即什么也不做。引用日志的时间是该次移动的时间(--date=unix下的%gd),不是提交时间;选中按事件本身(提交、原因、时间与它在全部引用日志中自最早起的序号,后者新移动不改,同一秒同原因的两次也分得开),同一提交的多条不会一起选中;之后有新的移动时该事件的位置后移,选中与详情跟着它,事件已不在引用日志里即不显示。清理:对话框先由git clean -n -d(勾“包括被忽略的文件”另加-x)得出 Git 会删除的路径(core.quotePath=false,其余 C 引号照 Git 规则还原),其中的文件夹当场展开为其中的文件(ls-files --others,同一忽略规则,另一仓库不入),对话框逐个列出文件与文件夹;确认后先逐段查每个所列路径:仍在仓库里、途中没有符号链接、文件仍是文件、文件夹仍是文件夹(任一变了即整体拒绝,什么也不删),再以git clean -f [-x] -- 所列文件删去(只给文件,Git 不递归;此后被跟踪了的文件 Git 留下,也不穿过链接),最后自深而浅删去所列文件夹中已空的(预览后在其中新出现的文件不动,文件夹随之留下),每次都问。文件标题栏多出“在编辑器中打开”(用户在“仓库›选择编辑器…”选的程序,存在数据目录tools.json,字段类型不对与读不出同样报错且不覆盖;macOS 的 .app 经open -a,在后台等它结束,失败即显示其原话;未选时先问)与 Markdown 的“在 ElyMark 中打开”(ely_platform::apps::open:按安装布局找同级的应用,macOS 的名称.app/Contents/MacOS,Windows 的Ely Office\名称,开发构建的同一目录;找不到即报错)。锁(§13.3):任何写操作因 Git 的锁文件失败(….lock': File exists,或配置文件的could not lock config file …: File exists)时,对话框说明另一个 Git 正在工作、Ely 从不删除锁文件,“重试”把同一写操作重新排队。QA-19:测试都以独立的 Git 命令核对索引、提交、引用与工作区。QA-20:冲突、钩子失败、凭据被拒(本机 401 服务器,显示 Git 的 “terminal prompts disabled” 原话)、锁、变基中断后中止、取消(停止取回)都有窗口测试,结束时仓库状态以 Git 读回核对。切换到系统 Git 目前经ELY_GIT(设置页随 C18.1);状态栏显示所用 Git 的路径与版本。真窗口(macOS):仓库页工具行、清理预览对话框、文件标题栏的编辑器与 ElyMark 按钮,按下后同级的 ely-mark 以该文件启动。未验证:Windows 与 Linux 的终端;ElyMark 打开文件的内容(C14)。 - 2026-10-07:ElyTree 储藏、远程、工作树、子模块、LFS(C13.5,TREE-10 至 12,§13.3)。左栏第四页“仓库”:上方取回(
fetch --progress --all)、拉取(pull --progress,照用户的 pull 设置)、推送、强制推送…;储藏(stash list -z,新的在前;“储藏改动…”问说明与是否包括未跟踪的文件,stash push),远程(名称与地址,remote加config --get-regexp读出取回与推送地址),工作树(worktree list --porcelain -z,行名为文件夹名,有失效的可“清理”即worktree prune),子模块(索引里的 gitlink 与文件夹自身.git的 HEAD:未检出、在记录的提交上、在别的提交上、冲突),LFS 文件(git lfs ls-files --long --json:工作树里是内容、对象在本机而工作树是指针、只有指针而对象不在本机);没有子模块或 LFS 文件时不列。各列表分别读出,一个读不出只在该处显示 Git 原话;切到此页时读,此后每次状态重读时重读,只取最新一次。选中一项,中间显示:储藏为提交详情的同一套(hist::Picked,历史与储藏共用读取与面板):改动的文件(对第一个父提交)与储藏的未跟踪文件(第三个父提交,各自对空树),选文件看差异,上方应用、弹出、删除…;远程为地址与推送地址,取回、修改…(改名remote rename、改地址remote set-url)、移除…(先问);工作树为路径与分支或提交、锁定与失效原因,打开(作为标签)、移除…(先问,主工作树与当前的不给);子模块为记录的与检出的提交,“检出记录的提交”(submodule update --init,经远程运行)与打开。用户输入的名称、地址、路径前一律加--(推送的远程名同样)。表单与选择对话框(新建分支、标签、改名、身份、储藏、远程、工作树、选远程与上游)记下打开时的仓库,提交时写进那个仓库,不随当前标签变;储藏列表按每条三个字段读(说明可为空)。储藏的应用、弹出、删除按stash@{n}执行,执行前在写队列里核对它仍是所见的那个储藏(另有储藏插入即拒绝,什么也不做);弹出遇冲突时 Git 保留储藏,对话框显示 Git 原话。删除储藏、移除远程与工作树每次都问。分支页的本地分支多出“设置上游…”(列出远程分支,branch --set-upstream-to=refs/remotes/名)与“取消上游”。推送:有上游即push --progress;没有上游时后台读出远程,问推到哪个远程(push --set-upstream -- 远程 refs/heads/分支:refs/heads/分支),没有远程或 HEAD 游离即说明。强制推送(§13.3):后台读出当前分支、上游的远程与远端分支、上次取回时远端的提交(租约)、此刻 HEAD 的提交(源)与远端有而源没有的提交,对话框写明远程、分支与将被覆盖的提交(或写明没有),确认后推送的就是所示的源提交(push --force-with-lease=远端分支:租约提交 -- 远程 源提交:远端分支),确认前本地分支再动也不改变推送内容;远端自上次取回后变了即被拒绝;没有上游或游离即说明不能。远程命令(取回、拉取、推送、子模块更新)是写队列里的一种(Write::Remote),经running::Running运行:Git 在自己的会话里(setsid,SSH 找不到终端,不会挂在终端提示上;凭据照用 credential helper 与 ssh-agent),进度逐行显示在中间上方的提示条,可停止(向整个进程组发 SIGTERM,Git 自行清理;已结束的不再发);用户停止时提示“已停止”、清掉其后的排队写,不当作失败;失败时对话框显示 Git 的 stdout 与 stderr(以回车结尾的进度行被下一行改写,只留最后一次;以换行结尾的行全部保留,钩子的每一行都在);拉取遇冲突照合并类命令判定为停下。关窗与退出时停下所有远程命令与克隆。克隆改用同一运行器。真窗口(macOS):仓库页五类列表、储藏详情与文件、工作树详情、分支行。未验证:Windows 与 Linux(停止远程命令在非 Unix 上报未构建);真实网络与 HTTPS、SSH 凭据;LFS 对象的下载(只显示状态)。 - 2026-10-07(用户定):跟随组件库最新的 main。组件库 K01 把 gpui 从 zed 仓库的提交换成 crates.io 的
gpui-pre=0.3.8(gpui-pre-platform同版本),K02 让 Tab 绑在 FocusScope 的 ElyFocus 上下文下、更深的上下文先得;本项目工作区的gpui、gpui_platform随之改为这两个包(以package =保留原名,源码不变),组件库 pin 到 dabc1eb(其上加本项目一个提交:树与树行的无障碍角色)。升级后全工作区编译无改动。 - 2026-10-08:首版打包(R1.1)。图标(用户定:logo-design 技能出方案,选 C“实心几何”;macOS 标准 squircle,Linux 与 Windows 无底形,八个同一套设计):母版在
assets/icons(我按圆角矩形、圆与 30°/45° 斜线在 256 网格上画;每个颜色对白与#1E1E1E都不低于 3:1,ElyMark 原定的石板灰在深色上 1.75:1,改为琥珀#B87A00;ElyTable 在 48 像素以下用简化的一版),packaging/icons/build.sh以 AppKit 渲染出 icns(macOS)、512 像素 png(Linux 的 AppImage 与桌面文件)、16 至 256 像素 ico(Windows 安装程序、开始菜单快捷方式与卸载项);生成物入库,Linux 与 Windows 构建不需要 macOS。系统交给应用的文件:ely_ui::run收下启动参数与on_open_urls(访达的打开请求,启动前到达的也在内),应用先取launch_files(有则不开空白文档;ElyMark 有则不恢复上次的标签),再以on_open处理运行中的请求;六个应用都走这一条(ElyDoc、ElyTable、ElyShow 原先不收启动参数)。macOS 包:LOK 应用只带内核,按 LibreOffice 自己的布局并入本应用:内核的库进Contents/Frameworks(合并库构建留下的“invalid - merged lib”占位文件不拷,其余非代码文件即报错),内核的资料进Contents/Resources,另有MacOS/ely-lok-worker;install_dir先找包内(Frameworks/libmergedlo.dylib)。不带LibreOffice.app本身:原先整份嵌入时,其快速查看与聚焦插件、soffice等启动程序与文档类型被系统登记,系统设置里出现“LibreOffice 扩展”(用户发现;已注销本机七处登记);测试也不再运行 LibreOffice 应用(样本改为tests/fixtures/office的固定文件,运行开发构建的soffice会让系统重新登记它)。ElyPDF 带MacOS/ely-pdf-worker与Resources/tessdata(三种语言),OCR 数据先找应用旁再找包内;ElyTree 带Resources/git,其中每个 Mach-O 先签(Git 以INSTALL_SYMLINKS构建,内建命令是指向同一程序的相对链接;原先是硬链接,拷进包后变成约 150 份副本、689 MB;以MACOSX_DEPLOYMENT_TARGET=14.0构建,原先随本机 SDK 要求 macOS 27);ElyMark 用同级 ElyTree 包里的 Git。文档类型(packaging/macos/documents.sh)一律Alternate,不抢系统默认:Doc 为 docx、odt、doc、rtf;Table 为 xlsx、ods、xls、csv、tsv;Show 为 pptx、odp、ppt;PDF;Markdown;Tree 为文件夹(查看者)。worker、内核、OCR 数据、CEF 与页面的所在路径都写日志,tests/scripts/release-macos.sh据此核对都在包内。许可(§21.2、§21.3):每个包的Resources/Licenses有 Ely 的 AGPL 全文(仓库根LICENSE)、rust-crates.txt(packaging/crates.py:本机目标下链进各二进制的全部 crate 与其许可文件,无许可文件的取packaging/licenses里该表达式的标准文本并写明作者,都没有即失败)与所带引擎的许可:LibreOffice 的 LICENSE、NOTICE、CREDITS;MuPDF 的 COPYING 与其编进去的每个第三方及每组内嵌字体(按构建实际嵌入的)的许可、OpenSSL、tessdata(Apache-2.0,按提交与散列取)、Great Vibes 的 OFL;Git 的 COPYING 与 LGPL-2.1、git-lfs 的 LICENSE.md(发布包里没有,按标签与散列取);CEF 的 LICENSE 与 Chromium 的 CREDITS、页面的第三方通知;缺任何一份即打包失败(packaging/licenses.sh)。release.sh另出ElyOffice-<版本>-source.tar.gz:自有源码、锁文件、上游补丁与构建脚本(上游各自钉在提交,取得方式即其中的 fetch 脚本)。正式签名(hardened runtime)时 CEF 的渲染与 GPU 辅助程序带allow-jit,插件辅助程序带allow-unsigned-executable-memory与disable-library-validation(packaging/macos/entitlements;原先不带,V8 保留不到代码区,页面载入失败);tests/scripts/hardened-macos.sh以ELY_SIGN_IDENTITY签 ElyMark 并核对页面画出。ElyTable 同时收到多个文本文件(访达一次交来多份 CSV)时,读完的导入排队,没有对话框开着才逐个询问(原先后到的一份替换前一份,前一份静默丢失)。packaging/macos/release.sh按apps.tsv的批次出各应用的 dmg;本机只有 Apple Development 证书,无 Developer ID,未公证。未验证:Intel 包(只建了 arm64)、公证、Linux 与 Windows 的包(本机不能建)。 - 2026-10-08(用户定):深色主题不用纯黑或近黑,底色取柔和的灰
#252525。组件库 fd0efc7:深色色板 bg#252525、surface#2c2c2c、sunken#202020、overlay#323232、hover#383838、active 与 border#3f3f3f、border_strong#555555;为让说明文字在较亮的 hover 与 active 上仍有 4.5:1,muted 提到#c4c2c0、subtle 与代码注释提到#acaaa8;色盲友好图表的紫与橙改为#9d8dec、#ffd93d(3:1 且各色两两分得开)。ely.xcd的深色方案随之:页面#2c2c2c、页外#252525、边界与网格#555555、域底纹#3f3f3f。跨应用同步外观、设置里的浅色/深色/跟随系统、Omarchy 下随 Omarchy 主题,本来已有(共用settings.json加文件监视;settings.sh核对 ElyPDF 选深色后 ElyTree 即刻变深);Omarchy 的读法与 OmaPhoto 相同(~/.local/state/omarchy/current/theme/colors.toml、mode与具名颜色)。 - 2026-10-08:图像的显存与内存(C20.2 长时测试查出)。GPUI 把每张画过的
RenderImage留在窗口的图集里,直到调window.drop_image;LOK 的图块、PDF 的图块与缩略图、演示的缩略图、动画预览与放映图层原先都不释放,ElyDoc 编辑保存 60 轮后应用进程涨到约 2 GB(内核不涨)。组件库primitives::Retired:不再画的图先交给它,下一次绘制时释放(release只放别处已不持有的,release_all给自知不再画的属主,append接过关闭者的);LokView、PdfView 在替换、挤出、清空图块时交出旧图,绘制前释放;ElyShow 每个标签一份,关闭的标签与结束的预览交给 Deck;放映按窗口各一份(观众屏、演讲者视图),离场中的幻灯片还在画时其图层等它结束。修后同样 100 轮在 340 至 440 MB 之间起落。 - 2026-10-08:性能基准(C20.1,§20.1)。
tests/perf/bench.py在发行构建上测 PERF-01 至 08(每项 30 次,P50、P95,含全部子进程的内存),样本由tests/perf/samples.py按固定种子生成到engines/bench,记录写到docs/perf/<日期>-<机型>.md(格式见其 README);ELY_STALLS=1开主线程忙碌记录(只供测量,空闲的应用照样不做事)。实测改动:Doc、Table、Show 原先要等内核启动完(约 2.5 至 3.9 秒)才开窗口,ElyDoc 日常启动 P95 3.9 秒不达标;现Worker::launch只起进程,内核的版本与图块核对在第一个请求前做(Worker::ready,任何请求都先过它,未核对的 worker 收不到请求),ely_office::start在后台核对并设作者名,失败即startup::fail;窗口约 0.15 秒可操作,空白文档在内核首次载入后出现(中位约 2.7 秒,其中约 2.2 秒是内核首次载入文档)。启动参数改为按当前目录取绝对路径(原相对路径成了file://相对而打不开)。补丁0039-skia-macos-no-unused-font-manager.patch:macOS Skia 首次画字时以全部系统字体再建一个无人使用的SkFontMgr(约 370 个字体枚举加排序,主线程约 175 毫秒),新文档的第一个字与打开文档的首块带字图块因此迟到;去掉后 PERF-03 的 10 次对比 P95 由 2.6 秒降到 1.5 秒。PERF-02 的计时原以输入落地时的代际为基准,内核的作废事件先于落地回报到达时,该次编辑的图块不计,钟一直走到下一次按键(记下的是按键间隔约 250 毫秒);现按编辑的修订认定(view/clock.rs:修订不低于该编辑的批次所升的代际,其图块即为所求),实测中位 2 毫秒、P95 5 毫秒。PERF-08:关闭的标签原把其图块留在 GPUI 的图集里(每个文档约 20 MB 显存,40 次新建关闭后应用涨约 250 MB);Engine::retire交出关闭视图的全部图像(LOK 图块、PDF 图块与缩略图、ElyMark 页面的画面),外壳在窗口的下一帧释放,修后 40 次在 454 至 462 MB 之间。历史首屏改为从按下“历史”计(原从进程启动计,含打开仓库)。R1 后基准更严:PERF-03、04 到视图收下的第一块图块(lok: tile (x, y) shown),其间有图块画失败即整次作废;DOCX 样本原以单独一段的分页符分页,内核只排出 60 余页(Heading1段上的pageBreakBefore同样丢页),改为分页符放在标题段内,PERF-03 之前先核对恰为 100 页(视图记录页数);PERF-02 每键须有自己的计时,缺即失败;PERF-08 循环前记基线,记内核中的文档数(worker 每次载入关闭写N documents open),文档数须回到基线,文件数自第一次之后不变(第一次打开模板对话框时进程一次性载入两个系统字体与两个 AppKit 本地化表)。worker 的启动核对失败后保持失败,此后一切请求被拒(原核对失败的状态与成功相同,第二次即放行)。空白文档就绪由约 2.5 秒降到约 1.0 秒,发生在本次重建内核之后,与 0039 无关(对比实测),原因未查明。 - 2026-10-08:打印输出(C19.2,§19.3)。
ely_platform::print::print_pdf每次取系统打印设置的副本,纸张取 PDF 中最大的一页的尺寸与方向(一次打印只有一种纸张,较小的页按原大印在其上),四边距为零(文档的页边距已在 PDF 里),缩放为 1;ELY_PRINT_TO=路径让作业写成文件、不弹面板(测试用);原先用共用设置,打印机默认纸张与边距使 A4 以外的文档被裁切或偏移。Doc、Table、Show、PDF、Mark 的打印都先等输入落地、在后台写出同一份 PDF(同一修订、页面设置与缩放),对话框给出页数与范围,再交系统面板(ely_office::print_later);取消不动文档,副本随即删去。tests/scripts/print.sh真窗口核对。 - 2026-10-08:兼容清单与往返验证(C19.1,§19.1、§19.2)。
docs/compatibility.md是随版本发布的兼容清单(版本、内核与补丁、验证机,各应用的编辑保存、导入受限与输出,已验证与限制),版本或格式行为变了即同步改它。往返用例在crates/ely-lok/src/client/tests/independent.rs:Ely 编辑、保存、关闭、再打开、再编辑、再保存,再由tests/scripts/ooxml.py检查;它不用 LibreOffice,自己解 OOXML,预期值自己算(整数、日期序号、错误值、文本严格相等,浮点按用例声明的容差),DOCX 另交 macOS 的textutil;没有 python3 时报 SKIP。与 Microsoft Office 的交叉检查未做(本机没有 Office)。 - 2026-10-08:恢复副本、引擎停止与自动保存(C06.5,§6.2、§6.3)。Doc、Table、Show、PDF 共用
ely_office::Recovering:每 10 秒为有改动、已落定、不在导出的标签经Engine::snapshot(写副本,文档不动)写一个恢复点(ely_files::Recovery,限额取存储设置;加密文档封存),索引在数据目录recovery/index.json;崩溃标记照 ElyMark 的Crash,下次启动把崩溃留下的最新点开成未命名副本并标为未保存,提示写明只恢复了内容,光标与撤销历史没有。引擎进程退出(Engine::ended,客户端try_wait)时ely_office::Stopping在上方显示提示条(原因、最近一次恢复点的时间)与“重新打开文档”:Engine::restart换上新 worker(LOK 照原配置与作者名重起,PDF 下次打开再起),有改动的标签从最新点开成副本,无改动的从文件重开,有改动却没有恢复点的列出;被放下的标签迟到的失败不再提示,指向它们的关闭询问一并撤下。重开的副本接过原恢复点的文件夹(封存随之),旧点留到新标签写下新点为止;载入或解封失败的条目留在索引里,下次崩溃后的启动再给。需要密码的副本(加密 PDF 的恢复点照原加密写出)照样询问,答后仍是未命名副本(Source::Locked)。引擎是否退出在出错时与每次恢复计时时查,空闲的引擎退出也会显示。恢复索引经ely_files::save_atomic暂存后替换,写不进时上一份照旧可读;写不进即提示一次、每次计时重试。干净结束后的下次启动只删已接过或正常的条目,没能重开的留着再试。自动保存(设置“文档”页,每个应用一个开关,存在应用的settings.json的autosave,默认关):只对本应用自有格式(Kind::save_as)、无风险(宏、签名、修复)、状态为“已编辑”的标签,编辑计数停 2 秒不变即走普通保存;保存失败、外部改动后状态不再是“已编辑”,不再自动写,等用户处理。Cmd+W 关闭标签是共用命令(ely_ui::commands::CloseTab,原来只有 ElyPDF 绑了;文档应用里 Cmd+W 落进 LOK,内核按自己的 Ctrl+W 关掉框架,worker 下一次绘制或读取即在已释放的文档上段错误);worker 另拒绝内核关闭框架与退出的快捷键(Mod1 加 W、F4、Q)。真窗口(macOS):recovery.sh(崩溃后副本回来、关闭询问;只杀引擎后提示条、重新打开、文字回来、关闭询问),autosave.sh(关时不写,开关存下,开后停手即写,外部改动后不覆盖)。未验证:Table、Show、PDF 的真窗口恢复(同一外壳);Windows 与 Linux。 - 2026-10-08(用户定):首版发布六个应用(ElyDoc、ElyTable、ElyShow、ElyPDF、ElyTree、ElyMark);ElyMail 与 ElyAgent 第二批发布。ElyMark(C14)做完即开始首版的完整测试与打包,C12、C15 留到第二批。
- 2026-10-08:数据管理(C18.3,§18.3)。设置的“存储”页(每个应用):本应用数据目录里的日志(
ely.log)、网页内容缓存(web,只 ElyMark 显示)、恢复副本(recovery)各自的大小与清除(ely_platform::storage);日志当场清空(日志器以追加打开,截短不坏句柄),缓存与恢复副本在用着,写下记号(.clear-web、.clear-recovery),下次启动在设置与窗口之前删去(删不掉即启动失败、写明原因,记号留着下次再试,不打开没删掉的数据);每次清除先问,写明删什么、何时删;每个文档保留的恢复点(10、20、50)与空间(32、64、256 MB)存在应用设置的recovery(严格读,不合即报错),ElyMark 写恢复点时照用(C06.5 的文档应用同)。日志超过 8 MB 时,启动时只留后 4 MB、从整行起。数据库的升级前备份与失败只读只属 ElyMail(第二批),首版的六个应用没有数据库;邮件与 Agent 历史导出同属第二批。组件库 ea4ed4c:SettingsRow是以标题命名、以说明描述的组(原先行标题不进 AX 树,两个同名按钮分不出);待清除的状态以禁用的按钮写明。 - 2026-10-08(用户定:图标照 Papirus 重画):每个应用是 Papirus 192 网格上的一个大的扁平物件(页、讲台、信封、终端),主体一种饱和色,折角为其浅色,内容为白色、至多一个点缀色,主体与内容带 Papirus 的柔和投影(下移 4 或 2、模糊、20% 黑)。macOS 为浅色 squircle 上的同一物件(占主体 86%),Linux 与 Windows 只有物件。48 像素以下用无投影、无小细节的简化版。母版由
packaging/icons/masters.py写出,配色见assets/icons/README.md。Papirus 图标包(assets/papirus_icons-master.zip,用户放入)只作参照,不随包。 - 2026-10-08(用户要求核对各系统的文件关联):文件类型只写在
packaging/types.tsv(应用、扩展名、MIME;/为文件夹)。Linux 的桌面项由它写MimeType=(原先没有,桌面环境从不提供 Ely 打开文件);Windows 安装程序由packaging/windows/types.sh生成的 NSIS 宏注册(原先没有):HKCU 下每个扩展名的OpenWithProgids与Applications\<程序>\SupportedTypes,ProgIDElyOffice.<名称>带图标与打开命令,ElyTree 在文件夹右键菜单加“Open in ElyTree”;只进“打开方式”,从不改默认(同 macOS 的 Alternate),卸载时删去。makensis 在本机编过 ElyDoc 与 ElyTree 的安装程序;Windows 与 Linux 上实际打开文件未验证。 - 2026-10-08(用户定:窗口缩窄时控件挤成一团、越出边框,不可接受):窄窗口的版面。外壳一次排两侧栏(
shell/panel.rs的room,有单测):内容区至少留 360,放不下时两侧先缩到各自最窄,再先藏右栏、后藏左栏,窗口变宽即回来,用户拖的宽度不变。组件库 6d95b24 至 69265b4:ScrollArea::vertical只纵向滚动、内容不得宽于区域(侧栏一律用它,原先横向不受限,整栏按自然宽度排后被裁);PropertyGrid每行可折行,放不下时控件到标签下方,标签列 104;全部图标的SegmentedControl与Tabs放不下全部文字时只显示图标,文字作提示与无障碍名(宽度取上一帧量得的条宽与文字排版宽度);StatusBar两侧与各项可缩,文字以省略号结尾;Banner文字折行;Button不再拒绝收缩,只在无法折行时缩并以省略号结尾;未聚焦的单行文本框显示文字开头(原插入点在末尾,看到的是尾部);Switch::name只给无障碍名(行已有标签时不再重复画字);CommitInput的勾选与按钮一行可折。各应用固定宽度的控件框(w_32等)改为占满、以原宽为上限。tests/scripts/layout.sh:六个应用各开样本,640、800、1100 宽下读 AX 树,任何元素越出窗口左右即失败,并留截图供目视(栏内裁切 AX 看不出)。缩放(用户问为何 Doc、Table、Show 不能缩放;视图早有set_zoom,界面从未接上):ely_office::Zooming,Cmd+= / Cmd+- / Cmd+0,“视图”菜单三项,状态栏右侧ZoomControl的属主模式(组件 69265b4:显示属主给的百分比、按下交属主;原只缩放整个界面);Cmd+滚轮与触控板捏合以指针处为中心缩放(view/zoom.rs,档位 25% 至 400%);ElyShow 的 Cmd+0 回到适合幻灯片,手动缩放即停止适合。缩放只在内存里随标签,不跨重启。 - 2026-10-08(用户定:侧栏太占地方,要可调):侧栏宽度(C05.6)。外壳的左右侧栏(
shell/panel.rs,八个应用共用)默认左 220、右 320(原取sidebar_width240 与sheet_size380;右 320 是 ElyDoc 属性栏不截断控件的最窄宽度,实测 280 截断数值框);内侧边缘是组件库的layout::resize_edge,拖动改宽,左至少 160、右至少 280(原 220,属性栏放不下)、至多窗口的 45%;拖动停 0.5 秒后存入应用设置的panels(settings::panels),退出时还没存的当场写入(窗口位置同),启动时严格读回(不是正数即在设置窗口写明并用默认),写入失败也在设置窗口写明;开合动画期间不显示拖动边。 - 2026-10-08:凭据与私密内容(C18.2,§18.2)。恢复密钥(加密文档的恢复副本用)存在系统凭据库(keyring:钥匙串、Windows 凭据、Secret Service);凭据库不可用或拒绝时改用本次运行的随机密钥,只在内存(
RecoveryKey { key, lasting },lasting为假即提示恢复不跨重启),从不写成普通文件;用旧密钥封存的恢复点读不开即报“无法恢复”,不导出明文。PDF 打开密码与证书密码只在 worker 内存里、文档关闭即弃。日志:worker 的 stderr 经ely_ipc::relay按其级别并入应用日志(原先从访达启动时 worker 的日志丢失),两个 worker 与应用一样读ELY_LOG、默认 info(LOK worker 原读RUST_LOG,平时只出错误);日志调用不打印正文、密码、密钥;引擎(worker、内核、MuPDF)给的错误串可能引用文档,日志只记请求号与错误类别,全文只经回复交给界面;worker 的 stderr 只放行其结构化日志记录与崩溃栈帧,引擎自己打印的文字不进日志(只计数)。worker 启动失败也写成结构化记录,最后一条错误由Relay::last_error带进客户端的报错。诊断导出时姓名来源(账户名、设置)读不出即不导出并写明原因;日志尾部从整行开始。诊断(设置里的“诊断”,每个应用):先列出要导出的内容(本应用日志的最新 4 MB、macOS 上 7 天内本应用与其 worker 的崩溃报告,列表在 AX 树里),开关“去掉路径与个人姓名”默认开(凡路径与 URL 整段换成<path>:有引号到引号为止,否则到行末;分段标题只写文件名;账户全名、登录名与作者名按从长到短换成<user>),导出为一个文本文件(ely_platform::diagnostics),不上传。Git 服务的凭据照用用户自己的 credential helper 与 ssh-agent;打开文件不发任何请求。未验证:Windows 与 Linux 的凭据库与崩溃报告位置(那两端诊断只含日志)。 - 2026-10-08:配置(C18.1,§18.1)。通用偏好在
<本机数据目录>/ElyOffice/settings.json,各应用的在其数据目录的settings.json;都经ely_platform::settings::Store:带version(本构建为 1;更新的版本或读不通即报错,不覆盖),写入在同目录的.settings.lock(ely_platform::lock::FileLock,flock 或 LockFileEx,由 ely-files 移来共用)下先重读、只改所写的键、未知字段原样留下,临时文件 fsync 后原子替换;多个应用同时写不丢字段(单测八路并发)。通用字段(settings::Shared,类型不对即报错):外观(跟随系统、浅色、深色)、语言(跟随系统、English、简体中文;下次启动生效,设置里写明)、动效(跟随系统、减少、完整)、跟随 Omarchy(只在有 Omarchy 时显示)、作者名(空即账户名;user::name先取它,ElyPDF 的批注、LOK 的批注与修订都用它)。环境变量ELY_APPEARANCE、ELY_OMARCHY_SYNC、ELY_REDUCE_MOTION、ELY_LOCALE仍优先。ely_ui::settings启动时读入(读不出即用默认值、记下原因),以notify监视文件夹,别的应用写入后即刻重读:外观、Omarchy、动效当场换,作者名经observe_global在后台交给 LOK worker(set_author)。设置窗口(应用菜单“设置…”,Cmd+,,每个应用一个)用组件库SettingsLayout、SettingsSection、SettingsRow、ThemeSelector(组件 489ef31 起三项文字进内建中英目录)、SegmentedControl、Switch、forms::Input:通用;Doc、Table、Show、PDF 另有“文档”(作者名,失焦或回车写入);ElyTree 另有“Git”(Git 程序,空即随带的;写入前在后台核对 2.40 或更新,不是即在框下以 Banner 说明、不写;ely_platform::git::locate次序为ELY_GIT、此设置、随带的,版本核对移入ely_platform::git::system_version,下次启动 ElyTree 生效)。文件读不出时窗口上方以 Banner 写明原因,改动不保存。作者名与 Git 程序每次提交都编号,写入经Store::set_if在文件锁下核对仍是最新的提交才落盘(等锁期间改了主意,旧写入作废);写入失败另以 Banner 写明,键入的值留着可重试;作者框在未编辑时跟随别的应用写入的名字。版本核对先看--version的退出状态(git::version_of)。各应用主窗口的位置、大小与最大化、全屏存在应用的设置里(window,停止移动 0.5 秒后写),启动时所记之处的中心仍在某块显示器上才用。另:ElyMark(CEF)直接启动的应用继承了 Chromium 忽略或屏蔽的 SIGTERM,测试的 pkill 杀不掉、留下进程(实测数个钟头的残留);apps::open在 exec 前把 SIGTERM、SIGINT、SIGHUP、SIGQUIT 复原并清空信号屏蔽(单测:启动者忽略并屏蔽 SIGTERM 时子进程照样被终止)。未验证:Windows 与 Linux 的锁与窗口位置。 - 2026-10-08:进程与状态核对(C17.1,§17.2、§17.4)。帧(
ely-ipc)由长度、标签、请求号与内容组成:客户端每个请求一个递增的请求号,worker 的回复带回同一个,不符即报错(check_reply;原先只靠一问一答的次序配对,丢一个回复即整串错位而不自知);LOK 主动推送的事件请求号为 0(UNASKED);PDF worker 的版本戳包含ely-ipc的源码。帧超过上限(64 MB)发送前即报错,什么也不写;worker 的结果超限时回一个错误,worker 不退出。LOK 的输出读不下去(坏帧、提前结束)时,原因经回复通道交给等待的请求,客户端停下并回收 worker 再报出(原先请求一直等进程结束)。会话:LOK 每次载入一个会话号(递增,原先用内核文档指针,释放后的地址被下一份文档复用,旧句柄读到新文档、事件被当作已关的丢掉);PDF 的Doc带打开它的 worker 会话,客户端发送前核对,别的 worker 的旧句柄被拒;worker 的文档号带进程前缀(进程号与启动时刻),别的进程的号在此不存在。修订(PDF):Doc记着读到时的历史标记(标记相同即文档相同,撤销回到原标记),指向页、对象、位置的请求带上它,worker 在动内核之前核对,不符即以stale:拒绝;整份文档的请求(页数、保存、撤销、元数据、附件列表与添加、保护等)带“任意”;按键读取或移除附件带标记。ElyPDF 的编辑在排队时记下当时的句柄,落地后按新标记换句柄,过期的读取照旧按版本丢弃(画不出的块不因过期记为失败),过期的编辑提示“文档已改动,未执行”:排在别的编辑后面的编辑因此被拒(它对着的是前一个编辑落地之前的页面),再按一次即可。LOK 的修订(R2):worker 给每份文档记一个修订,会改文档的请求(键、鼠标按下与松开、拖动、文字、命令、粘贴、剪切、函数补全;不按键的鼠标移动不算)在执行前加一;按编号指向工作表、幻灯片、对象、参考线、制表位、行列的命令(参数里有Index、nTabNumber、TabNo、Part、Row、Col、Column或区域的四角,值不是 -1;-1 是新建)先发EXPECT带读到时的修订,不符即以stale:拒绝,内核不动;同一次读取得出的一组命令(对象列表的多选)只核对一次,在一次 worker 调用里依次执行(Worker::run_all、LokView::run_all_at、Commands::run_all_in)。LokView::run拒绝带编号的命令,须经run_at;外壳的run_in对带编号的命令用Host::read_at(ElyTable、ElyShow 为其读取时同一次 worker 调用里取的Worker::now,ElyDoc 为视图所见:推送的事件批次带修订,每个输入落地后视图另取一次修订);Tabs::read_in也给读取闭包这个句柄(ElyPDF 为视图当前的句柄)。ElyTable、ElyShow 在视图所见修订新于其读取时重读,所以键入之后按编号的命令不会一直过期;记着编号的对话框(删除与重命名工作表、删除重复项、幻灯片备注栏键入期间)在读取时记下修订,确认按它执行(Commands::run_on),其间文档结构变了即拒绝;ElyDoc 属性栏按视图所见的修订发(删除所示的制表位)。输入失败时视图照样取回修订(请求在执行前已计数),之后的编号命令不会一直过期。过期的拒绝提示“文档在此间已改动,请重试”(office.error.changed)。MuPDF 打开中途(加锁检查、日志、修复)失败即释放已打开的文档与修复副本(原先每次失败留下整个文档;MuPDF 的存储另有 256 MB 上限的缓存,满后不再增长)。FFI:MuPDF 每个调用在垫片的fz_try/fz_catch里,返回码加错误信息;LibreOffice 只经 LOK 的 C 接口;worker 崩溃时客户端报出退出状态。工作进程:LOK 随应用窗口启动(启动即开空白文档),MuPDF 在打开第一个 PDF 时启动;不建进程池。后台:Git、OCR、缩略图、文件夹搜索、导出与系统打开都在后台任务里。不建设 §17.4 所列设施(无 RPC 服务发现、无常驻服务、Git 只有一个内核、Markdown 只有 Milkdown 与 CodeMirror)。 - 2026-10-08:应用之间的连接(C16.1,§16;首版只做六个应用之间的三项,邮件与 Agent 的三项随第二批)。启动别的 Ely 应用(
ely_platform::apps):先找与本应用同一安装或构建的同级(.app旁、Windows 的Ely Office\名称、开发构建同一目录),再找 macOS 登记过的(按包标识AppId::bundle_id,NSWorkspace);.app一律经open -a交给 Launch Services,正在运行的应用经其打开请求收下文件(不另起一个),其余直接启动。应用切换:每个应用的应用菜单有“打开 Ely 应用”子菜单,只列已安装的其他 Ely 应用(commands::switch_to,八个无参数动作),启动失败即提示原因。导出后查看:导出、提取、拆分、删除标记内容、清理、保护、签名与附件另存成功后,提示带“打开”:PDF 在已安装的 ElyPDF 打开(作为新标签,不当作原编辑会话),否则与其他文件、文件夹一样交系统默认程序(gpuiopen_with_system);ElyPDF 打开失败即提示原因,不改交系统。ElyMark 导出缺图、图或公式时的警告同样带“打开”。Markdown 查看版本:ElyMark 状态栏的 Git 项把文件本身交给 ElyTree,ElyTree 收到文件即打开其所在仓库并显示该文件的历史(Tree::open_path按文件所在文件夹开仓库,再以仓库内路径file_log);不自动提交,不建仓库;未安装 ElyTree 时提示从发行包安装。真窗口(macOS):mark-output.sh核对 ElyTree 打开该文件的历史、导出警告的“打开”在 ElyPDF 打开 PDF;release-macos.sh从 ElyDoc 的应用菜单启动 ElyMark。AppKit 自己的窗口恢复关掉(startup::no_window_restore,ApplePersistenceIgnoreState):Ely 自己恢复会话;原先应用被强退后再启动,AppKit 先弹模态“是否重新打开窗口”,访达交来的文件被挡在后面(打包测试实测)。窗口测试的seen不看系统菜单(苹果菜单的“最近使用的项目”会列出别处打开过的同名文件)。未验证:Windows 与 Linux 的同级查找与系统打开;系统默认程序打开(会在本机弹出别的应用,未自动测)。 - 2026-10-08:ElyMark 的单行
$$…$$(用户以 Pandoc 示例发现):remark-math 把它读成行内公式,序列化只写一个$,与原文不同,整段退成原文块。现按原文记下每个行内公式的$数(math.js),两个及以上以显示模式排出(同 GitHub),写回照原数;导出同样以显示模式。Pandoc 专有的表格、定义列表、行块不是 CommonMark/GFM,照 §14.3 以原文块保留(---- ----在 CommonMark 里是分隔线)。 - 2026-10-08:ElyMark 书写视图、输出、Git 配合、大文件(C14.7,MARK-09、11、13、14,PERF-07)。书写视图:“视图”菜单的专注模式(⇧⌘F,收起标签条、左栏与状态栏;可视模式里光标所在的顶层块之外淡到 0.35,源码里当前行之外同),打字机滚动(插入点所在行保持在视口中部;上下各留半屏,首尾行也能居中),正文宽度窄、中、宽(36、48、64rem),字体无衬线、衬线、等宽,文字大小(⌘= ⌘- ⌘0,70% 至 200%,每档 10%)。全部在页面里以 CSS 实现(
look.js:根元素的 data 属性与--ely-text-scale),Markdown 不变;设置存在数据目录的look.json(读不出即报错并用默认值),菜单随之打勾,启动时恢复;淡入淡出遵守系统的减少动效。组件库的ZenMode不用(见缺口)。输出(“文件›导出…”⌘E 一个对话框选 HTML、HTML 与资源文件夹、PDF、纯文本;“打印…”⌘P):页面以 unified(remark-parse、remark-gfm、remark-math、remark-rehype、rehype-katex、rehype-stringify,版本写死)把编辑器此刻的全文(与懒挂载、大文件无关,不截断)渲染成 HTML,Mermaid 用浅色主题画成 SVG(画不出的照原代码块并在导出后提示);front matter 不进输出。HTML 内嵌导出样式表(构建时单独产出export.css:KaTeX 样式与 woff2 字体内嵌、浅色排版)与图片(文档相对路径、百分号转义、file URL 读成 data URL;网址照原样;读不到的图片照原链接并提示);HTML 与资源文件夹写名称.html与名称_files/(样式表与图片副本,同名图片按序号区分;文件夹已在即拒绝,写坏即删去);纯文本以 hast-util-to-text 按块换行,公式写 TeX;PDF 先把同一份内嵌 HTML 暂存到工作目录,在不显示的 CEF 视图里由 Chromium 打印(WebView::print_pdf,结束经Event::Printed报回),再交到所选位置;打印同一份 PDF 交系统打印面板(ely_office::print_later)。导出都经ely_files::export(不覆盖打开着的文件)。富文本复制用 Chromium 自己的复制(剪贴板带 HTML)。Git(MARK-13,不做第二套客户端):Git 的查找与运行环境移入ely_platform::git(locate:ELY_GIT,应用旁,ElyTree 包内的 Git,本构建的engines/git;command:只读不抢锁等同一套环境变量),ElyTree 照用;ElyMark 在后台对当前文件问rev-parse --show-toplevel与status --porcelain=v2 --ignored,状态栏显示未改动、有改动、未跟踪、已忽略、有冲突(问不出即“未知”,提示里是原因;不在仓库里不显示),按一下以仓库根打开 ElyTree(ely_platform::apps::open);切换标签、每次保存(指纹变化)、外部改动与窗口回到前台时重问。大文件(MARK-14):超过 2 MiB 的文本以源码打开,上方提示条写明大小与“可视与预览已关闭,保存保留全部内容”,切到可视或预览即提示不切;保存与导出都是全文。PERF-07 实测(macOS,release):1 MB 文档自启动到可视可编辑 1.4 秒(首次冷启动 2.0 秒);4 MB 在可视下约 5 秒,故按源码打开。真窗口(macOS,tests/scripts/mark-output.sh):Git 状态未改动→键入自动保存后有改动→按下打开 ElyTree 于该仓库;专注模式收起标签、存入look.json、重启仍在,文件不变;四种导出的内容(KaTeX、SVG、内嵌图片、全文;资源文件夹的样式与图片;PDF 以 pdftotext 读出文字;纯文本含 TeX);打印生成 PDF;3.3 MB 文件以源码打开、⌘1 不切、末尾编辑后全文保存、纯文本导出含最后一行;ELY_CLIPBOARD_TEST=1时可视全选复制带 HTML。mark-content.sh的 look 步核对淡出、窄列、衬线与插入点居中。去掉专注收起、图片内嵌、大文件阈值、Git 状态或截断文本导出时各自失败。窗口测试须在英文输入法下运行(中文输入法会把模拟键入收进组字)。导出时每张图定为一个src(去掉srcset、sizes与picture的source,无src时取srcset的首个地址);公式的 KaTeX 错误随导出提示(行号与 KaTeX 的说明),不当作完整导出。引用正文的提示走外壳的toast_unlogged,日志只记计数与行号;组件库 28a6bba 起Toaster只记提示的编号与级别,不记标题。未验证:Windows 与 Linux;Windows 的 Git 路径在 ElyTree 包内的位置。 - 2026-10-07:ElyMark 外部变更、自动保存与会话恢复(C14.6,MARK-08、12,§6.2、§6.3、§14.4,QA-22)。监视:一个
notify监视器盖住各标签文件所在的文件夹(不递归),200 毫秒内的一串改动合成一次查看;按会话指纹(长度与修改时间)比对,自己的保存不触发。文件改了:读到稳定的字节(读前读后指纹相同,最多五次),无未保存内容(含页面还没送回的编辑)即原地换成磁盘的文字(ely.refresh:插入点所在的文件行与滚动尽量保持,时间线从这份文字重新开始),会话重载指纹并以新文件身份登记;有未保存内容即不动磁盘与编辑器,状态为“外部已改”,自动保存停止,上方提示条“比较…”。比较:组件库git::regions对“上次载入或保存时”的文字、你的与磁盘上的三方合并,ThreeWayMerge逐个冲突选边(编辑器的文字先落定;比较期间磁盘再改即重读重比,选定前核对所读内容的指纹,不符即提示再比较一次、不写入),可“用磁盘上的”“保留你的”(立即覆盖写入)或冲突都选完后“用合并的”(磁盘的版本成为新的基底,合并结果作为一步可撤销的编辑,随后自动保存)。路径不见了:等 300 毫秒(原子替换的空档)仍不在,即在原文件夹里按文件身份(设备与 inode,不认符号链接)找改名后的文件(Sessions::renamed_in),找到即标签跟过去(与 F2 改名同一条路);找不到即为已删:内容留着,提示条“另存为…”,不再自动保存、不重建文件。读不出(权限等)或监视器出错:提示条写明原因与“再查一次”;“视图›检查磁盘上的文件”随时手动查看(网络目录等不保证有事件)。自动保存(§6.2):有路径的文件停手 1 秒后保存(ely_files::PolicyEngine),外部改动或删除后停止,选定处理后重新开始;失败照常留为未保存。会话(MARK-12):数据目录的session.json记各标签的路径(未命名为空)、名称、模式、插入点所在的文件行与滚动;每 10 秒有未保存内容且有变化的标签写一个恢复点(ely_files::Recovery,每标签一个文件夹,20 点 7 天);退出(Cmd+Q 或关窗)开始时先记下当时的标签;session.lock写进程号,正常退出删去它与全部恢复点;下次启动锁还在且进程已不在即为崩溃(ely_files::alive)。启动时打开记下的文件,标签显示后换成记下的模式与位置;崩溃后的未保存内容从最新恢复点放回,一律开成未命名副本(有路径的文件照磁盘打开,从不先覆盖原件,§6.3;原件已不在也照样开副本并提示哪个文件没能打开)、标为未保存,并提示“恢复了某时未保存的内容;撤销历史未恢复”(§6.3 区分内容与历史)。显式“不保存”后正常退出,下次不当作崩溃,未命名的内容不回来。真窗口(macOS,tests/scripts/mark-outside.sh):原子替换后干净标签换成新文字、键入后自动保存;未保存时外部写入不被覆盖,比较三栏、保留你的写回;互不冲突的两处改动合并后都在;外部改名后标签名跟随、保存写到新名、旧名不重现;改成无权限读即提示、恢复权限后“再查一次”重载;删除后不再自动保存、不重建;退出后重开回到源码模式与原来的行;未命名文档写过恢复点后kill -9,重开内容回来且关闭时问是否保存,选不保存后再开不回来。去掉干净重载、改名跟随、删除时停自动保存、读错提示、崩溃内容、退出时记下标签或恢复的位置时各自失败。启动时先显示可视再换到记下的模式(一次多余的绘制,已知)。启动参数给了文件时只开它们与崩溃恢复的副本,不连带上次的标签。“保留你的”以所比较的指纹为基线(Session::rebase)走普通保存,之后的外部改动照样冲突;自动保存撞上外部改动时交给比较(Host::conflicted),不弹外壳的冲突框;外壳在询问取消、保存面板取消、保存失败、冲突询问取消时都调Host::close_cancelled,关闭标签时调Host::tab_closed(ElyMark 即刻删其恢复点并更新清单);每个标签只在首次显示时恢复一次;缺列的位置读作 0。组件库 f756043:脏标签与非当前标签的关闭按钮透明而非隐藏,辅助技术可按。外壳冲突询问的“保留我的”以此刻磁盘的指纹为基线走普通保存(导出期间再改照样冲突;文件已不在时直接写),“从磁盘重新载入”先结束挂起的关闭;退出途中首次保存的标签以新路径记入清单。取消退出(未保存询问点取消)后清单照常跟随(Host::close_cancelled);关闭或放弃的标签的恢复点在下一次保存会话时删去。位置记文件行与行内列。“不截断导出”随导出在 C14.7。未验证:Windows 与 Linux(alive的 Windows 实现未构建);网络目录。 - 2026-10-07:ElyMark 编辑工具与附件(C14.5,MARK-06、07)。查找替换(Cmd+F):组件库
FindWidget盖在页面右上,查询与替换是两个TextInput,带区分大小写、全词、正则三个开关;可视用prosemirror-search,源码用@codemirror/search;总数与当前序号由页面按不重叠的命中报回;正则写错时框内显示 JS 的原话(按两个编辑器所用的gmu标志编译);空的或无效的查询不执行查找命令(CodeMirror 对它会打开自带的面板);零宽命中按码点前进(落在代理对中间时 Unicode 正则会往回答,原先反复收集同一处直至页面崩溃);预览不替换;换模式或换标签后按同一查询重查。查找框整窗一个,各标签的页面各记其查询:下一处、替换每次都带上框里此刻的查询与替换文字,标签一换即按框里的重查。查询变了从当前命中的起点往后找,键入替换文字不移动命中。替换框回车替换一处。缩进:源码 Tab 与 Shift-Tab 为indentWithTab;可视在列表里 Tab 下沉、Shift-Tab 上提,不在列表时吞掉;代码块同源码。WebView 在自己的键位上下文绑定 Tab 与 Shift-Tab 交给页面(组件库 K02 起更深的上下文先得),焦点不离开编辑器。列表续写:可视为 Milkdown 自带,源码为 lang-markdown 的回车续写。“表格”菜单:插入表格(3×3)、上下加行、左右加列、删行、删列(prosemirror-tables);插入后插入点落在新表第一格(Milkdown 在段末插入时把插入点留在段里,之后的行列操作落空);插入点不在表格里、或在源码与预览里即提示。代码高亮见 C14.3。附件:拖放图片文件或粘贴剪贴板里的 PNG,复制到文档旁的assets/:拖放保留原名、重名加序号,粘贴名为pasted-毫秒.png;都以create_new占名,从不覆盖已有文件,写坏即删去半成品(ely_files::import_attachment原先先查后拷,其间出现的文件会被覆盖);在插入点写入相对链接,名称按百分号转义,源码里替代文字的方括号与反斜杠加转义;复制期间文档另存到别的文件夹即不写链接,提示图片留在原文件夹的 assets;未命名文档、预览与非图片文件拒绝并说明。粘贴经WebView::intercept_paste:宿主先看剪贴板,图片自己处理,其余照常交给页面。macOS:CEF 离屏输入不带 AppKit 编辑命令,而 Blink 在 macOS 上只凭这些命令处理 Cmd+方向键与 Cmd+Backspace(Option 组合 Blink 自己处理),可视编辑器里这些键原来不动;ely-web-content的键盘处理器(edit.rs)在页面没处理这些键时照 AppKit 标准键位以Selection.modify移动或扩选到行首行尾、文首文末,Cmd+Backspace 与 Cmd+Delete 删到行首、行尾;CodeMirror 自己处理的键不经此。组件库 8f4729c 与 aa0c99d:开关按钮(ToggleButton、ToggleGroup的各项)在 AX 树里是按钮,名为其文字或提示,带按下状态。真窗口(macOS,tests/scripts/mark-tools.sh):可视全部替换,源码正则替换,Tab 与 Shift-Tab,列表续写,Cmd+Down 后插入表格、加行加列、删列,拖放图片(自带的拖动源)复制到 assets 并写入转义的链接;ELY_CLIPBOARD_TEST=1时粘贴图片同样落地;复制被拖住(FIFO)时经菜单另存到别处,不写链接;两个标签各设查询后回到前一个全部替换按框里的;键入替换文字后替换一处落在原命中;源码拖入a]b.png写成![a\]b](…)。去掉 AppKit 键、正则、Tab 绑定、插入点修正、文件夹核对、起点查找、标签跟随或转义时各自失败。tests/scripts/comments.py另查 Swift 测试助手与页面的 JS 源码。本机有全局快捷键占用 Cmd+Shift+S 与 Cmd+Shift+Z(空白 AppKit 窗口也收不到),所以测试经菜单另存。未验证:Windows 与 Linux。 - 2026-10-07:ElyMark 导航与搜索(C14.4,MARK-05、MARK-10,MARK-01 的打开文件夹)。左栏三页(分段控件):文件、大纲、搜索。打开(面板可选文件与文件夹,启动参数同)文件夹即在后台按其真实路径列出其下的 Markdown 文件(
folder.rs:.md、.markdown,不进点开头的文件与文件夹,不跟随链接,只在所开的文件夹里,不扫整盘),以组件库lists::FileTree显示(只画可见行,可过滤),选中按回车或双击打开;窗口回到前台、改名之后即重列,列表变了即重搜文件夹。没开文件夹时文件页是最近列表(ely_files::recent:数据目录的recent.json,新的在前,留 20 个,读不出即报错且不覆盖;ElyTree 的同一套移入此处共用),打开成功的文件与列出的文件夹记入,按真实路径(同一文件只一条);标签的路径变了(改名、另存为)即记入新路径,旧路径已不存在即移出。大纲:页面在显示后与每次送出全文时报出当前编辑器的标题(可视与预览取文档里的标题节点,源码取 CodeMirror 的语法树,ATX 与 Setext),以lists::Outline显示,原文块里的标题也在内(以 remark 读其源码,引用、列表中的标题同样列出,跳转落在标题文字上),选一项即把插入点放到该标题并把键盘焦点交给文档(预览滚到该处)。搜索:本文件或文件夹(打开了文件夹才有),按字面、不分大小写逐行找,打开着的文件按其当前文字(含未保存的),其余读盘,读不出或不是 UTF-8 的文件以“某文件未能搜索”的行列在结果最前(带原因),最多 500 条;本文件的结果随当前标签:切换标签即重搜,较早的搜索迟到即弃;结果列出行文字与“文件:行”,选中回车即打开(已开即转到,按真实路径认标签)并把插入点放到该行的那一处:源码定位到该行的命中,可视按写出时每个块在全文里的位置(preserve.js的写出同时给出各块的区间)找到该行所在的块,再按块内此前命中的个数落到块中对应的命中(此前的行先经 Milkdown 解析,按编辑器所见的文字计数,链接地址等不算,与块内查找同按不重叠计;原文块照源码计数;该行所见文字没有查询词时——命中只在链接地址等处——落在该行开头);行号自文件开头算,可视与预览先减去 front matter 的行数,落在 front matter 里即滚到它。页面未显示完就要求的定位等显示后再做。多标签由外壳。真窗口(macOS,tests/scripts/mark-places.sh):文件树不列 .txt、点选回车打开,大纲跳到二级标题后键入落在标题里,文件夹搜索找到两个文件的行、不搜 .txt、选中结果打开并在该行键入,最近列表重启后还在、从中打开文件夹。组件库 dabc1eb 起树与树行有无障碍角色(树、树项,名称、层级、选中、展开);大纲项仍没有(测试按位置点选)。未验证:Windows 与 Linux。 - 2026-10-07:ElyMark 编辑模式、文件与语法(C14.3,MARK-01 至 04,§14.3)。模式:可视、源码、预览,“视图”菜单与 Cmd+1/2/3,状态栏右侧分段控件;每个标签各记其模式,页面回落到源码时标签随之。切换在页面里进行(
ely.mode):先取当前编辑器的全文并立即送出待送的全文,再以同一文字显示新模式;每次切换都新建目标模式的编辑器(不再保留隐藏的编辑器)。本文件只有一条时间线:带着编辑离开一个模式时,把离开时的全文记入时间线;编辑器自己的撤销用尽(各看其撤销深度,Milkdown 的历史键位排在我们的之后)时再按撤销,回到时间线上一条全文、以当前模式重建(另一段的编辑作为一步撤销),重做用尽时向后走;只有真正的编辑会截掉时间线上其后的部分,撤销、重做的跳转不算编辑;离开时编辑器还有可重做的步骤,就把全部重做后的文字记在时间线前方,回来后重做即到那里。模式请求按序号只认最新的:编辑器构建期间再要的模式记下,构建完且仍是最新请求时显示它,此后的新请求清掉它;较旧的显示回报回来时序号不符即退出,不碰排队的请求。预览每次新建、只读(正文与代码块)。切换本身不改文件。文件:新建(空的未命名untitled.md,保存走另存为)、打开、保存、另存为(外壳)、重命名(F2,“文件”菜单):commands::RENAME与commands::RENAMING(含重命名的文件命令表),document_root/set_document_menus改收文件命令表;ElyMark 的rename.rs在原文件夹里改名(只有它用,不放外壳),ely_files::rename_new不覆盖已有文件(macOSrenamex_np加RENAME_EXCL,Linuxrenameat2加RENAME_NOREPLACE,WindowsMoveFileExW不带替换;后两者未构建验证),名称须不含斜杠且扩展名为可保存的格式,未命名或正在保存的标签不改名,改名完成时标签须仍开着且仍是原路径,否则只记日志、不动登记;同文件登记记下打开时的文件身份,原路径已不存在而身份相同即为同一文件(改名间隙里打开新名字会转到原标签),改名后以Sessions::track迁移登记(不关再开);外壳每次保存成功后也以它更新登记的路径与身份(原子保存换了文件);标签名与Session::renamed的路径随之,内容与指纹不变。文件 URL:Windows 的驱动器路径写成file:///C:/…、UNC 写成file://主机/…,去掉\\?\前缀,围栏反向换回 Windows 路径再取真实路径(字符串转换有单测,Windows 上未运行)。CEF 的请求处理移入fence.rs(handlers.rs原超过 500 行)。文档旁的图片:页面<base>设为文件所在文件夹,WebView::allow_images让该文件夹只放行图片请求(资源类型为图片;导航、脚本仍只限内容文件夹),另存为或改名后随文件移动(已显示的相对图片不重载,已知限制);Mermaid 的包按脚本自身位置载入,不受<base>影响。Milkdown 7.22.2 的图片解析把无标题图片的null标题交给要求字符串的属性,ProseMirror 拒绝、Milkdown 记错误后丢掉整张图;image.js改为空字符串。实测(macOS,tests/scripts/mark-modes.sh):文档旁的图片画出(截窗口量色),三种模式往返不改文件,预览不收键入,可视里键入后经预览回来撤销得回原样,源码里的改动回到可视并保存,改名到已有文件被拒、已有文件不动,改名到新名后文件移动、继续编辑保存到新文件,新建打开空的可视文档(读外壳写下的untitled.md须为空),可视键入、源码键入、回可视后两次撤销回到两者之前、重做回来(撤销跳转当作编辑、丢掉时间线时失败)(去掉预览只读、去掉保留编辑器、去掉旧编辑器的链接时测试失败);mark-content.sh在同一段脚本里连要两种模式,最后显示后者(去掉记下的模式时失败);可视已显示、其回报未答时再要源码,最后为源码(不核对最新请求时失败)。“直接打开文件夹”随文件树在 C14.4。未验证:Windows 与 Linux。 - 2026-10-07:ElyMark 文件会话与原文保留(C14.2,§14.3,QA-21)。ElyMark 用
ely-office的外壳(标签、打开、保存、另存为、关闭询问、冲突),引擎Mark(apps/ely-mark/src/engine.rs):载入读字节,UTF-8 带不带 BOM 都可,UTF-16 与非 UTF-8 即报错并指出字节位置(text.rs);编辑器拿到去掉 BOM、CRLF 换成 LF 的文本;写出时文本与载入时相同即原样写回原字节,否则补回 BOM 并按载入时多数的换行(CRLF 多于单独 LF 即 CRLF;混用换行的文件编辑后统一成多数的那种,已知限制)。外壳的Engine::open改为收窗口、返回io::Result(网页视图要窗口,页面开不了即为打开失败)。每个标签一个页面;页面每次改动立即报{"kind":"changed","version"},停手 300 毫秒后报该版本的全文{"kind":"text"};外壳的“已落定”即最新改动的全文已到,保存等它;历史标记为全文的长度与散列,撤销回原样即为未修改;外壳保存时先写出再读标记,两者之间页面可能送来新全文,所以写出时记下所写文字的标记,紧接的读标记交出它,之后的编辑仍算未保存。可视模式保留原文(preserve.js):照 Milkdown 自己的 remark 流程(parse加runSync)读出原文各顶层块的位置,与编辑器的顶层块一一对应(数目不同或有块无位置即不能对应;空文件 Milkdown 仍有一个空段落,照空处理)。每个顶层块单独序列化,与原文逐字相同的才是可视块,否则换成原文块(ely_raw:等宽、可编辑的纯文字,写出即其文字,不进撤销历史的一次替换);块间 Milkdown 不显示的原文(如链接定义:Milkdown 把引用链接内联、删去定义)也成原文块,所以没有藏起来的原文。序列化的标记照该文档自己多数的写法(style.js:无序与有序列表标记、强调、加粗、代码围栏、分隔线、Setext;以 remark-parse 统计后设remarkStringifyOptionsCtx),写法一致的文档几乎全为可视块,与多数写法不同的块成原文块。写出时按块做 Myers 差异(diff.js;ProseMirror 未改的块是同一对象,否则按内容比较),未改的块与块间原文逐字照抄,只有改动的块用 Milkdown 序列化。YAML front matter 不进可视编辑器,以原文显示在上方,写出时原样接回。不能对应的文档直接以源码模式打开(可编辑,无损),并报fallback。打开后插入点在文档开头。页面每次载入是一代(应用计数,随ely.show传入),回报带代数,别的代的回报丢弃;载入时版本计数归零,重载后显示最后收到的全文。故障视图按“重新载入”后焦点回到页面(原先留在消失的按钮上,键入无处可去)。普通 Markdown 读文件的权限错误不交外壳当作加密文件(外壳把 PermissionDenied 当作要密码),照系统原话报出;外壳打开失败另写日志。实测(macOS,tests/scripts/mark-session.sh):BOM、CRLF、front matter、Setext 标题、1)列表、HTML 块、~删除线、硬换行、链接定义与[[...]]的文件,不改保存逐字节相同;在首段键入一字保存,除该段多一个字外逐字节相同(整篇序列化、去掉 BOM 时该测试失败);段落里的[[...]]、引用里的 Setext 与[[...]]、只有链接定义的文件都以原文块显示,键入一字只多一字;在链接定义前向前删除不移动、不修剪它;渲染进程被杀、按重新载入后再键入的字照样保存;无读权限的文件报“Permission denied”、不问密码(不用原文块、不按代丢弃回报时测试失败)。未验证:Windows 与 Linux;模式切换与撤销(C14.3)。 - 2026-10-07:ElyMark 内容包(C14.1,§14、§17.3)。网页源码在
apps/ely-mark/web:版本全部写死,package-lock.json锁定;scripts/build-mark-content.sh以npm ci --ignore-scripts安装、esbuild 打成经典脚本(file:// 下模块脚本被 Chromium 拦下)到engines/content/ely-mark,附THIRD-PARTY-NOTICES.txt(按打进包的每个包取其许可文件;没有许可文件的只许 MIT,以清单的作者与 MIT 原文写出,构建时列出)。包:Milkdown 7.22.2(commonmark、gfm 含脚注与任务、history、代码块组件)、CodeMirror 6(代码块与源码模式,语言取 language-data,全部打进包)、KaTeX 0.19.0(字体随包)、Mermaid 12.1.0(单独的diagram.js,首次遇到图时才载入;securityLevel: strict,不在页面上留错误图)。数学按 Crepe 的做法(remark-math,块公式为 LaTeX 代码块)。只渲染文档里保存的源码;公式与图出错时显示错误(公式带位置,图带行号)与原文,不清空代码块。页面接口ely.show(markdown, mode, texts),mode 为 visual、source、preview;每次显示一个新容器与序号,较早的显示若后完成即销毁其编辑器、不报告,页面上始终只有最后一次的编辑器;预览时正文与代码块都只读;显示后经elyQuery报{"kind":"shown"}。颜色全部取--ely-*(CodeMirror 的高亮、Mermaid 的主题变量),随系统深浅色:Milkdown 把预览净化后拷进页面(不保留我们给的元素),且每次调用给的回调都是新函数,所以 Mermaid 预览先返回带编号的空占位,绘制结果只在该占位仍在页面上时经回调落下(改语言、改文字、卸载都换掉占位,旧结果即弃);绘制在全页一条队列里依次进行,每次绘制带代次,调用 Mermaid 前与落下前都核对属主、占位与代次,失效即跳过(编辑器销毁后排着的绘制不再执行);:root的样式一变,凡占位仍在页面上的图(含正在绘制的)都以新颜色重画,此前的结果作废,首次落下的就是新颜色。记录归各编辑器所有,编辑器销毁即释放,占位已离开页面的记录在下一次绘制或换主题时清掉。Milkdown 把视口外的代码块换成占位(懒挂载),滚入视口时再建。界面文字由应用给(mark.*)。打包时bundle.sh把engines/content/<应用>放进Resources/content,缺即失败;应用先找包内,再找本构建的engines/content,都没有即报错。ElyMark 启动后显示一个空的可视文档(文件会话在 C14.2)。实测(macOS,tests/scripts/mark-content.sh):三种模式下标题、任务、表格、脚注、行内与块公式、代码块(逐块滚入视口,须可见、未被遮挡)、Mermaid 图(不小于 100×80)与两种错误,切换深浅色后图的颜色随之变,连续两次显示只留并只报后一次,截窗口背景色占比低于 0.97,KaTeX 字体已载入,无越界请求;.app包内页面显示。未验证:Windows 与 Linux;树莓派上 2.5 MB 主脚本的载入时间。 - 2026-10-07:ElyTree 分支、标签、合并、变基、冲突(C13.4,TREE-07 至 09,§13.3)。Git 一律以
GIT_EDITOR=true、GIT_SEQUENCE_EDITOR=true运行(没有终端可开编辑器,合并与撤销照 Git 自己拟的信息);失败时 stderr 为空即报 stdout(合并的冲突写在 stdout)。左栏第三页“分支”:本地分支(当前在前)、远程分支、标签(新的在前),for-each-ref读出(附注标签取所指提交与标签自身的说明,origin/HEAD不算分支,上游已删即不显示领先落后),上方“新建分支…”“新建标签…”;选中一个,中间列出提交、日期、上游与说明,以及适用的动作:切换(远程分支为switch --track refs/remotes/名,不被同名标签劫走)、合并到当前分支、把当前分支变基到此、在此新建分支、重命名、删除(branch -d,Git 拒绝时报原话)、强制删除(-D)、删除标签。历史页提交详情多出拣选、撤销提交(合并提交以-m 1)、在此新建分支、在此新建标签。新建分支可选“并切换过去”(switch -c);标签有说明即附注标签(tag -a -m,签名照用户的tag.gpgSign)。变基、删除、强制删除、删除标签、中止、跳过每次都先问(每个确认只对这一次,§13.3),合并、拣选、撤销、继续不问。进行中的操作按 Git 目录里的rebase-merge/rebase-apply、MERGE_HEAD、CHERRY_PICK_HEAD、REVERT_HEAD判定(每次状态重读时读,只取最新一次),中间上方的提示写明正在把谁并到谁(变基时“当前侧”是新基底、“另一侧”是正在重放的提交),给继续(<操作> --continue)、跳过(合并无此项)、中止。命令失败时,只有合并、变基、拣选、撤销,且 Git 的输出有以CONFLICT (起头的行、失败后索引仍有未合并条目(ls-files --unmerged),才提示“因冲突停下”;其余(如钩子拒绝、冲突中checkout --ours的钩子失败)对话框显示 Git 的原话。失败的报错带上 stdout 与 stderr 两者(拣选的 CONFLICT 行在 stdout,钩子多写 stderr)。分支与标签的名称取自完整引用名(refs/heads/、refs/remotes/、refs/tags/之后),同名的分支与标签各归各的;合并与变基用完整引用。分支行内的切换与删除接到同样的入口(删除照样先问)。冲突文件:中间上方是三侧与ThreeWayMerge的块级选择,各块照工作树文件里 Git 写下的标记分组(不另算一套合并;标记宽度取check-attr conflict-marker-size,照 Git 的strtol_i读,不是正整数即 7;行须恰为该宽度,长短不同的同形行照正文;只按 LF 断行,同 Git,正文里的独立 CR 不算行界),基底取自 diff3 标记,没有时取 Git 自己merge-file --zdiff3 --marker-size对三个索引段的结果(块数相同时,只填没有基底的块,diff3 里空的基底照空);下方是可编辑的结果(CodeEditor,初为工作树里的带标记文件);选一侧即在结果里解决对应的那一块标记(其余手改照留),结果里的标记数与未选的冲突数对不上时(已手改过)不再按块选,提示在结果里改;“保存”只写工作树文件(经同一仓库的写队列,与 Git 的写操作不重叠;落地时按仓库与路径记下;路径已成符号链接即拒绝,不顺着链接写),“标为已解决”只做git add(须先保存;结果里还有标记时先问),两者分开(§13.3);另有“用当前侧的文件”“用传入侧的文件”(checkout --ours/--theirs),一侧没有该文件时还有“以删除解决…”(git rm,先问)。符号链接、子模块、任一侧含 NUL(Git 视为二进制)、不是 UTF-8 或缺少的冲突整份解决:写明情形,给“用当前侧的文件”“用传入侧的文件”(只给有该文件的一侧)、“标为已解决”与“以删除解决…”。重读时结果有未保存的手改即保留并提示。真窗口(macOS):合并冲突的提示条、三侧与按块取另一侧后结果更新。未验证:Windows 与 Linux;交互式变基(不提供)。 - 2026-10-07:ElyTree 提交、历史、Blame(C13.3,TREE-05、06)。左栏分“改动”与“历史”两页(
navigation::Tabs)。提交:改动页下方是git::CommitInput(摘要与说明两个框,摘要满 50 变黄、72 变红),上面一行是 Git 设置里的身份(每次状态重读与窗口回到前台时重读,只取最新一次;user.name <user.email>,未设即写明,点它开身份对话框:姓名、邮箱(两个输入框以其标签为无障碍名),可勾“用于所有仓库”写--global,否则写本仓库;空着即提示不写)与签名(commit.gpgsign为真时写明以gpg.format签名,默认 openpgp),签名照用户自己的 Git 与工具,Ely 不另设。提交框按仓库各存一份(换标签时存下当前的、换上另一个的:摘要、说明、修改最近提交与其草稿),提交为commit --cleanup=whitespace -F -(信息经标准输入,#开头的行照留,用户的commit.cleanup不删它们,不经 shell),“修改最近一次提交”勾上即显示为勾选、读出上次的信息填入(读回前不能提交;先存下已键入的,取消勾选时放回;每次勾选或取消、换仓库都使在途的读取作废,读回时须仍是发起的仓库与那一次),提交成功后只在提交框仍是所提交的内容时清空(提交期间又键入的留着,别的仓库的提交框不动);钩子或签名失败时以对话框显示 Git 的全部原话,不只说失败。提交同其他写操作排队。历史:log --topo-order --exclude=refs/stash --all,每页 2000 个,滚到距末尾 20 行内读下一页;每次状态重读后比对show-ref --head,引用变了(含外部提交、切分支、取回)即从头重读;读取期间来的比对记下,读完再比;引用读不出时历史栏显示 Git 的原话,之后读通即重读并清掉;提交图由git::lanes按已读的全部提交算出;时间按本机时区写到分钟(不用相对时间,长列表不必随时刷新)。搜索:在“提交信息”(--grep)、“作者”(--author)、“改动的文字”(-S)中按字面、不分大小写找,或按提交号(rev-parse --verify,前缀可);搜索时不画图。选中一个提交:中间上方是摘要、说明、作者与提交者(不同时才列)、完整提交号、父提交(可点,转到父提交)、签名状态(%G?按字母译出,带签名者)、引用;下方左列是相对第一个父提交改动的文件(diff --name-status -M与--numstat,根提交对空树,空树由hash-object -t tree算,不写死 SHA-1),右边是所选文件的差异(与改动区同一读法:Git 自己的差异,图片、二进制、非 UTF-8 同样处理,无块的纯重命名显示为无改动)。改动区差异与历史页所选文件的标题栏都有“文件历史”与“逐行追溯”(改动区限已跟踪的文件):文件历史为log --follow --numstat -z,跨重命名,每个提交带当时的路径与行数,选中即显示该提交里这个文件的差异(状态按两侧是否存在判定);逐行追溯为工作树文件的blame --porcelain,未提交的行写“尚未提交”,每段首行可按,转到历史页的该提交;条色按提交时间新旧。读取都在后台,按发起时的仓库、提交与文件核对;文件历史与逐行追溯每次打开有窗口内唯一的序号,迟到的结果即弃。提交详情的作者、提交者、提交号、签名、引用、摘要与说明在 AX 树里有文字。真窗口(macOS)经 AX:历史页、提交图、引用、提交详情、文件列表与差异。未验证:Windows 与 Linux;GPG 与 SSH 实际签名(只读设置,签名由用户的 Git 完成)。 - 2026-10-07:ElyTree 差异与暂存(C13.2,TREE-03、04)。差异一律取 Git 自己的输出(
-c diff.suppressBlankEmpty=false diff --no-color --no-ext-diff --no-textconv -U3,前缀写明a/、b/,覆盖用户的 noprefix 等设置;空白的上下文行照样带前导空格):已暂存为--cached -M(HEAD 对索引,即提交前的实际索引内容),改动为索引对工作树,未跟踪为--no-index对/dev/null(退出码 1 即有差异);Git 的清理过滤(换行转换等)由它自己做,按块、按行暂存的补丁对得上索引。块之间未改动的行从旧侧的 blob 取(cat-file blob,已暂存为HEAD:旧路径,改动为:路径),可展开;行号按块头推算,旧文件比差异短即报错。改动的词按每对删去与加入的行标出(Unicode 词)。空白:精确、忽略空白变化(-b)、忽略全部空白(-w);行内与并排。按块或按行:只在精确比较、且是普通内容改动时(无新建、删除、重命名、复制、权限变化的头),已暂存区为取消暂存(apply --cached --reverse),改动区为暂存(apply --cached);未跟踪、重命名等整份暂存,界面写明。补丁由所选行切出:暂存时未选的删去行留作上下文、未选的加入行不写;取消暂存时反之;块头按“范围前的行数”换算,空范围照 Git 的写法;“无换行结尾”按切出后两侧的实际行尾:无换行的行在哪一侧后面还有行,那一侧就带上换行(所选的删去或加入行同样),两侧不同的上下文行拆成一删一加;apply带--whitespace=nowarn,用户的apply.whitespace不改所选的字节。写入前在后台重读同一条差异,与所选时读到的字节不同即拒绝(“文件在显示后改了,未暂存”),索引不动;同一仓库的写仍排队。所选行按住 Shift 取同一块内的范围;差异一变(任何重读得到不同的输出)所选即清空;每次读取有窗口内唯一的序号,换文件后旧文件迟到的读取即弃。图片(png、jpg、gif、webp、bmp、tiff、ico)以BeforeAfter左右拖动对比,一侧缺失时只显示另一侧;其他二进制文件,以及两侧任一侧不全是 UTF-8 的文件(差异以外的字节也算),显示两侧大小,整份暂存;文件与符号链接互换时 Git 为同一路径打出两段(删去与新增),两段都显示(不折叠:两段没有共同的旧文),整份暂存;工作树侧照 Git 读:符号链接读其自身的路径,不跟随;未跟踪的文件夹与冲突文件(冲突在 C13.4 处理)只说明。单击文件即在中间显示其差异(已忽略的文件以系统程序打开),标题栏另有“打开文件”。外部改动后状态重读,正显示的差异随之重读;文件不再列在原分区即收起。真窗口(macOS):改动区选两行暂存,索引恰为所选,已暂存区显示实际索引内容,并排视图;AX 树中各行、块按钮与分段可按。 - 2026-10-07(用户定):ElyTree 的 Git。随包自建钉住的 Git v2.56.0(a018953)加 git-lfs v3.8.0(各平台散列钉在
scripts/fetch-git-lfs.sh),scripts/build-git.sh以 RUNTIME_PREFIX 构建到engines/git,LFS 放在libexec/git-core。查找次序:ELY_GIT(系统 Git,低于 2.40 即拒绝),否则应用旁或Resources/git,否则本构建的engines/git;没有即报错,不退回别的 Git。每次运行都是参数数组,带GIT_OPTIONAL_LOCKS=0(读取不抢索引锁)、GIT_TERMINAL_PROMPT=0(从不在终端问凭据)、GIT_LITERAL_PATHSPECS=1(路径不当通配)。 - 2026-10-07:ElyTree 仓库与工作区(C13.1,TREE-01、02)。窗口经
ely_ui::tool_root:“文件”只有新建(选文件夹git init)与打开(可多选文件夹),commands::menus改收各应用自己的文件命令(文档应用照旧六项),保存、另存、导出、打印的动作在工具窗口里不挂;“仓库”菜单:克隆…、关闭仓库(Cmd+W)。每个仓库一个标签,同一工作树(按--show-toplevel的真实路径)只开一个,再开即切过去;裸仓库拒绝。最近列表在数据目录的recent.json(新的在前,留 20 个),读不出即报错且不覆盖;最近项打不开即移出并说明。克隆:先问地址,再在最近仓库旁问新文件夹(名取地址末段去.git),文件夹非空即拒绝;Git 的进度逐行显示在正中(回车与换行都断行,读到即显示),可停止(SIGTERM,Git 自己删去半成品;已结束的进程不再发信号,免误伤重用的进程号);关窗或退出时停下进行中的克隆。状态读status --porcelain=v2 -z --branch --untracked-files=normal --ignored,未知字母即报错;行数取diff --numstat -z(已暂存与未暂存各一次,二进制不计),未跟踪、冲突、忽略的不计行数。左栏git::ChangesList分冲突、已暂存、改动、未跟踪、已忽略;单击文件以系统程序打开;暂存(add --all)、取消暂存(有提交时restore --staged,首个提交前rm --cached -f:索引与文件不同也照取消,文件不动)、全部暂存(add --update)、全部取消(reset -q);取消暂存重命名连旧路径,暂存只用新路径(已暂存的重命名再改时旧路径已不在);放弃改动每次都问(已跟踪的restore --worktree,未跟踪的clean -f -d并写明会删除)。同一仓库的写操作排队、一次一条,失败即清掉其后的排队并报出 Git 原话,关闭标签要等排队写完。外部改动:notify监视工作树、Git 目录与共用目录(--git-common-dir;链接的 worktree 的分支与引用在主仓库里),250 毫秒内的一串改动合成一次重读,读取中再来的合成一次;空闲不读。状态栏:分支(游离显示短提交号,无提交写明)、上游与领先落后(上游引用已删即写明不存在,不显示为 0)、真实路径、Git 版本、随附或系统与所用 Git 的路径。中间区在 C13.2 前只显示改动数或“没有要提交的改动”。组件库 4f8f3bd 与 37c5187:Changed::lines可缺。真窗口(macOS):五个分区、行数、状态栏。未验证:Windows 与 Linux(停止克隆在非 Unix 上报未构建);LFS 仓库的克隆。 - 2026-10-07:ElyPDF 输出与文件恢复(C11.8,PDF-13、14,QA-13)。“文件›导出…”(Cmd+E)一个对话框三种:页面图片(PNG,72、150、300 dpi,全部或所选页,总是浅色,选文件夹,
名称-序号.png按页数补零,先以ely_files::claim_all占下全部目标,任一失败即删去本次全部);文字(UTF-8,各页以换页符分开,MuPDF 的 stext);优化的 PDF(可看可改的设置:压缩数据流、字体与图片;图片高于某 dpi 降到某 dpi,双三次重采样到确切目标,重压缩:无损的仍无损、有损的保持原格式,默认 300 降到 150;降到的 dpi 须低于阈值,否则不可导出;完成提示写明原文件与新文件大小)。导出图片与文字要“复制”许可,优化副本要“修改”许可。“打印…”(Cmd+P)把文档此刻写成副本(要“打印”许可,worker 的PRINT_COPY)再交系统打印面板(ely_office::print_later)。文件恢复:MuPDF 打开时修复了文件结构(pdf_was_repaired),worker 即把修复后的文档整份写成临时副本、改开这份副本(日志与快照从此正常;原先修复过的文件不能保存),关闭文档时删去;打开时 MuPDF 的警告记下作修复报告(ely_open期间接管警告回调,照样转给原回调)。外壳的Engine::signed改为Engine::risk(宏由外壳判,签名、修复由引擎答),加Risk::Repaired:提示条说明打开的是修复的副本、“另存为”写新文件、原件不动,带“报告…”按钮显示 MuPDF 的原话;保存一律另存,另存到原件被拒;签名或修复的文件另存成功后标签记下“待重开”,到内存与文件一致(已保存、无待落地编辑、不在导出)时才按新文件重开;另存期间又有编辑则留着编辑,下一次保存后再重开。MuPDF 有时读到坏对象才修复(尾部完好、某对象偏移坏):打开与解锁后先把每个对象读一遍,并解码图片以外的每个数据流(ely_scan,读不出的对象记入报告);MuPDF 修复了结构或读的过程中有任何警告(如内容流解压失败),都当作损坏文件,改开整份写出的副本、报告列出警告、保存一律另存。图片(用户定)在画出时检查:画图块或缩略图时 MuPDF 有警告即为损坏,报告补上警告,标签转为修复风险(保存一律另存);没画过的图片在原位保存(覆盖文件)之前统一解码检查(外壳的Engine::unsound,有问题即转为另存为)。全部解码放在打开时的代价实测:100 张 2000×2000 的 JPEG(281 MB)打开到首块 2.6 秒(release,本机)。导出图片与文字期间 MuPDF 有警告即报错不写(读不全的内容不当作导出)。打印副本不带加密(已核过打印许可;否则系统打印面板打不开加了打开密码的副本),print_later在面板关闭后删去它。导出对话框的降采样数值随键入生效(不等回车或失焦),直接点导出用的是所见的数。补丁0005见上(保存打不开输出时的保存中标志)。QA-13:worker 改字、加并填文本字段、移页后保存,以 PDFKit(另一阅读器)读回:页序、新字取代旧字、字段值都对(tests/independent.rs,只在 macOS);图片编辑在 PDFKit 里未单独核对(PDFKit 无图片清单接口)。组件库 a6b1bb0 的init改为返回错误(缺资源时),启动时出错即以startup::fail报出。真窗口(macOS):损坏文件的提示条与“修复的副本”状态、导出对话框。未验证:Windows 与 Linux 的打印(打印面板只在 macOS 实现);真窗口里实际打印。 - 2026-10-06:ElyPDF 密码与签名(C11.7,PDF-11、12,QA-15,§11.4)。权限:worker 记下每个文档的访问(MuPDF 的认证结果,4 为所有者;未加密即全权),
PERMISSIONS按 Ely 的位报出(打印、复制、修改、批注、填表、页面与书签、所有者),每个改动请求先按表核对(secure::needs:页面增删移转与书签要“页面与书签”或“修改”,内容、链接、属性、附件、OCR 层、删除与清理要“修改”,批注要“批注”,建改字段要修改加批注,填表要填表或批注,提取要复制,改保护要所有者),不许即以restricted:名报错,客户端转成PermissionDenied,界面按名说明并提示输入所有者密码;复制文字在应用里按位拦(读文字本身不拦)。“文档”菜单“输入所有者密码…”:对时解除限制;错时 MuPDF 已丢了原密钥(RC4 与 AES-128 实测文字读不出),worker 以原密码重新认证。“密码与权限…”只在所有者权限下:写新文件(AES-256,ely_protect经ely_save_copy带写出选项),打开密码可空,所有者密码必填且须与打开密码不同,六项许可勾选;或不保护(去掉加密)。只凭用户密码打开的文件改保护即拒绝。签名:MuPDF 以HAVE_LIBCRYPTO构建,worker 另链libmupdf-pkcs7.a与 OpenSSL 的静态libcrypto.a(pkg-config 给目录)。SIGNATURES按表单字段树列出全部签名字段(不画在页面上的也列,页与框可无;控件无 /P 时从各页 /Annots 找页):已签的给出摘要核对(pdf_check_widget_digest:未改、已改、读不出)、其后有无更新与更新是否越出签名所许(pdf_signature_incremental_change_since_signing、pdf_validate_signature)、签名者(MuPDF 的识别名与通用名)、签名者自称的时间、原因、地点,以及签名里的证书(签名者在前,DER);单个签名读不出只报该项的原因。证书信任由应用交系统判定(ely_platform::trust:macOS 用SecTrust、SecPolicy::create_x509、禁网,故从不核验吊销;Windows 与 Linux 未构建,报“此平台未构建”)。界面“文档›签名…”对话框逐个列出完整性、证书、吊销(一律“未核验吊销状态”)、时间(带时区)、原因、地点与所在页,不给笼统的“有效”。新签:“以证书签名…”工具,拖出框或点空的签名字段,对话框选 .p12/.pfx、填证书密码、原因、地点,另存:快照副本上建字段(名SignatureN避开表单已有名)并签,以增量更新写出(旧签名覆盖的字节不动),写完重开核对新签名的摘要,不过即报错删文件;原文档不动;发起时绑定视图版本与所选位置。补丁patches/mupdf/0004-signature-reason-and-location.patch:原因与地点写进签名字典(MuPDF 原只画进外观;签后再改会落到第二个更新段,字节范围算成负数)。外观只画标签、名字与时间(不画空的识别名各栏,不画 MuPDF 标志)。已签文件(ely_signed数表单里有值的签名字段)打开为Risk::Signed:上方提示条说明修改会使签名失效、“另存为”写出未签名副本,带“签名…”按钮;保存不覆盖原文件。外壳的risk由布尔改为Option<Risk>(宏或签名),Engine::signed由引擎回答。Ely 一切整份写出(保存、另存、提取、删除、清理、保护)在ely_save_copy里去掉签名值与其外观(字段留下、空着,去掉 AppendOnly 标志):整份重写本就使签名失效,不留假签名。签名核对的是文件,不是编辑中的文档:SIGNATURES带路径,worker 以文档的密码另开该文件读出再关闭;签名列表按标签当前的文件路径读、按路径缓存,保存后重读。已签文件另存为成功后,标签按新文件重新打开(外壳的reload,同冲突时;撤销历史随之重新开始),打开着的文档、签名列表与以后的签名都对应这份未签名的副本;另存失败时标签与签名都仍是原文件。签名对话框每次打开都重读文件,磁盘上被改动即显示已改。去掉签名值时一并去掉该签名字段自身的只读位(签名时由它的锁设下),以便再签;它锁住的其他字段的只读位留着(分不出原本是否只读,已知限制)。补丁patches/mupdf/0005-failed-save-clears-save-in-progress.patch:MuPDF 的prepare_for_save置起保存中标志、只有写出过程清除它;打不开输出文件时标志留着,此后一切改动绕过日志,编辑没有撤销步(实测:存到不存在的目录后旋转,历史标记仍为 0);补丁让打开输出或预排签名失败时清除它。可视签名(用户定三种来源):“添加可视签名…”对话框,手写(组件库SignaturePad,笔画按较长边归一)、键入(姓名加字体:手写体为随包的 Great Vibes,OFL,crates/ely-pdf-engine/fonts,取自 google/fonts c1eda92,嵌入 worker 二进制,以 CID 字体写入;或已有的拉丁与中日韩字体;缺字即报出是哪个字)、图片(按原比例);随后单击放下(宽 160 点)或拖框(按比例放进框内),各为一步:手写成墨迹批注,键入成文字,图片成图片内容;对话框写明它不是数字签名。真窗口(macOS):已签文件打开有提示条,状态为已签名,签名对话框三项分列;可视签名对话框。未验证:Windows 与 Linux 的证书信任;时间戳与吊销;带图片外观的数字签名。 - 2026-10-06:ElyPDF 内容删除与文档清理(C11.6,PDF-09、10,§11.4,QA-14)。标记只在视图里(“标记文字以删除”按所选文字每行一框,“标记区域以删除”拖框,单击已有标记取消;预览为页面上的深色框;任何编辑落地即清空,页码可能已变),文档本身不动。“删除标记的内容…”与“清理文档…”都写新文件:先把文档快照写出、重开副本(
ely_save_copy,提取与保存同用),在副本上改,再以garbage=2整份写出(不增量,旧修订不留),写到工作目录、核对通过后经ely_files::export交到所选位置,工作副本普通删除(对话框写明:原件、旧备份、同步历史与已发出的副本仍可能含有被删内容;不是磁盘级擦除)。删除:先删去碰到区域的批注与字段(MuPDF 的删除只管链接与文本框批注,其他批注的内容、字段的值会留下),字段失去最后一个控件即连同空了的上级一起从表单去掉(否则值留在字段树里);全部区域的删除做完后才建 Redact 批注(否则后一区域会删掉前一区域的标记)并pdf_redact_page(黑框;图片在区域内的像素由 MuPDF 改成白色;碰到区域的线条整条删去,只删被盖满的会留下可复原的路径;碰到的文字删去,含隐藏文字)。有区域的页再过一遍内容(含表单):标记内容的 /ActualText、/Alt、/E 一律去掉,结构树里属于该页的元素(自身或继承的Pg,或子项 MCR、OBJR 的Pg指向该页,连同其上级)去掉这三项与 /T;整页去掉而不只区域内的,宁多勿漏。页面缩略图 /Thumb 删去(它可引用原图),核对时区域所在页仍有缩略图即失败。写出后重开核对:区域内的字(与删除同一判法:字框各边缩一成后相交)为零;区域内画出来不是黑色的像素为零;逐页以设备重放,落进区域的路径、渐变、图像蒙版除我们的黑框外为零,图片单独画出在区域内只剩白或黑;文件只有一个版本;任一不过即报错并删去输出。确认时绑定标记与文档版本,选完路径后标记或版本变了即不写。清理:先扫出数量(属性与元数据条数、批注、附件含批注里的文件、有隐藏文字的页、脚本入口),勾选后写出:删 Info;其余按一遍遍历全部对象及其中的直接字典与数组:/Metadata 与 /PieceInfo,文件说明的 /EF 与 /RF 及关联文件 /AF,脚本;删批注(链接与字段不算批注,保留),删附件(名称树与文件批注;附件按带 /EF 的文件说明计数,每个一次),隐藏内容为:内容过滤(包住 MuPDF 的 sanitize 过滤器,用途设为 View 才认关着的图层)删去关着的图层里的一切、3 与 7 渲染模式及零透明度的文字,脚本(名称树 JavaScript;任何对象的 /A、/OpenAction、/Next 与 /AA 各项里 S 为 JavaScript 或带 /JS 的动作,从链中摘除,其后的 /Next 链接回原处,非脚本动作保留),带隐藏或不可见标志的批注(含链接与字段控件:链接不在 MuPDF 的批注表里,按页面 /Annots 删;控件删去时字段连同值从表单去掉);有隐藏文字被删的页,标记内容的替代文字照删除的做法去掉。扫描与写出后的核对都在快照副本上跑同一遍清理来计数,所以计数与执行一致;所选类别核对不为零即报错。扫描回来时须仍是当前标签且版本未变,对话框写明文件名。未验证:Windows 与 Linux。 - 2026-10-06:ElyPDF 文档结构与 OCR(C11.5,PDF-07、08)。MuPDF 以
tesseract=yes构建(自带的 Tesseract 与 Leptonica,C++,worker 另链 libc++/libstdc++);构建脚本记下所用参数,参数一变即整个重建(make 不跟踪参数,曾留下未开 OCR 的旧目标文件)。语言数据(用户定 tessdata_fast:eng、chi_sim、chi_tra)由scripts/fetch-tessdata.sh按钉住的提交取到engines/tessdata并核对散列;运行时取ELY_TESSDATA、应用旁的tessdata、或本构建的目录,缺 eng 即报错。OCR:worker 把页面按 300 dpi 画成灰度图,只让这张图经 MuPDF 的 OCR 设备进 stext(不把页面原有内容交给它),按行回报页面坐标的外框与文字,不改文档;应用逐页在后台读,可取消,每页回来时核对视图版本,读完以一步写入隐藏文字层:无字形的 Identity-H 字体(ToUnicode 恒等映射),每行一个3 Tr的文字对象按外框横向拉伸,写在单独的内容流里(自带q … Q,3 Tr 不漏到后来写的字)并以/ElyOCR标记;“重新识别”先删去这些流再写,原有文字不动;“跳过”不读已有文字的页。BMP 以外的字拒绝整层(已知限制)。补丁0003-ocr-needs-every-language.patch:Tesseract 只载入能载入的语言、静默丢掉其余,补丁让载入数少于所请求时报错。每次识别有自己的编号,取消后的页回来即弃(不再按版本与页码认);页码范围随键入更新。补丁patches/mupdf/0002-embedded-file-and-link-uri.patch:pdf_add_embedded_file成功时未结束它开的操作,之后撤销与状态读取都抛错;pdf_set_link_uri写了/A却留着旧/Dest(读取先看/Dest),改链接目标无效。书签:添加(在选中项之后,否则末尾)、改名、改目标、删除(连子项),按自顶的路径定位并核对标题,经 MuPDF 的大纲迭代器在一步内完成。链接:读取每页的外框、地址与内部目标;阅读时单击即跳转(内部)或打开(只 http、https、mailto,其他拒绝并告知);“添加链接”工具拖出外框,目标为页码或网址;单击已有链接改目标或删除,按序号并核对地址。文档属性:标题、作者、主题、关键词可改(只写改过的,一步),创建与生成程序及时间只读,文件没有的显示“无”。附件:EmbeddedFiles 名称树按名列出,再列各页 FileAttachment 批注的文件(标出页码),以键区分(n:名称、a:页:对象号,重名不混);大小取/Params /Size,缺即按实际字节(不把缺失显示成 0);类型、说明,另存为(经ely_files::export)、添加(文件名为名,重名拒绝)、移除;改动时名称树重写为一层有序的/Names。左栏分段为页面、书签、批注、附件,书签与附件为空时显示空状态;“文档”菜单:文档属性…、识别文字…、添加书签、添加链接、添加附件…。真窗口:扫描页识别后可查到文字、页面观感不变。移除批注里的文件即删去该批注。未验证:XMP 元数据不随属性修改;Windows 与 Linux 上的 OCR。 - 2026-10-06:ElyPDF 批注与表单(C11.4,PDF-05、06)。垫片
ely_annot.c、ely_form.c:读一页的批注与字段(JSON:对象号、种类、外框、颜色、内容、作者、印章名;字段的全名、值、开状态、是否选中、只读、选项与显示文字);批注与字段按对象号认定,撤销后不变,页上没有即拒绝。添加批注收 JSON 列表(可跨页,一步):高亮、下划线、删除线(按所选文字每行一框)、便签(内容)、矩形、椭圆、直线、手绘、印章(MuPDF 的 14 种标准印章);全部检查在开步之前,不合即不改、重做不丢。作者取账户全名(ely_platform::user::name)。改批注的颜色与内容、删除批注各一步。字段(MuPDF 无建字段接口,垫片自建):文本、复选、单选、选择框(组合框);名字不可含点、不可重名;同名单选并入同一组(各按钮的开状态为 ChoiceN,开状态先画外观再设回 Off);改名、改选项(原值不在新选项里即清空)、删除(组里最后一个按钮删去即删组)各一步。填写:文本与选择框经pdf_set_field_value(不跑脚本),复选与单选设组头 /V 与各按钮 /AS;只读字段拒绝。外观在同一步里生成。界面:“批注”菜单(高亮、下划线、删除线、便签、印章、矩形、椭圆、直线、手绘)与“表单”菜单(文本字段、复选框、单选按钮、选择框)各为工具,Esc 退出。阅读时单击复选与单选即填写,单击文本与选择框在右栏填写,单击批注在右栏改颜色、内容或删除;表单工具下单击已有字段改名、选项或删除,拖出新字段(单击给默认大小)后在右栏取名再添加;新单选默认取本页最后一组的名字。按下即改的操作须视图无排队编辑,右栏的改动按发起的标签与版本核对。右栏的开合在拖动与绘制期间保持手势开始时的样子,手势按开始时该页在窗口中的位置与比例换算(之后页面因右栏开合或滚动移动也不影响);单击按窗口像素判定(移动不足 3 像素)。文字未读到时拖出的标记等文字到后再加。新字段的默认名避开表单里的全部名称(worker 读整棵/AcroForm/Fields,含没有页面控件的节点与父名),右栏等它读完再给;改名只改名字末段,按父路径下的全名查重。左栏“批注”列出全文批注(每批 20 页在后台读),点一行转到所在页。组件库 3d08744:DescriptionList的值占满所在列。未验证:已有文件里的签名字段与按钮只读不改;多选列表框只当单选填;Windows 与 Linux。 - 2026-10-06:ElyPDF 文字、图片与对象编辑(C11.3,PDF-03、04,§11.3)。字体(用户定):文档自带字体(能写新字时)或 MuPDF 内置的 Helvetica、Times、Courier(不嵌入,WinAnsi)与 Noto 中日韩(不嵌入,UTF-16 的 Adobe CJK 字体),不扫描系统字体;自带字体以 CID 字体(Identity-H,按字形号写)写入。对象(用户定):图片与表单 XObject。文字块以 MuPDF 的 stext 为准(同一读法,块序号稳定);可编辑的块须横排,且每个字形都在页面自身的内容流里(以不落盘的过滤试跑核对,表单里的字不可改)。改字是一步:用内容过滤的
text_filter去掉该块的字形(按字形原点在用户空间 0.5 点内且字符相符,不靠白块遮盖),再在原首行基线处按框宽排版写新字(空格处折行,太长的词断开,行距沿用原块);所有检查(块文字未变、横排、字形都在页面内容里、字体画得出每个字、排版)都在开步之前,拒绝时文档与重做都不动;画不出某字即告知是哪个字,界面请用户换字体;换了字体、字号改变或新文字越出原块的框时先出预览(在快照副本上改并画出该区域,文档与撤销历史都不动),确认后才写。行距取首行与下一条不同基线的距离(同一基线的几段不算行),没有即字号的 1.2 倍。新文字在点击处起框。新增内容第一次写入时把页面原内容包进q … Q(以%Ely标记,只包一次)。图片与表单:清点处理器按q、Q、cm跟踪,列出页面顶层的每次Do与其页面框;移动、缩放(右下角柄,保持比例、左上不动)以X = C·T·C⁻¹包住该次绘制重写内容流,删除即不写该次Do,替换图片是新图片资源、按比例放进原框中央;添加图片落在当前页中央(像素按点、缩到页面一半以内)。MuPDF 的字体与图片资源缓存不随撤销与放弃回退(实测:预览后再写,字体引用指向已不存在的对象),每次放弃、撤销、重做后清空。编辑工具在“编辑”菜单(编辑文字和图片、添加文字、添加图片…),Esc 退出(键位上下文在窗口根上,页面与面板里都管用);替换、添加与删除图片和表单、拖动移动与缩放、写字与预览的确认都绑定发起的标签与视图版本,执行前视图须未变且无排队的编辑,否则提示不执行;任何编辑落地即关掉此前的预览;图层读取在回来时总清掉进行中的标记,编辑之前发出的读取作废后重读;右侧编辑面板:文字、字体、字号、颜色、框宽,应用、删除文字;对象:替换图片…、删除。真窗口(macOS)改过一次字:同字体同位置替换,标签为已编辑。 - 2026-10-06:ElyPDF 页面组织(C11.2,PDF-02)。worker 打开即开 MuPDF journal;插入空白页、从文件插入(整份 PDF 嫁接)、删除、移动、旋转(90 度倍数,写页面的
/Rotate)、裁剪(按所见的上右下左边距设/CropBox,全零即去掉)、撤销与重做都是一步,回复带历史标记(worker 按 MuPDF 的撤销位置记每步是编辑还是保存,标记是最后一个已做的编辑;撤销重做跳过保存步)。删除时指向已删页的书签保留但失去跳转,链接移除,表单字段去掉已删页上的控件;页码标签由 MuPDF 的删除与插入随之平移。坏的编辑(删光、页码乱序、非 90 度、边距超出页面)拒绝并回滚整步。保存(Save、Save As 走外壳)先pdf_save_snapshot写出(不动内存中的文档与撤销历史,靠补丁 0001),再打开该副本以garbage=2紧缩写到工作目录(已删页的对象不留在文件里),加密文件以用户输入的密码重开副本、照原加密写出(密码只在 worker 内存);外壳读回字节、原子写入。提取(选中页写新文件,经ely_files::export,不覆盖打开着的文件,被别的标签持有即拒绝)在快照副本上删去其余页再紧缩写出,原文档的撤销与重做都不动;拆分按每份页数写到所选文件夹(名称-序号.pdf,先以ely_files::claim_all占下全部目标,任一已存在即一个不写;原 ElyShow 的同名函数移入 ely-files 共用)。合并即从文件插入到末尾再另存;插入的页带着自己的批注、链接与表单字段(指向源文件自身页的链接改指插入后的页,字段并入表单,重名的加序号,如field0 2(名称按需分配,长名也终止),源表单的默认外观/DA与默认对齐/Q给到没有自己值的字段树根,源表单的字体资源并入目标/DR,同名而不同的字体改名为名称_2并改写字段外观里的引用(按 PDF 名称语法解#xx转义,写出时照样转义);源文件的书签不并入)。删除时表单控件按被删页的/Annots认定,不只看可省略的/P。裁剪边距以所见为准:先按视图旋转、再按页面/Rotate换到页面自身方向,并按/UserUnit换算。编辑失败也通知外壳队列已动,等着的保存、关闭不会挂住。结构编辑后区间点选的起点作废。应用:每个视图一条编辑队列,一次一条、按序执行,落地后重读页数、尺寸与书签,清掉图块、文字、缩略图、找到的词与选择,视图版本加一,此前发出的读取回来即丢;查找在编辑后重查。修改标记是当前历史与载入或上次保存时的历史不同,撤销回到保存点即为已保存(外壳新增Engine::saved与Session::reverted:引擎回报未修改时 Dirty 回到 Saved,保存中、冲突、失败不变)。左栏是页面缩略图(side.rs;有书签时上方分段切换到书签):点选、Shift 连选、Cmd 增减,右键菜单,拖到某页之前即移动(移动后仍选中),Delete 删除;无选中时作用于当前页。“页面”菜单与“编辑”菜单的撤销(Cmd+Z)、重做(Shift+Cmd+Z)。裁剪对话框以毫米输入。MuPDF 的构建改为在engines/mupdf-src(reference 只读)打patches/mupdf后编译。 - 2026-10-06:ElyPDF 阅读(C11.1,PDF-01)。MuPDF 只链进
ely-pdf-worker(crates/ely-pdf-engine,build.rs 把 C 垫片shim/ely_mupdf.c与libmupdf.a、libmupdf-third.a只链给这个二进制),应用不链 MuPDF。build.rs 以协议、垫片与 worker 源码的散列为版本戳,应用启动 worker 先握手(HELLO),戳不同即拒绝(单测不重建 worker,旧二进制曾被误用)。worker 在打开第一个文件时才启动(PERF-01),tests/scripts/pdf-idle.sh查空白窗口无子进程。垫片每个调用都在fz_try/fz_catch里,返回码加最后一条错误信息,MuPDF 的跳转从不越过 Rust。worker 与应用之间是ely-ipc的帧(从 ely-lok 抽出,两者共用):打开(锁定即报)、解锁、页数、页框、渲染一块(设备像素、缩放、旋转、深色)、按阅读顺序的字符与外框、页内搜索(每处命中及其全部四边形,跨行短语算一处,数量不设上限)、书签(JSON)、关闭;文件读不出只报错,worker 照常工作。查看器:打开时只读页数、前 100 页尺寸与书签,其余尺寸后台每批 100 页补齐;视图只排屏上的页(连续、单页、双页;适合宽度、适合页面、实际大小与缩放档;视图旋转 90°),按 512 设备像素一块请求,一次一块、由近及远,只留屏上的块(横纵两向),后台回来的块不在屏上即弃;画不出的块记下不再请求,换文档或重开前不重试;深色外观下页面按亮度反相(MuPDFfz_invert_pixmap_luminance,色相保留)。文字选择按字符、可跨页,复制按行换行,未读的页先读;查找每批 20 页在后台进行,命中逐个跳转并标出,Esc 关闭(组件库 617a8d6);书签栏用lists::Outline,点即跳到页与位置。切换视图、缩放、旋转后仍停在正在读的那页。加密文件打开时问密码(外壳的密码框,见上一条),错了再问,取消即不打开。没有页的文件报错并关闭,不开标签。双页时同一行在视口内的两页都可为当前页;当前页只在横纵两向都可见的页里定,跳转在目标页左缘不在视口时横向移过去。图块按视口半开区间取。首次按下时文字未读,读到后照原按下处起选;查找结果按查找实例与所属标签核对,只在该标签为当前时跳转。书签为空时不留左栏。启动参数里的文件直接打开。 - 2026-10-09(用户定:“打开方式”里不得出现 LibreOffice):系统会登记磁盘上能找到的 .app、挂载镜像里的 .app 与运行过的包,构建目录里的
LibreOffice.app与测试挂载的官方镜像曾因此成为 xlsx 等的默认程序。现构建后由scripts/inert-lo.sh去掉其全部文件与 URL 声明、快速查看与聚焦插件,注销它,并在engines/放.metadata_never_index;Ely 只取其 Frameworks 与 Resources。任何脚本与测试都不挂载 LibreOffice 镜像:LOK 探测用本构建的内核,单测所需的官方内核由scripts/extract-official-lo.sh以 7zz 解成无包的文件夹。tests/scripts/lo-unregistered.sh以 NSWorkspace 核对 docx、xlsx、pptx、odt、csv 的“打开方式”里没有 LibreOffice。 - 2026-10-09(用户报:ElyTable 点格跳到别处、拖不出选区):Calc 在
sc_print_twips_msgs下以打印 twips(行高原值)报光标、选区与.uno:SheetGeometryData,图块与鼠标却在图块 twips(每行按ScViewData::ToPixel截成整像素),两者随行号漂移(5000 行处约 140 行)。视图对外一律打印 twips;LokView::set_geometry收下工作表几何后,sheet::Axis::tile、print照内核的截断换算(每像素 twips 取图块的像素与 twips 之比),绘制、显示、跳转与冻结都先换成图块 twips,鼠标以图块 twips 发出(tile_point),拖动句柄仍为打印 twips;.uno:读回与应用(表头、填充柄)照旧用打印 twips,表头的起点取LokView::scrolled_start。内核读鼠标用的比例原取最后一块图块,worker 现于每次setClientVisibleArea前以setClientZoom告知图块比例,点击不再依赖先画过图块。 - 2026-10-09(用户报:ElyPDF 改字报“这种字体画不出 V”):文档自带的子集字体常无可用的 Unicode 表(LibreOffice 导出的子集表为占位,0x00 起逐一对应字形号),按 Unicode 找字形一律失败。现以“文档自带字体”写字时,从该页字体资源里找到同一字体(
pdf_load_font按对象缓存,与文字层的字体同一对象),按文字提取的读法(ToUnicode 或 cid_to_ucs、pdf_font_cid_to_gid)倒出 Unicode 到字形号的表,写字只用页面上已有的字形;新字体的/ToUnicode以这张表重写(MuPDF 按子集的占位表生成的那份会把新字读成乱码)。页面上没有的字仍报缺字。