From 888179f7f011ca987374c82ee828fe96b857cc88 Mon Sep 17 00:00:00 2001 From: dotta Date: Fri, 20 Mar 2026 08:27:27 -0500 Subject: [PATCH] fix: use fixed 1280x640 dimensions for org chart export image GitHub recommends 1280x640 for repository social media previews. The org chart SVG/PNG now always outputs at these dimensions, scaling and centering the content to fit any org size. Co-Authored-By: Paperclip --- server/src/routes/org-chart-svg.ts | 37 ++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/server/src/routes/org-chart-svg.ts b/server/src/routes/org-chart-svg.ts index d0d83159..3d7c3f5e 100644 --- a/server/src/routes/org-chart-svg.ts +++ b/server/src/routes/org-chart-svg.ts @@ -493,6 +493,10 @@ const PAPERCLIP_LOGO_SVG = ` // ── Public API ─────────────────────────────────────────────────── +// GitHub recommended social media preview dimensions +const TARGET_W = 1280; +const TARGET_H = 640; + export function renderOrgChartSvg(orgTree: OrgNode[], style: OrgChartStyle = "warmth"): string { const theme = THEMES[style] || THEMES.warmth; @@ -512,26 +516,39 @@ export function renderOrgChartSvg(orgTree: OrgNode[], style: OrgChartStyle = "wa const layout = layoutTree(root, PADDING, PADDING + 24); const bounds = treeBounds(layout); - const svgW = bounds.maxX + PADDING; - const svgH = bounds.maxY + PADDING; + const contentW = bounds.maxX + PADDING; + const contentH = bounds.maxY + PADDING; - const logoX = svgW - 110 - LOGO_PADDING; + // Scale content to fit within the fixed target dimensions + const scale = Math.min(TARGET_W / contentW, TARGET_H / contentH, 1); + const scaledW = contentW * scale; + const scaledH = contentH * scale; + // Center the scaled content within the target frame + const offsetX = (TARGET_W - scaledW) / 2; + const offsetY = (TARGET_H - scaledH) / 2; + + const logoX = TARGET_W - 110 - LOGO_PADDING; const logoY = LOGO_PADDING; - return ` - ${theme.defs(svgW, svgH)} + return ` + ${theme.defs(TARGET_W, TARGET_H)} - ${theme.bgExtras(svgW, svgH)} + ${theme.bgExtras(TARGET_W, TARGET_H)} ${PAPERCLIP_LOGO_SVG} - ${renderConnectors(layout, theme)} - ${renderCards(layout, theme)} + + ${renderConnectors(layout, theme)} + ${renderCards(layout, theme)} + `; } export async function renderOrgChartPng(orgTree: OrgNode[], style: OrgChartStyle = "warmth"): Promise { const svg = renderOrgChartSvg(orgTree, style); - // Render at 2x density for retina-quality output - return sharp(Buffer.from(svg), { density: 144 }).png().toBuffer(); + // Render at 2x density for retina quality, resize to exact target dimensions + return sharp(Buffer.from(svg), { density: 144 }) + .resize(TARGET_W, TARGET_H) + .png() + .toBuffer(); }