Skip to content

fix: reject trashed Content writes and recover restored editors - #4607

Draft
3mdistal wants to merge 3 commits into
mainfrom
codex/content-deletion-reliability
Draft

fix: reject trashed Content writes and recover restored editors#4607
3mdistal wants to merge 3 commits into
mainfrom
codex/content-deletion-reliability

Conversation

@3mdistal

@3mdistal 3mdistal commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Problem

A Content page could keep accepting writes from an already-open editor after it entered Trash. Collaboration also ignored rejected update responses, leaving stale client state eligible for replay. A recipient could also retain a revoked page in a mounted list because revocation events reached only the actor. Separately, the page error latch could survive a successful fetch, so restoring a page left its editor on an error screen until Retry.

Approach

Treat deletion as a transactional write boundary and a terminal collaboration response. Restore admits the page only after a successful server fetch; optimistic cache seeds cannot clear a prior failure. Rejected edits use Content's existing private recovery-draft save path.

Changes

  • Lock and check live document state in title/body, property, block/batch, history restoration, and comment mutations while preserving current access checks and lock ordering.

  • Add optional source lifecycle enforcement to core collaboration persistence. Failed mutations evict cached state; typed missing/trashed responses stop the client transport and prevent pending updates from being replayed on reconnect.

  • Track authoritative document fetches and key editor sessions by document context. Refresh affected document contexts, lists, Recent, and Trash after deletion/restoration, including external action events.

  • On recovery conflict, show the current saved page beside the retained draft and require explicit Keep my version with the displayed version check; concurrent changes require another review. Update all 11 locale labels.

  • Keep preview Delete canonical, use an explicit favorite update for Unpin, and return keyboard focus to the page menu after Cancel.

  • Notify the recipient after an authorized direct-user share is actually removed. The generic event carries no resource details and routes across active organizations. Revocation success remains distinct from whether notification recording is confirmed; the additive result contract is documented in English and all locale mirrors.

Validation

Focused checks passed: core persistence/client 83 tests, mounted collaboration HTTP routes 24 tests, Content mutation suites 80 tests, comment suites 46 tests, editor/cache 135 tests, and draft recovery 7 tests. Core build and Content TypeScript passed. An additional 46 sharing, notification-failure, and recipient-routing tests passed; the corrected lifecycle suite passed 42 tests and scoped mutation fixtures passed 11. Tests cover typed rejection, rollback/cache eviction, stale writes, current access compatibility, fetch/cache ordering, and recovery conflicts.

Browser checks on isolated local data covered two-tab ancestor deletion/restoration without reload, current unavailable-page restore, Retry, new page/child/database creation, preview Delete/Unpin, Cancel focus, and responsive layouts. A deliberately stale editor received collaboration 409 DOCUMENT_TRASHED and SQL 409, became read-only, and saved its rejected text as a separate draft. After restore, the saved body remained unchanged until explicit Keep my version; both editors then read the chosen draft. Recovery comparison fits 390px.

Current-head Linux CI is fully green: lint/format, typechecking, build, security guards, Content parity, Content database tests and PostgreSQL locking, core integration, and targeted fast tests all passed. Earlier CI failures in formatting, obsolete Trash assertions, and unscoped test fixtures were corrected. Local Windows guard limitations remain recorded in the QA log; no guard or baseline was weakened. All recovery labels and sharing-result documentation were updated in English and their locale mirrors.

Composed cross-account browser replay passed: an owner revoked a direct user grant while the recipient, in another active organization, kept Recent mounted. The action reported notification recording confirmed; within about five seconds, a fresh Recent response omitted the page and the mounted link disappeared without reload. This verifies normal direct-user notification, not guaranteed delivery during infrastructure failure.

Limits and coordination

No schema migration or deployment is included. Core lifecycle enforcement is opt-in; Content enables it. Cascade collection, multi-database surviving-page reads, and new Trash/recovery interfaces belong to companion changes. This patch supports both existing lifecycle action responses and companion affected-ID metadata.

Direct-user revocation notification is covered; role changes and group, organization, or public-audience refresh are outside this repair. Notification status is not a delivery guarantee, and unconfirmed recording has no durable retry. The separate collaboration permission-revocation race remains unverified and is not claimed fixed. New injected lifecycle and notification regressions use PGlite; the existing Linux PostgreSQL locking lane also passed. These checks do not establish correctness of the separately scoped authorization-revocation race. Content's changelog command refused because its changelog is disabled; this change does not enable it.

@netlify

This comment has been minimized.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant