Skip to content

Fix Open to Work badge export alignment - #146

Merged
jldeen merged 5 commits into
mainfrom
jldeen-fix-download-button-alignment
Aug 18, 2026
Merged

jldeen merged 5 commits into
mainfrom
jldeen-fix-download-button-alignment

Conversation

@jldeen

@jldeen jldeen commented Aug 17, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #59

Symptom

Downloaded PNGs didn't match what the browser rendered. Pill labels like LEGENDARY / MYTHICAL and OPEN TO WORK sank to the bottom of their pills, stat labels (REPOSITORIES, FOLLOWERS, STARS…) sat low in their cells, and the rarity pill looked like it floated above the @login handle instead of sharing a center line with it.

Root cause

html2canvas reimplements text layout rather than deferring to the browser. It paints each text run at textRect.top + <its own font baseline probe>. With the Monaspace Neon / Mona Sans webfonts this project uses, that probe overshoots the real content-box ascent by roughly half an em:

element font-size html2canvas baseline real ascent drift
Username 20px 29 19 +10
@login 14px 22 15 +7
RarityBadgeText 14px 21 13 +8
StatLabelText 11px 16 10 +6

So every text run sank, while the pill boxes — which are real layout boxes — stayed where they belonged. That's why the pill read as "raised" relative to the handle: the pill was correct and the text around it had dropped.

This is why per-element translateY compensation was the wrong tool. It only ever patched a handful of the affected nodes, and the constants were specific to one font size and one viewport.

Fix

Serialize the node into an SVG <foreignObject> and let the browser rasterize it (modern-screenshot, MIT). Layout, font metrics, CSS filters and colors are then produced by the same engine that painted the preview, so export matches preview by construction — no compensation constants anywhere.

Two things fall out of this for free:

  • the Tailwind v4 oklch() parse failure goes away, so the .CardFooter CSS-module workaround is no longer needed;
  • the avatar's filter: blur() glow renders again — html2canvas had been silently dropping it.

All ten export call sites (5 in DevemonCard.tsx, 5 in ReadmeBanner.tsx) now share src/utils/domExport.ts, which removes a good deal of duplicated boilerplate. ReadmeBanner keeps its original 2x scale; the Devémon card keeps 3x.

Separately: an actually-undefined custom property

--color-brand-on-primary was referenced by .HireBadge / .BadgeHireBadge but never declared — not in global.css, not anywhere. An undefined custom property makes color inherit, which left near-invisible light text on the bright brand-green "OPEN TO WORK" pill in dark mode. Now uses var(--color-bg-primary).

Result

Browser render vs downloaded PNG, side by side

The downloaded card on its own:

Downloaded Devemon card after the fix

Validation

  • npm run check — 0 errors, 0 warnings
  • npm run build — clean
  • npx playwright test — 4/4 passing
  • Measured export-vs-render difference: 1.41% of pixels (pure antialiasing noise from a 1px width rounding difference), against an 8% assertion threshold.
  • DOM centerDelta between the rarity pill and @login is now 0.

tests/devemon-download-layout.spec.ts is rewritten to assert the property that actually matters: it triggers a real download, screenshots the same DOM node at the same 3x scale, and compares them pixel-by-pixel. That means it fails on any renderer that reinvents layout or text metrics, rather than asserting on incidental class names. Confirmed to have teeth — swapping the renderer back to html2canvas fails the test.

Notes for reviewers

  • Dependency change: removes html2canvas, adds modern-screenshot@^4.7.0 (MIT, no transitive deps).
  • The earlier commits on this branch introduced per-element nudging hacks; this commit resets those files and takes the root-cause route instead, so the net diff vs main is small (+143 lines, most of which is the new test).

Use html2canvas-safe CSS markup for the Open to Work badge icon and text so downloaded Devémon and README banner assets match the live layout. Add a Playwright regression that exercises Devémon badge/card downloads with mocked GitHub data.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@jldeen
jldeen requested a review from Damovisa as a code owner August 17, 2026 23:57
Copilot AI balanced review requested due to automatic review settings August 17, 2026 23:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes Open to Work badge alignment and PNG export compatibility.

Changes:

  • Replaces fragile SVG briefcase icons with CSS-rendered icons.
  • Stabilizes badge flex alignment and export-safe footer colors.
  • Adds Playwright coverage for Devémon PNG downloads and badge geometry.
Show a summary per file
File Description
tests/devemon-download-layout.spec.ts Tests badge alignment and PNG exports.
src/components/ReadmeBanner.tsx Updates the banner badge icon and alignment.
src/components/DevemonCard.tsx Updates card badges and footer styling.
src/components/DevemonCard.module.css Defines export-safe icon, badge, and footer styles.

Review details

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 4/4 changed files
  • Comments generated: 0
  • Review effort level: Balanced

jldeen and others added 4 commits August 17, 2026 17:08
Wrap Devémon card label text so html2canvas renders rarity, Open to Work, and stat labels on the same visual centerline as the browser preview. Relax PNG dimension assertions to account for CI font metric differences while still verifying valid downloads.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Set the Available for Hire badge text/icon to the dark background token for readable contrast, increase the rarity pill text offset for html2canvas export centering, and add a regression assertion for the badge text color.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Increase the Devémon card header gap so the rarity pill no longer overlaps the username in exported PNGs, and add a regression assertion that the name-to-rarity spacing stays non-overlapping.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Downloads and social shares rasterize the same DOM the user is looking at,
so an exported PNG should be a faithful copy of the on-screen render. It
wasn't: labels like "LEGENDARY" and "OPEN TO WORK" sank to the bottom of
their pills, stat labels sat low in their cells, and the rarity pill looked
like it floated above the @login handle.

Root cause: html2canvas reimplements text layout rather than deferring to
the browser. It paints each run at `textRect.top + <its own baseline probe>`,
and with the Monaspace Neon / Mona Sans webfonts that probe overshoots the
real ascent by ~0.5em:

  element           size   h2c baseline   real ascent   drift
  Username          20px   29             19            +10
  @login            14px   22             15             +7
  RarityBadgeText   14px   21             13             +8
  StatLabelText     11px   16             10             +6

Because *every* run sank while the pill boxes stayed put, the pill read as
raised relative to the handle. Per-element translateY compensation only ever
patched a few of the affected nodes, so it was replaced here.

Serialize the node into an SVG <foreignObject> (modern-screenshot) and let
the browser rasterize it. Layout, font metrics, filters and colors then come
from the same engine that painted the preview, so export matches preview by
construction. This also fixes the Tailwind v4 oklch() parse failure and
restores the avatar's blur glow, which html2canvas silently dropped.

Also define readable text on the brand-green "OPEN TO WORK" pill:
--color-brand-on-primary was referenced but never declared anywhere, so the
color inherited and rendered near-invisible in dark mode.

All ten export call sites now share src/utils/domExport.ts.

Fixes #59

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3a633ff9-3c7e-4499-825f-af5baab48b1c
@jldeen
jldeen merged commit 7dd3c2a into main Aug 18, 2026
8 checks passed
@jldeen
jldeen deleted the jldeen-fix-download-button-alignment branch August 18, 2026 00:53
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.

Button misalignment after download

2 participants