CRITICAL BUG FIX: Three methods in TdClientTrait impl were calling themselves recursively instead of delegating to actual implementations, causing stack overflow and application panic on startup.
Fixed methods:
1. user_cache_mut() - now returns &mut self.user_cache directly
2. sync_notification_muted_chats() - now delegates to notification_manager.sync_muted_chats()
3. handle_update() - now properly delegates to TdClient::handle_update() using qualified path
This bug caused the app to hang on "Инициализация TDLib..." screen and exit raw mode, displaying escape sequences ("CB52") on key presses when user tried to interact.
Root cause: Introduced in refactoring commit bd5e5be where trait implementations were created but incorrectly delegated to self.method() instead of accessing struct fields directly or using qualified path syntax.
Also added panic hook in main.rs to ensure terminal restoration on panic for better debugging experience.
Impact: Application completely broken - couldn't start. Stack overflow on first update.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Извлечены state модули и сервисы из монолитных файлов для улучшения структуры:
State модули:
- auth_state.rs: состояние авторизации
- chat_list_state.rs: состояние списка чатов
- compose_state.rs: состояние ввода сообщений
- message_view_state.rs: состояние просмотра сообщений
- ui_state.rs: UI состояние
Сервисы и утилиты:
- chat_filter.rs: централизованная фильтрация чатов (470+ строк)
- message_service.rs: сервис работы с сообщениями (17KB)
- key_handler.rs: trait для обработки клавиш (380+ строк)
Config модуль:
- config.rs -> config/mod.rs: основной конфиг
- config/keybindings.rs: настраиваемые горячие клавиши (420+ строк)
Тесты: 626 passed ✅
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Проблема:
- При открытии чата видно только последнее сообщение
- TDLib возвращал 1 сообщение при первом запросе
- Не было retry логики для ожидания синхронизации с сервером
Решение:
1. Динамическая загрузка с retry (до 20 попыток на чанк)
2. Загрузка всей доступной истории (без лимита)
3. Retry при получении малого количества сообщений
4. Корректная чанковая загрузка по 50 сообщений
Алгоритм:
- При открытии чата: get_chat_history(i32::MAX) - загружает всё
- Чанками по 50: TDLIB_MESSAGE_LIMIT
- Retry если получено < 50 при первой загрузке
- Остановка если 3 раза подряд пусто
- Порядок: старые чанки вставляются в начало (splice)
- При скролле: load_older_messages_if_needed() подгружает автоматически
Изменения:
src/tdlib/messages.rs:
- Убрана фиксированная задержка 100ms после open_chat
- Добавлен счетчик consecutive_empty_results
- Retry логика без искусственных sleep()
- Проверка: если получено мало - продолжить попытки
src/input/main_input.rs:
- limit: 100 → i32::MAX (без ограничений)
- timeout: 10s → 30s
tests/chat_list.rs:
- test_chat_history_chunked_loading: проверка 100, 120, 200 сообщений
- test_chat_history_loads_all_without_limit: загрузка 200 без лимита
- test_load_older_messages_pagination: подгрузка при скролле
Все тесты: 104/104 ✅
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Проблема:
- get_chat_history() загружала только один чанк (50 сообщений max)
- При запросе 100 сообщений возвращалось только 50
- Экран не заполнялся полностью при открытии чата
Решение:
- Добавлена чанковая загрузка по TDLIB_MESSAGE_LIMIT (50) сообщений
- Автоматическая подгрузка пока не достигнут запрошенный limit
- Правильная сборка сообщений (старые чанки вставляются в начало)
- Retry логика для каждого чанка (до 3 попыток)
Изменения в src/tdlib/messages.rs:
- get_chat_history(): цикл загрузки чанков вместо одного запроса
- Вставка более старых чанков в начало списка (splice)
- Обработка edge cases (пустые результаты, ошибки, конец истории)
Тесты:
- test_chat_history_chunked_loading: проверка загрузки 100, 120, 200 сообщений
- Проверка правильного порядка сообщений (от старых к новым)
- Проверка границы между чанками (messages 50/51)
Все тесты пройдены: 343/343 ✅
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixes two critical bugs:
1. Unread badge not clearing when opening chat - incoming messages weren't marked as viewed
2. Only last 2-3 messages loaded instead of full history due to incorrect break condition
Changes:
- Add incoming message IDs to pending_view_messages queue on chat open
- Remove premature break in get_chat_history() that stopped after 2 messages
- Add FakeTdClient.pending_view_messages field for testing
- Implement process_pending_view_messages() in FakeTdClient
Tests added:
- test_incoming_message_shows_unread_badge: verify "(1)" appears for unread
- test_opening_chat_clears_unread_badge: verify badge clears after opening
- test_opening_chat_loads_many_messages: verify all 50 messages load, not just last few
All 28 chat_list tests pass.
Извлечены функции из render() (Phase 5 начало):
- render_chat_header() - заголовок чата с typing status (~50 строк)
- render_pinned_bar() - панель закреплённого сообщения (~30 строк)
Результат:
- Главная функция render() сокращена на ~65 строк
- Применены let-else guards для упрощения
- Каждая функция имеет чёткую ответственность
Phase 5: Рефакторинг ui/messages.rs (879 строк)
Цель: разделить монолитную функцию render() на модули
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Применены дополнительные упрощения:
- handle_escape_key: преобразован в early returns
- handle_message_selection: применены let-else guards для всех веток
- Блоки 'd', 'y', 'e' теперь с явными guards
Результат Phase 4:
- Уменьшена вложенность во всех извлечённых функциях
- Применены паттерны: early returns, let-else guards, вспомогательные функции
- Код стал максимально линейным и читаемым
- Глубина вложенности: 6+ → 2-3 уровня
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Применены паттерны упрощения вложенности:
- handle_profile_mode: упрощён блок Enter с let-else
- handle_profile_open: применён early return guard
- handle_enter_key: разделена на 3 функции + early returns
- edit_message() - редактирование сообщения
- send_new_message() - отправка нового сообщения
- Сокращено с ~130 до ~40 строк
- handle_message_search_mode: извлечена функция perform_message_search()
- Упрощены блоки Backspace и Char с let-else
Результат: код стал более линейным, уменьшена глубина вложенности с 6+ до 2-3 уровней
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Обновлена документация с результатами Phase 3:
- Функция handle() сократилась с 891 до 82 строк (91% сокращение!)
- Всего извлечено 13 специализированных функций
- Phase 2: 2 функции (~163 строки)
- Phase 3: 11 функций (~783 строки)
Код стал линейным и простым для понимания.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Извлечены все оставшиеся блоки из функции handle():
- handle_profile_mode() - режим профиля с модалкой (~120 строк)
- handle_message_search_mode() - поиск по сообщениям (~73 строки)
- handle_pinned_mode() - закреплённые сообщения (~42 строки)
- handle_reaction_picker_mode() - emoji picker (~90 строк)
- handle_delete_confirmation() - подтверждение удаления (~60 строк)
- handle_forward_mode() - пересылка сообщений (~52 строки)
- handle_chat_search_mode() - поиск по чатам (~43 строки)
- handle_enter_key() - обработка Enter (~145 строк)
- handle_escape_key() - обработка Esc (~35 строк)
- handle_message_selection() - режим выбора сообщения (~95 строк)
- handle_profile_open() - Ctrl+U для профиля (~28 строк)
Результат:
- Функция handle() сокращена с 734 до 82 строк (89% сокращение!)
- Всего извлечено 13 специализированных функций
- Каждая функция имеет чёткую ответственность
- Код стал линейным и легко читаемым
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Извлечены оставшиеся обработчики из функции handle():
- handle_open_chat_keyboard_input() - ввод текста, навигация курсора, скролл (~129 строк)
- handle_chat_list_navigation() - навигация по чатам и папкам (~34 строки)
Результат:
- Функция handle() сокращена с 891 до 734 строк
- Всего извлечено 12 специализированных функций
- Каждая функция имеет чёткую ответственность и документацию
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Упрощение функции handle() в main_input.rs путём извлечения
обработчиков режимов в отдельные функции.
Извлечённые функции:
- handle_profile_mode() - режим профиля с модалкой выхода (~114 строк)
- handle_message_search_mode() - поиск по сообщениям (~73 строки)
- handle_pinned_mode() - закреплённые сообщения (~34 строки)
- handle_reaction_picker_mode() - выбор реакции (~79 строк)
- handle_delete_confirmation() - подтверждение удаления (~53 строки)
- handle_forward_mode() - выбор чата для пересылки (~48 строк)
- handle_chat_search_mode() - поиск по чатам (~32 строки)
- handle_escape_key() - обработка Esc (~25 строк)
- handle_message_selection() - выбор сообщения (~85 строк)
Итого извлечено: ~543 строки из основной функции handle()
Результат:
- handle() сократилась с 891 до ~350 строк (на 61%)
- Каждый режим теперь изолирован и легко тестируется
- Улучшена читаемость и maintainability кода
- Все тесты проходят успешно
Также:
- Обновлён tdlib-rs с 1.1 на 1.2.0
Added 13 integration tests for keyboard navigation:
Arrow Keys Navigation:
- test_arrow_navigation_in_chat_list: Up/Down arrows, circular wrapping
- test_vim_navigation_in_chat_list: j/k vim-style navigation
- test_russian_keyboard_navigation: Russian layout (о/р) support
- test_enter_opens_chat: Enter to open selected chat
- test_esc_closes_chat: Esc to close open chat
Cursor Navigation in Input:
- test_cursor_navigation_in_input: Left/Right arrow keys
- test_home_end_in_input: Home/End keys
- test_backspace_with_cursor: Backspace at different positions
- test_insert_char_at_cursor_position: Insert char in middle
Message Navigation:
- test_up_arrow_selects_last_message_when_input_empty: Up arrow for message selection
Additional:
- test_circular_navigation_optional: Circular list navigation
These tests verify that the navigation functionality works correctly
through the main_input handler, protecting against future refactoring
that might break keyboard input.
All tests compile and verify actual input handling via handle_main_input().
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Verified that P4.14 - Async/await consistency is already implemented.
**Verification Results**:
✅ No blocking calls in async context:
- std::fs used only in Config::load() at startup (before async runtime)
- std::thread::sleep not found anywhere
✅ Async-friendly APIs used correctly:
- tokio::time::sleep in messages.rs for delays
- tokio::time::timeout in auth.rs, main_input.rs, main.rs
- All async operations are non-blocking
✅ Config operations properly organized:
- Config::load() called once in main.rs:36 BEFORE async runtime
- Synchronous initialization, not a problem
**Conclusion**: Code is already async-clean! No blocking operations in async
context. P4.14 was already completed during development.
**Progress**:
- Priority 4: 4/4 tasks COMPLETE! 🎉🎉🎉
- Total: 16/17 tasks complete (94%)
**Remaining**:
- Priority 5: Feature flags, LRU generics, CLI args (optional improvements)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added 9 unit tests for src/utils.rs to cover time/date formatting logic.
**Tests added**:
- ✅ format_timestamp_with_tz with positive offset (+03:00)
- ✅ format_timestamp_with_tz with negative offset (-05:00)
- ✅ format_timestamp_with_tz with zero offset (UTC)
- ✅ format_timestamp_with_tz midnight wrap (23:00 + 2h = 01:00)
- ✅ format_timestamp_with_tz invalid timezone (fallback to +03:00)
- ✅ get_day - extract day from timestamp
- ✅ get_day_grouping - messages on same day
- ✅ format_datetime - full date and time with MSK
- ✅ parse_timezone_offset via public API
**Coverage**:
- format_timestamp_with_tz() - all edge cases
- parse_timezone_offset() - tested indirectly (private fn)
- get_day() - day calculation and grouping
- format_datetime() - partial coverage
**Result**: 54 unit tests pass (was 45, +9 new)
Related: REFACTORING_ROADMAP.md P4.11
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added 15 tests for config validation to ensure invalid values are caught.
**What's tested**:
- ✅ Valid default config
- ✅ Timezone validation (must start with + or -)
- ✅ Valid positive/negative timezones (+09:00, -05:00)
- ✅ Invalid timezone without sign
- ✅ Color validation (all 18 standard ratatui colors)
- ✅ Invalid colors (rainbow, purple, pink)
- ✅ Case-insensitive color parsing (RED, Green, YELLOW)
- ✅ parse_color() for all variants (standard, light, gray/grey)
- ✅ Fallback to White for invalid colors
**Coverage**:
- Config::validate() - timezone and color checks
- Config::parse_color() - all color parsing logic
- All 23 config tests pass (8 existing + 15 new)
**Note**: Validation was already implemented in config.rs:344-389
and called in load():450-456. This PR adds comprehensive test coverage.
Related: REFACTORING_ROADMAP.md P4.13
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>