Skip to content

Render edited messages in encrypted search results and stop URL queries failing the search#34004

Open
hayaksi1 wants to merge 1 commit into
element-hq:developfrom
hayaksi1:pr/search-resilience
Open

Render edited messages in encrypted search results and stop URL queries failing the search#34004
hayaksi1 wants to merge 1 commit into
element-hq:developfrom
hayaksi1:pr/search-resilience

Conversation

@hayaksi1

Copy link
Copy Markdown

Problem

Two correctness bugs in encrypted (Seshat) message search:

  • Edited messages don't render in results (Search result in encrypted rooms doesn't render edited messages (seshat) #32356). Seshat indexes an edit
    (m.replace) as a standalone event, so a search for the edited text matches the
    edit event. The search render path has no tile for a replace relation, so the
    match silently fails to render even though it is still counted. The live timeline
    never hits this because the SDK aggregates the edit onto its target — that
    aggregation does not run in the search path.
  • A URL/colon query fails the whole "All Rooms" search (Search failed: unable to search URL in All Rooms #32341). Seshat's
    full-text engine (tantivy) treats an unescaped colon as a field:value operator,
    so a query such as https://github.com is parsed as a search of the non-existent
    field https and Seshat rejects it with FieldDoesNotExist("https"). Because the
    combined (all-rooms) search awaited both legs with Promise.all, that rejection
    failed the entire search instead of falling back to the homeserver results.

Fix

All changes are confined to Searching.ts — no UX or API surface change.

  • Edited messages (Search result in encrypted rooms doesn't render edited messages (seshat) #32356): a new sanitizeSeshatResults post-processes each raw
    Seshat response. It keeps the existing state_key: null strip, and additionally
    promotes a matched edit by re-keying the result to the edit's target (the original
    message id) and lifting m.new_content into place. The SDK event mapper then resolves
    the renderable original (which carries the aggregated edit in a loaded room) instead
    of the live m.replace; for an unloaded original it builds a fresh event from the
    promoted content. Promoted edits that collide with their original are de-duplicated to
    avoid a duplicate tile / React-key clash. The encryption sidecar fields that
    restoreEncryptionInfo consumes are preserved. Only the matched event is re-keyed —
    an edit appearing solely as context is left untouched.
  • URL queries (Search failed: unable to search URL in All Rooms #32341): a new hardenSeshatSearchTerm phrase-wraps a term
    containing a colon so tantivy parses it literally instead of as a field operator. It
    is applied only to the local Seshat term; the homeserver leg keeps the raw term.
    Additionally, combinedSearch now runs the two legs with Promise.allSettled and
    degrades to the surviving leg if one rejects, failing only when both legs fail —
    so a query one backend can't parse no longer takes down the whole search.

Tests

Searching-test.ts adds coverage for: term-hardening (plain term, colon/URL, already-quoted
phrase, unbalanced quote, embedded quotes, and that the hardened term reaches Seshat while the
homeserver keeps the raw term); edit promotion (re-key + content lift, non-edit left alone,
context-only edit left alone, dedup of edit+original, and empty m.new_content left alone so
no blank tile); and combined-search degradation (server-only fallback, local-only fallback,
and reject only when both legs fail). The existing state_key tests still pass.

Fixes #32356
Fixes #32341

Checklist

…es failing the search

Seshat indexes message edits as standalone events, so an encrypted-room search
matched the edit event, which the search render path cannot render — the result
was silently dropped though still counted (element-hq#32356). And tantivy parses an
unescaped colon as a field operator, so a URL query was rejected with
FieldDoesNotExist and, because the all-rooms search awaited both legs together,
failed the entire search (element-hq#32341).

sanitizeSeshatResults now promotes a matched edit by re-keying the result to the
original message id and lifting m.new_content into place (de-duplicating against
the original, preserving the encryption sidecar), so edits render. A new
hardenSeshatSearchTerm phrase-wraps a colon-bearing term for the local Seshat leg
only, and combinedSearch runs the two legs with Promise.allSettled, degrading to
the surviving leg and failing only when both fail.
@hayaksi1 hayaksi1 requested a review from a team as a code owner June 27, 2026 10:02
@hayaksi1 hayaksi1 requested review from Half-Shot and dbkr June 27, 2026 10:02
@github-actions github-actions Bot added the Z-Community-PR Issue is solved by a community member's PR label Jun 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Z-Community-PR Issue is solved by a community member's PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Search result in encrypted rooms doesn't render edited messages (seshat) Search failed: unable to search URL in All Rooms

1 participant