Search (PG backend):
- '@' terms match address substrings (from/to/cc), so a partial email like
"dyrektor@wfdif" resolves and narrows predictably as you type
- index each address split into parts, so a bare domain/local word ("wfdif")
finds the address (indexed as one lexeme otherwise); dotted terms fall back to
an address substring
- rank sender/recipient name+address matches above body/subject-only ones
(addr_hit tier, then recency); cursor extended to keep keyset pagination correct
- scripts/reindex_address_parts.py backfills address tokens into existing rows
Inline images:
- extract attachments via a full MIME walk instead of shallow iter_attachments(),
which missed inline images nested in mixed>related>alternative (cid: rendered
broken); has_attachments counts only real (non-cid) attachments
- scripts/backfill_inline_attachments.py stores previously-missed inline images
Folder-name i18n (SPEC §12 M2):
- accent/punctuation-insensitive, multi-delimiter (/ \ .) special-use inference
across ~13 languages; curated names (no substring false positives)
Link security (reading panel):
- links never navigate the message iframe: a parent-side click interceptor opens
http/https in a new tab (noopener) and mailto in the composer (recipient
pre-filled); all other schemes blocked
Backend 292 tests green, ruff + mypy --strict clean; frontend tsc/eslint/vitest clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U72FZiF1W1pb8XxyWBTREA
75 lines
2.3 KiB
Python
75 lines
2.3 KiB
Python
"""Folder-name special-use heuristics (SPEC §6.3 / §12 M2)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from app.sync.folder_names import infer_special_use
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"path,expected",
|
|
[
|
|
# inbox
|
|
("INBOX", "inbox"),
|
|
("inbox", "inbox"),
|
|
("INBOX.", "inbox"), # Dovecot may show a trailing delimiter
|
|
# English / Outlook / Gmail
|
|
("Sent", "sent"),
|
|
("Sent Items", "sent"),
|
|
("[Gmail]/Sent Mail", "sent"),
|
|
("Drafts", "drafts"),
|
|
("Deleted Items", "trash"),
|
|
("Bin", "trash"),
|
|
("Junk", "junk"),
|
|
("Junk E-mail", "junk"), # Outlook, punctuation-insensitive
|
|
("[Gmail]/Spam", "junk"),
|
|
("Archive", "archive"),
|
|
("[Gmail]/All Mail", "archive"),
|
|
# Polish (accents)
|
|
("Wysłane", "sent"),
|
|
("Elementy wysłane", "sent"),
|
|
("Robocze", "drafts"),
|
|
("Kosz", "trash"),
|
|
("Wiadomości-śmieci", "junk"),
|
|
("Archiwum", "archive"),
|
|
# German
|
|
("Gesendet", "sent"),
|
|
("Gesendete Elemente", "sent"),
|
|
("Entwürfe", "drafts"),
|
|
("Papierkorb", "trash"),
|
|
("Gelöschte Elemente", "trash"),
|
|
# French
|
|
("Éléments envoyés", "sent"),
|
|
("Brouillons", "drafts"),
|
|
("Corbeille", "trash"),
|
|
("Courrier indésirable", "junk"),
|
|
# Spanish / Italian / Dutch / Portuguese
|
|
("Enviados", "sent"),
|
|
("Papelera", "trash"),
|
|
("Posta inviata", "sent"),
|
|
("Cestino", "trash"),
|
|
("Verzonden", "sent"),
|
|
("Prullenbak", "trash"),
|
|
("Itens enviados", "sent"),
|
|
("Lixeira", "trash"),
|
|
# Russian (Cyrillic)
|
|
("Отправленные", "sent"),
|
|
("Корзина", "trash"),
|
|
("Черновики", "drafts"),
|
|
("Спам", "junk"),
|
|
# delimiters / nesting
|
|
("INBOX/Sent", "sent"),
|
|
("INBOX.Sent", "sent"), # Dovecot dot delimiter
|
|
("INBOX.Trash", "trash"),
|
|
# negatives — must not misfire
|
|
("Work", None),
|
|
("Random Folder", None),
|
|
("Sent to John", None), # curated names, not substring match
|
|
("Newsletter", None),
|
|
("Projects/Client", None),
|
|
],
|
|
)
|
|
def test_infer_special_use(path: str, expected: str | None) -> None:
|
|
assert infer_special_use(path) == expected
|