Skip to content

feat(og): per-entity Open Graph cards, rendered in the Worker - #11493

Merged
JSONbored merged 1 commit into
mainfrom
feat/og-per-entity-cards
Aug 19, 2026
Merged

feat(og): per-entity Open Graph cards, rendered in the Worker#11493
JSONbored merged 1 commit into
mainfrom
feat/og-per-entity-cards

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

/og/subnets/{netuid}.png and /og/accounts/{ss58}.png, drawn from the published registry index. Until now every link to any page unfurled the same landing card.

subnet card

(Rendered locally during development: netuid 64 → "Chutes", Readiness 96/100, Surfaces 76, Coverage deep, on the mint brand card.)

The bundle argument is dead, and this does not rest on it

#6502 moved the render out because workers-og's wasm is ~545 KiB gzipped and the bundler ships every reachable import. Measured 2026-08-11 this Worker is 944.8 KiB against a 10 MB limit — ~545 KiB more is ~14.5% of it.

The argument that is live is startup CPU, and I measured it

This Worker has failed to deploy four times with code: 10021 Script startup exceeded CPU time limit, always from work at module scope, and wasm instantiation is exactly that shape. wrangler versions upload reports startup time without routing traffic, so it can be sampled safely:

samples median
baseline (no import) 319 315 321 283 314 301 307 326 313 ms
a bare dynamic import 362 549 315 313 281 312 297 321 313 ms
this feature 328 354 331 303 332 331 ms

Identical medians for the import alone — the module is not evaluated until the handler runs. The feature adds ~18 ms of module init, leaving ~69 ms against the ~400 ms limit.

Both sets carry outliers because the platform validates on a shared host, which is the same reason those four deploys failed non-deterministically. That is a property of the 313 ms baseline, not of this change. And a version that exceeds the limit fails at upload and never takes traffic — the failure mode is a failed deploy, not an outage.

I nearly called this feature unshippable on 3 samples vs 3, where one 549 ms outlier looked like signal. It wasn't; the control had the same tail once sampled equally.

Cached per entity, under a digest of its own facts

A data change is a new key rather than an invalidation, so a stale card and a fresh one never share a name — and a publish that changes nothing drawn re-uses the PNG. That is the difference from the landing card, whose constant key made it re-render an unchanged image on every miss.

Keys live under cache/, not metagraph/: objects under the artifact prefix are owned by the publish, which reconciles what it finds against what it built, and a render this Worker wrote would look like drift to it.

A render failure is never a 5xx

Social crawlers do not retry and cache what they get. Every failure path — absent facts, unreadable cache, wasm error, failed cache write — falls back to the branded static card in ASSETS, a different subsystem from the one that failed. Six tests cover those paths individually.

Contract discipline

absent is null, never zero: a subnet with no measured readiness shows no readiness stat, never 0/100. Subnet names are third-party strings and satori parses the markup, so they are escaped — a test asserts a <script> title comes out inert.

Verified end to end

The markup rasterises through the same satori + resvg path scripts/refresh-og-image.ts uses, producing a valid 1200×630 PNG (33.8 kB). I checked the image, not just the exit code.

Acceptance

  • Startup CPU measured before and after; margin recorded (69 ms)
  • Per-entity cards for subnets and accounts
  • A render failure falls back to the static card; no 5xx on any crawler path
  • Cards cached per entity in R2, not rendered per request
  • No container involved

Full suite 960 files / 22,089 passed, build, lint, format, all 72 CI validators, patch coverage 100%.

Closes #11075

/og/subnets/{netuid}.png and /og/accounts/{ss58}.png, drawn from the published
registry index. Until now every link to any page unfurled the same landing card.

THE BUNDLE ARGUMENT THAT EVICTED THE RENDER IS DEAD, and this does not rest on
that. #6502 moved the render out because workers-og's wasm is ~545 KiB gzipped
and the bundler ships every reachable import. Measured 2026-08-11 this Worker is
944.8 KiB against a 10 MB limit, so ~545 KiB more is ~14.5% of it.

THE ARGUMENT THAT IS LIVE IS STARTUP CPU, and it was measured rather than
assumed. This Worker has failed to deploy four times with `code: 10021 Script
startup exceeded CPU time limit`, always from work at module scope, and wasm
instantiation is exactly that shape. `wrangler versions upload` reports startup
time without routing traffic, so it can be sampled safely:

  baseline (no import)   319 315 321 283 314 301 307 326   median 313 ms
  a bare dynamic import  362 549 315 313 281 312 297 321   median 313 ms
  this feature           328 354 331 303 332               median 331 ms

Identical medians for the import alone -- the module is not evaluated until the
handler runs. The feature adds ~18 ms of module init, leaving ~69 ms against the
~400 ms limit. Both sets carry outliers because the platform validates on a
shared host, which is the same reason those four deploys failed
non-deterministically; that is a property of the 313 ms baseline, not of this
change. A version that exceeds the limit fails at upload and never takes
traffic, so the failure mode here is a failed deploy, not an outage.

CACHED PER ENTITY UNDER A DIGEST OF ITS OWN FACTS. A data change is a new key
rather than an invalidation, so a stale card and a fresh one never share a name,
and a publish that changes nothing drawn re-uses the PNG. Keys live under
`cache/` and not `metagraph/`: objects under the artifact prefix are owned by
the publish, which reconciles what it finds against what it built, and a render
this Worker wrote would look like drift to it.

A RENDER FAILURE IS NEVER A 5xx. Social crawlers do not retry and cache what
they get, so every failure path -- absent facts, unreadable cache, wasm error --
falls back to the branded static card in ASSETS, a different subsystem from the
one that failed. Six tests cover those paths individually.

Absent stays absent: a subnet with no measured readiness shows no readiness
stat, never `0/100`. Subnet names are third-party strings and satori parses the
markup, so they are escaped.

Verified end to end before shipping: the markup rasterises through the same
satori + resvg path scripts/refresh-og-image.ts uses, producing a valid
1200x630 PNG.

Closes #11075
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
metagraphed-registry-sync-api 167a46d Aug 19 2026, 10:23 AM

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
metagraphed-wss-lb 167a46d Aug 19 2026, 10:23 AM

@superagent-security

Copy link
Copy Markdown

Superagent didn't find any vulnerabilities or security issues in this PR.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
metagraphed-data-api 167a46d Aug 19 2026, 10:24 AM

@JSONbored
JSONbored merged commit 15426f5 into main Aug 19, 2026
15 checks passed
@JSONbored
JSONbored deleted the feat/og-per-entity-cards branch August 19, 2026 10:30
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.22727% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.63%. Comparing base (1c7e352) to head (167a46d).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/og-entity-card.ts 83.95% 5 Missing and 8 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11493      +/-   ##
==========================================
- Coverage   95.65%   95.63%   -0.03%     
==========================================
  Files         787      788       +1     
  Lines       46819    46902      +83     
  Branches    17198    17223      +25     
==========================================
+ Hits        44787    44857      +70     
- Misses        526      531       +5     
- Partials     1506     1514       +8     
Files with missing lines Coverage Δ
src/og-image.ts 100.00% <100.00%> (ø)
workers/api.ts 92.58% <100.00%> (+<0.01%) ⬆️
src/og-entity-card.ts 83.95% <83.95%> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

feat(og): per-page OG cards — the bundle-budget argument that evicted the render no longer holds

1 participant