1.3.1

Fixed (#389)

  • Реактивное добавление в панель «Избранное» (#389): закладка на сообщении в чате при открытой split-view панели «Избранное» не появлялась в списке до её переоткрытия — FavoritesPanel держал одноразовый снапшот из onMount и не был подписан ни на что реактивное (снятие закладки из панели работало только потому, что сама панель вызывала favoritesStore.toggle() и вручную фильтровала свой список). Теперь favoritesStore публикует реактивное поле lastChange {msgId, agentId, favorited}, заполняемое только успешным toggle() (syncFromMessages при poll-обновлении чата его не трогает — массовая замена не должна выглядеть как действие пользователя; clear() сбрасывает; поле всегда заменяется новым объектом — панель полагается на это в identity-гарде), а панель через $effect точечно обновляет список: добавление — догрузка карточки существующим api.getMessage + prepend + total += 1 без перезагрузки списка и сброса пагинации, снятие — filter + декремент total (в т.ч. для карточек за пределами загруженной страницы). Локальная фильтрация в handleRemoveFavorite («Убрать») удалена — оба направления чат↔панель идут через одно место. Защиты: change.agentId !== agentId; пропуск начального значения lastChange, существовавшего до монтирования (уже отражено в данных loadFavorites — иначе декремент total срабатывал бы при каждом открытии панели); dedupe по id до и после await; пер-проверка favoritesStore.get(msgId) после ответа — если закладку сняли в чате, пока шёл запрос, карточка не добавляется.

Review (#389)

  • ocr review (3 прогона) — закрыты: high «закладку сняли, пока шёл getMessage» (пер-проверка favoritesStore.get после await — карточка и total не мутируются); medium «устаревший total при unfavoritе карточки вне загруженной страницы» (декремент при total > 0 безусловно, смонтировавшийся со старым lastChange эффект нейтрализован identity-гардом начального значения); low «молчаливый catch» (console.warn при ошибке догрузки карточки); low «неочевидное сцепление файлов» (комментарии о контракте «lastChange всегда новый объект» и о перемонтировании панели через {#key} при смене агента). Отклонены с обоснованием: high/medium «пиннинг agentId в async-пути» — посылка «инстанс панели переживает смену агента» неверна, {#key switcher.selectedAgent} на странице чата уничтожает панель, мутации in-flight продолжений достаются мёртвому инстансу (зафиксировано комментарием вместо мёртвого кода); low «SvelteSet для dedupe» — преждевременно при пагинации по 20 (сам ревьюер: not urgent); low «сигнализация неуспеха в handleRemoveFavorite» — toggle() не бросает исключений (внутренний откат), эффект перезапускается только присваиванием нового lastChange, которого при ошибке не происходит — ложного срабатывания нет, UI и так соответствует серверу; «guard msg.agent_id в ответе» — бэкенд скоупит GET /agents/{id}/messages/{msgID} по агенту. Проверки: npm run check (0), npm test (877), make build-webui — зелёные; npm run lint — единственная ошибка предсуществующая (WorkspaceMDViewer.svelte:247, воспроизводится на чистом дереве без изменений задачи); Go-код не менялся.