fix(diffs): tighten rendering quality

This commit is contained in:
Gustavo Madeira Santana
2026-02-28 23:03:28 -05:00
parent 0f72000c96
commit 9257dfb5c0
4 changed files with 79 additions and 28 deletions

View File

@@ -58,6 +58,12 @@ describe("PlaywrightDiffScreenshotter", () => {
expect(launchMock).toHaveBeenCalledTimes(1);
expect(browser.newPage).toHaveBeenCalledTimes(2);
expect(browser.newPage).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
deviceScaleFactor: 2,
}),
);
expect(pages).toHaveLength(2);
expect(pages[0]?.close).toHaveBeenCalledTimes(1);
expect(pages[1]?.close).toHaveBeenCalledTimes(1);

View File

@@ -122,15 +122,29 @@ export class PlaywrightDiffScreenshotter implements DiffScreenshotter {
throw new Error("Diff frame was lost after resizing.");
}
const dpr = await page.evaluate(() => window.devicePixelRatio || 1);
// Raw clip in CSS px
const rawX = Math.max(box.x - padding, 0);
const rawY = Math.max(box.y - padding, 0);
const rawRight = rawX + clipWidth;
const rawBottom = rawY + clipHeight;
// Snap to device-pixel grid to avoid soft text from sub-pixel crop
const x = Math.floor(rawX * dpr) / dpr;
const y = Math.floor(rawY * dpr) / dpr;
const right = Math.ceil(rawRight * dpr) / dpr;
const bottom = Math.ceil(rawBottom * dpr) / dpr;
await page.screenshot({
path: params.outputPath,
type: "png",
scale: "device",
clip: {
x: Math.max(box.x - padding, 0),
y: Math.max(box.y - padding, 0),
width: clipWidth,
height: clipHeight,
x,
y,
width: right - x,
height: bottom - y,
},
});
return params.outputPath;
@@ -233,7 +247,7 @@ async function acquireSharedBrowser(params: {
.launch({
headless: true,
...(executablePath ? { executablePath } : {}),
args: ["--disable-dev-shm-usage", "--disable-gpu"],
args: ["--disable-dev-shm-usage"],
})
.then((browser) => {
if (sharedBrowserState?.browserPromise === browserPromise) {

View File

@@ -24,6 +24,9 @@ describe("renderDiffDocument", () => {
expect(rendered.html).toContain("/plugins/diffs/assets/viewer.js");
expect(rendered.imageHtml).not.toContain("/plugins/diffs/assets/viewer.js");
expect(rendered.imageHtml).toContain('data-openclaw-diffs-ready="true"');
expect(rendered.imageHtml).toContain("max-width: 960px;");
expect(rendered.imageHtml).toContain("--diffs-font-size: 16px;");
expect(rendered.html).toContain("--diffs-font-size: 15px;");
expect(rendered.html).not.toContain("fonts.googleapis.com");
});

View File

@@ -136,6 +136,16 @@ function buildDiffOptions(options: DiffRenderOptions): DiffViewerOptions {
};
}
function buildImageRenderOptions(options: DiffRenderOptions): DiffRenderOptions {
return {
...options,
presentation: {
...options.presentation,
fontSize: Math.max(16, options.presentation.fontSize),
},
};
}
function normalizeSupportedLanguage(value?: string): SupportedLanguages | undefined {
const normalized = value?.trim();
return normalized ? (normalized as SupportedLanguages) : undefined;
@@ -225,7 +235,7 @@ function buildHtmlDocument(params: {
}
.oc-frame[data-render-mode="image"] {
max-width: 1120px;
max-width: 960px;
}
[data-openclaw-diff-root] {
@@ -293,22 +303,33 @@ async function renderBeforeAfterDiff(
contents: input.after,
...(lang ? { lang } : {}),
};
const payloadOptions = buildDiffOptions(options);
const result = await preloadMultiFileDiff({
oldFile,
newFile,
options: payloadOptions,
});
const viewerPayloadOptions = buildDiffOptions(options);
const imagePayloadOptions = buildDiffOptions(buildImageRenderOptions(options));
const [viewerResult, imageResult] = await Promise.all([
preloadMultiFileDiff({
oldFile,
newFile,
options: viewerPayloadOptions,
}),
preloadMultiFileDiff({
oldFile,
newFile,
options: imagePayloadOptions,
}),
]);
return {
viewerBodyHtml: renderDiffCard({
prerenderedHTML: result.prerenderedHTML,
oldFile: result.oldFile,
newFile: result.newFile,
options: payloadOptions,
langs: buildPayloadLanguages({ oldFile: result.oldFile, newFile: result.newFile }),
prerenderedHTML: viewerResult.prerenderedHTML,
oldFile: viewerResult.oldFile,
newFile: viewerResult.newFile,
options: viewerPayloadOptions,
langs: buildPayloadLanguages({
oldFile: viewerResult.oldFile,
newFile: viewerResult.newFile,
}),
}),
imageBodyHtml: renderStaticDiffCard(result.prerenderedHTML),
imageBodyHtml: renderStaticDiffCard(imageResult.prerenderedHTML),
fileCount: 1,
};
}
@@ -322,22 +343,29 @@ async function renderPatchDiff(
throw new Error("Patch input did not contain any file diffs.");
}
const payloadOptions = buildDiffOptions(options);
const viewerPayloadOptions = buildDiffOptions(options);
const imagePayloadOptions = buildDiffOptions(buildImageRenderOptions(options));
const sections = await Promise.all(
files.map(async (fileDiff) => {
const result = await preloadFileDiff({
fileDiff,
options: payloadOptions,
});
const [viewerResult, imageResult] = await Promise.all([
preloadFileDiff({
fileDiff,
options: viewerPayloadOptions,
}),
preloadFileDiff({
fileDiff,
options: imagePayloadOptions,
}),
]);
return {
viewer: renderDiffCard({
prerenderedHTML: result.prerenderedHTML,
fileDiff: result.fileDiff,
options: payloadOptions,
langs: buildPayloadLanguages({ fileDiff: result.fileDiff }),
prerenderedHTML: viewerResult.prerenderedHTML,
fileDiff: viewerResult.fileDiff,
options: viewerPayloadOptions,
langs: buildPayloadLanguages({ fileDiff: viewerResult.fileDiff }),
}),
image: renderStaticDiffCard(result.prerenderedHTML),
image: renderStaticDiffCard(imageResult.prerenderedHTML),
};
}),
);