Skip to content

pages: trash cascade implemented twice, plus raw non-cascading writers — the class of bug repair-orphaned-trashed-pages.ts fixes can recur #2157

Description

@2witstudios

What

"This subtree is trashed" is stored N times — every page carries its own isTrashed/trashedAt (schema/core.ts:45,87), pages.parentId has no FK (core.ts:91), and consistency depends entirely on app-level recursion. That recursion exists in two independent implementations, and several writers skip it entirely:

Cascade implementations (duplicated logic)

  • apps/web/src/services/api/page-service.ts:286-322 recursivelyTrash (canonical)
  • apps/web/src/lib/ai/tools/page-write-tools.ts:233-278 — its own copy (withChildren recursion / move-children-up), mirroring pageService by hand

Non-cascading raw writers (the drift generators)

  • packages/lib/src/repositories/page-repository.ts:222-245 (trash, trashMany — single-level)
  • lib/integrations/google-calendar/sync-service.ts:319,505 — direct db.update(pages).set({isTrashed:true})
  • lib/machines/machine-settings-runtime.ts:147
  • api/pages/[pageId]/tasks/[taskId]/route.ts:560 — trashes a single task page even though task pages can have children

Proof the drift class is real: scripts/repair-orphaned-trashed-pages.ts — "Historically, trashing a page did NOT trash its children … those live descendants surfaced as bogus top-level items" — a recursive-CTE repair script. Any of the remaining non-cascading writers can recreate exactly the bug that script repairs.

Proposed direction

One cascade, one owner: move recursivelyTrash into the shared service/repository layer, have the AI tool call it instead of maintaining a copy, and make the raw single-level writers either call the cascade or be renamed to make their non-cascading nature impossible to reach for by accident (trashSinglePageUnsafe). Alternatively, derive trash state at read time from the nearest trashed ancestor (recursive CTE / closure table), so subtree-trash is stored once and the repair script class disappears entirely.


Filed from the sources-of-truth audit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions