docs: clean up project markdown
This commit is contained in:
359
CONTEXT.md
359
CONTEXT.md
@@ -1,311 +1,48 @@
|
||||
# Текущий контекст проекта
|
||||
|
||||
## Статус: Фаза 14 — Мультиаккаунт (IN PROGRESS)
|
||||
|
||||
### Per-Account Lock File Protection — DONE
|
||||
|
||||
Защита от запуска двух экземпляров tele-tui с одним аккаунтом + логирование ошибок TDLib.
|
||||
|
||||
**Проблема**: При запуске второго экземпляра с тем же аккаунтом, TDLib не может залочить свою БД. `set_tdlib_parameters` молча падает (`let _ = ...`), и приложение зависает на "Инициализация TDLib...".
|
||||
|
||||
**Решение**: Advisory file locks через `fs2` (flock):
|
||||
- **Lock файл**: `~/.local/share/tele-tui/accounts/{name}/tele-tui.lock`
|
||||
- **Автоматическое освобождение** при crash/SIGKILL (ядро ОС закрывает file descriptors)
|
||||
- **При старте**: acquire lock ДО `enable_raw_mode()` → ошибка выводится в обычный терминал
|
||||
- **При переключении аккаунтов**: acquire new → release old → switch (при ошибке — остаёмся на старом)
|
||||
- **Логирование**: `set_tdlib_parameters` ошибки теперь логируются через `tracing::error!`
|
||||
|
||||
**Новые файлы:**
|
||||
- `src/accounts/lock.rs` — `acquire_lock()`, `release_lock()`, `account_lock_path()` + 4 теста
|
||||
|
||||
**Модифицированные файлы:**
|
||||
- `Cargo.toml` — зависимость `fs2 = "0.4"`
|
||||
- `src/accounts/mod.rs` — `pub mod lock;` + re-exports
|
||||
- `src/app/mod.rs` — поле `account_lock: Option<File>` в `App<T>`
|
||||
- `src/main.rs` — acquire lock при старте, lock при переключении аккаунтов, логирование set_tdlib_parameters
|
||||
- `src/tdlib/client.rs` — логирование set_tdlib_parameters в `recreate_client()`
|
||||
|
||||
---
|
||||
|
||||
### Photo Albums (Media Groups) — DONE
|
||||
|
||||
Фото-альбомы (несколько фото в одном сообщении) теперь группируются в один пузырь с сеткой фото.
|
||||
|
||||
**Проблема**: TDLib отправляет альбомы как отдельные `Message` с общим `media_album_id: i64`. Ранее проект это поле игнорировал — каждое фото отображалось как отдельный пузырь.
|
||||
|
||||
**Решение:**
|
||||
|
||||
1. **Data Model** — `media_album_id: i64` в `MessageMetadata`, `MessageBuilder`, getter `MessageInfo::media_album_id()`. Оба конвертера (async + sync) передают поле из TDLib.
|
||||
|
||||
2. **Message Grouping** — новый вариант `MessageGroup::Album(Vec<MessageInfo>)`. Сообщения с одинаковым `media_album_id != 0` группируются; одиночное сообщение с album_id остаётся `Message`.
|
||||
|
||||
3. **Album Grid Constants** — `ALBUM_PHOTO_WIDTH: 16`, `ALBUM_PHOTO_HEIGHT: 8`, `ALBUM_PHOTO_GAP: 1`, `ALBUM_GRID_MAX_COLS: 3` (3×16 + 2×1 = 50 = `INLINE_IMAGE_MAX_WIDTH`).
|
||||
|
||||
4. **`render_album_bubble()`** — сетка фото (до 3 в ряд), `DeferredImageRender` с `x_offset` для каждого фото, общая подпись и timestamp, индикация выбора, статусы загрузки.
|
||||
|
||||
5. **Integration** — `Album` arm в `render_message_list`, `x_offset` в second pass. Без feature `images` — fallback через отдельные bubble.
|
||||
|
||||
**Модифицированные файлы:**
|
||||
- `src/tdlib/types.rs` — `media_album_id` в `MessageMetadata`, `MessageBuilder`, getter
|
||||
- `src/tdlib/messages/convert.rs` — передача `media_album_id` в builder
|
||||
- `src/tdlib/message_converter.rs` — передача `media_album_id` в builder
|
||||
- `src/message_grouping.rs` — `Album` variant + album detection + 4 новых теста
|
||||
- `src/constants.rs` — album grid constants
|
||||
- `src/ui/components/message_bubble.rs` — `x_offset` в `DeferredImageRender`, `render_album_bubble()`
|
||||
- `src/ui/components/mod.rs` — export `render_album_bubble`
|
||||
- `src/ui/messages.rs` — `Album` arm + `x_offset` в second pass
|
||||
|
||||
6. **Навигация j/k по альбомам** — альбом обрабатывается как одно сообщение. `select_previous_message()` / `select_next_message()` перескакивают через все сообщения альбома. `start_message_selection()` встаёт на первый элемент альбома если последнее сообщение — часть альбома.
|
||||
|
||||
7. **Тесты** — 4 unit-теста в `message_grouping.rs`, 5 snapshot-тестов в `tests/messages.rs`, 3 теста навигации в `tests/input_navigation.rs`.
|
||||
|
||||
**Дополнительно модифицированные файлы:**
|
||||
- `src/app/methods/messages.rs` — навигация перескакивает альбомы
|
||||
- `tests/helpers/test_data.rs` — `TestMessageBuilder::media_album_id()`
|
||||
- `tests/messages.rs` — 5 snapshot-тестов для альбомов
|
||||
- `tests/input_navigation.rs` — 3 теста навигации по альбомам
|
||||
|
||||
**Что НЕ меняется:** image modal (v), auto-download, одиночные фото.
|
||||
|
||||
---
|
||||
|
||||
### Оптимизация: Ленивая загрузка сообщений при открытии чата (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_init`
|
||||
- `src/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`**: enum `SelectAccount` / `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` — `AccountSwitcherState` enum, 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-export `add_account`
|
||||
- `src/tdlib/trait.rs` — `recreate_client(&mut self, db_path)` в TdClientTrait
|
||||
- `src/tdlib/client.rs` — реализация `recreate_client` (close → new → set_tdlib_parameters)
|
||||
- `src/tdlib/client_impl.rs` — trait impl делегирование
|
||||
- `tests/helpers/fake_tdclient_impl.rs` — no-op `recreate_client`
|
||||
- `src/input/main_input.rs` — account_switcher роутинг (highest priority)
|
||||
- `src/input/handlers/global.rs` — `Ctrl+A` → open_account_switcher
|
||||
- `src/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_switch` check в 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` — enum `InputMode`
|
||||
- `app/mod.rs` — поле `input_mode` в `App<T>`
|
||||
- `config/keybindings.rs` — `Command::EnterInsertMode` + keybinding `i`/`ш`
|
||||
- `app/methods/navigation.rs` — `close_chat()` сбрасывает input_mode
|
||||
- `input/main_input.rs` — главный роутер Insert/Normal
|
||||
- `input/handlers/chat.rs` — EnterInsertMode, auto-Insert при Reply/Edit
|
||||
- `input/handlers/chat_list.rs` — auto-MessageSelection при открытии чата
|
||||
- `ui/footer.rs` — mode indicator
|
||||
- `ui/compose_bar.rs` — visual mode differentiation
|
||||
- `tests/` — обновлены для нового поведения
|
||||
|
||||
---
|
||||
|
||||
## Предыдущий статус: Фаза 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 архитектура:
|
||||
|
||||
**Базовая реализация:**
|
||||
1. **Типы**: `MediaInfo`, `PhotoInfo`, `PhotoDownloadState`, `ImageModalState`, `ImagesConfig`
|
||||
2. **Зависимости**: `ratatui-image 8.1`, `image 0.25` (feature-gated)
|
||||
3. **Media модуль**: `ImageCache` (LRU), dual `ImageRenderer` (inline + modal)
|
||||
4. **UX**: Always-show inline preview (фикс. ширина 50 chars) + полноэкранная модалка на `v`/`м`
|
||||
5. **Метаданные**: `extract_media_info()` из TDLib MessagePhoto; auto-download visible photos
|
||||
|
||||
**Оптимизации производительности:**
|
||||
1. **Dual protocol strategy**:
|
||||
- `inline_image_renderer`: Halfblocks → быстро (Unicode блоки), для навигации
|
||||
- `modal_image_renderer`: iTerm2/Sixel → медленно (high quality), для просмотра
|
||||
2. **Frame throttling**: inline images 15 FPS (66ms), текст 60 FPS
|
||||
3. **Lazy loading**: загрузка только видимых изображений (не все сразу)
|
||||
4. **LRU кэш**: max 100 протоколов, eviction старых
|
||||
5. **Loading indicator**: "⏳ Загрузка..." в модалке при первом открытии
|
||||
6. **Navigation hotkeys**: `←`/`→` между фото, `Esc`/`q` закрыть модалку
|
||||
|
||||
**UI рендеринг**:
|
||||
- `message_bubble.rs`: photo status (Downloading/Error/placeholder), inline preview
|
||||
- `messages.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**: `starting` flag + pid ownership guard в потоках AudioPlayer
|
||||
- **Seek**: `resume_from()` перезапускает ffplay с `-ss` offset; 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
|
||||
|
||||
### Ключевые решения
|
||||
|
||||
1. **Неблокирующий receive**: TDLib updates через `mpsc::channel` в отдельном потоке
|
||||
2. **Trait-based App**: методы разбиты на traits — требуют `use` import на call site
|
||||
3. **FakeTdClient**: mock для тестов без TDLib (реализует TdClientTrait)
|
||||
4. **Оптимизация рендеринга**: `needs_redraw` флаг, рендеринг только при изменениях
|
||||
5. **Конфиг**: TOML `~/.config/tele-tui/config.toml`, credentials с приоритетом (XDG → .env)
|
||||
6. **Feature-gated images**: `images` feature flag для ratatui-image + image deps
|
||||
7. **Dual renderer**: inline (Halfblocks, 15 FPS) + modal (iTerm2/Sixel, high quality) для баланса скорости/качества
|
||||
8. **Audio via ffplay**: subprocess с SIGSTOP/SIGCONT для pause/resume, автостоп при навигации
|
||||
|
||||
### Зависимости (основные)
|
||||
|
||||
```toml
|
||||
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](PROJECT_STRUCTURE.md)
|
||||
Подробный план: см. [ROADMAP.md](ROADMAP.md)
|
||||
# Контекст проекта
|
||||
|
||||
## Текущий статус
|
||||
|
||||
Фаза 14: мультиаккаунт, в работе.
|
||||
|
||||
Цель фазы: безопасные профили Telegram-аккаунтов, переключение аккаунтов и подготовка к более быстрому multi-client UX.
|
||||
|
||||
## Уже сделано
|
||||
|
||||
- Профили аккаунтов: `AccountProfile`, `accounts.toml`, XDG data paths.
|
||||
- Миграция старого `tdlib_data/` в per-account directory.
|
||||
- CLI `--account <name>`.
|
||||
- Account switcher modal по `Ctrl+A`: выбор и добавление аккаунта.
|
||||
- Single-client переключение через `recreate_client()`.
|
||||
- Footer показывает имя аккаунта, если он не `default`.
|
||||
- Per-account lock file защищает TDLib database от двух процессов.
|
||||
- TDLib receive loop передаёт `client_id`; UI применяет updates только активного клиента.
|
||||
- `pending_chat_init` не должен блокировать первый redraw; reply-info и photo downloads уходят в фоновые tasks.
|
||||
- Keybindings стали детерминированными; русская vim-раскладка: `h/j/k/l` -> `р/о/л/д`.
|
||||
- `AudioPlayer` проверяет наличие `ffplay`.
|
||||
- `message_grouping` группирует альбомы без клонирования сообщений.
|
||||
|
||||
## Осталось
|
||||
|
||||
- Быстрые hotkeys `Ctrl+1`..`Ctrl+9` для аккаунтов без модалки.
|
||||
- Удаление/переименование аккаунта в account switcher.
|
||||
- Бейджи непрочитанных с других аккаунтов.
|
||||
- Решить, нужен ли переход от single-client reinit к одновременным клиентам.
|
||||
- Добавить/уточнить tests для accounts + TDLib update routing.
|
||||
|
||||
## Риски
|
||||
|
||||
- Multi-account код должен фильтровать TDLib updates по `client_id`.
|
||||
- Инициализация чата и фоновые downloads не должны блокировать первый redraw.
|
||||
- Read/unread статус исходящих сообщений зависит от корректной TDLib metadata.
|
||||
- Конфликтующие keybindings должны оставаться детерминированными.
|
||||
- Переключение аккаунтов требует проверки lock release/acquire и auth flow.
|
||||
|
||||
## Ключевые решения
|
||||
|
||||
- Главный state хранится в `App<T: TdClientTrait>`, чтобы тесты могли использовать `FakeTdClient`.
|
||||
- Методы `App` разбиты на traits: navigation, messages, compose, search, modal.
|
||||
- UI рендерится только при `needs_redraw`; текстовый интерфейс целится в 60 FPS.
|
||||
- Фото под feature `images`: inline Halfblocks + modal iTerm2/Sixel.
|
||||
- Голосовые сообщения проигрываются через `ffplay`, cache живёт в `~/.cache/tele-tui/voice/`.
|
||||
- Credentials читаются из `~/.config/tele-tui/credentials`, fallback `.env`.
|
||||
- TDLib data аккаунтов хранится в `~/.local/share/tele-tui/accounts/{name}/tdlib_data/`.
|
||||
|
||||
Reference in New Issue
Block a user