refactor(diffs): dedupe functions

This commit is contained in:
Gustavo Madeira Santana
2026-03-02 10:37:43 -05:00
parent ee1b147631
commit 12be9a08fe
3 changed files with 84 additions and 67 deletions

View File

@@ -150,6 +150,16 @@ function buildImageRenderOptions(options: DiffRenderOptions): DiffRenderOptions
};
}
function buildRenderVariants(options: DiffRenderOptions): {
viewerOptions: DiffViewerOptions;
imageOptions: DiffViewerOptions;
} {
return {
viewerOptions: buildDiffOptions(options),
imageOptions: buildDiffOptions(buildImageRenderOptions(options)),
};
}
function normalizeSupportedLanguage(value?: string): SupportedLanguages | undefined {
const normalized = value?.trim();
return normalized ? (normalized as SupportedLanguages) : undefined;
@@ -298,6 +308,35 @@ function buildHtmlDocument(params: {
</html>`;
}
type RenderedSection = {
viewer: string;
image: string;
};
function buildRenderedSection(params: {
viewerPrerenderedHtml: string;
imagePrerenderedHtml: string;
payload: Omit<DiffViewerPayload, "prerenderedHTML">;
}): RenderedSection {
return {
viewer: renderDiffCard({
prerenderedHTML: params.viewerPrerenderedHtml,
...params.payload,
}),
image: renderStaticDiffCard(params.imagePrerenderedHtml),
};
}
function buildRenderedBodies(sections: ReadonlyArray<RenderedSection>): {
viewerBodyHtml: string;
imageBodyHtml: string;
} {
return {
viewerBodyHtml: sections.map((section) => section.viewer).join("\n"),
imageBodyHtml: sections.map((section) => section.image).join("\n"),
};
}
async function renderBeforeAfterDiff(
input: Extract<DiffInput, { kind: "before_after" }>,
options: DiffRenderOptions,
@@ -314,33 +353,35 @@ async function renderBeforeAfterDiff(
contents: input.after,
...(lang ? { lang } : {}),
};
const viewerPayloadOptions = buildDiffOptions(options);
const imagePayloadOptions = buildDiffOptions(buildImageRenderOptions(options));
const { viewerOptions, imageOptions } = buildRenderVariants(options);
const [viewerResult, imageResult] = await Promise.all([
preloadMultiFileDiff({
oldFile,
newFile,
options: viewerPayloadOptions,
options: viewerOptions,
}),
preloadMultiFileDiff({
oldFile,
newFile,
options: imagePayloadOptions,
options: imageOptions,
}),
]);
return {
viewerBodyHtml: renderDiffCard({
prerenderedHTML: viewerResult.prerenderedHTML,
const section = buildRenderedSection({
viewerPrerenderedHtml: viewerResult.prerenderedHTML,
imagePrerenderedHtml: imageResult.prerenderedHTML,
payload: {
oldFile: viewerResult.oldFile,
newFile: viewerResult.newFile,
options: viewerPayloadOptions,
options: viewerOptions,
langs: buildPayloadLanguages({
oldFile: viewerResult.oldFile,
newFile: viewerResult.newFile,
}),
}),
imageBodyHtml: renderStaticDiffCard(imageResult.prerenderedHTML),
},
});
return {
...buildRenderedBodies([section]),
fileCount: 1,
};
}
@@ -365,36 +406,34 @@ async function renderPatchDiff(
throw new Error(`Patch input is too large to render (max ${MAX_PATCH_TOTAL_LINES} lines).`);
}
const viewerPayloadOptions = buildDiffOptions(options);
const imagePayloadOptions = buildDiffOptions(buildImageRenderOptions(options));
const { viewerOptions, imageOptions } = buildRenderVariants(options);
const sections = await Promise.all(
files.map(async (fileDiff) => {
const [viewerResult, imageResult] = await Promise.all([
preloadFileDiff({
fileDiff,
options: viewerPayloadOptions,
options: viewerOptions,
}),
preloadFileDiff({
fileDiff,
options: imagePayloadOptions,
options: imageOptions,
}),
]);
return {
viewer: renderDiffCard({
prerenderedHTML: viewerResult.prerenderedHTML,
return buildRenderedSection({
viewerPrerenderedHtml: viewerResult.prerenderedHTML,
imagePrerenderedHtml: imageResult.prerenderedHTML,
payload: {
fileDiff: viewerResult.fileDiff,
options: viewerPayloadOptions,
options: viewerOptions,
langs: buildPayloadLanguages({ fileDiff: viewerResult.fileDiff }),
}),
image: renderStaticDiffCard(imageResult.prerenderedHTML),
};
},
});
}),
);
return {
viewerBodyHtml: sections.map((section) => section.viewer).join("\n"),
imageBodyHtml: sections.map((section) => section.image).join("\n"),
...buildRenderedBodies(sections),
fileCount: files.length,
};
}

View File

@@ -187,9 +187,10 @@ export function createDiffsTool(params: {
content: [
{
type: "text",
text:
`Diff ${image.format.toUpperCase()} generated at: ${artifactFile.path}\n` +
"Use the `message` tool with `path` or `filePath` to send this file.",
text: buildFileArtifactMessage({
format: image.format,
filePath: artifactFile.path,
}),
},
],
details: buildArtifactDetails({
@@ -257,10 +258,11 @@ export function createDiffsTool(params: {
content: [
{
type: "text",
text:
`Diff viewer: ${viewerUrl}\n` +
`Diff ${image.format.toUpperCase()} generated at: ${artifactFile.path}\n` +
"Use the `message` tool with `path` or `filePath` to send this file.",
text: buildFileArtifactMessage({
format: image.format,
filePath: artifactFile.path,
viewerUrl,
}),
},
],
details: buildArtifactDetails({
@@ -330,6 +332,17 @@ function buildArtifactDetails(params: {
};
}
function buildFileArtifactMessage(params: {
format: DiffOutputFormat;
filePath: string;
viewerUrl?: string;
}): string {
const lines = params.viewerUrl ? [`Diff viewer: ${params.viewerUrl}`] : [];
lines.push(`Diff ${params.format.toUpperCase()} generated at: ${params.filePath}`);
lines.push("Use the `message` tool with `path` or `filePath` to send this file.");
return lines.join("\n");
}
async function renderDiffArtifactFile(params: {
screenshotter: DiffScreenshotter;
store: DiffArtifactStore;

View File

@@ -106,39 +106,9 @@ function createToolbarButton(params: {
}
function applyToolbarButtonStyles(button: HTMLButtonElement, active: boolean): void {
button.style.display = "inline-flex";
button.style.alignItems = "center";
button.style.justifyContent = "center";
button.style.width = "24px";
button.style.height = "24px";
button.style.padding = "0";
button.style.margin = "0";
button.style.border = "0";
button.style.borderRadius = "0";
button.style.background = "transparent";
button.style.boxShadow = "none";
button.style.lineHeight = "0";
button.style.cursor = "pointer";
button.style.overflow = "visible";
button.style.flex = "0 0 auto";
button.style.opacity = active ? "0.92" : "0.6";
button.style.color =
viewerState.theme === "dark" ? "rgba(226, 232, 240, 0.74)" : "rgba(15, 23, 42, 0.52)";
const svg = button.querySelector<SVGElement>("svg");
if (!svg) {
return;
}
svg.style.display = "block";
svg.style.width = "16px";
svg.style.height = "16px";
svg.style.minWidth = "16px";
svg.style.minHeight = "16px";
svg.style.overflow = "visible";
svg.style.flex = "0 0 auto";
svg.style.color = "inherit";
svg.style.fill = "currentColor";
svg.style.pointerEvents = "none";
button.dataset.active = String(active);
}
function splitIcon(): string {
@@ -193,11 +163,6 @@ function themeIcon(theme: DiffTheme): string {
function createToolbar(): HTMLElement {
const toolbar = document.createElement("div");
toolbar.className = "oc-diff-toolbar";
toolbar.style.display = "inline-flex";
toolbar.style.alignItems = "center";
toolbar.style.gap = "6px";
toolbar.style.marginInlineStart = "6px";
toolbar.style.flex = "0 0 auto";
toolbar.append(
createToolbarButton({