Кнопка «Ответить» у каждого сообщения: цитата показывается в новом
сообщении и в поле ввода, отмена — ✕ или Escape, переключение треда
сбрасывает ответ. Клик по цитате прокручивает ленту к исходному
сообщению и подсвечивает его.
Цитата повторяет вложение оригинала: имя файла с иконкой, для
картинок — миниатюра WebP через существующий /api/chat/files/:token.
- chat_messages.reply_to_id: FK на саму таблицу с ON DELETE SET NULL,
миграция и ensureChatTables, индекс
- POST .../messages принимает reply_to_id и проверяет, что родитель
в этом же треде и не удалён; вложения при ошибке подчищаются
- цитата едет в списке, при отправке и в SSE правки/удаления, так что
у собеседника она обновляется по живому каналу
- бэкап: reply_to_id в normalizeRestoreData, при restore ссылка
принимается только если родитель восстановился, иначе NULL
- shorts: скрывать поле «Резидент» при добавлении без разбора строк
- purgeScheduledDeletions() теперь чистит short_messages по purge_at
- short_messages добавлена в BACKUP_TABLES/BACKUP_SEQUENCE_TABLES,
normalizeRestoreData и restore (поле автора проверяется на живость)
- сид short_messages_retention_days перенесён и в db/init.sql
- used_at по умолчанию берётся в зоне приложения (appTodayIso),
а не по UTC
- поле «Использовано» скрыто при создании, открыто при правке
- удалён пустой escInline и мёртвые data-message/canEdit: сырой
текст сообщения попадал в атрибут неэкранированным
Таблица short_messages (резидент, дата занятия, тема, текст, отметка
об использовании), CRUD API /api/shorts с мягким удалением и плановой
очисткой через purge_at, ИИ-коррекция текста по профилю, страница
shorts.html с массовым добавлением разбором строк «ФИО - текст».