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-код не менялся.