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 -1
View File
@@ -1,6 +1,10 @@
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 { pageInternals, normalizeWorkspaceRelativePath } from './mindspace-pages.mjs';
import { createPageService, pageInternals, normalizeWorkspaceRelativePath } from './mindspace-pages.mjs';
import { PUBLISH_ROOT_DIR } from './user-publish.mjs';
test('parseJsonColumn accepts mysql json objects and strings', () => {
const objectValue = { relative_path: 'public/demo.html', auto_synced: true };
@@ -208,3 +212,86 @@ test('registerPageArtifactForConversation skips missing registry or session safe
null,
);
});
test('renderThumbnail falls back to workspace html when storage asset is missing', async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-thumb-'));
const storageRoot = path.join(tmp, 'storage');
const h5Root = tmp;
const userId = 'user-1';
const pageId = 'page-1';
const publishDir = path.join(h5Root, PUBLISH_ROOT_DIR, userId);
const html =
'<!doctype html><html><head><title>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 pool = {
query: async (sql) => {
if (String(sql).includes('FROM h5_page_records')) {
return [[{
title: 'Demo',
summary: '',
page_type: 'html',
version_no: 1,
storage_key: `users/${userId}/pages/missing/versions/v1.md`,
source_snapshot_json: { relative_path: 'public/index.html' },
}]];
}
if (String(sql).includes('FROM h5_users')) {
return [[{ username: 'demo' }]];
}
return [[]];
},
};
const pageService = createPageService(pool, { storageRoot, h5Root });
const svg = await pageService.renderThumbnail(userId, pageId);
assert.match(svg, /<svg/i);
assert.match(svg, /Demo/);
});
test('getPage falls back to workspace html when storage asset is missing', async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-get-page-'));
const storageRoot = path.join(tmp, 'storage');
const h5Root = tmp;
const userId = 'user-1';
const pageId = 'page-1';
const publishDir = path.join(h5Root, PUBLISH_ROOT_DIR, userId);
const html =
'<!doctype html><html><head><title>Page Demo</title></head><body><h1>Body</h1></body></html>';
await fs.mkdir(path.join(publishDir, 'public'), { recursive: true });
await fs.writeFile(path.join(publishDir, 'public/index.html'), html);
const pool = {
query: async (sql) => {
if (String(sql).includes('FROM h5_page_records')) {
return [[{
id: pageId,
user_id: userId,
title: 'Page Demo',
summary: '',
page_type: 'html',
template_id: 'static-html',
current_version_id: 'version-1',
status: 'draft',
visibility: 'private',
category_code: 'draft',
version_no: 1,
storage_key: `users/${userId}/pages/missing/versions/v1.md`,
source_snapshot_json: { relative_path: 'public/index.html' },
created_at: 1,
updated_at: 1,
}]];
}
if (String(sql).includes('FROM h5_users')) {
return [[{ username: 'demo' }]];
}
return [[]];
},
};
const pageService = createPageService(pool, { storageRoot, h5Root });
const page = await pageService.getPage(userId, pageId);
assert.equal(page.title, 'Page Demo');
assert.match(page.content, /<h1>Body<\/h1>/);
});