mirror of
https://github.com/karatektus/pl_mail.git
synced 2026-10-25 10:04:18 +00:00
A row asserted a match and then showed something else. The preview was the opening hundred characters of the body, which for most hits does not contain the search term at all, and nothing was marked — so the only way to learn why a conversation was in the list was to open it. Every occurrence is marked now, in the subject and in the preview, and the preview is a window of the message AROUND the match. The fragment comes from the same Postgres `ts_headline` the JMAP snippet has always used, over the same query that found the mail, so the marking is stemmed exactly as the search is and cannot mark something the search did not match on. **It also closes a hole that was open in the JMAP API.** `ts_headline` is a highlighter and not an escaper: it inserts the delimiters it is given and returns the rest of the document byte for byte. Asking it for `StartSel=<mark>` produced a string in which the sender's markup and ours were the same kind of thing, and the check that stood in for escaping — "does it contain `<mark>`" — could not tell them apart. A subject or text part carrying `<img src=x onerror=…>` shipped it to every client as snippet HTML, and per RFC 8621 a client is right to render that. The class docblock claimed the opposite and had done since it was written. The delimiters are two control characters now. The whole string is escaped and only then are they swapped, pairs only, in one class that owns both the options and the conversion so they cannot be changed apart. `toHtml()` answers JMAP; `toMarkup()` answers Twig, which is what lets the row print a fragment without `|raw` — that file still does not contain one. Three things worth knowing: - `ShortWord` went from 3 to 0. At 3 `ts_headline` trims short leading words, so "Oak for the alcove" searched for `alcove` came back as the bare word — a row that has rewritten the subject rather than highlighted it. - A highlighted subject is used only when it still equals the thread's subject with the markers removed; anything else falls back. And it goes to a separate variable, because `subject` is also spent on `aria-label`, where a screen reader announcing the markup would be a worse regression than the one this fixes. - The headline replaces the preview only when it actually contains a marker. That makes the two documented `body_text` regressions self-sorting: a bulk sender's "this email is only available in HTML" does not contain the term, so it does not win. One batched query per account per page, not one per row: the budget test is a hard ceiling and a per-row headline would blow it instantly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>