From 7bbd087cb7c656fe97f8be1051a77aa0a6ce7c6b Mon Sep 17 00:00:00 2001 From: Ziyang Guo <121015044+RerankerGuo@users.noreply.github.com> Date: Fri, 24 Jul 2026 02:25:00 +0800 Subject: [PATCH] fix(pages): restore soft-deleted rows on putPage (#2779) --- src/core/pglite-engine.ts | 1 + src/core/postgres-engine.ts | 1 + test/e2e/engine-parity.test.ts | 22 ++++++++++++++++++++++ test/pglite-engine.test.ts | 24 ++++++++++++++++++++++++ 4 files changed, 48 insertions(+) diff --git a/src/core/pglite-engine.ts b/src/core/pglite-engine.ts index f74e4e964..b8b23aa0e 100644 --- a/src/core/pglite-engine.ts +++ b/src/core/pglite-engine.ts @@ -1048,6 +1048,7 @@ export class PGLiteEngine implements BrainEngine { frontmatter = EXCLUDED.frontmatter, content_hash = EXCLUDED.content_hash, updated_at = now(), + deleted_at = NULL, effective_date = COALESCE(EXCLUDED.effective_date, pages.effective_date), effective_date_source = COALESCE(EXCLUDED.effective_date_source, pages.effective_date_source), import_filename = COALESCE(EXCLUDED.import_filename, pages.import_filename), diff --git a/src/core/postgres-engine.ts b/src/core/postgres-engine.ts index 6deaad784..b58d748f6 100644 --- a/src/core/postgres-engine.ts +++ b/src/core/postgres-engine.ts @@ -1110,6 +1110,7 @@ export class PostgresEngine implements BrainEngine { frontmatter = EXCLUDED.frontmatter, content_hash = EXCLUDED.content_hash, updated_at = now(), + deleted_at = NULL, effective_date = COALESCE(EXCLUDED.effective_date, pages.effective_date), effective_date_source = COALESCE(EXCLUDED.effective_date_source, pages.effective_date_source), import_filename = COALESCE(EXCLUDED.import_filename, pages.import_filename), diff --git a/test/e2e/engine-parity.test.ts b/test/e2e/engine-parity.test.ts index 3abe512c5..85b7a0144 100644 --- a/test/e2e/engine-parity.test.ts +++ b/test/e2e/engine-parity.test.ts @@ -371,6 +371,28 @@ describeBoth('Engine parity — Postgres vs PGLite', () => { expect(pglitePage!.title).toBe('V2'); }); + test('putPage restores soft-deleted rows on both engines', async () => { + const slug = 'notes/put-page-restore-parity'; + for (const engine of [pgEngine, pgliteEngine]) { + await engine.putPage(slug, { + type: 'note', + title: 'Before delete', + compiled_truth: 'before', + timeline: '', + }); + await engine.softDeletePage(slug, { sourceId: 'default' }); + expect(await engine.getPage(slug, { sourceId: 'default' })).toBeNull(); + + await engine.putPage(slug, { + type: 'note', + title: 'After restore', + compiled_truth: 'after', + timeline: '', + }); + expect((await engine.getPage(slug, { sourceId: 'default' }))?.title).toBe('After restore'); + } + }); + test('v0.41.19.0 deletePages parity: both engines return same confirmed-deleted slugs', async () => { const realSlugs = ['wiki/dpp-1', 'wiki/dpp-2', 'wiki/dpp-3']; for (const slug of realSlugs) { diff --git a/test/pglite-engine.test.ts b/test/pglite-engine.test.ts index acba63b16..b41624267 100644 --- a/test/pglite-engine.test.ts +++ b/test/pglite-engine.test.ts @@ -88,6 +88,30 @@ describe('PGLiteEngine: Pages', () => { expect(matches.length).toBe(1); }); + test('putPage restores a soft-deleted page', async () => { + const slug = 'notes/restore-on-put'; + await engine.putPage(slug, testPage); + await engine.upsertChunks(slug, [{ + chunk_index: 0, + chunk_text: 'restored visibility marker', + chunk_source: 'compiled_truth', + token_count: 3, + }]); + await engine.softDeletePage(slug, { sourceId: 'default' }); + expect(await engine.getPage(slug)).toBeNull(); + expect((await engine.searchKeyword('restored visibility marker')).map(result => result.slug)).not.toContain(slug); + + const restored = await engine.putPage(slug, { + ...testPage, + title: 'Restored Title', + compiled_truth: 'restored visibility marker', + }); + + expect(restored.title).toBe('Restored Title'); + expect((await engine.getPage(slug))?.title).toBe('Restored Title'); + expect((await engine.searchKeyword('restored visibility marker')).map(result => result.slug)).toContain(slug); + }); + test('getPage returns null for missing slug', async () => { const result = await engine.getPage('nonexistent/slug'); expect(result).toBeNull();