fix: MindSpace remote sync, thumbnail fallback, and Plaza URL guards

Ensure page sync runs via pageSyncService in remote mode, fall back to
workspace HTML when storage assets are missing, and prevent production
Portal from linking to loopback Plaza URLs baked in at build time.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-05 07:57:04 +08:00
parent 4781bbc156
commit d3239ff292
20 changed files with 646 additions and 58 deletions
+88
View File
@@ -0,0 +1,88 @@
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { createPageSyncService } from './mindspace-page-sync-service.mjs';
import { PUBLISH_ROOT_DIR } from './user-publish.mjs';
test('syncUserGeneratedPages registers html files from publish public dir', async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-page-sync-service-'));
const h5Root = tmp;
const userId = 'user-1';
const publishDir = path.join(h5Root, PUBLISH_ROOT_DIR, userId);
const html =
'<!doctype html><html><head><title>Sync Demo</title></head><body><h1>Hi</h1></body></html>';
await fs.mkdir(path.join(publishDir, 'public'), { recursive: true });
await fs.writeFile(path.join(publishDir, 'public/index.html'), html);
const calls = [];
const pageService = {
createFromChat: async (_userId, input, source) => {
calls.push({ input, source });
return { id: 'page-1' };
},
updatePage: async () => assert.fail('should create, not update'),
};
const pool = {
query: async (sql) => {
if (String(sql).includes('FROM h5_page_records')) return [[]];
return [[]];
},
};
const pageSyncService = createPageSyncService({
pool,
pageService,
assetService: null,
h5Root,
});
const result = await pageSyncService.syncUserGeneratedPages(userId);
assert.equal(result.created, 1);
assert.equal(calls.length, 1);
assert.equal(calls[0].input.title, 'Sync Demo');
assert.equal(calls[0].source.snapshot.relative_path, 'public/index.html');
});
test('syncUserGeneratedPages deduplicates concurrent sync for the same user', async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-page-sync-dedupe-'));
const h5Root = tmp;
const userId = 'user-1';
const publishDir = path.join(h5Root, PUBLISH_ROOT_DIR, userId);
await fs.mkdir(path.join(publishDir, 'public'), { recursive: true });
await fs.writeFile(
path.join(publishDir, 'public/index.html'),
'<html><head><title>Dedupe</title></head><body>Hi</body></html>',
);
let active = 0;
let maxActive = 0;
const pageService = {
createFromChat: async () => {
active += 1;
maxActive = Math.max(maxActive, active);
await new Promise((resolve) => setTimeout(resolve, 20));
active -= 1;
return { id: 'page-1' };
},
};
const pool = {
query: async (sql) => {
if (String(sql).includes('FROM h5_page_records')) return [[]];
return [[]];
},
};
const pageSyncService = createPageSyncService({
pool,
pageService,
h5Root,
});
await Promise.all([
pageSyncService.syncUserGeneratedPages(userId),
pageSyncService.syncUserGeneratedPages(userId),
]);
assert.equal(maxActive, 1);
});