- LdapRepository требует активное соединение в конструкторе
- Добавлен вызов _conn() для получения LDAP соединения
- Исправлена ошибка: TypeError: LdapRepository.__init__() missing 1 required positional argument: 'conn'
- Теперь используется: LdapRepository(_conn())
- Использована встроенная система пермишенов DRF вместо ручной проверки
- Добавлен ldap_info в required_perms_by_action с правом view_ldap_info
- Добавлен ldap_info в get_permissions() для применения IsSelfOrStaff
- Удалена избыточная ручная проверка прав внутри метода ldap_info()
- Проверка прав теперь выполняется автоматически через get_object()
- Пользователь может видеть свой LDAP логин (IsSelfOrStaff)
- Админы и пользователи с view_ldap_info могут видеть любые логины
- Добавлено поле с LDAP логином (sAMAccountName) в профиле сотрудника
- Реализован blur-эффект с раскрытием по клику для безопасности
- Создан API endpoint /api/v1/employees/{id}/ldap-info/ для получения LDAP данных
- Добавлен новый пермишен employees.view_ldap_info
- Стили: SCSS компонент _ldap-login.scss с анимациями и состояниями
- JavaScript: класс LdapLoginField с AJAX запросами и обработкой ошибок
- Доступ только для staff или пользователей с правом view_ldap_info
- Данные запрашиваются из Active Directory on-demand (не хранятся в БД)
- Исправлен PollSerializer для создания опросов с опциями
- Добавлено поле read_only_fields для корректной валидации
- Реализована отправка WebSocket уведомлений при создании опроса
- Исправлен импорт serialize_message из communications.serialization
- Добавлен обработчик poll_update в UserConsumer для WebSocket событий
- Исправлен эндпоинт /results/ для возврата user_voted_option_ids
- Добавлено поле voters в PollOptionSerializer для списка проголосовавших
- Реализована инициализация poll виджетов после рендера сообщений
- Добавлено игнорирование poll_update в notification-manager
- Исправлены стили для request list badges (height, word-break, flex)
- Восстановлены стили team-wheel (.wheel-focus, .scroll-arc) из git истории
- Реализовано отображение forwarded message info в messageRendererV2
- Множественные улучшения UI и UX для опросов
Fixes: создание опросов, голосование, отображение результатов, список проголосовавших
- Create communications/serialization.py with serialize_message function
- Update imports in realtime/consumers.py, api views and serializers
- Remove communications/consumers.py (ChatConsumer and ChatListConsumer now obsolete)
- UserConsumer in realtime/consumers.py handles all WebSocket connections
- Add seed_test_data management command for database population
Обновлен consumers.py:
- serialize_message: использование forward_metadata вместо forwarded_from_*
- _get_message: prefetch forward_metadata и MessageEditHistory
- _edit_message: создание MessageEditHistory записей вместо JSON
Обновлен serializers.py:
- MessageCreateSerializer: удалены forwarded_from_* из fields
- Чистка Meta.fields от удаленных полей модели
Обратная совместимость:
- WebSocket payload не изменился (forwarded_from структура сохранена)
- API endpoints работают без изменений
Ref: COMMUNICATIONS_API_REFACTORING.md
Создание новых нормализованных моделей:
- MessageEditHistory: история редактирования (вместо JSON поля)
- MessageForwardMetadata: метаданные пересылки (OneToOne связь)
Миграции:
- 0025: создание MessageEditHistory и MessageForwardMetadata
- 0026: миграция данных из JSON/полей в новые таблицы (без потерь)
- 0027: удаление старых денормализованных полей
Удалены из Message:
- edit_history (JSONField) → MessageEditHistory модель
- forwarded_from_author (FK)
- forwarded_from_message_id (IntegerField)
- forwarded_from_created_at (DateTimeField)
- forwarded_from_chat_name (CharField)
- thread_reply_count (IntegerField)
Обновлена admin.py:
- Добавлен MessageEditHistoryInline
- Зарегистрированы новые модели
Преимущества:
- Соблюдение 3НФ (нормальная форма Бойса-Кодда)
- FK constraints для целостности данных
- Удобные запросы через ORM (вместо JSON)
- История редактирования с автором и временем
- Счетчик пересылок (forward_count)
Ref: docs/reports/MODELS_ARCHITECTURE_AUDIT.md
- Удалены 4 модели: ForwardedMessage, MessageReply, ChatAccessPermission, CrossChatMessage
- Миграции: 0023 (удаление MessageReply и др.), 0024 (удаление ForwardedMessage)
- Обоснование: модели не использовались в коде, 0 записей в БД
- Уменьшено количество моделей с 15 до 11 (-27%)
- Очищено ~400 строк мертвого кода
Ref: docs/reports/MODELS_ARCHITECTURE_AUDIT.md
Изменения:
- Новый endpoint /api/v1/communications/messages/upload-temp/ для загрузки файлов без создания сообщения
- Поле message в MessageAttachment теперь nullable для временных вложений
- Логика перемещения attachments между сообщениями при редактировании
- Frontend использует upload-temp вместо создания временных сообщений
- Celery задача cleanup_orphaned_attachments для очистки забытых файлов (каждый час)
- Исправлена проблема с WebSocket обновлениями при редактировании
- Удалены debug логи после успешного тестирования
Технические детали:
- communications/models.py: добавлено null=True, blank=True к MessageAttachment.message
- communications/migrations/0022: миграция для изменения поля
- api/v1/communications/viewsets.py: новый action upload_temp() + логика перемещения в update()
- static/js/components/chatComposer.js: использование upload-temp вместо upload
- communications/tasks.py: новая Celery задача для очистки старых orphaned attachments
- eusrr_backend/settings.py: добавлен CELERY_BEAT_SCHEDULE с задачей очистки
Проблема:
- У медиафайлов были большие пустые поля сверху и снизу
- Причины: padding/margin от .attachment-item и .bubble .attachment-item a
- inline стили конфликтовали с CSS
Решение:
CSS (_chat-detail.scss):
- Добавлен модификатор .attachment-item--media для медиа-контейнеров
- .attachment-item--media имеет margin-bottom: 0 (вместо 8px)
- Вложенные <a> в медиа-контейнерах: padding: 0, line-height: 0
- .chat-media: display: block (было inline-block), vertical-align: top
- Селектор для файлов: .attachment-item:not(.attachment-item--media)
JavaScript (messageRendererV2.js):
- Добавлен класс attachment-item--media к изображениям и видео
- Убраны inline стили (style="display: block; ...")
- Чистая разметка без конфликтов
Результат: медиафайлы отображаются плотно без пустых полей
Проблема:
- Изображения и видео отображались только как ссылки с иконками
- Нет preview изображений в composer
- Вся инфраструктура существовала (DB поля, CSS, WebSocket), но не была подключена
Реализация:
messageRendererV2.js (_renderAttachment):
- Добавлена проверка file_type === 'image': рендеринг <img class="chat-media chat-media--image">
- Добавлена проверка file_type === 'video': рендеринг <video class="chat-media chat-media--video"> с controls
- Используется thumbnail URL для изображений (если доступен), иначе file_url
- Атрибуты: loading="lazy", playsinline, preload="metadata" для оптимизации
- Файлы (PDF, Word, Audio) рендерятся как ссылки с иконками
chatComposer.js (renderPreview):
- Для изображений: показываем thumbnail через URL.createObjectURL
- Для других файлов: иконка (как раньше)
- Добавлен URL.revokeObjectURL при удалении для предотвращения утечек памяти
- Preview размеры: 60x60px с border-radius и object-fit: cover
CSS (_chat-detail.scss):
- Все стили .chat-media уже были готовы:
* max-width: min(640px, 85vw)
* border-radius: 12px
* object-fit: cover (изображения) / contain (видео)
* Responsive sizing для мобильных
- Стили применяются автоматически при рендеринге
Технические детали:
- WebSocket уже передает: file_type, file_url, thumbnail, mime_type
- Database модель MessageAttachment поддерживает thumbnail поле
- CSS классы .chat-media--image и .chat-media--video определены
- Lazy loading для изображений (атрибут loading="lazy")
- Video с preload="metadata" для быстрой загрузки первого кадра
Тестирование: Требуется загрузка реальных изображений/видео для проверки
Проблема:
- Аватары деформировались из-за object-fit на контейнере
- Аватары выравнивались по верху сообщения вместо низа
- Неконсистентная HTML структура: <img class="mini-ava"> vs <div class="mini-ava"><i></i></div>
Решение:
SCSS (_common.scss):
- object-fit: cover перемещен с контейнера на вложенный img
- Добавлен display: block для img элементов
SCSS (_chat-detail.scss):
- Добавлен align-items: flex-end к .d-flex.msg для выравнивания по низу
- .mini-ava рефакторинг: display: flex (было grid), border-radius: 50% (было 9999px)
- Добавлен background и border для контейнера
- Добавлен font-size: 20px для fallback иконки
- object-fit остается только на img тегах
JavaScript (messageRendererV2.js):
- Всегда создавать <div class="mini-ava"> как контейнер
- Вложенный <img> или <i class="bi bi-person-circle"> внутри
- Устранена несогласованность разметки
Рефакторинг архитектуры SCSS (бонус):
- Создан Design System: abstracts/_variables.scss (200+ токенов)
- Создано abstracts/_mixins.scss (40+ миксинов)
- Реорганизована файловая структура: layout/, components/, components/chat/
- Централизованы стили аватаров в _common.scss
- Устранены дубликаты (avatar определялся в 3 файлах)
- Применены design tokens к navbar, page-header
- Создана документация: SCSS_NAMING_CONVENTIONS.md
- Удалено 9 дублирующихся CSS файлов
Компиляция: 280KB, 0 ошибок, production ready
Two types of bottom:
1. Historical bottom (hasMoreAfter=true):
- Button ALWAYS visible (even at scroll bottom)
- Click → loads latest 50 messages to present time
- Renders and scrolls to bottom
2. Present bottom (hasMoreAfter=false):
- Button hidden when isNearBottom()
- Click → smart scroll (to unread or bottom)
Changes:
- _setupSmartButtonVisibility() monitors hasMoreAfter
- _handleScrollButtonClick() loads history if hasMoreAfter
- Added public loadLatest() method
- Emits 'chat:messagesLoaded' event for UI updates
Result:
- Historical view: button always shows, loads to present
- Present view: button hides at bottom, smart unread scroll
- No more stuck in history without way to present
Implements Telegram-like behavior for new messages:
1. Smart Auto-scroll (WS messages):
✅ User at bottom + present time → auto-scroll to new message
✅ User NOT at bottom + present time → show unread badge
❌ Historical view (hasMoreAfter=true) → no action
2. Smart Scroll Button (#scrollBtn):
- Shows unread badge when new messages arrive
- Click behavior:
* Has unread + lastReadId → scroll to first unread
* No unread → scroll to bottom
- Auto-hides badge when user scrolls to bottom
3. Badge Styling:
- Red badge with count on top-right of button
- Pulse animation on appearance
- Button subtle pulse when has unread
Key Logic:
- Only auto-scroll when hasMoreAfter=false (present time)
- Historical bottom (hasMoreAfter=true) doesn't trigger auto-scroll
- Badge resets on: manual scroll to bottom, button click
- Respects user's scroll position unless at bottom
Added:
- Test chat API script
- Date group CSS and JS utilities
- IntersectionObserver modules
- Bidirectional loading test files
- Autoscroll testing checklist
Added:
- Autoscroll audit and reports
- Date divider analysis and Telegram comparison
- Bidirectional loading tests and results
- Date group manager and utilities
- Test scripts and HTML test pages
- CSS for chat date groups
- IntersectionObserver utilities
Problem: After removing autoscroll, last read message wasn't visible on initial load
Solution:
- ✨ NEW: _positionToMessage() - instant viewport positioning WITHOUT scroll animation
- Sets scrollTop BEFORE user sees chat (no visible scrolling)
- Centers last read message with sticky header offset (96px)
- Falls back to bottom position for new chats
Difference:
- ❌ Old: render → scrollIntoView() → visible scroll animation
- ✅ New: render → scrollTop = X → instant positioning
Result:
- Chat opens IMMEDIATELY showing last read message
- NO visible scrolling or animation
- Telegram-like behavior: position is set, not scrolled to
- Natural user experience
BREAKTHROUGH: Discovered append doesn't need autoscroll!
Changes:
- ✅ KEPT: scrollTop correction after prepend (physics, not autoscroll)
Prevents infinite history loading by compensating scrollHeight growth
- ❌ REMOVED: scrollToBottom() - now empty stub
- ❌ REMOVED: scrollToMessage() - now empty stub
- ❌ REMOVED: Initial scroll on chat open
- ❌ REMOVED: Scroll after loadAround
Philosophy shift:
- Old: We control user's scroll position
- New: IntersectionObserver + physics = natural behavior
Results:
- Newer messages load perfectly WITHOUT autoscroll
- Older messages need ONLY physics correction
- User controls their own scroll position
- Natural infinite scroll experience
- Удалено 16 файлов (5560+ строк)
- Убраны все deprecated wrappers
- Мигрированы все компоненты на V2
- Исправлены критические баги
- Задокументированы все изменения
ПРОБЛЕМЫ:
- chatDetailV2.js не передавал lastReadTimestamp в config
- метод _loadAround не имел return statement
- scrollIntoView скроллил родительские контейнеры
ИСПРАВЛЕНИЯ:
✅ Добавлен lastReadTimestamp в readConfig() chatDetailV2.js
✅ Добавлена передача lastReadTimestamp в ChatControllerV2
✅ Исправлен _loadAround - добавлен return loadResult
✅ Заменен scrollIntoView на ручной расчет scrollTop в _scrollToMessage
✅ Добавлено подробное логирование для отладки
РЕЗУЛЬТАТ:
- Чат загружается вокруг последнего прочитанного (16 сообщений вместо 30)
- Скролл к правильной позиции
- Header не прячется под navbar
- IntersectionObserver не срабатывает при инициализации
- Прямой импорт ChatControllerV2 без промежуточных слоев
- Убраны deprecated обертки (window.messageRenderer, window.chatHistoryLoader)
- Меньше кода: 231 vs 264 строки
- Более явная архитектура
- Для тестирования работы в production
- Проблема: lastReadMessageId часто равен 0/null
- Решение: используем lastReadTimestamp (API поддерживает оба формата)
- Добавлен this.lastReadTimestamp в ChatControllerV2
- Передаем оба параметра из chatDetail.js
- anchorValue = lastReadMessageId || lastReadTimestamp
- Исправлена поврежденная структура в chatControllerV2.js
- Логируем сырое значение из data-last-read-message-id
- Логируем решение загрузки (around vs latest)
- Поможет понять почему скролит в низ вместо последнего прочитанного
- проблема: Observer срабатывал сразу после setup даже с проверкой isNearBottom
- isNearBottom() возвращает false когда scrollTop=0 (пользователь вверху)
- решение: игнорируем события Observer в течение 500ms после init
- флаг _isInitializing снимается через setTimeout
- это гарантирует что автозагрузка не произойдет при первом рендере
- проблема: Observer срабатывал сразу после init когда scrollTop=0
- первое сообщение было видимо → автоматическая загрузка истории
- решение: не загружаем историю если пользователь внизу (isNearBottom)
- это предотвращает ложное срабатывание при инициализации
- scrollTo() НЕ работает при первичной загрузке (scrollTop остается 0)
- используется прямое присвоение scrollTop как в legacy коде
- тройной RAF для надежности
- это должно исправить проблему с автозагрузкой истории
- scrollToBottom: использует scrollTop (мгновенно) вместо scrollTo smooth
- scrollToMessage: дефолт behavior изменен с 'smooth' на 'auto'
- это исправляет проблему с незавершенным плавным скроллом при инициализации