Ранняя pre-alpha. Corvo в активной разработке — возможны ошибки. Пока не полагайтесь на неё как на единственный почтовый клиент и храните резервные копии важной почты.

Дорожная карта

Приоритезированный список задач по этапам ТЗ (TZ_pochtovy_client.md) и

технический долг. Статусы: ✅ готово · 🟡 частично · ⬜ не начато.

ТЗ v0.5.0 «Стабильное ядро» (TZ_opus_1.md)

Этап A — критические дефекты:

  • ✅ A1 — синхронизация не сбрасывает контекст (v0.4.8)
  • ✅ A2 — ссылки в письмах открываются в браузере (v0.4.8)
  • ✅ A3 — реальная проверка соединения (IMAP+SMTP, таймаут 10 с, раздельные

ошибки, различение аутентификации и сети) (v0.4.10)

  • ✅ Переключатель цепочек писем в настройках (v0.4.10)
  • ✅ Лицензионное соглашение (EULA) — установщик, приложение, сайт (v0.4.10)
  • ✅ A4 — роли папок через SPECIAL-USE, запрет молчаливого EXPUNGE

(диалог подтверждения, TRASH_NOT_FOUND) (v0.4.10)

  • ✅ A5 — сохранение отправленных в «Отправленные» (IMAP APPEND, миграция V006,

per-account smtp_saves_sent, тост при appendFailed) (v0.4.10)

  • ✅ A6 — цитирование, Cc, реальные получатели, references (миграция V007) (v0.4.10)
  • ✅ A7 — понятная причина ошибки загрузки + «Повторить» + автоповтор при лимите

подключений (api/error.ts) (v0.4.11)

  • ✅ A8 — картинки в письмах: cid: inline (data:), проксирование внешних

http(s)-картинок ядром (reqwest, лимиты, кэш html_with_images, миграция

V008), скрытие «битых» внешних, mime/content_id вложений (v0.4.11).

  • ✅ Опция «всегда показывать картинки от этого отправителя»: кнопка «Всегда»

в плашке блокировки, доверенные адреса в таблице trusted_senders

(миграция V011), у них картинки встраиваются автоматически при открытии.

Этап A ТЗ v0.5.0 полностью закрыт.

Этап B — честная синхронизация:

  • ✅ B1 — реконсиляция: удаления с сервера чистят кэш (UID SEARCH ALL), флаги

обновляются по всему кэшу папки, а не в окне 500 UID.

  • ✅ B2 — догрузка истории страницами (messages_load_older, миграция V010,

folders.history_complete) + бесконечная прокрутка в UI.

  • ✅ B3 — серверный поиск (UID SEARCH с CHARSET UTF-8 и фоллбэком):

кнопка «Искать на сервере» в поиске и в пустом состоянии; найденные письма

догружаются в кэш (search_server, sync_engine::search_on_server).

Операторы from:/to:/subject: транслируются в IMAP, остальные слова — TEXT

(сервер ищет и по телам); has:/in:/before:/after: применяются локально.

  • ⬜ CONDSTORE/QRESYNC (RFC 7162): сейчас реконсиляция каждый цикл делает

UID SEARCH ALL + FETCH FLAGS по всей папке. Корректно, но на ящике в

50k писем это заметный трафик — с CONDSTORE сервер отдавал бы только

изменения с прошлого раза.

Этап C — фон и мультиаккаунт:

  • ✅ C1 — IDLE для OAuth-аккаунтов: вход по XOAUTH2 в IDLE-воркере, токен

обновляется по refresh при каждом переподключении. Фоновые уведомления

работают у всех аккаунтов, а не только у парольных.

  • ✅ C2 — пул IMAP-соединений (≤ 3 на аккаунт, health-check NOOP,

core::imap_pool). Устранена причина лимита сессий у Яндекса.

  • ✅ C3 — воркеры IDLE поднимаются/останавливаются на лету: реестр воркеров

с флагом остановки, start_for_account при добавлении аккаунта и

stop_for_account при удалении. Перезапуск приложения не нужен.

Этап D — качество продукта:

  • ✅ D2 — виртуализация списка (react-virtuoso).
  • 🟡 D7 — тесты ядра: покрыты очередь операций (retry/backoff/терминальный

отказ/UIDVALIDITY), outbox (undo-гонка, отложенная отправка, восстановление

после крэша), правила фильтрации, санитайзер (включая CSS/label-векторы),

доверенные отправители, адресная книга и vCard. Не покрыто: инкрементальный

синк и реконсиляция (нужен фейковый IMAP), compute_thread_key,

идемпотентность миграций; на фронте не подключён vitest.

  • ✅ D1 — видимые ошибки: тосты вместо пустых catch в store и компонентах.
  • ⬜ D3 — i18n (ru/en, i18next): строки UI захардкожены по-русски.
  • ⬜ D4 — авто-высота письма (corvo:height из моста вместо

clamp(280px,55vh,820px)).

  • ⬜ D5 — inline-CSS в письмах (allowlist свойств в ammonia). ВНИМАНИЕ:

требует ослабить санитайзер, который сейчас вырезает style целиком —

это защищает от класса атак «разметка проходит фильтр, но опасна при

отрисовке» (см. регрессионные тесты в sanitizer.rs). Делать только с

жёстким allowlist и тестами на url(...), expression(...), position.

  • 🟡 D6 — CI написан (.github/workflows/ci.yml: матрица трёх ОС, clippy

-D warnings, cargo test), но не выполняется: репозиторий на GitFlic,

где GitHub Actions не работают. Нужен пайплайн GitFlic либо зеркало.

  • 🟡 D8 — гигиена репозитория сделана (мусор убран из git, .gitignore

дополнен); описание OAuth в README приведено к фактическому OOB-потоку.

Не сделано: loopback-поток как таковой (опционален по ТЗ).

Сверх ТЗ (решение по итогам обсуждения конкурентности — фокус на ПК-версии):

  • ✅ Трей: иконка, меню (Открыть/Написать/Проверить/Выход), клик по иконке,

сворачивание в трей вместо закрытия (настройка minimizeToTray).

  • ✅ Автозапуск с системой (tauri-plugin-autostart) + старт свёрнутым

(--minimized), переключатель в настройках.

  • ✅ Счётчик непрочитанных в подсказке трея («Corvo — 5 непрочитанных писем»,

с русским склонением), обновляется после синка и пометки «прочитано».

  • ⬜ Экспорт/импорт .eml/.mbox (возвращено в объём: пользователи не хотят

«запирать» почту в клиенте).

Этап 3 — Отправка почты и композер ✅ (завершён)

  • ✅ smtp_client.rs (lettre) + outbox + фоновый воркер, статусы в UI.
  • ✅ Композер: текстовый редактор, вложения (файлы) + предупреждение > 20 МБ.
  • ✅ Ответ / Ответить всем / Переслать (цитирование, In-Reply-To/References,

префиксы Re:/Fwd:).

  • ✅ Черновики: локальное автосохранение (localStorage) + сохранение на сервере

(IMAP APPEND в Drafts, кнопка «Черновик»).

  • ✅ Отмена отправки: окно undo (по умолчанию 10 сек) с тостом «Отменить».
  • ✅ Отложенная отправка: выбор даты/времени; воркер отправляет по сроку

(в т.ч. после перезапуска — очередь персистентна).

  • ✅ Подписи per-account (редактор в композере, автоподстановка).
  • ✅ Проверки «пустая тема» и «упомянули вложение, но не приложили».
  • ✅ WYSIWYG-редактор (TipTap): жирный/курсив/списки/цитата.

Этап 4 — OAuth 2.0 (Яндекс, Mail.ru) ✅ (завершён)

  • ✅ OAuth через системный браузер + PKCE, OOB-поток (verification_code);

ключи из oauth.json; создание аккаунта; refresh-токен в keyring.

  • ✅ XOAUTH2 для IMAP и SMTP — чтение и отправка по OAuth-токену.
  • ✅ Автообновление access-токена по refresh (при каждом подключении).
  • ✅ Статус «требуется вход» при отзыве токена: IDLE отличает ошибку

аутентификации от сетевой (не реконнектится вечно), шлёт reauth,

индикатор в сайдбаре кликабелен и ведёт в настройки аккаунта; успешный

синк снимает статус и заново поднимает IDLE-воркер.

  • ⛔ Gmail/Outlook — исключены (только РФ-провайдеры). У Яндекса почтовый scope

выдаётся только по запросу — на практике для Яндекса надёжнее пароль приложения.

Этап 5 — Мультиаккаунт, цепочки, объединённый inbox ✅ (завершён)

  • 🟡 Мультиаккаунт (базово есть; нужен параллельный синк без блокировок UI).
  • ✅ Объединённый inbox «Все входящие» с цветовой полоской аккаунта (v0.3.7).
  • ✅ Смарт-папки «Непрочитанные» и «С вложениями» по всем аккаунтам (v0.3.7).
  • ✅ Группировка в цепочки (thread_key по References/In-Reply-To), свёрнутый

вид с разворотом (v0.3.9).

  • ✅ Snooze (отложить до…) + смарт-папка «Отложенные», авто-возврат (v0.3.10).
  • ✅ Правила фильтрации (от/тема/содержит → папка/пометить/удалить) (v0.3.11).

Этап 6 — Поиск (FTS5) ✅ (завершён)

  • ✅ Индексация заголовков при синке + тел при открытии письма (v0.4.0).
  • ✅ Операторы from:, to:, subject:, has:attachment, in:, before:/after:.
  • ✅ Подсветка совпадений в результатах.
  • ✅ Серверный UID SEARCH кнопкой — сделано в рамках B3.
  • 🟡 Фоновая доиндексация тел всех писем (сейчас тело индексируется при открытии).

Этап 7 — Полировка и дистрибуция

  • ✅ Счётчики непрочитанных у папок в сайдбаре (v0.4.1).
  • ✅ Горячие клавиши (стиль Gmail) + шпаргалка по «?» (v0.4.1).
  • 🟡 Настройки: тема, плотность, интервал синка, уведомления (v0.4.2), окно

undo и выбор папки загрузок (нативный диалог, tauri-plugin-dialog).

Осталось: язык (i18next).

  • ✅ Автозапуск и трей (см. «Сверх ТЗ»).
  • ⬜ Экспорт/импорт .eml/.mbox.
  • ✅ Установщик NSIS (.exe) + автообновления с подписью (Tauri updater),

сервер обновлений в Docker, диалог «О программе» (v0.4.3). Осталось:

DMG/AppImage/deb для macOS/Linux.

  • ⬜ Профилирование на 100k писем, аудит безопасности.

Этап 8 (после 1.0)

  • ✅ Адресная книга: автосбор контактов из переписки (кто писал нам и кому

писали мы, свои адреса не собираются), подсказки адресов в композере,

диалог «Контакты» (поиск, ручное добавление/правка/удаление, «Написать»),

импорт и экспорт vCard (.vcf). Автосбор не затирает имя, введённое вручную.

  • ⬜ Локальный ИИ (суммаризация, черновики), OpenPGP, CalDAV/CardDAV, шаблоны, плагины.

---

Найдено на живом ящике (проверка сборки 0.5.2)

  • ✅ Удаление писем падало с UID MOVE No such folder (9 операций в очереди

провалились после 7 попыток). Причина: папки только добавлялись в кэш и

никогда не удалялись, поэтому у аккаунта копились «призраки» — у Яндекса

на роль trash приходилось три записи (Trash, Удаленные,

Deleted Messages), а find_by_role брал произвольную (LIMIT 1 без

сортировки) и попадал в несуществующую. Исправлено: синк удаляет папки,

которых нет в ответе LIST (delete_missing, защита от пустого списка);

find_by_role предпочитает реально синхронизировавшуюся папку. Заодно

repo_folders::upsert переведён на RETURNING id (тот же дефект

last_insert_rowid, что чинили в upsert_message).

  • ✅ Не было синхронизации при запуске: initApp поднимал только локальный

кэш, а первый синк приходил лишь по интервалу — до 5 минут «старой» почты

после открытия приложения. Теперь синк всех аккаунтов идёт сразу после

инициализации (проверено на живом ящике: старт 11:34:11 → синк 11:34:18).

  • ✅ Операции удалённых аккаунтов и давние failed копились в очереди

(9 записей от 4–7 июля, часть — от аккаунта, удалённого месяц назад).

Добавлена уборка repo_ops::cleanup: сироты (аккаунта нет — внешнего ключа

у op_queue тоже) и failed старше 30 дней. Вызывается при старте воркера

и при удалении аккаунта. Ожидающие операции не трогаются.

Очередь пользователя разово очищена (9 записей).

  • ⬜ Показывать проваленные операции в UI: сейчас пользователь не узнаёт, что

удаление/перемещение не доехало до сервера (письмо исчезло локально, но

осталось на сервере).

Технический долг и известные ограничения

  • ~~IDLE-воркеры требуют перезапуска приложения~~ — закрыто в C3.
  • ~~Очередь операций открывает отдельное IMAP-соединение на каждую операцию~~

— операции группируются по аккаунту и выполняются на одной сессии из пула.

Денормализованные креды из строк очереди убраны (миграция V012): воркер берёт

соединение по account_id, а копии host/email/keyring_ref устаревали после

изменения настроек аккаунта.

  • Локализация i18next ещё не подключена: часть строк UI захардкожена по-русски.

Нужно вынести в i18next (ru/en), как требует ТЗ.

  • ~~Виртуализация списка не внедрена~~ — react-virtuoso подключён в

MessageList.tsx (D2).

  • ~~#![allow(dead_code)] в ядре~~ — снят. Мёртвый код удалён (OutboxItem,

repo_folders::delete_by_account, repo_ops::enqueue/pending_count, неиспользуемые

константы событий, ImapFolder.delimiter); осознанные исключения помечены

точечно с объяснением (AppError::Smtp/Network, ParsedEmail). Сборка и

cargo clippy --all-targets — без предупреждений.

  • Свёрнутая цепочка в панели чтения (из макета) появится с группировкой в

цепочки (Этап 5).

  • CI (GitHub Actions/аналог): сборка под три ОС, clippy, тесты — не настроен.
  • imap-proto выдаёт future-incompat предупреждение (транзитивная зависимость).