Skip to content

Social: rebuild the daily card with real type, real map, and provenance - #12

Merged
aurph merged 1 commit into
mainfrom
worktree-daily-card
Aug 6, 2026
Merged

Social: rebuild the daily card with real type, real map, and provenance#12
aurph merged 1 commit into
mainfrom
worktree-daily-card

Conversation

@aurph

@aurph aurph commented Aug 3, 2026

Copy link
Copy Markdown
Owner

What

The image attached to every weekday tweet was ugly for four reasons, three of which failed silently.

Before / after are attached in the conversation; the short version:

before after
bold weights none (silently 400) real Inter Bold
stat numbers Inter fallback JetBrains Mono
dead space ~300px void data visual
as-of / source absent required fields

The silent failures

  1. satori was given one font file (Inter-Regular.ttf, weight 400) while the layout asked for fontWeight: 700/800 and fontFamily: "monospace". satori cannot synthesize weights or substitute an unregistered family, so every "bold" heading and every stat number rendered as Inter 400. No error, ever.
  2. satori silently truncates large inline SVG. Splitting the US outline into per-state <path> elements rendered a country missing 44 of 49 states, with no error. The outline must stay one concatenated path; assertMapPathIntact() guards it.
  3. Virginia's outer ring in us-states.geo.json is wound backwards. d3 reads that as the whole sphere minus Virginia, putting continental bounds at [[-180,-90],[180,90]]. geoAlbersUsa's clipping hid it, so the homepage map has been fitting to a global extent and rendering ~18% undersized and off-centre (scale 329.7 vs 402.0). Fixed as a one-line diff.

Provenance is now structural

asOf and source are required fields on OgCard. A card cannot ship without saying when it was measured and where it came from. A missing date prints AS OF — rather than quietly substituting today. The Monday card reports 235/235 entries sourced computed from the data, not asserted.

This also surfaces staleness honestly: the Thursday card currently prints AS OF 20 MAY 2026 because interconnection-queue.json really is that old.

No new runtime dependency

Projecting cluster coordinates would have meant importing d3-geo, which is ESM-only. The server ships as a CJS bundle with all deps external, so require("d3-geo") throws ERR_REQUIRE_ESM on Node < 22 — inside the try/catch around media upload, which would have degraded to silently image-less tweets forever.

Instead scripts/generate-map-path.mjs bakes closed-form Albers constants into server/us-map.ts and the server projects with plain arithmetic. d3-geo is a devDependency used only by the generator and a parity test that pins the arithmetic to d3's own output (worst drift 3e-13 px over 1,400 sample points). Verified: the built server bundle contains zero references to d3-geo.

Verification

  • npm test300 pass, 0 fail
  • npx tsc --noEmit — clean on server/
  • server bundle builds via the real esbuild config
  • all 10 templates render: npx tsx scripts/preview-cards.ts

The client build currently fails locally on framer-motion and @visx/*. Both are declared in package.json and package-lock.json but missing from the local node_modules; that is a stale local install, not a regression from this branch.

Note on scope

This branch is cut from origin/main, which already replaced the hand-drawn PowerMapDiagram with RealUSMap. The local checkout at ~/gridtilt is behind and still has the fabricated blob.

The card the daily poster attaches to every weekday tweet had four defects,
three of them silent.

satori was given one font file (Inter-Regular, weight 400) while the layout
asked for fontWeight 700 and 800 on the wordmark, title and stat values, and
fontFamily "monospace" on the numbers. satori cannot synthesize weights or
substitute a family it was never given, so all of it rendered as Inter 400.
Nothing errored; the card just looked flat, and had since it shipped.

The frame also used justify-content: space-between with two children on a
630px canvas, leaving roughly 300px of dead space through the middle, and it
showed no chart, no as-of date and no source. For a product whose claim is
"tracked with sourced numbers", a card that shows three numbers with no date
and no provenance argues against itself.

Changes:
- server/og-card.ts owns layout, fonts and the map. routes.ts only gathers
  data. asOf and source are required fields on OgCard, so a card physically
  cannot ship without saying when it was measured and where it came from.
  A missing date prints "AS OF —" rather than quietly substituting today.
- Registers Inter-Bold and JetBrainsMono-Bold (both OFL, same licence as the
  Inter already bundled). Stat values now render in the brand mono.
- Each weekday template gets a visual built from the same data as its tweet:
  Monday the 235-cluster US map, Tuesday the GPU price ladder, Wednesday the
  spotlight cluster located on the map, Thursday the queue breakdown,
  Friday the energy mix.
- server/us-map.ts is generated by scripts/generate-map-path.mjs and carries
  the outline plus closed-form Albers constants, so the server projects with
  plain arithmetic. Importing d3-geo would have meant require()-ing an
  ESM-only module from the CJS bundle, which throws on older Node, inside a
  try/catch that would have degraded it to silently image-less tweets.
  d3-geo is now a devDependency used by the generator and a parity test that
  pins the arithmetic to d3's own output.
- Fixes Virginia in client/src/data/us-states.geo.json, whose outer ring was
  wound backwards. d3 reads that as the whole sphere minus Virginia, which
  put the continental bounds at [[-180,-90],[180,90]]. geoAlbersUsa's
  clipping hid it, so the homepage map has been fitting to a global extent
  and rendering ~18% undersized and off-centre (scale 329.7 vs 402.0).
- scripts/preview-cards.ts renders every template to .card-preview/ for
  eyeballing. It posts nothing.

Tests cover the two silent failures directly: a missing font face now fails
the suite, and a truncated outline path fails assertMapPathIntact. satori
silently truncates large inline SVG, which is why the outline must stay a
single concatenated path.

300 tests pass, tsc clean on server, server bundle verified to contain no
d3-geo reference.
@aurph

aurph commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

Verified against current main (e887487) in a clean worktree:

Checked the deployment risk on the font loading specifically: loadFonts() reads from process.cwd()/server/fonts, which is the pattern Inter-Regular.ttf already uses on main, so the two new .woff files land in a directory that already ships. No new runtime path.

d3-geo is correctly a devDependency: it is only used by scripts/generate-map-path.mjs to precompute server/us-map.ts, so nothing new is pulled into the server bundle.

@aurph
aurph merged commit 90a268d into main Aug 6, 2026
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.

1 participant