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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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