grain-serve draws the OpenGraph cards links unfurl into
MergedThis adds the three /og/... card routes to grain-serve. It draws each card as an SVG, rasterizes it with resvg, and keeps it in process for five minutes with a 200-card cap. Cards go through the moderation filters the description lists.
I read og/mod.rs in full. I did not read collage.rs, draw.rs, unlabeled in read/moderation.rs, or the tests, so I can't confirm the collage port or the test coverage. I found nothing to block on.
- Moderation: it matches the description. A card is "not found" for an unknown or taken-down account. The same goes for a gallery or story that hides under
HIDDEN_LABELSor a self-label. Any label on the account, gallery or story removes photos and avatar. Photo labels are filtered in the SQL. Self-labels exist only on galleries and stories, so checkingself_labelonly on those is correct. - Cache and load: the cache key is the request path, the cache is capped, and drawing is limited to one card per core by a semaphore. Unknown paths do get drawn and cached, but the cap and the semaphore bound the cost.
- Checks: all four (
check,rust,test,harness) were still queued when I looked, so nothing has failed or passed yet. Theharnesscheck enforces the coverage gate and the answers check that AGENTS.md describes, so read its result before merging.
My earlier review asked for nothing. Since then the pull has gained a merge of feature/rust, a clippy chunking fix (as_chunks in the tests), a Cargo.toml format fix, and a simplification of the collage's floor to one condition. I did not re-read those changes line by line, so I'm relying on the collage test that pins the old layout's output to catch any change in placement.
The checks on this commit are still running or queued (harness and check running, test and rust queued), so nothing has failed or passed yet. Read test and harness before merging, since they run the collage and og tests and the coverage gate.
Carried forward: the change this pull makes at 291b3724fa1aaffd4162f9c95cab29f0c151054d is the one reviewed at 4c21d180d7d79116786b0937ac53d1b4675a32be, so that review stands. What is new comes from its base.
Carried forward: the change this pull makes at 5868302700c1f85c8bf2d62e44f37cb5f3a2615d is the one reviewed at 291b3724fa1aaffd4162f9c95cab29f0c151054d, so that review stands. What is new comes from its base.
- grain-serve draws the OpenGraph cards links unfurl into
- main
What changes
grain-serve draws the OpenGraph cards at the paths the web's pages already name in
og:imageand the embed route builds:/og/profile/<actor>,/og/profile/<actor>/gallery/<rkey>and/og/profile/<actor>/story/<rkey>, the actor a handle or a DID. Each answer is a 1200x630 PNG withCache-Control: public, max-age=300andAccess-Control-Allow-Origin: *, and a card is kept in process for five minutes, the 200 newest (App::cards).The cards look as they do today. Each is an SVG written in
crates/grain-serve/src/og/and rasterized in process with resvg, as knotless's cards are, with the fonts compiled in fromcrates/grain-serve/fonts/(Inter Regular, the face the cards have always set text in, Syne ExtraBold for grain's name, Noto Sans JP and Noto Emoji for what Inter lacks; OFL, infonts/OFL.txt). The collage layout is the old one step for step, including the order Node's sort asks its comparator for random numbers in, so a gallery's photos land where they land today:og::collage's test pins the old layout's output for the same photos, and the attached gallery card, drawn from production's own images, matches production's pixel for pixel in placement.What a label touches stays off a card. A card cannot blur or warn:
HIDDEN_LABELS, grain's or a self-label): a "not found" card;read::moderation::unlabeledis the new filter: no grain label standing, whatever it says, expiry and negation asReader::labelsreads them.Why
The step of the plan that moves the cards off the old server. resvg over a headless browser or a satori port: the cards are a few boxes, images and lines of text, which an SVG says directly, and knotless already draws its cards this way.
How it was checked
tests/og.rs, against a fake imgproxy CDN serving solid-color PNGs: each card's photos and avatar land where expected (pixels read back), photos the CDN cannot serve are left out, every moderation case above, the not-found cards, the in-process cache, no index (404) and an index that fails to read (500).toFixed(3), font fallback and truncation, the cache's expiry and cap.Shortcomings
og:meta tags into the SPA fallback's HTML; the SvelteKit pages set their own, so that is not ported./og/*to grain-serve in production yet; that is the cutover's Caddy change.server/og/, so git stores no new blobs for three of the four.Review
og/mod.rsfor what reads the index and the moderation rules,og/collage.rsis a port and is pinned by its test.🤖 Generated with Claude Code