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>
This commit is contained in:
1 parent
4a2dcbb222
commit
c5cb153f72
13 files changed
+101
-30
No files matched your search
@@ -225,7 +225,7 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely
|
||||
- 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,核对页面跳不出Line truncated
|
||||
- 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` 交给应用;页面的警告与错误只记行号(警告文字与来源都可由文档伪造,不进日志);页面自己的诊断经 `elyQuery` 交应用记录。载入失败(主框架,停止不算)、渲染进程退出、CEF 起不来都报 `Event::Failed`,页面区改显示组件库的错误视图与“重新载入”(浏览器在即重载,否则重建;CEF 起不来时只说明),载入成功才报 `Event::Loaded`。浏览器关闭时断开它与表面的互相持有,页面的内存随之释放(释放时写日志)。真窗口(macOS,开发构建与 `dist/ElyMark.app`):页面以应用深色主题画出,点选、键入中英文、回车、退格、方向键、拷贝粘贴、`elyQuery` 往返;`tests/scripts/webviewLine truncated
|
||||
- 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(其上加本项目一个提交:树与树行的无障碍角色)。升级后全工作区编译无改动。
|
||||
|
||||
@@ -5,7 +5,9 @@ use std::path::{Path, PathBuf};
|
||||
use anyhow::{Result, bail};
|
||||
use ely_web_content::WebView;
|
||||
use gpui::App;
|
||||
use serde_json::json;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::view::Heading;
|
||||
|
||||
/// The app's Resources, else this build's `engines/content`.
|
||||
pub fn content() -> Result<PathBuf> {
|
||||
@@ -95,3 +97,18 @@ pub fn folder(view: &WebView, folder: &Path) {
|
||||
pub fn call(view: &WebView, name: &str, argument: &serde_json::Value) {
|
||||
view.run_js(&format!("ely.{name}({argument})"));
|
||||
}
|
||||
|
||||
/// Headings from the page; anything malformed is None.
|
||||
pub fn headings(items: &Value) -> Option<Vec<Heading>> {
|
||||
let heading = |item: &Value| {
|
||||
let level = item["level"]
|
||||
.as_u64()
|
||||
.filter(|level| (1..=6).contains(level))?;
|
||||
let title = item["title"].as_str()?.to_owned();
|
||||
Some(Heading {
|
||||
level: level as usize,
|
||||
title,
|
||||
})
|
||||
};
|
||||
items.as_array()?.iter().map(heading).collect()
|
||||
}
|
||||
@@ -353,7 +353,11 @@ impl MarkView {
|
||||
log::error!("mark: the page built no editor ({what})");
|
||||
cx.emit(Failed(ely_ui::i18n::text(cx, "mark.page.broke", &[("what", what)]).to_string()));
|
||||
}
|
||||
(Some("outline"), ..) => match headings(&said["items"]) {
|
||||
(Some("missed"), ..) => match said["line"].as_u64() {
|
||||
Some(line) => log::warn!("mark: nothing to reveal at line {line}"),
|
||||
None => log::warn!("mark: no such heading to reveal"),
|
||||
},
|
||||
(Some("outline"), ..) => match crate::page::headings(&said["items"]) {
|
||||
Some(outline) => {
|
||||
self.outline = outline;
|
||||
cx.notify();
|
||||
@@ -466,21 +470,6 @@ fn found(said: &Value) -> Option<crate::finding::Found> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Headings from the page; anything malformed is None.
|
||||
fn headings(items: &Value) -> Option<Vec<Heading>> {
|
||||
let heading = |item: &Value| {
|
||||
let level = item["level"]
|
||||
.as_u64()
|
||||
.filter(|level| (1..=6).contains(level))?;
|
||||
let title = item["title"].as_str()?.to_owned();
|
||||
Some(Heading {
|
||||
level: level as usize,
|
||||
title,
|
||||
})
|
||||
};
|
||||
items.as_array()?.iter().map(heading).collect()
|
||||
}
|
||||
|
||||
impl Render for MarkView {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
// Dropped files: images go beside the document.
|
||||
|
||||
@@ -89,7 +89,6 @@ async function build(text, mode) {
|
||||
made = await visual(place, body, mode === 'visual', texts, edited, edge, linked)
|
||||
} catch (error) {
|
||||
// A broken editor falls back to source and says so.
|
||||
console.error(`ely: the visual editor failed: ${error.name}`)
|
||||
entry.broke = error.name
|
||||
}
|
||||
if (made) {
|
||||
@@ -262,8 +261,8 @@ function reveal(place) {
|
||||
}
|
||||
const shown =
|
||||
'heading' in place ? current.heading(place.heading) : current.line(place.line, place.query)
|
||||
// Logs keep no search text; a line number at most.
|
||||
if (shown === false) console.warn('heading' in place ? 'ely: no such heading' : `ely: nothing at line ${place.line}`)
|
||||
// The app logs a line number, never the query.
|
||||
if (shown === false) tell({ kind: 'missed', generation, line: place.line ?? null })
|
||||
}
|
||||
|
||||
/** The app's find box hears where the search stands. */
|
||||
|
||||
@@ -27,9 +27,8 @@ async function draw(tree) {
|
||||
try {
|
||||
const svg = await paint(node.value, 'paper')
|
||||
Object.assign(node, { type: 'html', value: `<figure class="diagram">${svg}</figure>` })
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// Lines only: Mermaid's message quotes the document.
|
||||
console.warn(`ely: a diagram did not draw: ${error.name}`)
|
||||
failed.push(node.position.start.line)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,15 +264,13 @@ wrap_display_handler! {
|
||||
&self,
|
||||
_browser: Option<&mut Browser>,
|
||||
level: LogSeverity,
|
||||
message: Option<&CefString>,
|
||||
source: Option<&CefString>,
|
||||
_message: Option<&CefString>,
|
||||
_source: Option<&CefString>,
|
||||
line: ::std::os::raw::c_int,
|
||||
) -> ::std::os::raw::c_int {
|
||||
// A page's warnings and errors, never its other chatter.
|
||||
// Words and sources a document may forge stay out.
|
||||
if level.get_raw() >= LogSeverity::WARNING.get_raw() {
|
||||
let said = message.map(CefString::to_string).unwrap_or_default();
|
||||
let at = source.map(CefString::to_string).unwrap_or_default();
|
||||
log::warn!("web: page says {said} at {at}:{line}");
|
||||
log::warn!("web: the page warned at line {line}");
|
||||
}
|
||||
0
|
||||
}
|
||||
@@ -449,3 +447,47 @@ fn keys() -> Option<KeyboardHandler> {
|
||||
fn keys() -> Option<KeyboardHandler> {
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
use cef::{CefString, ImplDisplayHandler, LogSeverity};
|
||||
|
||||
struct Kept;
|
||||
|
||||
fn kept() -> &'static Mutex<Vec<String>> {
|
||||
static KEPT: OnceLock<Mutex<Vec<String>>> = OnceLock::new();
|
||||
KEPT.get_or_init(Mutex::default)
|
||||
}
|
||||
|
||||
impl log::Log for Kept {
|
||||
fn enabled(&self, _: &log::Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record) {
|
||||
kept().lock().expect("the kept lock").push(record.args().to_string());
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pages_words_and_sources_never_reach_the_log() {
|
||||
log::set_boxed_logger(Box::new(Kept)).expect("the only logger");
|
||||
log::set_max_level(log::LevelFilter::Trace);
|
||||
let surface = crate::surface::Surface::new(std::env::temp_dir(), 1.0);
|
||||
let display = super::Display::new(Arc::new(Mutex::new(surface)));
|
||||
let said = CefString::from("ely: M11_SECRET");
|
||||
let at = CefString::from("file:///content/M11_SECRET.js");
|
||||
for level in [LogSeverity::WARNING, LogSeverity::ERROR, LogSeverity::INFO] {
|
||||
display.on_console_message(None, level, Some(&said), Some(&at), 7);
|
||||
}
|
||||
let records = kept().lock().expect("the kept lock").clone();
|
||||
let warned = records.iter().filter(|said| *said == "web: the page warned at line 7");
|
||||
assert_eq!(warned.count(), 2, "warnings and errors stay seen: {records:#?}");
|
||||
assert!(!records.iter().any(|said| said.contains("M11_SECRET")), "{records:#?}");
|
||||
}
|
||||
}
|
||||
@@ -428,6 +428,13 @@
|
||||
- 位置:`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 打开“文件历史”或“逐行追溯”后,中间区不再响应任何别的选择
|
||||
|
||||
+2
-1
@@ -71,10 +71,11 @@
|
||||
- [x] M-08 页面重载后悬着的渲染回调永不完成,导出既不成功也不报错 — 页面失败后挂起与新到的渲染都以错误结束,载入成功才恢复(审查 2 轮)
|
||||
- [x] M-09 导出的保存面板出错被当作取消 — 面板出错走 panel_failed,取消只记日志(审查 1 轮)
|
||||
- [x] M-10 关闭查找框只清当前标签的高亮 — 关闭查找框对每个标签的页面清除高亮(审查 1 轮)
|
||||
- [ ] M-11 页面把搜索词写进应用日志
|
||||
- [x] M-11 页面把搜索词写进应用日志 — 控制台的文字与来源都可由文档伪造,只记行号;页面自己的诊断经 elyQuery;R1 前缀可伪造、R2 data 来源、R3 sourceURL、R4 编码路径与目录、R5 缺回归测试:已补并经变异核对(五轮上限,未再审)
|
||||
- [ ] M-12 浏览器创建完成前 `WebView` 已释放,浏览器泄漏
|
||||
- [ ] M-13 编辑器构建失败时页面空白且不上报
|
||||
- [ ] M-14 `render_pdf` 在界面线程内嵌图片并写文件
|
||||
- [ ] M-15 打印与导出 PDF 的暂存页执行文档里的脚本
|
||||
- [ ] G-01 打开“文件历史”或“逐行追溯”后,中间区不再响应任何别的选择
|
||||
- [ ] G-02 进行中操作读取失败被吞掉;`git am` 冲突无提示条无错误
|
||||
- [ ] G-03 新建的空仓库在“仓库”页显示两段 Git 致命错误(引用日志、LFS)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
FAIL
|
||||
|
||||
- crates/ely-web-content/src/handlers.rs:455 — The `ely: ` prefix does not establish that a message came from application code. Open Markdown containing `$\errmessage{ely: M11_PRIVATE_DOCUMENT_SENTINEL}$` in visual or preview mode: the bundled KaTeX executes `console.error` with the formula's argument verbatim, and `console_line` preserves it as `web: page says ely: M11_PRIVATE_DOCUMENT_SENTINEL ...`. This writes document contents to the application log, violating PRD §18.2 and agents.md rule 4. Reproduced with the freshly built ElyMark page; probes using the unchanged Rust helper fail on both Windows and WSL while the existing unit test passes. Evidence: `.work/m11-r1/page-console.log`, `.work/m11-r1/privacy-probe.log`, `.work/m11-r1/linux-privacy-probe.log`. Fix direction: do not trust a text prefix to authorize arbitrary console contents; withhold third-party messages and preserve application diagnostics through controlled messages that cannot contain document text. Add a regression covering KaTeX's `\errmessage` with this prefix, and update agents.md's claim accordingly.
|
||||
@@ -0,0 +1,3 @@
|
||||
FAIL
|
||||
|
||||
- crates/ely-web-content/src/handlers.rs:274 — Console `source` can contain document text, so withholding `_message` does not satisfy PRD §18.2 or agents.md rule 4. Open Markdown containing `<div><script src="data:text/javascript,console.warn('M11_PRIVATE_DOCUMENT_SENTINEL')"></script></div>` and print or export PDF: the real page's HTML renderer preserves this script, the staged page executes it (`export.rs`'s `render_pdf`/`print_page`; the resource fence permits `data:`), and CEF supplies the entire data URL as `source`. The unchanged display handler logs `web: the page warned at data:text/javascript,console.warn('M11_PRIVATE_DOCUMENT_SENTINEL'):1`. Reproduced with the freshly built page and native CEF on both Windows and WSL; the assertion excluding the document marker fails on both. The previous KaTeX prefix leak is fixed, but this source-field leak remains untested by the repository tests. Evidence: `.work/m11-r2/windows-cef-source.log`, `.work/m11-r2/linux-cef-source.log`, and `.work/m11-r2/evidence.log`. Fix direction: omit arbitrary console source strings or reduce them to validated, trusted diagnostic identifiers; add a regression that asserts document text cannot reach logs through either console message or source fields.
|
||||
@@ -0,0 +1,3 @@
|
||||
FAIL
|
||||
|
||||
- crates/ely-web-content/src/handlers.rs:453 — `warned()` trusts every `file://` console source as a bundled script and logs its final URL segment, including query/fragment text. JavaScript's `sourceURL` can supply this metadata without loading that file. Extend round 2's data-script Markdown fixture with `console.warn('hidden message');\n//# sourceURL=file:///not-bundled/M11_PRIVATE_SOURCE_SENTINEL.js`: the actual page renderer retains it, and the staged print page's warning becomes `web: the page warned at M11_PRIVATE_SOURCE_SENTINEL.js:1`. Native CEF probes using the unchanged production display handler and resource fence reproduce this on Windows and WSL; both privacy assertions fail. A source ending in `main.js?M11_PRIVATE_QUERY_SENTINEL` also leaks its query. This still violates PRD §18.2 and agents.md rule 4. The previous plain `data:` source and KaTeX message cases are fixed; script execution itself is outside this finding. Fix direction: log only verified bundled script identifiers, exclude URL queries/fragments, and use the fixed inline-script diagnostic for all other sources. Add regressions for forged `file://` sources and URL suffixes; the current two-case test passes despite this leak. Evidence: `.work/m11-r3/windows-cef-export.log`, `.work/m11-r3/linux-cef-export.log`, both `*-privacy-assert.log`, and `evidence.log`.
|
||||
@@ -0,0 +1,5 @@
|
||||
FAIL
|
||||
|
||||
- crates/ely-web-content/src/surface.rs:82 — The logged name still comes from the untrusted URL rather than the verified file. On Windows, with a real `main.js` under the page root, execute `console.warn('hidden');` with `//# sourceURL=file:///.../content/M11_PRIVATE_DOCUMENT_SENTINEL%2F..%2Fmain.js`. Windows canonicalization removes the decoded nonexistent intermediate directory followed by `..`, so `holds()` accepts the real `main.js`; `script()` returns `M11_PRIVATE_DOCUMENT_SENTINEL%2F..%2Fmain.js`. Native CEF preserves that source metadata, and the unchanged display handler logs the document marker verbatim. This violates PRD §18.2 and rule 4; it is a leak introduced by the new name extraction, independent of the pre-existing W-02 test failures. Round 3's missing-file, query, fragment and data-source cases are fixed, but the new repository test passes despite this encoded-path leak. Fix direction: derive the diagnostic name from the canonical file verified by the fence, and add a regression excluding document text carried by encoded path components. Evidence: `.work/m11-r4/windows-probe.log`, `windows-native.log`, and `evidence.log`.
|
||||
|
||||
- crates/ely-web-content/src/surface.rs:81 — `holds()` verifies canonical containment but does not require a file, so `script()` also trusts directories ending in `.js`. Create `content/M11_DIRECTORY_SENTINEL.js` as a directory and use its file URL as a console `sourceURL`: both Windows and WSL native CEF probes log `web: the page warned at M11_DIRECTORY_SENTINEL.js:1`, although no such script file exists. This breaks the required real-file restriction and agents.md's updated claim. The added regression omits directories and therefore passes. Fix direction: require the canonical target to be a regular file under the same root before returning its script name; test a `.js` directory explicitly. Evidence: `.work/m11-r4/windows-probe.log`, `linux-probe.log`, `windows-native.log`, and `linux-native.log`. Probe copies and their dedicated targets were deleted before this verdict.
|
||||
@@ -0,0 +1,3 @@
|
||||
FAIL
|
||||
|
||||
- crates/ely-web-content/src/handlers.rs:273 — The new console privacy boundary has no repository regression test; the previous `Surface::script` test was removed without replacement. In a review copy, restoring logging of `_message` and `_source` leaves all 32 ElyMark/Web-content Cargo tests passing on WSL, although a warning carrying document text now writes it verbatim to logs, violating PRD §18.2 and rule 4. A probe capturing this actual callback's log records passes with the current implementation and fails with that mutation on both Windows and WSL. Retain such a regression in the repository: private markers in warning/error messages and arbitrary sources must never reach logs, while the line diagnostic remains observable. Evidence: `.work/m11-r5/linux-mutant-repository.log`, `windows-mutant-probe.log`, `linux-mutant-probe.log`, and `evidence.log`. Both round 4 leaks are fixed, confirmed by native CEF probes on both platforms. Probe copies and their dedicated targets were deleted before this verdict.
|
||||
Reference in new issue
Block a user