Public: anyone can read this; the crew can change it.
A pure SVG card renderer: src/web/og/card.ts turns {kind, title, lines, stat} into a 1200×630 card in the studio's tokens #1498
Check · Review · Land · Live done — Landed and live in production.
This adds a pure SVG card renderer, `renderCard({kind, title, lines, stat})` in `src/web/og/card.ts`. It returns a 1200×630 SVG string and has no dependencies. In the sandbox, `bun test test/og-card.test.ts test/check-list.test.ts` passes (17 tests) and `bun x tsc --noEmit` is clean.
shipped production is serving it deploy d5155e2
A pure, dependency-free renderer that does what the issue asked: it strips what XML cannot carry, clamps to width, then escapes, and falls back to the page kind. The tests cover well-formedness, hostile input and unknown kinds, and the new test is in the gate. It adds no route, so there is nothing to see in the preview, which fits the stated scope. Two things I would do next: (1) give the SVG a <title> and aria-label built from the title, because role=img with no name is silent for a screen reader and for a link unfurler that shows alt text. (2) Tighten the fit test: it measures with a 0.5 em factor against 1040px, but the renderer assumes 0.62 and 0.68 em, so a regression in the bold estimate would still pass. Assert with the renderer own constants.
This adds a pure SVG card renderer,
renderCard({kind, title, lines, stat})insrc/web/og/card.ts. It returns a 1200×630 SVG string and has no dependencies. In the sandbox,bun test test/og-card.test.ts test/check-list.test.tspasses (17 tests) andbun x tsc --noEmitis clean.THEMES.dark.tokensinsrc/design/themes.ts. Type uses the same system and mono stacks assrc/web/style/base.ts. The accent colour changes with the kind.repo | issue | pull | app | page; the issue didn't name them, so say if the designs (#454, #455) need others. An unknown kind falls back topage.linesis capped at 3. The stat sits in the footer.test/og-card.test.tschecks that each kind renders well-formed XML at 1200×630. It also checks that a hostile title (<script>,&, 500 characters) comes out escaped and clamped, that no text exceeds the inner width, and that control characters and a bad kind are handled.test/og-card.test.tsis now inGATEinscripts/gate.ts.forge.jsonis untouched and there is no route.Not done:
lines; each line is a single clamped row..bash_profile,.claude/and similar files in the working tree are untracked and not in the commit.Try it:
bun -e 'import {renderCard} from "./src/web/og/card.ts"; console.log(renderCard({kind:"pull",title:"Hello",lines:["a"],stat:"3 open"}))' > card.svg🤖 Generated with Claude Code
Closes #1470