From edc5dae1038ac5a26cec9ad636f197284d60bfcd Mon Sep 17 00:00:00 2001 From: claude-stand Date: Thu, 2 Jul 2026 19:21:21 +0300 Subject: [PATCH] fix(git-sync): honor a git-side page clear instead of diverging (review warning) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clearing a page's body in git advanced the vault ref past the empty commit, but the persistence store-side empty-guard rejected the empty write (reloading the non-empty DB content) — so Docmost kept the old body while the vault held the empty one, a permanent silent vault<->Docmost divergence that never re-detects. A git-sync write is authoritative and its content IS the vault file, so an empty incoming doc there is a DELIBERATE clear (no transient-glitch empties for a file-sourced write). Allow it (lastUpdatedSource==='git-sync'), mirroring the #251 intentional-clear allowance for the user-signalled source. Verified on stand: a git-side body clear takes the page body 28 -> 0 (page not trashed); previously it stayed 28 (diverged). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../extensions/persistence.extension.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/server/src/collaboration/extensions/persistence.extension.ts b/apps/server/src/collaboration/extensions/persistence.extension.ts index 5824d0ce..8653d974 100644 --- a/apps/server/src/collaboration/extensions/persistence.extension.ts +++ b/apps/server/src/collaboration/extensions/persistence.extension.ts @@ -329,17 +329,25 @@ export class PersistenceExtension implements Extension { // flag via that same hoisted consume (a "cleared then retyped" // sequence can't leave a usable one behind). const incomingEmpty = isEmptyParagraphDoc(tiptapJson as any); + // A git-sync write is authoritative and its content IS the vault file: + // an empty incoming doc there means the user DELIBERATELY cleared the + // page's markdown in git (there is no "transient glitch empty" for a + // file-sourced write). Honor it, otherwise the empty-guard rejects the + // clear, the vault ref has already advanced past the empty commit, and + // vault<->Docmost diverge permanently (review warning). This mirrors the + // #251 intentional-clear allowance for a different authoritative source. + const gitSyncClear = lastUpdatedSource === 'git-sync'; if ( incomingEmpty && page.content && !isEmptyParagraphDoc(page.content as any) ) { - if (allowIntentionalClear) { + if (allowIntentionalClear || gitSyncClear) { this.logger.debug( `Intentional clear for ${pageId}: persisting empty doc over ` + - `non-empty content (user-signalled)`, + `non-empty content (${gitSyncClear ? 'git-sync' : 'user-signalled'})`, ); - // fall through — the empty write is allowed exactly once. + // fall through — the empty write is allowed. } else { this.logger.warn( `Skipping store for ${pageId}: empty live doc would overwrite ` +