Repository navigation
Draw the software cards as SVGs at build, without a browser - #345
Merged
Merged
Conversation
Contributor
|
Preview: https://2606bf3a.nstarkman-space.pages.dev Built from |
nstarman
force-pushed
the
add/software-card-svgs
branch
3 times, most recently
from
October 6, 2026 19:22
0a88edf to
3583448
Compare
nstarman
added a commit
that referenced
this pull request
Oct 6, 2026
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
/cards/<id>-<theme>.svg and /cards/index.json, from a static endpoint: the lead and headline packages' cards, laid out in node (src/lib/cardlayout.js) from the same presets the site uses, text measured from the Plex files, written by cardsvg.js. For the profile README, which needs images and no longer needs Playwright to get them. tests/cardlayout.test.js holds it to the CSS: its colours, steps and formulas are read from global.css; it reproduces the browser's own measurement of every such card; that measurement is stamped with a hash of the card's CSS and markup, so a change to either fails the test until it is measured again (scripts/record-card-layouts.mjs). It throws on any name that asks for more than it draws. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ties over generated data The recording of Chrome's layout is now at 200, 280, 400 and 640 px (both themes at 400), so wrapping and a second row of buttons are compared, not reasoned about. That found a real gap: a button is never under 24 px (.iconbtn's min size), which a narrow card's formula goes below. tests/cardlayout.property.test.js, with fast-check: over generated widths, texts, roles and buttons, nothing is dropped or added, no line passes the padding, buttons never overlap and wrap only for want of room, the card is no lower for more words, the SVG parses with everything escaped, and a name it cannot draw is refused as such. Wrapping is monotone in the box and loses no character. Writing it found that a title is broken anywhere (overflow-wrap:anywhere), which the renderer did not do. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
With none, there is nothing after the words but the padding. (Found by asking Chrome about a card with every button removed; the unit tests have the case.) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
opentype.js did not apply every kerning pair or any ligature, and was up to 1.8 px off Chrome on a line at a small size (found by the differential fuzz). HarfBuzz is Chrome's own shaper: across every line of the recorded cards it is within 0.15 px, so the golden tolerance for a line's width drops from 1.5 to 0.25. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A button of a few stars reads as a liability, not a credential (the README's old pills had the same rule, at 50). STARS_MIN is the one place that says so, so the site's cards, the embeds and the README's images agree. The recording of Chrome's layout is made again without those buttons. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… sizes too near a step to call Chrome's step in a line's rounded ascent is within about 0.003px of font size of where the font's metrics put it, with no one rule that fits (the differential fuzz found a card of 412px, whose text is 16.0992px, a pixel short). nearStep says which sizes that is, for the fuzz to leave out. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
nstarman
force-pushed
the
add/software-card-svgs
branch
from
October 6, 2026 20:00
7022d3d to
dde3f08
Compare
…s.test.js Chrome shapes and measures a line at its font size taken down to a hundredth of a px (10.656px is 10.65px), in a float, and rounds a half down in the ascent and descent of the line box. Found by measuring Chrome, after the differential fuzz found a line a hair too wide to fit that Chrome fits. An earlier commit left a duplicate closing brace at the end of tests/cards.test.js, so that file did not load and its tests were not running (the totals were lower and nothing said why). They run again: 684. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Only the least a button is (24px) bounds it below; a one-digit star count made a button narrower than the plain ones beside it. Found by the differential fuzz. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The box is a grid of three columns with a .4em gap between; the slots with nothing in them take no column but their gaps stay, so the row is 0.8em narrower than the card's content. A third button that fitted ours went to a second row in Chrome. Found by the differential fuzz. Also: the first SVG render in the tests gets 30s, for a busy machine. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… item's strut Chrome lays lengths out in 1/64 of a px, down. And a button sits in its list item on its baseline, so the item is as high as the box and the item's own strut (its font at the body's 1.55) need together: at some sizes the strut reaches below the box and the item is taller than the button (26.594 for a 26.1875 button at 320px). Both found by the differential fuzz. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 task done
…s built for JS does not Found by the differential fuzz (a line of jTA… was 0.75px too wide), then by measuring every pair of ASCII letters, digits and marks in Chrome against HarfBuzz: 6241 pairs, three differ: jT, jV, jW. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Looks: only look:feature, which the lead preset names, is drawn, and a name is accepted if it is a tier's preset at a set width, which replaces a 17-line whitelist of what it may and may not say. The recording of Chrome's layout is one case to a line with each icon's markup once: 52 lines, not 8,222, and marked generated. Tests that restated others (the CSS proportions the hash already guards, a property for names it refuses, a second render of the SVG) are gone, and so are two duplicates in softwarecards.js and a flag in the layout. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A 17.9929px line of 18px content has no half-leading in Chrome, not -1 (found by the differential fuzz at a card of 238px). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n en dash, an ellipsis, a question mark Found by the differential fuzz (a line broken after Ol- before 99), then by trying each mark in a narrow box in Chrome. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…toward zero A 26.9998px line of 25px content has a whole px above it in Chrome (found by the differential fuzz at a card of 524px). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 task done
…e product down to a 64th Fitted to Chrome's line heights and offsets at 3000 font sizes (0 line-height and 1 offset mismatches): 1.5 of 13.3344px is 19.984375, not 20.0016, and a half-leading is halved in 64ths toward zero before it is floored. Replaces the two rounding rules the differential fuzz had forced one case at a time. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changing code or schema
Stacked on #343 (stars button) and #344 (SVG export): this branch contains both, so until they merge the diff shows their commits too. The change itself is the last commit.
What and why: the profile README needs the lead and headline software cards as images, and shouldn't need Playwright and Chromium to get them. The cards are laid out by CSS, so there was no server-side way to ask for one; this adds one.
/cards/<id>-<light|dark>.svgand/cards/index.json: static endpoints (src/pages/cards/), built with the site, plain node.src/lib/cardlayout.js: lays a software card out without a browser, as the same modelcardpdf.jsmeasures, socardsvg.jswrites it. Text is measured from the Plex files (opentype.js, to a hundredth of a px of what Chrome gets). The presets and width come fromTIER_PRESET/CARD_SVG_WIDTHincards.js, so the README follows the site's presets.<a>s in the file (live when opened or inlined; GitHub's<img>won't run them, see Add an SVG export to the Card Builder #344).Keeping it in sync (
tests/cardlayout.test.js, 45 tests):global.css: colours, the look's steps, the formulas of a set-width card, line heights and gaps;tests/fixtures/software-cards.json, recorded byscripts/record-card-layouts.mjs;Card.astro(scripts/lib/cardsync.mjs), so editing either fails the test until it's re-recorded and compared; an unrelated stylesheet edit doesn't;I checked the tests bite: changing a card margin or the title weight in the CSS fails the hash test; giving the headline preset a figure fails four.
Limits: drawn at the 16px root size the site has at 1280px+; fonts fall back to the reader's system face when shown through
<img>, with each line held to its measured width.npm run test:schemapasses; 623 unit tests and the build passScreenshots: the SVG cards against the CSS cards
Each row is one package. Left to right: the real card (Chrome, the embed page at 400 px); the SVG this PR builds, with the Plex fonts loaded; their difference (black is identical — what's left is anti-aliasing); and the SVG as the README shows it, through
<img>, where the page's fonts are not available so text falls back to the system face (each line is held to its measured width).Light
Dark
coordinax (38 ★) and potamides (3 ★) have no stars button: a package under 40 stars gets none (
STARS_MIN, in the site's cards and embeds as well, so all three agree).🤖 Generated with Claude Code