Skip to content

feat(desktop): Markdown 预览改用 web view,新增 SVG 与 PDF 预览,图片查看器对齐旧 macOS 标准 - #1903

Merged
xiaoyumuxi merged 3 commits into
previewfrom
feat/desktop-webview-previews
Nov 14, 2026
Merged

xiaoyumuxi merged 3 commits into
previewfrom
feat/desktop-webview-previews

Conversation

@xiaoyumuxi

@xiaoyumuxi xiaoyumuxi commented Nov 14, 2026 •

Copy link
Copy Markdown
Collaborator

关联 Issue / Linked issue

PR 类型 / Type

  • feat:新功能或用户可见的改进
  • fix:缺陷修复
  • docs:文档、Agent Note、Skill 等规则文件
  • release:稳定版发布准备

改动说明 / What changes

背景 / Why

Viva 原生的 Markdown 组件只排版 Markdown 本身。README 里常见的内嵌 HTML(徽章、align、details)、内嵌 <svg>、数学公式、Mermaid 和代码高亮都显示不出来,旧 macOS 版本用 WKWebView 能显示这些,迁移到 Viva 后成了功能倒退。图片查看器只能解码 PNG、JPEG、GIF;SVG 只能当文本看;PDF 只能交给外部应用。图片查看的交互也和旧 macOS 查看器(MediaViewerView.swift)不一致。

Viva 0.2.2 起提供 web view,本 PR 让编辑区的这几类预览在有系统 web 引擎时(macOS 的 WKWebView、Windows 的 WebView2)显示在内嵌 web view 里。页面能读什么文件由宿主和 Core 决定,页面不联网、不写发行目录。Linux 没有 web 引擎,继续用原生渲染。

改动 / Changes

web view 预览基础设施(新包 desktop/internal/webpreview)

  • 页面(markdown.html/js/css、image.html/js/css)以及 KaTeX 0.16.0(含字体和 mhchem)、Highlight.js 11.5.1、Mermaid 11.14.0、D3 6.7.0、markmap 0.14 用 go:embed 编进二进制,经应用自己的 lithe-preview 协议提供,不访问 CDN。这些文件取自旧 macOS 客户端已有的 vendor 目录和对应的 npm 包,版本、来源和许可证见 assets/THIRD_PARTY_NOTICES.md。文档用到哪个库才加载哪个库。
  • 每个页面一个协议服务器,只回答干净路径和内嵌文件。页面要加载的图片走 res/<base64url(src)>:宿主先问 Core 这个 src 指向哪里、能不能加载,再读文件回答;http/https 图片由宿主经应用代理下载,放进所有页面共用的内存 LRU(最多 64 张、48 MiB),失败的下载下次重试。
  • 页面里的跳转都被拦下交回宿主;页面不能开新窗口、不能下载文件。回答用户文件时带 Cache-Control: no-store。
  • 页面生命周期(workbench/preview_pages.go):标签打开期间页面一直存在;这一帧没有声明的页面隐藏起来,保留滚动位置和排版。隐藏页面最多保留 6 个,超出时关闭隐藏最久的那个。关闭项目时关闭全部页面。窗口的 web host 还没就绪、系统没有 web 引擎、或页面打开失败时,回落到原生预览。

Markdown 预览(markdown_web.go、markdown_preview.go、markdown_images.go)

  • 有 web 引擎时,显示 Core markdown.render 过滤后的 HTML:内嵌 HTML、内嵌 SVG、数学、Mermaid、markmap 和代码高亮都能显示。渲染在后台进行,同一时刻只有一次,渲染期间的多次编辑合并成一次;渲染失败时保留上一版页面。
  • 源码和页面按源码行双向同步滚动。
  • 链接和图片都按 Core 新增的 markdown.resolveReference 解析:点工作区内的文件就在编辑器打开,点 #标题 就跳到那个标题,外部地址交给系统浏览器;图片只加载 Core 判定在文档范围内、确实是图片的文件。原生页面也改用同一个解析。Go 侧原来的 markdownImageSource、markdownLinkTargetOf 和 readBounded 已删除。
  • “目录”按钮从页面上移到标签栏,放在源码/两者/仅预览切换之前(web 页上无法叠加原生控件),web 页和原生页共用。

SVG 预览(svg_preview.go)

  • 有 web 引擎时,.svg 文件和 Markdown 一样分源码、图片两栏打开,标签栏末尾可切换源码 / 两者 / 仅图片。
  • 编辑后最多每 150 毫秒把文本交给页面一次,地址带编辑版本号,避免引擎显示缓存的旧图;更新时保留缩放和滚动位置。新内容画不出来时保留上一张图,从来没画成功过才显示失败提示。超过 4 MiB 不绘制。

PDF 预览(viewer.go、viewer_web.go)

  • .pdf 不再只是“用外部应用打开”,而是在标签里用 web 引擎自带的 PDF 查看器显示,上限 256 MiB。没有 web 引擎时仍提示用外部应用打开。

图片查看器对齐旧 macOS 标准(viewer_image.go、assets/image.js、viewer_view.go)

  • 34 点工具栏,显示文件名、“宽 × 高 · 大小”和 −/百分比/+。
  • 打开时四周留 24 点边距适配视口,但从不放大小图;缩放范围从“适配比例与 25% 中较小者”到 400%,每步 25%;点百分比回到 100%。
  • 背景为 16 点棋盘格,颜色在编辑器底色上叠加 7% 黑或白。
  • 新增 WebP、BMP、TIFF、HEIC、HEIF(由 web 引擎解码),这些扩展名从“外部应用打开”列表移除。图片上限 64 MiB,文件在后台读取。
  • Linux 没有 web 引擎,由 Go 画布执行同一套规则(仍只解码 PNG、JPEG、GIF)。视图尺寸变化时立即重算工具栏上的缩放比例,修复首帧显示“1%”的问题。

其他

不在本 PR 范围内 / Out of scope

  • PlantUML 仍不显示(markdown-rendering 矩阵已注明)。
  • Linux 仍只有原生 Markdown(不显示 HTML 和富内容),SVG 只能看源码,PDF 交给外部应用。
  • 音频、视频、ICO、ICNS、PSD 仍由外部应用打开。

类型专属:feat

多平台对齐

  • 两端都具备:macOS 和 Windows 共用同一套 Viva 代码(WKWebView / WebView2),本 PR 同时实现。Windows 上 WebView2 能否解码 TIFF、HEIC,以及自定义协议下的 PDF 查看器是否可用,还没有真机验证,矩阵中 Windows 的验证状态保持 pending;解码失败时会落到“无法显示”和“用外部应用打开”。
  • 只实现了 macOS:已为 Windows 提交跟进 Issue:#
  • 只实现了 Windows:已为 macOS 提交跟进 Issue:#
  • 刻意只属于某个平台(功能矩阵中另一端记为 platform-specific),理由:

范围检查 / Scope checks

功能矩阵 / Feature matrix

  • 已更新 shared/platform-feature-matrix/features/<id>.json:embedded-viewers(两端 partial / pending,补证据和 Viva 验证步骤,并改掉了原来“二进制文件被拒绝”的过时说明)、markdown-rendering、markdown-images-links、markdown-preview-outline(目录按钮位置);未在真机验证的保持 pending
  • 已运行 ./scripts/platform-feature-matrix/verify-platform-feature-matrix.sh(224 条通过)和 ./scripts/platform-feature-matrix/verify-platform-feature-matrix-change.sh origin/preview HEAD(通过)
  • 没有提交 .artifacts/ 下生成的矩阵视图
  • [N/A] 没有更新矩阵的理由:已更新

统一日志系统 / Structured logging

  • 没有新增事件,复用桌面端已有的 app.notification.operation_failed(a.emit(operationFailedLevel, operationFailedEvent, operationFailedFields(...))),操作名为 web_preview_<阶段>、web_preview_open、markdown_render;没有 print、直接写 stderr 或手写事件 ID
  • 每个失败只在处理它的边界记录一次;编辑、滚动、缩放等高频路径不记日志
  • 不记录 Markdown 源码、HTML、文件内容或页面消息,只记操作名和错误
  • [N/A] scripts/logging/generate-log-events.mjs 和 scripts/logging/verify-log-usage.mjs 在当前仓库中已不存在(shared/logging/ 也已移除),无法运行

界面文案 / i18n

  • 新文案都在 shared/i18n/ 补齐中英词条,运行 node scripts/i18n/generate-i18n.mjs 并提交生成结果,没有手改生成文件;页面里的文案也由宿主经 i18n 取出后传给页面
  • 已运行 node scripts/i18n/verify-app-language-localization.mjs(通过)和 node scripts/i18n/generate-i18n.mjs --check(合并 preview 后 en-US、zh-CN 均 11125/11125),没有放宽脚本或扩大豁免名单

逻辑归属 / Logic placement

性能与内存 / Performance and memory

  • web view 在预览标签第一次显示时才创建;没有空闲轮询;隐藏页面最多 6 个,远程图片 LRU 最多 64 张、48 MiB;SVG 最多每 150 毫秒更新一次、超过 4 MiB 不绘制;图片 64 MiB、PDF 256 MiB 上限;关闭项目时关闭所有页面
  • 文件读取、Core 调用和下载都不在窗口线程。Markdown 每次渲染向页面发送整篇 HTML,但同一时刻只有一次渲染,渲染期间的编辑合并为一次,超过 1 MiB(markdownPreviewMaxBytes,沿用原值)不渲染;SVG 和图片只传当前标签的一个文件
  • 没有附改动前后的性能基线:本 PR 没有改动编辑器模块(desktop/internal/editor/**、editorview/**),没有运行 editor-bench.sh;大 Markdown 文件预览的帧率([Bug] Windows 打开 3.76 MB / 6.8 万行 Markdown 文档切换到预览后,界面帧率跌到 1–3 fps 并持续数分钟卡死 #1380 场景)未测量。二进制增加约 4.3 MiB 内嵌资源(主要是 Mermaid、KaTeX 字体和 Highlight.js),只在页面需要时读取,不影响启动;每个打开的页面占一份 web 引擎内存,由 6 个隐藏页面的上限约束

设置 / Settings

N/A:没有新增或修改设置项。

插件契约 / Plugin contract

N/A:没有改插件契约、能力版本、插件函数协议或宿主方法。markdown.resolveReference 是桌面端调用的 Core 方法,不属于插件契约;Core 固定版本已升到包含它的 2c1fed1。

原计划编号 方法 用途
— — 无

发行包与资源 / Bundle and resources

  • 运行时不向 app bundle 或安装目录写入:页面从二进制读取;WebView2 配置目录由 Viva 放在用户缓存目录(%LOCALAPPDATA%\<应用>\WebView2),WKWebView 的数据在用户的 ~/Library;页面不用临时配置,避免 WebView2 每个临时页面留下一份配置目录。已运行 ./scripts/desktop/verify/verify-runtime-bundle-immutability.sh(通过)
  • [N/A] 资源复用清单:内嵌资源是提交进仓库的源文件,构建时不下载、不解压、不生成,不需要登记到 worktree-resources.json

无用代码与重构 / Dead code and refactoring

  • 本次改动导致无用的代码已删除:Go 侧的 markdownImageSource、markdownLinkTargetOf、workbench 的 readBounded、只服务于旧解码路径的字段;.pdf、.webp、.heic、.tif、.tiff、.bmp 移出外部应用列表
  • [N/A] 本次没有发现之前就存在的无用代码或可重构之处

验证 / Verification

命令或操作 平台 结果
scripts/desktop/desktop-go.sh test -race -count=1 ./...(Core 0de67c1,即 #37 合入前的分支提交) macOS arm64 55 个包通过,没有失败
scripts/desktop/desktop-go.sh test -race -count=1 ./...(Core 2c1fed1,当前固定版本,合并 #1902 之前) macOS arm64 55 个包通过,没有失败
scripts/desktop/desktop-go.sh test -race -count=1 ./...(Core 2c1fed1,合并最新 preview 之后) macOS arm64 54 个包通过;settingswindow 的 TestToolchainDraftSavesOnlyExplicitChanges 报数据竞争(#1799 起就有的测试替身问题,见“其他”最后第二条),已在 b508356 修复
scripts/desktop/desktop-go.sh test -race -count=5 ./internal/settingswindow/(修复之后);修复前同一测试 -race -count=20 未能复现,属于偶发 macOS arm64 通过
.agents/skills/write-stable-tests/scripts/verify-test-stability.sh --base origin/preview macOS arm64 通过
gofmt -l .、go vet ./...(desktop) macOS arm64 通过
node --check 检查 markdown.js、image.js macOS arm64 通过
node scripts/i18n/verify-app-language-localization.mjs、node scripts/i18n/generate-i18n.mjs --check macOS arm64 通过,11125/11125(合并 preview 后)
./scripts/platform-feature-matrix/verify-platform-feature-matrix.sh、verify-platform-feature-matrix-change.sh origin/preview HEAD macOS arm64 224 条通过;变更检查通过
./scripts/agent-notes/verify-agent-notes.sh macOS arm64 222 条通过
./scripts/desktop/verify/verify-runtime-bundle-immutability.sh macOS arm64 通过(macOS 与 Windows 源码静态检查)
node scripts/desktop/validate-pins.mjs macOS arm64 Core 2c1fed1 可达,Viva 固定版本可解析
Core:cargo test markdown 12 项、dispatcher project 72 项,cargo fmt --check,cargo clippy(在 Lithe-core-rust#37) macOS arm64 通过

主要的新测试:webpreview_test.go(只回答干净路径和内嵌文件、跳转拦截、图片只按 Core 解析结果提供、读文件不跟随链接并限制大小、远程图片共享下载与失败重试)、markdown_web_test.go(随编辑渲染并与源码同步滚动、链接与图片按 Core 解析、隐藏页面上限、打不开时回落原生)、viewer_test.go(图片页与工具栏、PDF 查看器、超过上限的提示、适配留边距且不放大、围绕指针缩放与平移、棋盘格、没有 web 引擎时的 Go 画布)、svg_preview_test.go(源码旁显示图片并跟随编辑、从未画成功时显示失败、没有 web 引擎时按文本打开)。

未能执行的检查和原因:

  • macOS 整机验收没有做:Go 测试用的是模拟的 web view,没有在真实 WKWebView 里打开 README、SVG、PDF 和各种图片格式看实际效果,也没有明暗主题、中英文截图。评审前需要人工打开应用检查。
  • Windows(WebView2)没有真机验证:TIFF、HEIC 解码,自定义协议下的 PDF 查看器,WebView2 配置目录位置都待验证。
  • Linux 回落路径只由 headless 测试覆盖。

提交前确认 / Final checks

  • 分支从 preview(445346b6b)创建,已合并最新 preview(37eb69423,feat(desktop): 构建选项新增忽略路径,默认跳过所有以 . 开头的文件夹 #1902);目标分支为 preview
  • 以 Draft 提交,已标记为 ready;macOS 整机验收和 Windows 真机验证仍待人工完成(见“未能执行的检查和原因”)
  • 没有无关改动,没有提交 .build/、target/、dist/、.artifacts/ 等产物,没有凭据、个人路径和未脱敏的日志或截图
  • 本次没有启动 Lithe 应用,没有进程残留

- 新增 internal/webpreview:页面与 KaTeX、mhchem、Highlight.js、Mermaid、markmap 内嵌进二进制,
  经 lithe-preview 协议提供;页面只能读宿主允许的内容,跳转交回宿主,不联网、不写发行目录
- Markdown 预览在有 web 引擎时用 Core markdown.render 的 HTML 显示内嵌 HTML、SVG、数学、图表与代码高亮,
  链接和图片按 Core markdown.resolveReference 解析,源码与页面按源码行双向同步滚动;Linux 保留原生渲染
- 目录按钮移到标签栏,web 页与原生页共用
- SVG 文件以源码与图片双栏打开,编辑后 150 毫秒内更新并保持缩放与滚动,画不出时保留上一张图
- PDF 用 web 引擎自带的查看器显示;没有 web 引擎时提示用外部应用打开
- 图片查看器按旧 macOS 规则重做:34 点工具栏、宽 × 高 · 大小、适配留 24 点边距且不放大、
  25%–400% 每步 25%、16 点棋盘格;新增 WebP、BMP、TIFF、HEIC、HEIF;Linux 用 Go 画布执行同一套规则
- 新增放大、缩小、图片失败图标与对应中英词条
- 悬浮提示里的链接也按 Core markdown.resolveReference 打开,删除 Go 侧的链接与图片路径解析
- Core 固定版本升到 Lithe-core-rust#37 的合入提交 2c1fed1(同时带上 #36)
- 更新功能矩阵与 Agent Note 2026-11-14-web-view-previews
@lithe-code-review

lithe-code-review Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Lithe Review

结论: ❓ 审查未完成

已由服务器取消。可以重新评论 @lithe review 召唤。

合入 #1902。build-desktop-core.sh 的 Core 固定版本保留 2c1fed1(Lithe-core-rust#37,已包含 #1902 依赖的 #35),注释保留 #1902 对 #35 的完整说明。
fakeToolchainLoader.Save 在 effect 协程里原地改写 snapshot.Tools 的 Pin,
而设置页绘制时读的 t.snapshot.Tools 与它共用同一个底层数组,-race 下偶发报错。
真实的 Save 会重新读取项目、不改传入的快照;测试替身改为先复制 Tools 再改。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: high ghfind author score; see https://ghfind.com

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant