Skip to content

fix(ui): 内联图片/媒体与链接点击的 minis:// 解析统一 - #6

Merged
tall-1997 merged 1 commit into
mainfrom
fix/minis-media-resolution-unify
Oct 13, 2026
Merged

tall-1997 merged 1 commit into
mainfrom
fix/minis-media-resolution-unify

Conversation

@tall-1997

Copy link
Copy Markdown
Owner

用户报告的问题

聊天界面渲染旧消息时,图片的预览缓存被清掉之后 minis:// 链接解析不出来,就会退化成“去浏览对话文件查看”;长消息有时候也会被渲染器折叠或截断……重启 app 或重新进入会话后恢复。

逐条核实结论(证据在源码)

报告说法 核实结果
数据层完好(消息/文件都在) ✅ 属实,无 bug
“预览缓存被清导致 minis:// 解析不出来” ❌ 代码里不存在该机制——没有任何缓存与链接解析绑定
“Settings → Storage 缓存清理太激进” ❌ 不存在自动清理;只有带确认框的显式会话删除
“长消息被折叠/截断” ⚠️ 属实但是设计行为:LargeContentGuard 对 >32K 字符折叠,带预览+展开+导出按钮,非数据丢失
“重启/重进后恢复” ✅ 症状与真实根因吻合(见下)

真实根因(3 个,全部修复)

[T-minis-decode-unify]

  1. URLDecoder 的 +→空格:媒体路径全部用 java.net.URLDecoder(form-urlencoded 语义),文件名里的 + 被解码成空格 → 永远找不到文件。链接点击路径(ChatLinkResolver)早已修为手写 %XX 解码,媒体路径一直没跟上。
  2. 无双重编码容错:链接解析有第二个解码候选(T-android-minis-url-double-encoding),媒体路径没有 → 双重编码的 URL 图片挂掉而点击同一链接正常。
  3. MarkdownText 走全局解析器(子代理栏、技能页):全局 bindMounts 是 last-writer-wins——多会话设备上旧消息的图片指向“最近启动 shell 的会话”,重进会话后重新绑定 → 正是“重启/重进后恢复”的机制。

顺带修复:

  • MarkdownText 图片块补 limitDisplaySize()(流式路径已修的 215MB 位图崩溃同类隐患)
  • 图片加载失败显示 T148 占位符而非无声空白
  • 显示文件名的 +→空格 bug

验证

  • 13 个新单测钉死统一行为(候选解码+磁盘存在性、+ 保留、双重编码恢复、basename 回退不跨会话、全局桶探测)
  • 逻辑已用等价模拟脚本全量验证
  • CI:编译 + 全量单测 + l10n 门禁

[T-minis-decode-unify] Field report: old messages' inline images
rendered broken while tapping the same URL worked, recovering after
an app restart or re-entering the session. The report's theory (a
cleared 'preview cache' breaking minis:// resolution) does not exist
in code — three real defects did:

1. URLDecoder everywhere in the media path. It implements
   application/x-www-form-urlencoded, where '+' means SPACE — a real
   filename character — so 'foo+bar.png' never resolved. ChatLinkResolver
   had already replaced it for link TAPS with a hand-rolled %XX decode
   plus a second, double-decode candidate (T-android-minis-url-double-
   encoding); the media path never got that fix, so a double-encoded URL
   broke the image while the tap on the same URL worked.

2. MarkdownText (sub-agent bar, skills screen) resolved minis:// against
   the GLOBAL bindMounts map — last-writer-wins across sessions — so an
   old message's image pointed at whichever session booted its shell
   last, and re-entering the chat re-pointed it (the 'restart fixes it'
   symptom). Its image/video/audio blocks now share the session-aware
   resolveMdMediaFile with the streaming renderer via
   LocalMarkdownSessionId.

3. MarkdownText.MinisImageBlock had no decode-size cap, so a very tall
   PNG decoded at intrinsic size — the 215 MB bitmap / RecordingCanvas
   crash class already fixed on the streaming path
   (T-android-canvas-large-bitmap-crash) — and a broken file rendered as
   a silent blank gap instead of the T148 placeholder.

MinisImageFetcher (Coil's last-line fallback for raw URLs) gets the
candidate decode too, and stays on the global resolver deliberately:
callers that know the session resolve to a File model before Coil sees
a URL, so a raw URL reaching the fetcher means no session context
existed.

Also fixes the '+' → space bug in the DISPLAYED captions
(filenameFromMdUrl / filenameFromUrl).

Verified: 13 new unit tests pin the unified behavior (candidate walk
with disk existence, '+' preserved, double-encoded recovered, basename
fallback stays inside the owning session, global bucket probe); logic
pre-validated with an equivalent simulation.
@tall-1997
tall-1997 force-pushed the fix/minis-media-resolution-unify branch from 56c3176 to c56c6af Compare October 13, 2026 11:02
@tall-1997
tall-1997 merged commit c74a3d5 into main Oct 13, 2026
1 check passed
@tall-1997
tall-1997 deleted the fix/minis-media-resolution-unify branch October 13, 2026 11:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant