Skip to content

fix(library): generate every post cover from one template - #5980

Merged
waleedlatif1 merged 5 commits into
stagingfrom
worktree-blog-og-images
Jul 27, 2026
Merged

fix(library): generate every post cover from one template#5980
waleedlatif1 merged 5 commits into
stagingfrom
worktree-blog-og-images

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Three posts (mcp-security, ai-agents-in-procurement, ai-agent-observability) shipped an ogImage pointing at a file that was never committed — broken images on the library index, plus 404s in og:image, JSON-LD, and the image sitemap
  • Several other covers were authored without Söhne loaded (fell back to DejaVu) or with the title clipping off the bottom edge
  • Root cause: covers were hand-made per post with no generator, so they drifted. Adds bun run library:covers, which renders each cover from the post's frontmatter title using the reference template already encoded in apps/docs/app/api/og/route.tsx
  • Regenerates all 20 covers so the grid is uniform — same template, font, wordmark, and sizing throughout
  • Adds bun run library:covers:check so CI can catch a missing or stale cover instead of shipping a 404
  • Drops six orphaned cover.png sources (3.9 MB) left over from the JPEG compression pass in fix(landing): revert unoptimized removal, compress source images instead #5528

Page weight

These render with unoptimized (per #5528, the runtime optimizer was breaking /blog on staging), so the committed bytes ship verbatim and file size is page weight directly. The generator uses mozjpeg quality 82 — the exact setting #5528 established, now applied automatically instead of by hand.

staging this PR
Cover files 17 (3 more 404'd) 20, all working
Total bytes 794 KB 528 KB

−266 KB (−33%) while serving three more images. Above the fold (the 3 priority cards) is 68.0 → 73.9 KB, the +5.9 KB being entirely mcp-security now actually loading. Quality measured at 54.9 dB PSNR — visually lossless; resolution unchanged at 1200×675.

Notes

  • Line widths come from the font's real advance metrics via opentype.js, not an average-glyph-width estimate. The template joins words with non-breaking spaces to dodge a Satori space-measurement bug, which leaves hyphens as the only fallback break points — an under-measured line breaks mid-compound (the BYOK title was rendering as BYOK Multi- / Model AI Agent)
  • Font sizes step 110 → 96 → 85 → 76, taking the largest that fits in three lines
  • Covers are derived artifacts, so every run re-renders from scratch. Rendering is deterministic, so an unchanged title re-encodes to identical bytes and a full run is a no-op in git
  • what-is-an-mcp-server loses a bespoke diagram — it was the only cover with artwork and it was in the fallback font. Happy to restore that one file if we'd rather keep it
  • Covers now carry the full frontmatter title; a few old ones had hand-shortened text (e.g. apache-2-0-vs-fair-code). If we want an escape hatch, an optional coverTitle frontmatter field is the clean way

Follow-up (not in this PR)

Type of Change

  • Bug fix

Testing

Tested manually — regenerated all 20 covers and reviewed them as a contact sheet; verified deterministic output across runs; verified --check fails on an edited frontmatter title and passes once regenerated.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@waleedlatif1
waleedlatif1 requested a review from a team as a code owner July 27, 2026 20:32
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 27, 2026 9:17pm

Request Review

@cursor

cursor Bot commented Jul 27, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Marketing/static assets and dev-only scripts; no auth, API, or runtime app logic changes in the shown diff.

Overview
Adds a build-time cover generator (scripts/generate-library-covers.tsx) plus root scripts library:covers and library:covers:check, so /library post covers are rendered from each MDX title using the same gray-field template as the docs OG route (Söhne, wordmark, corner arrow), then written as mozjpeg 82 JPEGs under apps/sim/public/library/.

The script uses @vercel/og, opentype.js advance metrics, and gray-matter (aligned with page parsing) for title wrapping and Satori workarounds; --check compares decoded pixels to committed files so stale or missing covers fail without brittle byte equality across platforms. scripts/tsconfig.json gains JSX so the generator can run as TSX.

Root devDependencies add @vercel/og, gray-matter, opentype.js, sharp, and types; lockfile updates follow. Regenerated covers fix missing/broken ogImage assets and unify styling; orphaned PNG sources are dropped per the PR description.

Reviewed by Cursor Bugbot for commit cbee086. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR standardizes library cover generation and validation.

  • Adds deterministic cover rendering from each post’s frontmatter title.
  • Adds a check mode that detects missing, dimensionally invalid, or visually stale covers.
  • Regenerates all library covers, adds three previously missing images, and removes orphaned PNG sources.
  • Adds the rendering dependencies and TypeScript JSX configuration required by the generator.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
scripts/generate-library-covers.tsx Adds deterministic generation and visual synchronization checks for every conventionally structured library cover.
package.json Exposes generation and check commands and adds the required rendering dependencies.
scripts/tsconfig.json Enables JSX compilation and includes TSX scripts.
bun.lock Locks the newly added cover-generation dependencies and their transitive packages.

Reviews (5): Last reviewed commit: "fix(library): parse frontmatter with gra..." | Re-trigger Greptile

Comment thread scripts/generate-library-covers.tsx Outdated
@waleedlatif1
waleedlatif1 force-pushed the worktree-blog-og-images branch from 013425f to cf94236 Compare July 27, 2026 20:38
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread scripts/generate-library-covers.tsx
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread scripts/generate-library-covers.tsx Outdated
Comment thread scripts/generate-library-covers.tsx
Comment thread scripts/generate-library-covers.tsx Outdated
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread scripts/generate-library-covers.tsx
Comment thread scripts/generate-library-covers.tsx
Three posts shipped an `ogImage` pointing at a file that was never
committed, so the library index rendered broken images and their
`og:image`, JSON-LD, and sitemap entries all 404'd. Several others were
authored without the brand font loaded or with the title clipping off the
bottom edge.

Covers were hand-made per post with no generator, which is why they
drifted. Adds `bun run library:covers`, rendering each cover from the
post's frontmatter title using the reference template already encoded in
the docs OG route, and regenerates all 20 so the grid is uniform.

Line widths come from the font's real advance metrics rather than an
average-glyph-width estimate: the template joins words with non-breaking
spaces to dodge a Satori space-measurement bug, which leaves hyphens as
the only fallback break points, so an under-measured line breaks
mid-compound.

Also drops six orphaned `cover.png` sources left over from the JPEG
compression pass in #5528.
Covers are derived artifacts, so skipping outputs that already exist left
an image showing the old title after a post's frontmatter `title` changed.
Every run now re-renders from scratch; rendering is deterministic, so an
unchanged title re-encodes to identical bytes and a full run stays a no-op
in git.

Replaces `--force` (now the default) with `--check`, which renders in
memory and compares against the committed bytes without writing, so CI can
catch both a stale cover and the missing-cover case that caused the
original breakage.
Byte-equality on the mozjpeg output assumed portable encoder bytes.
libvips/mozjpeg does not guarantee that across OS and CPU, so identical
input can encode differently on a contributor's machine or a Linux CI
runner and fail the check for no real reason — exactly where the check was
meant to run.

Decodes both images to greyscale and compares mean absolute difference
instead, which discards encoder variance while still testing what the
check is about. Measured on this cover set: re-encoding an identical
render with a deliberately different encoder moves it ~0.26, a one-word
title change moves it ~12; the threshold of 2 sits between them with ~8x
margin.
Averaging the difference diluted a local edit across all 810,000 pixels.
Changing a title's "2026" to "2027" moved the mean by 0.42 — under the
tolerance that absorbed encoder noise — so the check passed a cover still
showing the old year.

Counts pixels that moved more than 48 greyscale levels instead. Measured on
this cover set, that one-character edit redraws 2,559 pixels while three
deliberately different encodes of an identical render (quality 60/70 without
mozjpeg, quality 95 with) redraw none, so the count separates real drift
from encoder variance in both directions.
…tokens

Two issues in the cover generator, neither reachable from a current title.

The hand-rolled frontmatter regex could disagree with `gray-matter`, which
is what renders the page and its `og:title`. On a double-quoted escape or a
block scalar the cover would have rendered a title the page never shows,
with `--check` calling it in sync. Uses `gray-matter` directly so there is
one parser.

`wrapTitleLines` only breaks between space-separated words, so a token wider
than the title box on its own stayed on an overflowing line, and the
non-breaking spaces left Satori no recourse but to break it at a hyphen —
the mid-compound break this layout exists to prevent. Oversized tokens now
split here, at hyphens first and per-character only for something like a
URL, and a font size is accepted only if every line measures within the box.

All 20 covers re-render byte-identically, so neither change alters current
output.
@waleedlatif1
waleedlatif1 force-pushed the worktree-blog-og-images branch from 94aa82a to cbee086 Compare July 27, 2026 21:12
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit cbee086. Configure here.

@waleedlatif1
waleedlatif1 merged commit 66ac015 into staging Jul 27, 2026
15 checks passed
@waleedlatif1
waleedlatif1 deleted the worktree-blog-og-images branch July 27, 2026 21:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant