From 264c213fbe6f78d2cba5cb52c0479c0b70dc700a Mon Sep 17 00:00:00 2001 From: john Date: Wed, 16 Sep 2026 08:53:58 +0800 Subject: [PATCH] fix(mindspace): use lightweight page sync for background list refresh Background page list sync was calling syncAndDeliver over the remote adapter, which could exceed the 60s timeout for heavy users and block Portal. Route list sync through pageSyncService only via listSync while keeping full delivery on Finish and agent-run paths. Co-authored-by: Cursor --- scripts/verify-mindspace-page-sync-guards.mjs | 13 ++++++++++++- server.mjs | 8 +++++--- server/portal-mindspace-page-core-routes.mjs | 2 +- server/portal-mindspace-page-core-routes.test.mjs | 13 ++++++++----- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/scripts/verify-mindspace-page-sync-guards.mjs b/scripts/verify-mindspace-page-sync-guards.mjs index 36f65f1..d1129ea 100644 --- a/scripts/verify-mindspace-page-sync-guards.mjs +++ b/scripts/verify-mindspace-page-sync-guards.mjs @@ -20,7 +20,18 @@ const contractSource = read('mindspace-server-adapter-contract.mjs'); assertIncludes( 'server.mjs syncUserGeneratedPages', serverSource, - 'await mindSpacePageSync.syncUserGeneratedPages(userId);', + 'mindSpacePageSync.syncUserGeneratedPages(userId', +); +assertIncludes( + 'server.mjs list page sync bypasses full delivery', + serverSource, + 'listSync = false', +); +const pageCoreRoutesSource = read('server/portal-mindspace-page-core-routes.mjs'); +assertIncludes( + 'portal page list background sync', + pageCoreRoutesSource, + 'syncUserGeneratedPages(userId, { listSync: true })', ); if (serverSource.includes("mindSpaceServerRuntime.adapterKind === 'remote'")) { throw new Error( diff --git a/server.mjs b/server.mjs index f836c9d..c95793b 100644 --- a/server.mjs +++ b/server.mjs @@ -1292,7 +1292,7 @@ async function listSessionPublicHtmlRelativePaths(userId, sessionId, { sinceMs = async function syncUserGeneratedPages( userId, - { sessionId = null, sinceMs = null, onlyRelativePaths = null } = {}, + { sessionId = null, sinceMs = null, onlyRelativePaths = null, listSync = false } = {}, ) { if (!userId) return; // Agent-run/Finish delivery must stay scoped to the current conversation. @@ -1330,11 +1330,13 @@ async function syncUserGeneratedPages( : sessionId ? (discoveredRelativePaths?.length ? discoveredRelativePaths : recentWorkspaceRelativePaths) : null; - if (workspacePageDeliver?.syncAndDeliver) { + if (!listSync && workspacePageDeliver?.syncAndDeliver) { return await workspacePageDeliver.syncAndDeliver(userId, { pageDataRelativePaths }); } if (!mindSpacePageSync) return; - return await mindSpacePageSync.syncUserGeneratedPages(userId); + return await mindSpacePageSync.syncUserGeneratedPages(userId, { + onlyRelativePaths: pageDataRelativePaths, + }); } async function resolveChatSaveBundle(user, h5Root, input = {}) { diff --git a/server/portal-mindspace-page-core-routes.mjs b/server/portal-mindspace-page-core-routes.mjs index 3f5fde4..f6adba6 100644 --- a/server/portal-mindspace-page-core-routes.mjs +++ b/server/portal-mindspace-page-core-routes.mjs @@ -46,7 +46,7 @@ export function createMindSpacePageListSyncScheduler({ lastStartedAtByUser.set(userId, currentTime); const task = Promise.resolve() - .then(() => syncUserGeneratedPages(userId)) + .then(() => syncUserGeneratedPages(userId, { listSync: true })) .catch((error) => { logger?.warn?.( `[MindSpace] background page list sync failed for user ${userId}:`, diff --git a/server/portal-mindspace-page-core-routes.test.mjs b/server/portal-mindspace-page-core-routes.test.mjs index 48c1f0a..6548dc3 100644 --- a/server/portal-mindspace-page-core-routes.test.mjs +++ b/server/portal-mindspace-page-core-routes.test.mjs @@ -230,8 +230,8 @@ test('page list background sync coalesces in-flight and throttled requests', asy const scheduler = createMindSpacePageListSyncScheduler({ now: () => currentTime, minIntervalMs: 30_000, - syncUserGeneratedPages(userId) { - calls.push(userId); + syncUserGeneratedPages(userId, options) { + calls.push({ userId, options }); return new Promise((resolve) => { resolvers.push(resolve); }); @@ -242,19 +242,22 @@ test('page list background sync coalesces in-flight and throttled requests', asy const second = scheduler('user-1'); assert.equal(first, second); await Promise.resolve(); - assert.deepEqual(calls, ['user-1']); + assert.deepEqual(calls, [{ userId: 'user-1', options: { listSync: true } }]); resolvers.shift()?.({ created: 0, updated: 0, skipped: 0 }); await first; currentTime += 1_000; assert.equal(scheduler('user-1'), null); - assert.deepEqual(calls, ['user-1']); + assert.deepEqual(calls, [{ userId: 'user-1', options: { listSync: true } }]); currentTime += 30_000; const third = scheduler('user-1'); await Promise.resolve(); - assert.deepEqual(calls, ['user-1', 'user-1']); + assert.deepEqual(calls, [ + { userId: 'user-1', options: { listSync: true } }, + { userId: 'user-1', options: { listSync: true } }, + ]); resolvers.shift()?.({ created: 0, updated: 0, skipped: 0 }); await third; });