From 97b22a66e106170d6a88de4572fade989d9ce987 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 12 Aug 2026 18:24:11 -0700 Subject: [PATCH] fix(emails): re-export the wordmark onto its original canvas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #6648 shrank the header wordmark by re-exporting the raster edge-to-edge at 168x80, replacing a 272x164 file whose mark was inset. Same URL, new proportions — so every email already delivered, which keeps the width=68 height=41 it was sent with and refetches that URL forever, now stretches the mark ~20%, and a client still holding the old bytes squashes it ~22% into the new box. That is the squished logo on staging. Re-export onto the original 272x164 canvas instead, with the outlines at their own aspect (the previous asset was itself 3.9% squashed) and the textBody fill. Old mail renders against its own numbers as before; the header takes a 43x26 box for 20.7px of ink. A test pins the canvas, since that shape — not the display size — is what old mail depends on. --- apps/sim/lib/branding/wordmark.test.ts | 34 +++++++++--------- apps/sim/lib/branding/wordmark.ts | 26 ++++++++++---- .../sim/public/brand/color/email/wordmark.png | Bin 2538 -> 4499 bytes 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/apps/sim/lib/branding/wordmark.test.ts b/apps/sim/lib/branding/wordmark.test.ts index b73dd955804..e2fe70129e2 100644 --- a/apps/sim/lib/branding/wordmark.test.ts +++ b/apps/sim/lib/branding/wordmark.test.ts @@ -4,10 +4,12 @@ import { readFileSync } from 'node:fs' import path from 'node:path' import { describe, expect, it } from 'vitest' -import { EMAIL_WORDMARK_SIZE, WORDMARK_PATHS, WORDMARK_VIEW_BOX } from '@/lib/branding/wordmark' - -/** Email clients do no responsive image selection, so the asset carries retina detail itself. */ -const MIN_RETINA_SCALE = 2 +import { + EMAIL_WORDMARK_CANVAS, + EMAIL_WORDMARK_SIZE, + WORDMARK_PATHS, + WORDMARK_VIEW_BOX, +} from '@/lib/branding/wordmark' const WORDMARK_PNG = path.join( import.meta.dirname, @@ -27,27 +29,27 @@ function readPngSize(file: string): { width: number; height: number } { } describe('email wordmark asset', () => { - it('out-resolves the box the email header renders it in', () => { - const { width, height } = readPngSize(WORDMARK_PNG) - expect(width).toBeGreaterThanOrEqual(EMAIL_WORDMARK_SIZE.width * MIN_RETINA_SCALE) - expect(height).toBeGreaterThanOrEqual(EMAIL_WORDMARK_SIZE.height * MIN_RETINA_SCALE) + it('keeps the canvas every email already sent was measured against', () => { + expect(readPngSize(WORDMARK_PNG)).toEqual({ + width: EMAIL_WORDMARK_CANVAS.width, + height: EMAIL_WORDMARK_CANVAS.height, + }) }) - it('is committed at the aspect ratio the header displays it at', () => { - const { width, height } = readPngSize(WORDMARK_PNG) - const assetAspect = width / height + it('renders undistorted in the box the header displays it in', () => { const boxAspect = EMAIL_WORDMARK_SIZE.width / EMAIL_WORDMARK_SIZE.height - expect(Math.abs(assetAspect - boxAspect) / boxAspect).toBeLessThan(0.01) + const canvasAspect = EMAIL_WORDMARK_CANVAS.width / EMAIL_WORDMARK_CANVAS.height + expect(Math.abs(boxAspect - canvasAspect) / canvasAspect).toBeLessThan(0.01) }) - it('renders the mark at the brand outlines aspect ratio, within a rounding pixel', () => { - const boxAspect = EMAIL_WORDMARK_SIZE.width / EMAIL_WORDMARK_SIZE.height - const viewBoxAspect = WORDMARK_VIEW_BOX.width / WORDMARK_VIEW_BOX.height - expect(Math.abs(boxAspect - viewBoxAspect) / viewBoxAspect).toBeLessThan(0.01) + it('out-resolves that box, since email clients do no responsive selection', () => { + expect(EMAIL_WORDMARK_CANVAS.width).toBeGreaterThanOrEqual(EMAIL_WORDMARK_SIZE.width * 2) + expect(EMAIL_WORDMARK_CANVAS.height).toBeGreaterThanOrEqual(EMAIL_WORDMARK_SIZE.height * 2) }) it('carries every glyph of the logotype', () => { expect(WORDMARK_PATHS).toHaveLength(4) for (const d of WORDMARK_PATHS) expect(d.startsWith('M')).toBe(true) + expect(WORDMARK_VIEW_BOX.width).toBeGreaterThan(WORDMARK_VIEW_BOX.height) }) }) diff --git a/apps/sim/lib/branding/wordmark.ts b/apps/sim/lib/branding/wordmark.ts index 580fbf17c55..a02bf9cb782 100644 --- a/apps/sim/lib/branding/wordmark.ts +++ b/apps/sim/lib/branding/wordmark.ts @@ -17,17 +17,29 @@ export const WORDMARK_PATHS: readonly string[] = [ 'M179.43 212H149.16V49.76C153.76 51.91 158.88 53.12 164.29 53.12C169.7 53.12 174.83 51.91 179.43 49.76V212Z', ] +/** + * Canvas of `public/brand/color/email/wordmark.png`, which the header renders + * instead of these paths because email clients strip inline SVG. + * + * **These proportions are frozen.** A sent email is immutable — it keeps the + * `width`/`height` it was sent with and refetches this URL forever — so an + * export whose canvas is shaped differently would stretch the mark in every + * message already delivered. Re-export onto this canvas (the outlines centered + * at their own aspect, filled with the email palette's `textBody`) and the same + * file keeps serving old and new mail alike. + */ +export const EMAIL_WORDMARK_CANVAS = { width: 272, height: 164 } as const + /** * Display box for the email header wordmark. * * The landing navbar sizes the mark at its neighbouring text plus 2px above and * below (18px ink against 14px chip labels). Email body copy is 16px, so the - * mark stands 20px tall; the width follows the view box. Both dimensions are - * pinned because email clients do no responsive image selection. + * mark wants to stand ~20px tall. The canvas insets the outlines, so a 26px box + * renders 20.7px of ink; the width follows {@link EMAIL_WORDMARK_CANVAS}. Both + * dimensions are pinned because email clients do no responsive image selection. * - * The header renders `public/brand/color/email/wordmark.png` rather than these - * paths, because email clients strip inline SVG. That file is these outlines - * filled with the email palette's `textBody`, rasterized at 4x so the mark - * stays crisp on retina and this box can be retuned without re-exporting it. + * Retuning this box is safe on its own — the canvas is 6x the box, so the mark + * stays crisp, and older mail keeps rendering against its own numbers. */ -export const EMAIL_WORDMARK_SIZE = { width: 42, height: 20 } as const +export const EMAIL_WORDMARK_SIZE = { width: 43, height: 26 } as const diff --git a/apps/sim/public/brand/color/email/wordmark.png b/apps/sim/public/brand/color/email/wordmark.png index 6851edd5b39c04dfa4f736256cda64271356266b..582fac9fe93c3c89665c9152c9aa08e5f8aa8718 100644 GIT binary patch literal 4499 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 literal 2538 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 Am;e9(