Summary
scripts/generate-social-preview.py renders the committed .github/social-preview.png, but the render is environment-dependent and nothing in CI guards the asset. Four related problems, best fixed together. (For calibration: research confirms the overall approach is sound — GitHub's docs still recommend exactly 1280×640 PNG under 1 MB, and a committed asset + manual upload remains state of the art because GitHub has no API for the social preview, confirmed by GitHub staff in Sept 2025. This issue is about making the existing approach deterministic and guarded, not replacing it.)
1. System-font lookup makes the output non-reproducible — and the tracking pointer is stale
The script resolves fonts from the OS (arialbd.ttf → DejaVuSans-Bold.ttf → Helvetica.ttc), so re-rendering on a different OS produces different bytes. The in-code comment says vendoring a TTF is the fix and points to "issue #468 for follow-up" — but #468 is closed and never mentions font vendoring, so the follow-up is currently tracked nowhere. (This issue replaces that pointer; update the comment to reference this issue.)
Fix: vendor an OFL-licensed font (e.g. Inter or DejaVu Sans) under scripts/assets/ and load it by path. OFL 1.1 explicitly permits bundling and redistribution with software; the compliance requirement is committing the license text alongside — which fits the existing LICENSES/ directory (add LICENSES/OFL-1.1.txt).
2. macOS silently renders the wordmark non-bold (bug)
The bold candidate list falls back to /System/Library/Fonts/Helvetica.ttc, but ImageFont.truetype() on a .ttc without an index= loads face 0 — regular Helvetica, not bold. A maintainer regenerating on macOS would commit a visually wrong (non-bold wordmark) image with no error. Vendoring the font (point 1) eliminates this path entirely.
3. Unpinned dependencies
The docstring says pip install Pillow numpy with no versions; Pillow's text rasterization and zlib encoding can change across releases, which changes bytes. Modern practice for single-file scripts is PEP 723 inline metadata so uv run scripts/generate-social-preview.py is self-contained and pinned:
# /// script
# requires-python = ">=3.12"
# dependencies = ["Pillow==12.2.0", "numpy==2.4.0"]
# ///
(Current state verified 2026-06-10: with Pillow 12.2.0 on Windows, regeneration is byte-identical to the committed file, SHA256 48E7FEB8… — so determinism is achievable once font + versions are pinned. Pillow's PNG encoder embeds no timestamp chunk, so the remaining byte-stability risk is zlib/Pillow version skew; if that proves flaky across runners, the drift check can fall back to comparing decoded pixels, e.g. ImageChops.difference, instead of bytes.)
4. No CI drift check — despite the repo having a drift-check culture
drift-checks.yml, docs-drift.yml, and issue-templates-drift.yml already exist, but nothing verifies that the committed PNG still matches what the script + logo produce. Once points 1–3 make the render deterministic, add a job to drift-checks.yml:
- regenerate the preview and
git diff --exit-code .github/social-preview.png (or pixel-compare per the caveat above)
Optionally, a second check can detect upload drift: the currently uploaded preview is publicly fetchable via the repo's openGraphImageUrl (repository-images.githubusercontent.com/...); hash-comparing it to the committed file catches "committed a new preview but forgot to re-upload via Settings". (Verified in sync today.) If full automation is ever wanted, the only working paths drive the Settings UI with a browser — AnswerDotAI/gh-social-preview (Playwright, actively maintained) or mheap/github-social-image (Puppeteer) — probably not worth the complexity for this repo.
Bonus cleanup while in the file
- Render the logo from
public/logo.svg (clean vector, already in-repo) instead of chroma-keying the flat #2F2F2F backdrop out of public/logo.png — removes the LOGO_BG_COLOR/BG_THRESHOLD/BG_RAMP hack and gives crisper edges (resvg or cairosvg rasterize deterministically). For context, the 2026-mainstream OG tooling (satori/resvg via @vercel/og, Takumi) is HTML/CSS-to-image, but for a single static committed asset a Pillow script is perfectly defensible — no need to switch stacks.
- The vertical-centering math uses ink bounding boxes (
font.getbbox) with the default "la" anchor, so the text block sits ~bbox[1]px below true center and inter-line gaps vary with glyph extents. Cosmetic; worth fixing if the file is being touched anyway.
- No Python lint runs in CI; if more Python accumulates in
scripts/, add ruff to lint.yml.
Summary
scripts/generate-social-preview.pyrenders the committed.github/social-preview.png, but the render is environment-dependent and nothing in CI guards the asset. Four related problems, best fixed together. (For calibration: research confirms the overall approach is sound — GitHub's docs still recommend exactly 1280×640 PNG under 1 MB, and a committed asset + manual upload remains state of the art because GitHub has no API for the social preview, confirmed by GitHub staff in Sept 2025. This issue is about making the existing approach deterministic and guarded, not replacing it.)1. System-font lookup makes the output non-reproducible — and the tracking pointer is stale
The script resolves fonts from the OS (
arialbd.ttf→DejaVuSans-Bold.ttf→Helvetica.ttc), so re-rendering on a different OS produces different bytes. The in-code comment says vendoring a TTF is the fix and points to "issue #468 for follow-up" — but #468 is closed and never mentions font vendoring, so the follow-up is currently tracked nowhere. (This issue replaces that pointer; update the comment to reference this issue.)Fix: vendor an OFL-licensed font (e.g. Inter or DejaVu Sans) under
scripts/assets/and load it by path. OFL 1.1 explicitly permits bundling and redistribution with software; the compliance requirement is committing the license text alongside — which fits the existingLICENSES/directory (addLICENSES/OFL-1.1.txt).2. macOS silently renders the wordmark non-bold (bug)
The bold candidate list falls back to
/System/Library/Fonts/Helvetica.ttc, butImageFont.truetype()on a.ttcwithout anindex=loads face 0 — regular Helvetica, not bold. A maintainer regenerating on macOS would commit a visually wrong (non-bold wordmark) image with no error. Vendoring the font (point 1) eliminates this path entirely.3. Unpinned dependencies
The docstring says
pip install Pillow numpywith no versions; Pillow's text rasterization and zlib encoding can change across releases, which changes bytes. Modern practice for single-file scripts is PEP 723 inline metadata souv run scripts/generate-social-preview.pyis self-contained and pinned:(Current state verified 2026-06-10: with Pillow 12.2.0 on Windows, regeneration is byte-identical to the committed file, SHA256
48E7FEB8…— so determinism is achievable once font + versions are pinned. Pillow's PNG encoder embeds no timestamp chunk, so the remaining byte-stability risk is zlib/Pillow version skew; if that proves flaky across runners, the drift check can fall back to comparing decoded pixels, e.g.ImageChops.difference, instead of bytes.)4. No CI drift check — despite the repo having a drift-check culture
drift-checks.yml,docs-drift.yml, andissue-templates-drift.ymlalready exist, but nothing verifies that the committed PNG still matches what the script + logo produce. Once points 1–3 make the render deterministic, add a job todrift-checks.yml:git diff --exit-code .github/social-preview.png(or pixel-compare per the caveat above)Optionally, a second check can detect upload drift: the currently uploaded preview is publicly fetchable via the repo's
openGraphImageUrl(repository-images.githubusercontent.com/...); hash-comparing it to the committed file catches "committed a new preview but forgot to re-upload via Settings". (Verified in sync today.) If full automation is ever wanted, the only working paths drive the Settings UI with a browser — AnswerDotAI/gh-social-preview (Playwright, actively maintained) or mheap/github-social-image (Puppeteer) — probably not worth the complexity for this repo.Bonus cleanup while in the file
public/logo.svg(clean vector, already in-repo) instead of chroma-keying the flat#2F2F2Fbackdrop out ofpublic/logo.png— removes theLOGO_BG_COLOR/BG_THRESHOLD/BG_RAMPhack and gives crisper edges (resvgorcairosvgrasterize deterministically). For context, the 2026-mainstream OG tooling (satori/resvg via@vercel/og, Takumi) is HTML/CSS-to-image, but for a single static committed asset a Pillow script is perfectly defensible — no need to switch stacks.font.getbbox) with the default"la"anchor, so the text block sits ~bbox[1]px below true center and inter-line gaps vary with glyph extents. Cosmetic; worth fixing if the file is being touched anyway.scripts/, addrufftolint.yml.