Background
All three card renderers (renderStatCard, renderLanguageCard, renderErrorCard) build SVG output by manually constructing XML strings with inline coordinate math. This makes layout changes fragile — positions are calculated from CARD_WIDTH/CARD_HEIGHT ratios, text x/y values are derived from index arithmetic, and the TextAttr/CardAttr interfaces exist solely to thread positioning state through helper functions.
Satori renders a JSX/React element tree to an SVG string, replacing coordinate math with CSS flexbox. It runs in Node/Edge environments and returns a plain string, matching the existing return type.
Goal
Replace the string-template approach in all renderers with satori-based JSX components, eliminating manual layout math while keeping the public API surface (function signatures and SVG output) identical.
Migration plan
Phase 1 — Setup · #45
Phase 2 — Migrate renderErrorCard · #46
Start here because it is the simplest renderer (no dynamic data, no icons, no variants).
Phase 3 — Migrate renderStatCard · #47
Phase 4 — Migrate renderLanguageCard · #48
Most complex due to the pie chart (stroke-dasharray circles).
Phase 5 — Cleanup · #49
Constraints
- Satori only supports a subset of CSS — absolute positioning and
stroke/stroke-width on rects are not supported; layout must use flexbox
- Satori is async — all three renderer functions will become
async, and their callers must await them
- Font files must be bundled explicitly — no fallback to host system fonts
- The pie chart's
stroke-dasharray on <circle> elements may not be supported by satori and may require a workaround in Phase 4
Out of scope
- Visual redesign of the cards
- New card types
- Changes to the GitHub data-fetching layer
Background
All three card renderers (
renderStatCard,renderLanguageCard,renderErrorCard) build SVG output by manually constructing XML strings with inline coordinate math. This makes layout changes fragile — positions are calculated fromCARD_WIDTH/CARD_HEIGHTratios, textx/yvalues are derived from index arithmetic, and theTextAttr/CardAttrinterfaces exist solely to thread positioning state through helper functions.Satori renders a JSX/React element tree to an SVG string, replacing coordinate math with CSS flexbox. It runs in Node/Edge environments and returns a plain
string, matching the existing return type.Goal
Replace the string-template approach in all renderers with satori-based JSX components, eliminating manual layout math while keeping the public API surface (function signatures and SVG output) identical.
Migration plan
Phase 1 — Setup · #45
satoriandreact/react-dom+ their@typesas dependenciesjsxintsconfig.json("jsx": "react"or"react-jsx")InterorNoto Sans) underscripts/assets/fonts/— satori requires explicit font data; system fonts are unavailable at render timejestconfig to handle.tsxfiles if not alreadyPhase 2 — Migrate
renderErrorCard· #46Start here because it is the simplest renderer (no dynamic data, no icons, no variants).
scripts/renderers/ErrorCard.tsxas a pure JSX componentrenderErrorCard.tsbody with asatori(...)call wrapping the new componentasync, update the call site inapi/stats.tsrenderErrorCard.test.tsto useawaitPhase 3 — Migrate
renderStatCard· #47scripts/renderers/StatCard.tsxTextAttr/CardAttrinterfaces andmountText/mountIconsimperative loops with flexbox column layoutimgor inline<svg>— satori supports rendering inline SVGs as childrenpeng/colorvariants via propsrenderStatCardasync, updateapi/stats.tsPhase 4 — Migrate
renderLanguageCard· #48Most complex due to the pie chart (
stroke-dasharraycircles).scripts/renderers/LanguageCard.tsxstroke-dasharrayon<circle>— if not, replace the pie chart with a conic-gradient<div>or pre-rendered SVG fragment passed as a raw string childcalcPercentagesusage (already unit-tested separately — keep that logic as-is, only change rendering)renderLanguageCardasyncPhase 5 — Cleanup · #49
TextAttr,CardAttr,createText,createIcon,mountText,mountIconsfrom old filesDIVIDER_Y/ERROR_DIVIDER_Yconstants if no longer referencedtypecheck,test) passesConstraints
stroke/stroke-widthon rects are not supported; layout must use flexboxasync, and their callers mustawaitthemstroke-dasharrayon<circle>elements may not be supported by satori and may require a workaround in Phase 4Out of scope