- Восстановлен мягкий цвет пузырей: color-mix(92%) вместо жесткого var(--bs-primary) - Аватары: border-radius 9999px + grid layout для идеальных кругов - Разделители: правильный spacing, font-weight 700, pointer-events, color-mix прозрачность - Добавлены оптимизации скролла: rootMargin 300px, limit 50 сообщений (Telegram-стиль) - Добавлены новые модули: messageLoader, scrollManager, chatUtils - Рефакторинг логики загрузки сообщений для предотвращения дубликатов - Создана документация по архитектуре и тестированию
13 KiB
Прогресс рефакторинга - Фаза 2 в процессе
Дата: 12.01.2026
Статус: ✅ Фаза 1 DONE | 🔄 Фаза 2 в процессе (7/10 задач)
✅ Что сделано
1. MessageStore - Централизованное хранилище ✅
Файл: backend/static/js/stores/messageStore.js (458 строк)
Функционал:
- ✅ Map структура для сообщений (id → message)
- ✅ Сортированные списки по чатам (chatId → [messageIds])
- ✅ Observer pattern (подписки на изменения)
- ✅ Оптимистичные обновления (temp_id → real_id)
- ✅ Автоматическое вычисление day-dividers
- ✅ Методы: add, update, remove, get, subscribe
Преимущества:
- Единственный источник правды
- Нет дубликатов
- Легко откатывать изменения
- Централизованное кэширование
2. MessageStore.test.js - Тесты ✅
Файл: backend/static/js/stores/messageStore.test.js (222 строки)
Покрытие:
- ✅ 12 тестовых кейсов
- ✅ Создание Store
- ✅ Добавление/обновление/удаление
- ✅ Batch операции
- ✅ Предотвращение дубликатов
- ✅ Сортировка по timestamp
- ✅ Day-dividers вычисление
- ✅ Подписки (subscribe/unsubscribe)
- ✅ Оптимистичные сообщения
- ✅ Oldest/Newest message
Запуск:
// В консоли браузера
runMessageStoreTests()
3. MessageLoader - Единый загрузчик ✅
Файл: backend/static/js/loaders/messageLoader.js (292 строки)
Функционал:
- ✅ loadInitialMessages() - начальная загрузка
- ✅ loadHistoryBefore() - история при scroll up
- ✅ handleNewMessage() - новые сообщения из WS
- ✅ handleMessageEdited/Removed() - обновления
- ✅ handleReaction*() - реакции
- ✅ sendMessageOptimistically() - оптимистичная отправка
- ✅ Tracking состояния (loading, hasMore)
API:
const loader = new MessageLoader({
store: messageStore,
wsConnection: userWebSocket,
currentUserId: userId
});
await loader.loadInitialMessages(chatId);
await loader.loadHistoryBefore(chatId, { limit: 20 });
loader.handleNewMessage(message);
4. MessageRendererV2 - Рендеринг из Store ✅
Файл: backend/static/js/renderers/messageRendererV2.js (397 строк)
Функционал:
- ✅ render(chatId) - полный рендеринг из Store
- ✅ appendMessage() - incremental добавление
- ✅ updateMessage() - патчинг DOM (content, reactions, status)
- ✅ removeMessage() - удаление
- ✅ НЕТ дублирования логики day-dividers
- ✅ Использует Store.getMessagesWithDividers()
- ✅ Tracking отрендеренных сообщений (Set)
API:
const renderer = new MessageRendererV2({
store: messageStore,
containerId: 'chatScroll',
currentUserId: userId
});
await renderer.render(chatId);
renderer.appendMessage(message, chatId);
renderer.updateMessage(msgId, { content: 'Updated' });
5. ScrollManager - Управление прокруткой ✅
Файл: backend/static/js/managers/scrollManager.js (346 строк)
Функционал:
- ✅ IntersectionObserver для автозагрузки истории
- ✅ scrollToBottom() без прыжков (visibility + double RAF)
- ✅ scrollToMessage() к конкретному сообщению
- ✅ isNearBottom() определение позиции
- ✅ saveScrollPosition() / restoreScrollPosition()
- ✅ getFirstVisible/LastVisibleMessageId()
- ✅ Умное управление observer target
API:
const scrollMgr = new ScrollManager({
scrollElement: element,
messageLoader: loader,
messageRenderer: renderer,
messageStore: store,
chatId: chatId
});
scrollMgr.init();
scrollMgr.scrollToBottom({ instant: true });
scrollMgr.scrollToMessage(msgId, { block: 'center' });
6. ChatController - Координатор ✅
Файл: backend/static/js/controllers/chatController.js (385 строк)
Функционал:
- ✅ Создает и координирует все компоненты
- ✅ init() - единая точка инициализации
- ✅ Подписывается на Store и WebSocket
- ✅ Обрабатывает события (message_added, updated, removed)
- ✅ Публичное API для работы с чатом
- ✅ Автоматический autoscroll при новых сообщениях
- ✅ Incremental rendering
Единственная точка входа:
const chat = new ChatController({
chatId: 123,
currentUserId: userId,
scrollElement: document.getElementById('chatScroll'),
wsConnection: userWebSocket
});
await chat.init();
// Всё работает! Store, Loader, Renderer, ScrollManager
chat.sendMessage('Hello!');
chat.scrollToBottom();
await chat.loadMoreHistory();
7. Примеры использования ✅
Файл: backend/static/js/examples/chatControllerUsage.js
Демонстрирует:
- ✅ Сравнение старого vs нового подхода
- ✅ Инициализация ChatController
- ✅ Отправка сообщений
- ✅ Прокрутка и навигация
- ✅ Интеграция с другими модулями
- ✅ Обработка событий
✅ Фаза 2: Интеграция (ЧАСТИЧНО)
userWebSocket.js - Событийная архитектура ✅
Изменения:
- handleInitialMessages - теперь dispatch
ws:initial-messages:
window.dispatchEvent(new CustomEvent('ws:initial-messages', {
detail: { messages, chatId }
}));
- handleNewMessage - теперь dispatch
ws:new-message:
window.dispatchEvent(new CustomEvent('ws:new-message', {
detail: { message, chatId, isOwnMessage }
}));
- handleMessageUpdated - dispatch
ws:message-edited - handleMessageDeleted - dispatch
ws:message-removed - handleReactionAdded - dispatch
ws:reaction-added - handleReactionRemoved - dispatch
ws:reaction-removed
Backward Compatibility:
- Старая логика с
messageRendererсохранена - Старые события
chat:*продолжают работать - Код помечен как
[DEPRECATED]для удаления после миграции
Преимущества:
- ✅ Loose coupling - модули независимы
- ✅ Event-driven architecture
- ✅ Легко тестировать (можно mock события)
- ✅ ChatController автоматически получает обновления
ChatController.js - Подписка на WS события ✅
Обновлено:
_subscribeToWebSocket() теперь слушает новые события:
ws:initial-messages→ рендер + scrollws:new-message→ loader.handleNewMessage()ws:message-edited→ loader.handleMessageEdited()ws:message-removed→ loader.handleMessageRemoved()ws:reaction-added→ loader.handleReactionAdded()ws:reaction-removed→ loader.handleReactionRemoved()
Все обработчики проверяют chatId чтобы игнорировать события других чатов.
📊 Архитектура
Структура файлов:
backend/static/js/
├── stores/
│ ├── messageStore.js ✅ NEW
│ └── messageStore.test.js ✅ NEW
├── loaders/
│ └── messageLoader.js ✅ NEW
├── renderers/
│ └── messageRendererV2.js ✅ NEW
├── managers/
│ └── scrollManager.js ✅ NEW
├── controllers/
│ └── chatController.js ✅ NEW
└── examples/
└── chatControllerUsage.js ✅ NEW
Поток данных:
┌─────────────────────────────────────────────┐
│ ChatController │
│ (единая точка входа) │
└────┬─────────────────────────────────┬──────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ MessageLoader│◄────────────►│ MessageStore │
│ │ │ (Single Truth)│
└──────┬───────┘ └───────┬───────┘
│ │
│ ▼
│ ┌──────────────┐
│ │MessageRenderer│
│ │ V2 │
│ └───────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ WebSocket │ │ DOM │
└──────────────┘ └──────────────┘
▲
│
┌───────┴──────┐
│ScrollManager │
└──────────────┘
Преимущества:
До рефакторинга:
- ❌ 3 разных пути загрузки
- ❌ Дубликаты логики везде
- ❌ Прямое манипулирование DOM
- ❌ Нет единого состояния
- ❌ Сложно тестировать
- ❌ Непредсказуемое поведение
После рефакторинга:
- ✅ 1 точка входа (ChatController)
- ✅ Single Source of Truth (Store)
- ✅ Centralized rendering (RendererV2)
- ✅ Умный scroll (ScrollManager)
- ✅ Легко тестировать
- ✅ Предсказуемое поведение
📝 Что осталось сделать
Фаза 2: Интеграция (осталось 3 задачи)
7. Интеграция с userWebSocket ✅ DONE
- ✅ Переписаны обработчики для dispatch событий
ws:* - ✅ Сохранена backward compatibility со старым кодом
- ✅ ChatController подписан на новые события
- ✅ Создана документация WEBSOCKET_INTEGRATION.md
8. Удаление старого кода ⏳ PENDING
- Удалить chatHistoryLoader.js
- Удалить старый messageRenderer.js
- Cleanup неиспользуемых функций
9. Обновление chatDetail.js ⏳ NEXT
- Заменить инициализацию на ChatController
- Удалить старые вызовы модулей
- Упростить код в 10 раз
10. Тестирование ⏳ PENDING
- Мануальное тестирование всех сценариев
- Проверка scroll jumps (должны исчезнуть)
- Проверка day-dividers (единый стиль)
- Регрессионное тестирование
🎯 Следующие шаги
Сейчас можно:
Опция A: Протестировать базовую архитектуру
// Загрузить тесты в консоли
<script type="module" src="/static/js/stores/messageStore.test.js"></script>
runMessageStoreTests()
Опция B: Продолжить интеграцию
- Переписать userWebSocket для работы с ChatController
- Обновить chatDetail.js
- Удалить старый код
Опция C: Остановиться на сегодня
- Базовая архитектура готова
- Можно продолжить завтра
- Код уже коммитить не страшно
💡 Рекомендация
Я рекомендую Опцию C - остановиться на сегодня:
✅ Создано 6 новых модулей (1780+ строк нового кода)
✅ Архитектура полностью спроектирована
✅ Тесты написаны
✅ Примеры готовы
Завтра свежим взглядом:
- Интегрируем с существующим кодом
- Тестируем
- Удаляем старое
- Радуемся результату 🎉
Что скажешь?