16 KiB
Текущий контекст проекта
Статус: Фаза 14 — Мультиаккаунт (IN PROGRESS)
Оптимизация: Ленивая загрузка сообщений при открытии чата (DONE)
Чат открывается мгновенно (< 1 сек) вместо 5-30 сек для больших чатов.
Проблема: open_chat_and_load_data() блокировал UI до полной загрузки ВСЕХ сообщений (get_chat_history(chat_id, i32::MAX)). Для чата с 500+ сообщениями это 10+ запросов к TDLib.
Решение:
- Загрузка только 50 последних сообщений (один запрос) → чат виден сразу
- Фоновые задачи (reply info, pinned, photos) — на следующем тике main loop через
pending_chat_init - Старые сообщения подгружаются при скролле вверх (существующий
load_older_messages_if_needed)
Модифицированные файлы:
src/app/mod.rs— полеpending_chat_init: Option<ChatId>src/input/handlers/chat_list.rs—open_chat_and_load_data(): 50 сообщений +pending_chat_initsrc/main.rs— обработкаpending_chat_initв main loop (reply info, pinned, photos)src/app/methods/navigation.rs— сбросpending_chat_initвclose_chat()
Bugfix: Авто-загрузка фото в чате (DONE)
Фото не отображались — отсутствовал код загрузки файлов после открытия чата.
Проблема: extract_media_info() создавал PhotoInfo с PhotoDownloadState::NotDownloaded, но никакой код не инициировал download_file(). Фото оставались в состоянии "📷 [Фото]" без inline превью.
Исправление:
- Авто-загрузка при открытии чата: после загрузки истории сообщений скачиваются фото из последних 30 сообщений (если
auto_download_images = trueиshow_images = true). Каждый файл — с таймаутом 5 сек. - Загрузка по
v: вместо "Фото не загружено" — скачивание + открытие модалки. Также повторная попытка приError. - Обновление
PhotoDownloadStateв сообщении после успешной/неуспешной загрузки.
Модифицированные файлы:
src/input/handlers/chat_list.rs— авто-загрузка фото вopen_chat_and_load_data()src/input/handlers/chat.rs—handle_view_image(): download on NotDownloaded + retry on Error
Этап 2+3: Account Switcher Modal + Переключение аккаунтов (DONE)
Реализована модалка переключения аккаунтов и механизм переключения:
- Модалка
Ctrl+A: глобальный оверлей поверх любого экрана (Loading/Auth/Main) - Навигация:
j/kпо списку,Enterвыбор,aдобавление,Escзакрытие - Переключение: close TDLib →
recreate_client(new_db_path)→ auth flow - Добавление аккаунта: ввод имени в модалке → валидация →
add_account()→ переключение - Footer индикатор:
[account_name]если не "default" AccountSwitcherState: enumSelectAccount/AddAccount— глобальный оверлей вApp<T>recreate_client(): новый метод вTdClientTrait— close old → new TdClient → spawn set_tdlib_parameters
Новые файлы:
src/ui/modals/account_switcher.rs— UI рендеринг (SelectAccount + AddAccount)tests/account_switcher.rs— 12 тестов
Модифицированные файлы:
src/app/mod.rs—AccountSwitcherStateenum, 3 поля (account_switcher,current_account_name,pending_account_switch), 8 методовsrc/accounts/manager.rs—add_account()(validate + save + ensure_dir)src/accounts/mod.rs— re-exportadd_accountsrc/tdlib/trait.rs—recreate_client(&mut self, db_path)в TdClientTraitsrc/tdlib/client.rs— реализацияrecreate_client(close → new → set_tdlib_parameters)src/tdlib/client_impl.rs— trait impl делегированиеtests/helpers/fake_tdclient_impl.rs— no-oprecreate_clientsrc/input/main_input.rs— account_switcher роутинг (highest priority)src/input/handlers/global.rs—Ctrl+A→ open_account_switchersrc/input/handlers/modal.rs—handle_account_switcher()(SelectAccount + AddAccount input)src/ui/modals/mod.rs—pub mod account_switcher;src/ui/mod.rs— overlay поверх любого экранаsrc/ui/footer.rs—[account_name]индикаторsrc/main.rs—pending_account_switchcheck в run_app,Ctrl+Aиз любого экрана
Этап 1: Инфраструктура профилей аккаунтов (DONE)
Реализована инфраструктура для мультиаккаунта:
- Модуль
accounts/:profile.rs(типы + валидация) +manager.rs(загрузка/сохранение/миграция) accounts.toml: конфиг списка аккаунтов в~/.config/tele-tui/accounts.toml- XDG data dir: БД TDLib хранится в
~/.local/share/tele-tui/accounts/{name}/tdlib_data/ - Автомиграция:
./tdlib_data/→ XDG path при первом запуске - CLI флаг
--account <name>: выбор аккаунта при запуске - Параметризация
db_path:TdClient::new(db_path),App::new(config, db_path)
Предыдущий статус: Multiline Message Display (DONE)
Multiline в сообщениях
- Multiline в сообщениях:
\nкорректно отображается в пузырях сообщений (split по\n+ word wrap) - Маркер выделения: ▶ показывается только на первой строке multiline-сообщения
- Перенос строки в инпуте отключён (Shift+Enter/Alt+Enter/Ctrl+J не вставляют
\n)
Файлы изменены:
ui/components/message_bubble.rs—wrap_text_with_offsets()split по\n+wrap_paragraph()+ selection marker fix
Vim Normal/Insert Mode (DONE)
Реализован Vim-like режим работы с двумя состояниями:
- Normal mode (по умолчанию при открытии чата): навигация j/k, команды d/r/f/y, автоматический вход в MessageSelection
- Insert mode (нажать
i/ш): набор текста, Esc возвращает в Normal - Автопереключение в Insert при Reply (
r) и Edit (Enter) - Визуальные индикаторы:
[NORMAL]/[INSERT]в footer, зелёная/серая рамка compose bar - В Insert mode блокируются все команды кроме текстового ввода и Esc
Файлы изменены:
app/chat_state.rs— enumInputModeapp/mod.rs— полеinput_modeвApp<T>config/keybindings.rs—Command::EnterInsertMode+ keybindingi/шapp/methods/navigation.rs—close_chat()сбрасывает input_modeinput/main_input.rs— главный роутер Insert/Normalinput/handlers/chat.rs— EnterInsertMode, auto-Insert при Reply/Editinput/handlers/chat_list.rs— auto-MessageSelection при открытии чатаui/footer.rs— mode indicatorui/compose_bar.rs— visual mode differentiationtests/— обновлены для нового поведения
Предыдущий статус: Фаза 12 — Прослушивание голосовых сообщений (DONE)
Завершённые фазы (краткий итог)
| Фаза | Описание | Статус |
|---|---|---|
| 1 | Базовая инфраструктура (ratatui + crossterm, vim-навигация) | DONE |
| 2 | TDLib интеграция (авторизация, чаты, сообщения) | DONE |
| 3 | Улучшение UX (отправка, поиск, скролл, realtime) | DONE |
| 4 | Папки и фильтрация (загрузка папок, переключение 1-9) | DONE |
| 5 | Расширенный функционал (онлайн-статус, медиа-заглушки, muted) | DONE |
| 6 | Полировка (60 FPS, память, graceful shutdown, динамический инпут) | DONE |
| 7 | Рефакторинг памяти (единый источник данных, LRU-кэш) | DONE |
| 8 | Дополнительные фичи (markdown, edit/delete, reply/forward, блочный курсор) | DONE |
| 9 | Расширенные возможности (typing, pinned, поиск в чате, черновики, профиль, копирование, реакции, конфиг) | DONE |
| 10 | Desktop уведомления (notify-rust, muted фильтр, mentions, медиа) | DONE (83%) |
| 11 | Inline просмотр фото (ratatui-image, кэш, загрузка) | DONE |
| 12 | Прослушивание голосовых сообщений (ffplay, play/pause, seek, ticker, cache, config) | DONE |
| 13 | Глубокий рефакторинг архитектуры (7 этапов) | DONE |
Фаза 11: Inline фото + оптимизации (подробности)
Feature-gated (images), 2-tier архитектура:
Базовая реализация:
- Типы:
MediaInfo,PhotoInfo,PhotoDownloadState,ImageModalState,ImagesConfig - Зависимости:
ratatui-image 8.1,image 0.25(feature-gated) - Media модуль:
ImageCache(LRU), dualImageRenderer(inline + modal) - UX: Always-show inline preview (фикс. ширина 50 chars) + полноэкранная модалка на
v/м - Метаданные:
extract_media_info()из TDLib MessagePhoto; auto-download visible photos
Оптимизации производительности:
- Dual protocol strategy:
inline_image_renderer: Halfblocks → быстро (Unicode блоки), для навигацииmodal_image_renderer: iTerm2/Sixel → медленно (high quality), для просмотра
- Frame throttling: inline images 15 FPS (66ms), текст 60 FPS
- Lazy loading: загрузка только видимых изображений (не все сразу)
- LRU кэш: max 100 протоколов, eviction старых
- Loading indicator: "⏳ Загрузка..." в модалке при первом открытии
- Navigation hotkeys:
←/→между фото,Esc/qзакрыть модалку
UI рендеринг:
message_bubble.rs: photo status (Downloading/Error/placeholder), inline previewmessages.rs: второй проход сrender_images()+ throttling + только видимыеmodals/image_viewer.rs: fullscreen modal с aspect ratio + loading state
Фаза 13: Рефакторинг (подробности)
Разбиты 5 монолитных файлов (4582 строк) на модульную архитектуру:
- input/main_input.rs (1199→164): чистый роутер + 5 handler модулей в
handlers/ - app/mod.rs (1015→371): 5 trait модулей в
methods/(Navigation, Message, Compose, Search, Modal) - ui/messages.rs (893→365): модули
modals/(search, pinned, delete, reactions) +compose_bar.rs - tdlib/messages.rs (836→3 файла):
messages/(mod, convert, operations) - config/mod.rs (642→3 файла): validation.rs, loader.rs
- Очистка дублей: ~220 строк удалено (shared components, format_user_status, scroll_to_message)
- Документация: PROJECT_STRUCTURE.md переписан, 16 файлов получили
//!docs
Фаза 12: Голосовые сообщения (подробности)
Реализовано:
- AudioPlayer на ffplay (subprocess): play, pause (SIGSTOP), resume (SIGCONT), stop
- VoiceCache: LRU кэш OGG файлов в
~/.cache/tele-tui/voice/(max 100 MB) - Типы:
VoiceInfo,VoiceDownloadState,PlaybackState,PlaybackStatus - TDLib интеграция:
download_voice_note(), конвертацияMessageVoiceNote - Хоткеи: Space (play/pause), ←/→ (seek ±5s via ffplay restart с
-ss) - Автостоп: при навигации на другое сообщение воспроизведение останавливается
- Ticker:
last_playback_tickв App + обновление position в event loop (1 FPS redraw) - VoiceCache: проверка кэша перед загрузкой, кэширование после download
- AudioConfig:
[audio]секция в config.toml (cache_size_mb, auto_download_voice) - UI: progress bar (━●─) + waveform (▁▂▃▄▅▆▇█) + иконки статуса в
message_bubble.rs - Race condition fixes:
startingflag + pid ownership guard в потоках AudioPlayer - Seek:
resume_from()перезапускает ffplay с-ssoffset; MoveLeft/MoveRight как alias для SeekBackward/SeekForward - Resume with rewind: пауза→продолжение откатывает на 1 секунду назад
- Graceful shutdown:
stop_playback()+ Drop impl для AudioPlayer
Ключевая архитектура
main.rs → event loop (16ms poll)
├── input/ → роутер + handlers/ (chat, chat_list, compose, modal, search)
├── app/ → App<T: TdClientTrait> + methods/ (5 traits, 67 методов)
├── ui/ → рендеринг (messages, chat_list, modals/, compose_bar, components/)
├── audio/ → player.rs (ffplay), cache.rs (VoiceCache)
├── media/ → [feature=images] cache.rs, image_renderer.rs
└── tdlib/ → TDLib wrapper (client, auth, chats, messages/, users, reactions, types)
Тестирование
500+ тестов (0 failures):
- Snapshot tests: 57 (UI компоненты)
- Integration tests: 93 (user flows)
- E2E tests: 12 (smoke + journey)
- Utils tests: 18
- Performance benchmarks: 8
Ключевые решения
- Неблокирующий receive: TDLib updates через
mpsc::channelв отдельном потоке - Trait-based App: методы разбиты на traits — требуют
useimport на call site - FakeTdClient: mock для тестов без TDLib (реализует TdClientTrait)
- Оптимизация рендеринга:
needs_redrawфлаг, рендеринг только при изменениях - Конфиг: TOML
~/.config/tele-tui/config.toml, credentials с приоритетом (XDG → .env) - Feature-gated images:
imagesfeature flag для ratatui-image + image deps - Dual renderer: inline (Halfblocks, 15 FPS) + modal (iTerm2/Sixel, high quality) для баланса скорости/качества
- Audio via ffplay: subprocess с SIGSTOP/SIGCONT для pause/resume, автостоп при навигации
Зависимости (основные)
ratatui = "0.29" # TUI фреймворк
crossterm = "0.28" # Терминальный backend
tdlib-rs = "1.1" # Telegram TDLib binding
tokio = "1" # Async runtime
notify-rust = "4.11" # Desktop уведомления (feature flag)
ratatui-image = "8.1" # Inline images (feature flag)
image = "0.25" # Image decoding (feature flag)
Полная структура проекта: см. PROJECT_STRUCTURE.md Подробный план: см. ROADMAP.md