Page hierarchy: delete modes (promote/subtree) and trash semantics #107

Closed
opened 2026-07-14 09:32:43 +02:00 by fable-5 · 1 comment
Collaborator

Context

Deleting a page that has children must support two semantics, chosen by the user per case in the UI: promote the children to the deleted page's parent, or trash the whole subtree. Restore must be safe regardless of the order pages are restored in.

Scope

  • DELETE /pages/:id?mode=promote|subtree (default promote; the mode is irrelevant when the page has no live children) in apps/api/src/pages/pages.controller.ts / pages.service.ts.
    • promote: children's parentId := deleted page's parentId, same transaction as the soft delete.
    • subtree: soft-delete all live descendants with the same deletedAt/deletedBy. Requires write permission on every live descendant, otherwise 403 (no partial deletes; trash access equals write capability per ADR 0013).
  • Trashed pages keep their parentId.
  • TrashService.restore (apps/api/src/trash/trash.service.ts): re-attach the restored page to its nearest live ancestor, else root (walk the ancestor chain; update parentId on restore). This makes restore order-independent.
  • TrashService.purgePage: promote any remaining children (live or trashed) to the purged page's parent before deleting the row (FK SetNull stays as backstop only).

Acceptance criteria

  • Promote-delete reparents all children in one transaction; sidebar list reflects it immediately.
  • Subtree-delete trashes every descendant with one timestamp; a descendant the caller cannot write blocks the whole operation with 403.
  • Restoring a child whose ancestors are still trashed lands under the nearest live ancestor or at root; restoring the parent afterwards does not re-claim it.
  • Purging a parent never orphans children FK-wise and promotes them to the grandparent.
  • DB e2e tests in the trash suite cover all four bullets.

Technical notes

  • Trash list stays flat (deletedAt desc) — no tree rendering in trash.
  • Keep the existing single-page delete path as the promote default so current clients keep working unchanged.

Dependencies

Requires #106.

Size

~1 day.

## Context Deleting a page that has children must support two semantics, chosen by the user per case in the UI: promote the children to the deleted page's parent, or trash the whole subtree. Restore must be safe regardless of the order pages are restored in. ## Scope - `DELETE /pages/:id?mode=promote|subtree` (default `promote`; the mode is irrelevant when the page has no live children) in `apps/api/src/pages/pages.controller.ts` / `pages.service.ts`. - `promote`: children's `parentId` := deleted page's `parentId`, same transaction as the soft delete. - `subtree`: soft-delete all live descendants with the same `deletedAt`/`deletedBy`. Requires write permission on **every** live descendant, otherwise 403 (no partial deletes; trash access equals write capability per ADR 0013). - Trashed pages keep their `parentId`. - `TrashService.restore` (`apps/api/src/trash/trash.service.ts`): re-attach the restored page to its nearest **live** ancestor, else root (walk the ancestor chain; update `parentId` on restore). This makes restore order-independent. - `TrashService.purgePage`: promote any remaining children (live or trashed) to the purged page's parent before deleting the row (FK `SetNull` stays as backstop only). ## Acceptance criteria - [ ] Promote-delete reparents all children in one transaction; sidebar list reflects it immediately. - [ ] Subtree-delete trashes every descendant with one timestamp; a descendant the caller cannot write blocks the whole operation with 403. - [ ] Restoring a child whose ancestors are still trashed lands under the nearest live ancestor or at root; restoring the parent afterwards does not re-claim it. - [ ] Purging a parent never orphans children FK-wise and promotes them to the grandparent. - [ ] DB e2e tests in the trash suite cover all four bullets. ## Technical notes - Trash list stays flat (`deletedAt desc`) — no tree rendering in trash. - Keep the existing single-page delete path as the `promote` default so current clients keep working unchanged. ## Dependencies Requires #106. ## Size ~1 day.
fable-5 added this to the M12 — Page hierarchy & knowledge graph milestone 2026-07-14 09:32:43 +02:00
fable-5 added the
backend
label 2026-07-14 09:32:43 +02:00
Author
Collaborator

Implemented in 12ff3c0. DELETE /pages/:id?mode=promote|subtree — promote (default) re-attaches live children to the page's parent; subtree trashes all live descendants with one timestamp and requires write on every one of them (403 otherwise, no partial deletes). Trashed pages keep parentId; restore re-attaches to the nearest live ancestor (else root — order-independent, parents never re-claim); purge promotes remaining children. tree-trash.e2e.db.test.ts covers all four acceptance criteria.

Implemented in 12ff3c0. `DELETE /pages/:id?mode=promote|subtree` — promote (default) re-attaches live children to the page's parent; subtree trashes all live descendants with one timestamp and requires write on every one of them (403 otherwise, no partial deletes). Trashed pages keep `parentId`; restore re-attaches to the nearest live ancestor (else root — order-independent, parents never re-claim); purge promotes remaining children. `tree-trash.e2e.db.test.ts` covers all four acceptance criteria.
Sign in to join this conversation.
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: stwaidele/dorfteich#107
No description provided.