Skip to content

Improve Archive Viewer navigation, search and interaction - #2

Merged
taoking merged 6 commits into
mainfrom
feature/viewer-experience-v0.3
Aug 25, 2026
Merged

taoking merged 6 commits into
mainfrom
feature/viewer-experience-v0.3

Conversation

@taoking

@taoking taoking commented Aug 25, 2026 •

Copy link
Copy Markdown
Owner

概览

本 PR 是 v0.3.0 的纯 Archive Viewer 体验迭代,不修改 Archive schema,也不新增微信消息协议。

  • 为 Archive 增加私有、可删除重建的 FTS5 派生搜索索引;中文子串优先使用 trigram,并提供 LIKE 兼容回退。
  • 搜索和日期跳转可定位到消息附近,并以稳定 source cursor 双向翻页。
  • 增加当前会话的月份日历导航、消息计数和快速月份切换。
  • 增加文本/媒体/会话右键操作、⌘F/⌘E/⌘J 快捷键、搜索结果高亮与分页。
  • 将引用回复呈现为安全的发送者与内容摘要;新导入和既有 Archive 都不会在普通 Viewer、搜索或会话导出中显示微信内部 XML 元数据。
  • 保持 Archive 只读和 source-independent:搜索、日期和媒体操作均只使用 WeChatArchive。
  • 补充 10,000 条合成消息游标测试、同秒排序、时区、搜索索引权限/失效/取消/隐私字段测试。

隐私

搜索索引仅保存在本机 Application Support,目录 0700、数据库 0600;不保存来源数据库、原始行、原始 SQLite 值或任何密钥。删除索引不会影响 Archive。

验证

  • swift build PASS
  • swift test 143 passed / 0 failed / 2 opt-in skipped
  • git diff --check PASS
  • GitHub Actions macOS build and test PASS

范围

本 PR 不包含新的微信消息类型、增量备份、云同步或 Archive schema 变更。

taoking and others added 6 commits August 25, 2026 22:19
…ad code)

- Quoted-message XML boundary now ends at the FIRST matching close tag
  after the opening tag, not the last match across the whole remaining
  text. A reply that happens to contain the same tag substring (e.g.
  "</msg>") was previously swallowed into the discarded XML portion,
  silently truncating real reply text.
- ArchiveMessageSearchService.indexURL is now a stateless computed
  property derived from archiveRoot/indexRoot instead of mutable shared
  state written by a background task and read from the MainActor,
  removing a data race in a class marked @unchecked Sendable. This also
  lets prepareIndex() actually reach its corrupt-index removal/rebuild
  path: the reuse check's error was previously left uncaught there
  (unlike the equivalent check in search()), so a corrupted cached index
  file failed identically on every future launch instead of self-healing.
- Sidebar conversation context menu's "打开" button was a no-op; wired to
  select the conversation like the row's normal tap behavior.
- jumpToMessage no longer performs a full O(n) messageOffset scan just to
  discard the result before calling messageWindow, which already performs
  the same existence check via a cheap indexed lookup.
- "复制文字、发送者和时间" is no longer offered for outgoing messages,
  matching the bubble's own sender-name display condition; it previously
  copied a "消息" placeholder instead of an actual name.
- Message search result avatars are now resolved once per result set and
  cached, instead of re-querying the database on every SwiftUI re-render.
- Deduplicated the two nearly-identical snippet-building implementations
  into ArchiveTextSnippetBuilder, shared by the viewer's LIKE search and
  the derived FTS/LIKE search service.

Adds regression coverage for the XML boundary truncation and for
prepareIndex() actually rebuilding (not just search() falling back) when
the cached index is corrupt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@taoking

taoking commented Aug 25, 2026

Copy link
Copy Markdown
Owner Author

Pushed fixes for all 8 review findings in fc0360a:

Correctness / concurrency (high priority):

  • Quoted-message XML boundary now ends at the first matching close tag, not the last — a reply containing the same tag substring (e.g. </msg>) was previously getting silently truncated.
  • ArchiveMessageSearchService.indexURL is now a stateless computed property instead of mutable state written from a background task and read from the MainActor — removes the data race, and as a side effect lets prepareIndex() actually reach its corrupt-index rebuild path (previously the reuse-check error escaped uncaught there, so a corrupted cached index failed identically on every launch instead of self-healing).

Correctness (medium):

  • Sidebar "打开" context-menu button was a no-op; now wired up.
  • jumpToMessage no longer does a redundant O(n) existence scan before messageWindow, which already checks existence via a cheap indexed lookup.
  • "复制文字、发送者和时间" no longer offered on outgoing messages (was copying a "消息" placeholder instead of a real name).

Perf / cleanup (low):

  • Search-result avatars now resolved once per result set instead of per render.
  • Deduplicated the two identical snippet() implementations into ArchiveTextSnippetBuilder.

Added regression tests for the XML truncation bug and for prepareIndex() actually rebuilding (not just search() falling back) on a corrupt index. Full suite: 143 tests passing, 2 skipped (opt-in real-archive tests).

@taoking
taoking marked this pull request as ready for review August 25, 2026 18:06
@taoking
taoking merged commit 6764330 into main Aug 25, 2026
2 checks passed
@taoking
taoking deleted the feature/viewer-experience-v0.3 branch August 25, 2026 18: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