The changelog gets a face
Every link to this site unfurled as a grey box. It now ships five share cards and gives each platform its own URL to get the right one.
The changelog gets a face
Why Care?
Until today, every link to this site unfurled as a grey rectangle. For a site whose entire job is to make work legible, the preview was doing the opposite.
There are now five share cards, and the site hands each platform a different
URL so each one crawls the card cut for its own viewport. That last part is
the interesting bit, because a page can only advertise one og:image.
What's New?
Five share cards — Banner (1200×630), Messages (890×760), Square, BannerTall (3:4), Portrait (4:5). Generated with Ideogram, text composited on top as HTML.
Per-platform share targets at
/s/whatsapp/,/s/imessage/,/s/x/,/s/slack/, plus share buttons that use them.DESIGN.md— the site had none, which closes Phase 5 of the brand-layer spec.lossless-agent-skillsjoins the ledger as Context: 38 repos, 449 entries.The ledger's right-hand number says what it counts and links to the log.
How it Works
One tag, many platforms
WhatsApp, iMessage, LinkedIn, Slack and Discord all read the same og:image.
Only X can be addressed separately, via twitter:image. So a single page
cannot hand different platforms different cards.
The way around it is to stop sharing a single page. Each button links to
/s/<platform>/ — a pre-rendered stub carrying that platform's card,
rel=canonical back to the real page, noindex, and a redirect. The crawler
reads the head and stops. The human is bounced to the index and never sees it.
This is a path, not ?platform=, because output: 'static' means the head
cannot vary by query string — both would be the same bytes on disk.
It degrades safely, which is what made it worth shipping: a crawler that does
follow the redirect lands on the canonical page, whose tags are the chat card
and the wide twitter:image — correct for every platform in the list anyway.
The card is sized for the cropper
The default is not the 1200×630 banner. ai-labs had already solved this in
May: chat clients crop previews to a square-ish landscape, so a 16:9 banner
gets letterboxed and a 1:1 square gets cut. The answer is a card cut for the
cropper — 890×760.
Measured against the 1.91:1 crop LinkedIn documents:
| format | retained |
| Banner 1200×630 | 100% |
| Square 1080² | 52% |
| Portrait 1080×1350 | 42% |
| BannerTall 900×1200 | 39% |
Text is markup, not pixels
The illustration is wordless Ideogram output; everything legible is HTML
composited on top and rasterized through QuickLook. So the headline uses the
real Bodoni Moda the site serves, and the gradient on "Changelog" is the same
107° ramp lib/era.ts samples to colour every mark on the chart.
Under the Hood
QuickLook does not render at 1:1
qlmanage -t -s N yields an N×N square, and it lays the page out at some
internal CSS width before scaling to N. Absolute pixel sizes therefore come out
wrong — a 630px frame rendered ~945px tall inside a 1200px canvas and pushed the
footer below the crop, which looked like the footer had never rendered.
Every dimension is relative now (% / vw), which survives the scaling.
A second bug hid behind the first: the square canvas was sized to the frame's WIDTH, so any format taller than it was wide overflowed. Portrait at 1080×1350 is 125% of the square's height — it lost the top of the headline and the footer both. The square's side is the longer edge now.
Three type bands, because a square is not a landscape
w >= h was the wrong test: 1080×1080 satisfies it, so the square took the
landscape branch, got a two-line title at landscape scale, and left three
quarters of its dark region empty — which is the whole reason that crop exists.
Landscape, squarish, and tall are now separate bands with their own scale and line-breaking. The squarish band earned itself: at 1.17:1 the frame is wider than a square but much shorter, and square-scale type put the footer pill on top of the last line of the subheading.
The ramp's stops were washing out
--gradient__eastern-crimson places its last stop at 72.33%, which leaves the
final quarter of a wide word as flat amber. The stops are redistributed across
the full width for share imagery, with a saturation lift so the ramp holds at
thumbnail size. The hex values are the brand's, unchanged — theme.css is
untouched. This is a rendering choice for one surface, not a token change.
Files Touched
DESIGN.md— tokens read fromtheme.css, not inventedsrc/lib/seo.ts— the registry: formats, per-platform targets, char limitssrc/components/MetaTags.astro— one component owns the whole headsrc/components/ShareRow.astro— native sheet, platform intent, clipboardsrc/pages/s/[platform].astro— the four stubsscripts/og-compose/compose.mjs— the compositorscripts/sync-icons.mjs— project favicons, 21 of 40 collected
What's Next
Verify with a real send. Two house docs disagree about what WhatsApp does to a tall frame — the OG blueprint says it crops to ~1.91:1, the og-images skill says it top-crops and treats 3:4 as the default. Only an actual thread settles it, and whichever wins, one of those docs needs correcting.
Share targets exist for the home page only. Per-entry sharing would mean ~1,800 stubs; probably not worth it.
19 of 40 projects still have no favicon.
fullstack-vcships only an Affinity source file.
