Skip to content

feat(web): real per-commit OG card on cold-cache unfurls (#53)#55

Merged
lukaso merged 1 commit into
mainfrom
worktree-og-deferred-card
Jun 28, 2026
Merged

feat(web): real per-commit OG card on cold-cache unfurls (#53)#55
lukaso merged 1 commit into
mainfrom
worktree-og-deferred-card

Conversation

@lukaso-bot

Copy link
Copy Markdown
Collaborator

Closes #53 (human approved "Do 1").

What

Bot-driven unfurls hit the deferred path exactly when a link is first shared — the moment its cache is most likely cold — so they got the generic placeholder.png OG image instead of the per-commit card from #8.

This points the bot-deferred commit path (renderDeferred in result.tsx) at the dynamic web-og image URL, built straight from the route params (repo + the already-validated sha) with no resolved result:

  • new ogImageUrlForCommit(repo, sha, ogBaseUrl) in og-meta.tsx (shares the URL-builder with ogImageUrl, so it produces the same URL a resolved result would — covered by a test),
  • new imageOverride prop on OgMeta / ogImageOverride on Layout that wins over the result-derived image,
  • wired into the deferred path.

web-og already resolves the commit itself (cache-first, then findRelease via the relay-aware provider, single-flighted, 30-min cache) and degrades to its own placeholder on a miss/503. So the unfurl content is strictly better, with the failure path already handled on the web-og side.

Scope note (why PRs aren't included)

Only the commit permalink path. The PR-deferred path (pr.tsx) has no merge SHA before compute, and web-og has no PR→image route — so it can't point at a dynamic per-commit card without computing. Left on the placeholder, as today.

The tradeoff (already accepted on #53)

Crosses the explicit "bots never trigger a compute" line: one findRelease per unique cold permalink, on the web-og image fetch (a URL that's already public and already computes on a direct hit), cached 30 min after. Not a new attack surface.

Tests (TDD)

  • og-meta.test.ts: ogImageUrlForCommit mirrors the GitHub + federated schemes (sha shortened to 7) and equals the resolved-result URL.
  • integration.test.ts: the Slackbot deferred commit path now emits og:image = …/r/facebook/react/c/abc1234.png and not /placeholder.png.

Full gate (scripts/validate.sh) green locally: build, typecheck, 193 tests, a11y contrast, lint, gitleaks, publint, osv (no High/Critical).

🤖 Generated with Claude Code

Bot-driven unfurls hit the deferred path exactly when a link is first
shared — the moment its cache is most likely cold — and got the generic
placeholder OG image instead of the per-commit card from #8.

Point the bot-deferred commit path at the dynamic web-og image URL,
built from the route params (repo + already-validated sha) with no
resolved result. web-og resolves the commit itself (cache-first then
findRelease via the relay-aware provider) and degrades to its own
placeholder when it can't, so unfurl content is strictly better with the
failure path already handled on the web-og side.

Scope: the commit permalink path only. The PR-deferred path has no merge
SHA pre-compute and web-og has no PR->image route, so it can't point at a
dynamic per-commit card without computing — left on the placeholder.

Tradeoff (per #53, human approved "do 1"): crosses the "bots never
trigger a compute" line — one findRelease per unique cold permalink, on
the web-og image fetch (a URL already public + already computing on a
direct hit), cached 30 min after.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lukaso-bot

Copy link
Copy Markdown
Collaborator Author

In-house adversarial review (Copilot quota-blocked on this repo, so no automated review landed — per review-policy I run the skeptical pass myself):

Verdict: mergeable, no findings. CI green across the full matrix (test ubuntu/windows × node 20/22, a11y, osv, gitleaks, shell+workflow lint — 8/8).

What I checked:

  • Refactor is behavior-preserving. ogImageUrl keeps its null→placeholder branch and ?v= query; the non-null path delegates to the new commitImageUrl and yields a byte-identical URL. The "produces the SAME url a resolved result of the same commit would" test pins exactly this invariant, so the refactor can't silently drift.
  • Override is correctly isolated. imgUrl = imageOverride ?? ogImageUrl(result, ogBaseUrl) — only renderDeferred passes ogImageOverride, so every resolved/non-deferred card is untouched. No leak into the synchronous path.
  • Scope + types. renderDeferred already has repo: RepoRef and sha: string in scope (used by ogFallbackTitle), so the override call is well-typed.
  • No security regression. sha is interpolated into the URL path exactly as the pre-existing ogImageUrl already did; shas are hex and shape-validated before this path, and web-og resolves the commit itself and degrades to its own placeholder on a miss — so a cold unfurl never advertises a broken/unresolved card.
  • pr.tsx left on the placeholder intentionally — no merge-SHA pre-compute and no web-og PR→image route exist; changing it without a web-og route first would point at a 404. Correct scope call, documented in the PR.

Human-merge-only from here (the loop never merges).

@lukaso

lukaso commented Jun 28, 2026

Copy link
Copy Markdown
Owner

How can I test this?

lukaso pushed a commit that referenced this pull request Jun 28, 2026
…url)

The dynamic OG image (`/r/:owner/:repo/c/:sha.png` and the federated
`/h/...` variant) served HTTP 200 `image/png` with an EMPTY body for
every resolved result — so every social unfurl that used the real
per-commit card (issue #8's whole feature) showed a blank image. The
placeholder branch was unaffected, which is why the liveness probe
(it only checks `placeholder.png`) and any status/content-type check
were blind to it.

Root cause: satori (inside workers-og) throws
  "Expected <div> to have explicit display:flex ... if it has more
   than one child node"
when it renders `<div>commit {sha}</div>` — that JSX is TWO children
(the literal "commit " and the {sha} expression) on a div with no
`display: flex`. The throw happens mid-stream, AFTER the 200 + headers
are flushed, so the response is a valid-looking 200 with a 0-byte body.
Reproduced in the workerd runtime via `wrangler dev` (ResultCard → 0
bytes; fixed → 28KB valid PNG); the placeholder has only single-child
text nodes, so it always rendered.

Fix: make it a single text node (`{`commit ${sha}`}`).

Guard: the test harness already captures the satori node tree, so this
adds `satoriDisplayViolations()` — a structural assertion encoding
satori's multi-child-needs-display rule over the captured tree, run for
the released, not-yet-released, and placeholder cards. It fails on the
old `commit {sha}` and passes after the fix, catching any future
violation without a real WASM render.

Note: PR #55 wires bot deferred unfurls onto this dynamic path — it
should land on top of (or after) this fix, or bots get the blank card.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lukaso-bot

Copy link
Copy Markdown
Collaborator Author

Three ways, fastest first.

1. The automated check (already in the PR). The contract this changes is "a bot-UA cold-cache commit unfurl points at the per-commit card, not the placeholder":

pnpm --filter @released/web test -- integration

The relevant case lives in packages/web/test/integration.test.ts ("GET /r/:o/:r/c/:sha for an unfurl bot with no cache returns a deferred-render card") — it asserts og:image ends in /r/facebook/react/c/abc1234.png and is not /placeholder.png.

2. Locally, by hand. Bot detection is just a User-Agent allowlist (Slackbot, Twitterbot, Discordbot, … — packages/web/src/auth.ts), and a fresh dev server has a cold cache, so any commit hits the deferred path on the first bot request:

pnpm --filter @released/web dev      # wrangler dev on :8787
curl -s -A 'Slackbot 1.0 (+https://api.slack.com/robots)' \
  http://localhost:8787/r/facebook/react/c/<any-real-sha> | grep 'og:image'

Before this PR that prints …/placeholder.png; with it you get …/r/facebook/react/c/<sha7>.png. Drop the -A flag (a normal browser UA) and you should still see the per-commit image — only the cold-cache bot path changed, so the regular path must be untouched.

3. The real thing (post-merge). Since #56 is live, that per-commit image URL already renders a real PNG in prod — so once this merges, the end-to-end test is: paste a fresh commit permalink (one not recently looked up) into Slack/Discord/X and watch the unfurl show the actual release card instead of the generic placeholder. Same as a curl if you don't want to spam a channel:

curl -s -A Slackbot https://released.blabberate.com/r/<owner>/<repo>/c/<sha> | grep og:image
# then open that .png URL — it should be a real card, not placeholder.png

The catch with #3: the commit has to be cold (the deferred path only fires on a cache miss), so use one you haven't looked up in the last 30 min.

@lukaso-bot

Copy link
Copy Markdown
Collaborator Author

In-house adversarial review (Copilot is quota-blocked here, so no automated review lands)

Skeptical pass over the diff — no findings, safe to merge. Walked the failure modes that could turn a freshly-shared link into a broken unfurl:

  • Null deref on ogImageUrlForCommit(repo, sha)renderDeferred types repo: RepoRef and already dereferences it at displayName = repo.projectPath, and the only caller (result.tsx:118) sits behind the if (!repo || !sha) return 404 guard at result.tsx:50. No new null path.
  • Refactor behavior driftogImageUrl now delegates to commitImageUrl; placeholder branch and the ?v= query are preserved, and the new og-meta.test.ts case pins ogImageUrlForCommit(...) === ogImageUrl(resolvedResult, ...) so the two paths can't silently diverge.
  • Cold-resolve failure path (the real risk) — the override points the unfurl at the dynamic web-og URL with no resolved result. Verified web-og degrades cleanly: fetchResult swallows any miss/error → null, and renderImage(null, …) renders the placeholder card (web-og/src/index.tsx:22-32). So an unresolvable cold commit shows the neutral card, never a 500/broken image.
  • Dependency — this only produces a real card because fix(web-og): real per-commit OG card rendered a 0-byte PNG (blank unfurl) #56 (dynamic render, was a 0-byte PNG) is merged and live: prod serves a valid 28899B PNG on /r/honojs/hono/c/…png. Satisfied.
  • Scope — correctly limited to the commit-deferred path; PR-deferred stays on the placeholder (no pre-computed merge SHA), documented in the commit body.

Tests: unit (3, incl. federated scheme + equivalence) + integration (positive og:image assertion and a negative not placeholder assertion). CI 8/0. CLEAN/MERGEABLE.

Ready for your merge button.

@lukaso
lukaso merged commit ff7fafd into main Jun 28, 2026
8 checks passed
@lukaso
lukaso deleted the worktree-og-deferred-card branch June 28, 2026 16:00
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.

Proposal: render the real per-commit OG card on cold-cache unfurls (bots get the placeholder today)

2 participants