From 55100f8945742c2a38865457cdf6ad58becd8ece Mon Sep 17 00:00:00 2001 From: john Date: Sun, 2 Aug 2026 13:47:09 +0800 Subject: [PATCH] fix(mindspace): ensure thumbnail sidecars for WeChat link card og:image Generate SVG/PNG sidecars at HTML delivery and after share-preview repair so forwarded service-account links get a raster og:image when only meta exists. Co-authored-by: Cursor --- ...workspace-publication-delivery-service.mjs | 18 +++++++++++++ ...pace-publication-delivery-service.test.mjs | 26 +++++++++++++++++++ wechat/verify/share-preview-repair.mjs | 7 +++++ 3 files changed, 51 insertions(+) diff --git a/mindspace-workspace-publication-delivery-service.mjs b/mindspace-workspace-publication-delivery-service.mjs index f778a17..57c40a8 100644 --- a/mindspace-workspace-publication-delivery-service.mjs +++ b/mindspace-workspace-publication-delivery-service.mjs @@ -234,6 +234,24 @@ export function createMindSpaceWorkspacePublicationDeliveryService({ publishDir, thumbnailRelativePath, ); + // WeChat link cards and forwards read og:image at serve time. Pages may + // already have share-preview meta without a raster sidecar (e.g. service + // account auto-repair); ensure the SVG exists before building fallbackImageUrl. + if (!existsSyncFn(thumbnailAbsolutePath)) { + try { + await ensureWorkspaceThumbnailFn( + publishDir, + relativePath, + html, + ); + } catch (error) { + logger?.warn?.( + `[MindSpace] thumbnail sidecar ensure failed for ${relativePath}: ${ + error?.message || error + }`, + ); + } + } return { action: 'deliver', kind: 'html', diff --git a/mindspace-workspace-publication-delivery-service.test.mjs b/mindspace-workspace-publication-delivery-service.test.mjs index 66744d1..5bf78cc 100644 --- a/mindspace-workspace-publication-delivery-service.test.mjs +++ b/mindspace-workspace-publication-delivery-service.test.mjs @@ -198,6 +198,32 @@ test('enforces the page delivery contract inside MindSpace', async () => { ); }); +test('ensures a thumbnail sidecar before HTML delivery when share preview exists without one', async () => { + const calls = []; + const setup = createSetup({ + existsSyncFn() { + return false; + }, + async ensureWorkspaceThumbnailFn(publishDir, relativePath, html) { + calls.push([ + 'ensure-thumbnail', + publishDir, + relativePath, + html, + ]); + }, + }); + const result = + await setup.service.resolveWorkspaceRequest({ + requestPath: '/user-1/public/page.html', + }); + assert.equal(result.thumbnailName, null); + assert.equal( + calls.some(([kind]) => kind === 'ensure-thumbnail'), + true, + ); +}); + test('materializes and returns thumbnail binaries through the logical contract', async () => { const setup = createSetup({ async resolvePublicRequestFn() { diff --git a/wechat/verify/share-preview-repair.mjs b/wechat/verify/share-preview-repair.mjs index 5d48351..8cabc92 100644 --- a/wechat/verify/share-preview-repair.mjs +++ b/wechat/verify/share-preview-repair.mjs @@ -3,6 +3,7 @@ import path from 'node:path'; import { injectBeforeDocumentClosingHead } from '../../html-document-injection.mjs'; import { normalizeCoverMetaSuggestion, upsertMindspaceCoverMeta } from '../../mindspace-cover-meta.mjs'; import { preparePublishedPlatformBrand } from '../../mindspace-page-tag.mjs'; +import { scheduleWorkspaceHtmlThumbnailSidecars } from '../../mindspace-workspace-thumbnails.mjs'; import { artifactFileExists, resolveArtifactLocalPath, @@ -126,6 +127,12 @@ export function repairArtifactSharePreview(artifact, { topic = '', publishDir = if (!verified.ok) { return { ok: false, reason: verified.reason ?? 'repair_incomplete', changes }; } + if (publishDir && artifact?.relativePath) { + scheduleWorkspaceHtmlThumbnailSidecars( + publishDir, + String(artifact.relativePath).replace(/\\/g, '/'), + ); + } return { ok: true, reason: null, changes }; }