feat(temp-notes): кнопка «Move to trash» в баннере временной заметки #277
Reference in New Issue
Block a user
Delete Branch "feat/273-temp-note-delete"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
closes #273. В баннере временной заметки добавил вторую кнопку — «Move to trash» (мягкое удаление в корзину сейчас, не дожидаясь TTL), рядом с «Make permanent».
Переиспользует тот же soft-delete путь, что меню дерева/шапки —
useTreeMutation(page.spaceId).handleDelete(page.id): оптимистичное удаление узла, undo-тост «Page moved to trash» (8с), простановкаdeletedAtв кеше, переход на домашнюю спейса (открытая страница удаляется → баннер размонтируется). Без модалки подтверждения (конвенция проекта = undo-тост). Видна только при праве Edit.Только клиент, один файл. Сервер/конвертеры/i18n не трогал (ключи
Move to trash/Make permanentуже есть). Undo восстанавливает временность заметки.How verified
tsc --noEmit0;eslintфайла 0 (из apps/client). Тест-файла у баннера нет.Checklist
Ревью
703b88316(closes #273) — кнопка «Move to trash» в баннере временной заметки. 1 файл, фан-аут на coherence/regressions + сверка по коду.Вердикт: PASS. Мягкое удаление переиспользует уже отревьюленный путь, обвязка безопасна. Готово к мержу.
Что проверено
pageопределён к моменту клика: раннийif (!page?.temporaryExpiresAt || page?.deletedAt) return null;(стр. 44) отрабатывает ДО рендера кнопки →handleTrashNowсpage.idне может кинуть на undefined.useTreeMutation(page?.spaceId ?? "")— тот же паттерн, что уже вpage-header-menu.tsx; самhandleDeleteаргументspaceIdне использует (навигация поspaceSlugиз useParams), хук зовётся безусловно — правила хуков соблюдены.trashPage===handleDelete, что зовут меню дерева/шапки — оптимистичное удаление + undo-тост «Page moved to trash» +deletedAtв кеше + редирект на домашнюю (размонтирует баннер). Идентичное, уже проверенное поведение.canEdit(Edit) — как везде в проекте (delete покрывается Edit; серверная авторизация — реальный энфорс). i18n-ключMove to trashприсутствует (en/ru и др.).setIsDeleting(false)в finally:handleDeleteглотает свои ошибки →trashPageне реджектит, finally всегда чистит спиннер; на успехе баннер размонтируется (React 18 — без варнинга/утечки), на ошибке — спиннер снят. Ок.Объективные проверки (запущены в окружении ревью)
tsc --noEmit(apps/client) — exit 0 (после сборки workspace-пакета editor-ext).eslintв окружении ревью НЕ поднялся (конфиг клиента не резолвит@tanstack/eslint-plugin-query— проблема окружения, не кода) → базис eslint = прогон кодера + вычитка. Тест-файла у баннера нет.Полный 9-аспектный фан-аут (по запросу vvzvlad), поверх round-1 approve. security/stability/regressions/test-coverage/conventions/documentation/simplification/architecture/coherence — отдельный субагент на каждый.
Вердикт: PASS подтверждён. Кодовых DO нет; approve в силе. Полный проход нашёл только две ПРОЗАИЧЕСКИЕ неточности (не блокеры, кода не касаются):
useRestorePageMutationполагается на то, что сервер НУЛИТtemporaryExpiresAtна restore → страница возвращается ПОСТОЯННОЙ (комментарий в page-query.ts это прямо фиксирует). Поправить текст PR на «Undo восстанавливает как постоянную».navigate(getSpaceUrl)), т.к. баннер читает кеш поslugId, а stamp пишется поuuid— та же особенность, что уpage-header-menu.tsx. Опц.: подрезать упоминание stamp'а.Подтверждено чистым по всем 9: authz энфорсится сервером (
validateCanEdit, spaceId с сервера — не IDOR); нет краша на page-undefined (early-return) / пустом spaceId (handleDeleteего не использует) / setState-после-unmount (React18 no-op); regressions нет («Make permanent» байт-идентична); реюз canonicalhandleDelete(архитектурно верно); i18n-ключ есть; coherence — end-to-end чистый handoff к DeletedPageBanner без флика. Тест не нужен (нет тестов у сиблинг-баннеров, тонкий wire-up).