fix(emails): re-export the wordmark onto its original canvas (#6651)

#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.
This commit is contained in:
Waleed
2026-08-12 18:29:04 -07:00
committed by GitHub
parent 2da80150b8
commit 738006db33
3 changed files with 37 additions and 23 deletions
+18 -16
View File
@@ -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)
})
})
+19 -7
View File
@@ -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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB