Skip to content

web-next: OG images for profiles and articles#269

Merged
dahlia merged 22 commits into
hackers-pub:mainfrom
dahlia:web-next/og-image
May 1, 2026
Merged

web-next: OG images for profiles and articles#269
dahlia merged 22 commits into
hackers-pub:mainfrom
dahlia:web-next/og-image

Conversation

@dahlia

@dahlia dahlia commented Apr 30, 2026

Copy link
Copy Markdown
Member

Closes #265.

Profile OG image preview for HP Dev, showing the account avatar, fediverse handle, bio excerpt, and Hackers' Pub footer logo. Article OG image preview for “Stop writing if statements for your CLI flags (edited)” by HP Dev, with the author avatar, article excerpt, and Hackers' Pub footer logo.

What changed

This PR implements og:image support for web-next profile and article pages.

It adds Satori/Resvg-based image rendering in graphql/og.ts, exposes cached ogImageUrl fields from graphql/account.ts and graphql/post.ts, and wires those fields into the web-next profile and article routes under web-next/src/routes/(root)/[handle]/og.tsx and web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/ogimage.tsx.

The image design follows DESIGN.md: black and white first, quiet typography, Pubnyan plus the Hackers' Pub logotype in the footer, and no extra marketing copy inside the image.

Notes

The generated images are cached through the configured drive and keyed by a stable hash of the render input. Article cache keys include article/content identity so two articles with the same title and text do not collide.

The expensive ogImageUrl fields also carry high GraphQL complexity so public bulk queries cannot cheaply trigger many Satori/Resvg renders at once.

Article /ogimage requests now resolve a single language instead of asking GraphQL for every translation when no ?l= parameter is present.

dahlia added 11 commits May 1, 2026 01:06
Add the first Satori-based Open Graph renderer for profile cards and
expose it through Account.ogImageUrl.  The renderer stores regenerated
PNGs under the og/v2 cache namespace and vendors the official Pubnyan
asset used as the quiet brand seal.

The GraphQL test covers initial rendering and cache reuse.

hackers-pub#265

Assisted-by: Codex:gpt-5.5
Extend the Satori Open Graph renderer with the article card template
and expose ArticleContent.ogImageUrl.  Article content now renders and
caches per-language OG images under the existing og/v2 namespace.

The GraphQL test covers language-specific rendering and cache reuse.

hackers-pub#265

Assisted-by: Codex:gpt-5.5
Add profile and article Open Graph image endpoints that redirect through
the new GraphQL-backed renderers.

Set profile and article meta tags to the new web-next endpoint URLs,
including language-specific article image variants.

hackers-pub#265

Assisted-by: Codex:gpt-5.5
Keep the previous Open Graph image on disk until the database record has
been updated to point at the newly rendered image.  This prevents a
failed metadata update from leaving the stored key pointing at a file
that was already deleted.

Extend the GraphQL tests to cover helper ownership of deletion and stale
cache cleanup through the profile and article resolvers.

hackers-pub#265

Assisted-by: Codex:gpt-5.5
Resolve the default article language before querying the rendered Open
Graph image.  Requests without an explicit language now render one
article content instead of selecting ogImageUrl for every translation.

hackers-pub#265

Assisted-by: Codex:gpt-5.5
Include the article source identity in the hashed Open Graph image input
so distinct articles cannot produce the same article_content.og_image_key
when their visible preview text is otherwise identical.

Add a regression test that renders two matching article previews for the
same author and verifies they receive distinct image URLs instead of
hitting the unique cache key constraint.

hackers-pub#265

Assisted-by: Codex:gpt-5.5
Assign a high Pothos complexity to Account.ogImageUrl so single-account
profile OG lookups still work, while unpaginated account list selections
exceed the anonymous and signed query limits before rendering can fan out.

Add a regression test for the blocked bulk query and normalize article OG
test comparisons by language so translation ordering does not make the
suite flaky.

hackers-pub#265

Assisted-by: Codex:gpt-5.5
Assign a high Pothos complexity to ArticleContent.ogImageUrl and keep
single-language article OG lookups below the limit by lowering the
contents multiplier when a language is requested.

Add regression coverage for article list queries with and without a
language argument so bulk selections cannot fan out into OG rendering.

hackers-pub#265

Assisted-by: Codex:gpt-5.5
Replace the split footer copy and Pubnyan label with the official dark
Hackers' Pub logo asset on a plain black footer bar for both profile and
article OG images.

Bump the OG render version so existing cached images are regenerated with
the updated visual treatment.

hackers-pub#265

Assisted-by: Codex:gpt-5.5
Increase the line-height budget for OG title, excerpt, profile name, and
bio text so Satori and Resvg do not clip descenders at the bottom of
wrapped lines.

Remove the hard overflow clipping and tighten truncation lengths so long
profile and article text still stays within the preview layout.

hackers-pub#265

Assisted-by: Codex:gpt-5.5
Shorten the article OG excerpt and reduce its rendered height so wrapped
article previews keep clear space above the brand footer.

Bump the OG image cache version so existing article previews regenerate
with the adjusted layout.

hackers-pub#265

Assisted-by: Codex:gpt-5.5
@dahlia dahlia self-assigned this Apr 30, 2026
@dahlia dahlia added the enhancement New feature or request label Apr 30, 2026
@coderabbitai

coderabbitai Bot commented Apr 30, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@dahlia has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 28 minutes and 57 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a4777b08-2b1c-4be9-af77-b6487f3d43f1

📥 Commits

Reviewing files that changed from the base of the PR and between ad09a96 and 8649586.

📒 Files selected for processing (7)
  • graphql/account.test.ts
  • graphql/account.ts
  • graphql/og.test.ts
  • graphql/og.ts
  • graphql/post.more.test.ts
  • graphql/post.ts
  • web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/ogimage.tsx
📝 Walkthrough

Walkthrough

Adds deterministic OG image generation and caching: a new renderer module, GraphQL fields on Account and ArticleContent that compute/persist ogImageKey, web redirect routes for profile/article OG images, page meta tag wiring, and tests covering rendering, caching, and query-complexity guardrails.

Changes

Cohort / File(s) Summary
OG Image Core
graphql/og.ts, graphql/assets/README.md
New renderer and storage-backed APIs for profile/article OG images (Satori → SVG → Resvg → PNG). Loads fonts/logo, canonicalizes inputs, hashes to og/v2/<sha256>.png, uploads to Disk, and exports storage and preview functions. README documents visual-identity attribution.
GraphQL Schema & Resolvers
graphql/schema.graphql, graphql/account.ts, graphql/post.ts
Added ogImageUrl: URL! to Account and ArticleContent. Resolvers call putProfileOgImage/putArticleOgImage, update ogImageKey columns when keys change, delete old disk artifacts, and set query complexity guards.
Tests
graphql/og.test.ts, graphql/account.test.ts, graphql/post.more.test.ts
New tests for truncate/loadImageDataUri, renderer behavior, deterministic keys, disk put/delete semantics, per-language article images, and complexity guardrail preventing bulk ogImageUrl queries. Includes mocked disk and local PNG server.
Web Routes
web-next/src/routes/(root)/[handle]/og.tsx, web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/ogimage.tsx
New SolidStart API routes that resolve GraphQL ogImageUrl (per-account or per-ArticleContent language) and redirect (302) to the stored PNG; enforce necessary param/language checks and 404 on missing data.
Page Meta Tag Integration
web-next/src/routes/(root)/[handle]/(profile)/index.tsx, web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx
Profile and article pages query and emit og:image (1200×630) and twitter:card meta tags; article head supports per-language OG image URLs and helper to construct /ogimage targets.

Sequence Diagram

sequenceDiagram
    participant Client
    participant GraphQL_Resolver
    participant OG_Renderer
    participant Database
    participant Disk
    Client->>GraphQL_Resolver: Query Account.ogImageUrl / ArticleContent.ogImageUrl
    GraphQL_Resolver->>Database: read existing ogImageKey
    GraphQL_Resolver->>OG_Renderer: put...OgImage(disk, existingKey, input)
    OG_Renderer->>OG_Renderer: canonicalize input -> sha256 hash -> candidate key
    alt key matches existingKey
        OG_Renderer->>GraphQL_Resolver: return existing key
    else new key
        OG_Renderer->>OG_Renderer: render Satori -> SVG
        OG_Renderer->>OG_Renderer: convert SVG -> PNG (Resvg)
        OG_Renderer->>Disk: upload og/v2/<hash>.png
        OG_Renderer->>Disk: (if existingKey) delete existingKey
        OG_Renderer->>GraphQL_Resolver: return new key
    end
    GraphQL_Resolver->>Database: update ogImageKey (if changed)
    GraphQL_Resolver->>Client: return ogImageUrl (disk URL)
Loading
sequenceDiagram
    participant Browser
    participant Web_Route
    participant Relay_GraphQL
    participant Disk
    Browser->>Web_Route: GET /@handle/og or /.../ogimage?l=...
    Web_Route->>Relay_GraphQL: fetch account/article contents -> ogImageUrl
    Relay_GraphQL->>Web_Route: ogImageUrl
    alt ogImageUrl present
        Web_Route->>Browser: 302 redirect -> ogImageUrl
        Browser->>Disk: GET ogImageUrl
        Disk->>Browser: PNG bytes
    else missing
        Web_Route->>Browser: 404
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'web-next: OG images for profiles and articles' clearly summarizes the main objective of adding Open Graph image support for profile and article pages.
Description check ✅ Passed The description is directly related to the changeset, explaining the implementation of og:image support with Satori/Resvg rendering, caching, and GraphQL integration.
Linked Issues check ✅ Passed The PR satisfies all major coding requirements from issue #265: ports OG rendering to Node with Satori/Resvg, vendors Noto fonts, embeds visual-identity assets, adds Account/ArticleContent.ogImageUrl GraphQL fields with caching and persistence, implements new routes with language support, and wires meta tags correctly.
Out of Scope Changes check ✅ Passed All changes are within scope—no extraneous modifications to unrelated systems or legacy code removal beyond the PR's stated objectives are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 28 minutes and 57 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@dahlia

dahlia commented Apr 30, 2026

Copy link
Copy Markdown
Member Author

@codex review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces dynamic Open Graph (OG) image generation for user profiles and articles, utilizing Satori and Resvg for rendering and Flydrive for storage. The implementation includes new GraphQL fields, caching logic, and frontend meta tag integration. Feedback focuses on ensuring external images are converted to data URIs for proper rendering, utilizing standard library functions for more efficient Base64 encoding, and refining the cache key generation logic to handle transient URLs effectively.

Comment thread graphql/og.ts Outdated
Comment thread graphql/og.ts Outdated
Comment thread graphql/og.ts
Comment thread graphql/og.ts
Comment thread graphql/og.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@graphql/og.ts`:
- Around line 139-143: The truncateText function currently uses slice which can
split UTF-16 surrogate pairs or grapheme clusters (e.g., emoji or combining
marks); update truncateText to perform grapheme-safe truncation by first
compacting the text as now, then iterate grapheme clusters (using Intl.Segmenter
if available, falling back to Array.from([...text]) as a backup) to accumulate
clusters until adding the next cluster would exceed maxLength, join those
clusters, trimEnd and append the ellipsis character if truncated; keep the
function name truncateText and preserve the compact/trim behavior and returned
ellipsis.

In `@web-next/src/routes/`(root)/[handle]/[idOrYear]/[slug]/ogimage.tsx:
- Around line 21-24: Normalize the query param retrieval so an empty `?l=` is
treated as "no language supplied": when reading the language use the result of
requestUrl.searchParams.get("l") (requestedLanguage) but treat "" as
null/undefined before falling back to await getDefaultLanguage(handle, idOrYear,
slug); update the logic that sets language (the requestedLanguage ?? await
getDefaultLanguage(...)) to explicitly check for an empty string (e.g.,
requestedLanguage && requestedLanguage.length > 0) or coerce "" to null so the
default lookup runs and you never forward an empty locale into the GraphQL call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d9cc436-4678-41b4-9e56-b753be4478c9

📥 Commits

Reviewing files that changed from the base of the PR and between 5eb05b6 and 38b9c77.

⛔ Files ignored due to path filters (1)
  • graphql/assets/pubnyan-normal-transparent.svg is excluded by !**/*.svg
📒 Files selected for processing (11)
  • graphql/account.test.ts
  • graphql/account.ts
  • graphql/assets/README.md
  • graphql/og.ts
  • graphql/post.more.test.ts
  • graphql/post.ts
  • graphql/schema.graphql
  • web-next/src/routes/(root)/[handle]/(profile)/index.tsx
  • web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx
  • web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/ogimage.tsx
  • web-next/src/routes/(root)/[handle]/og.tsx

Comment thread graphql/og.ts Outdated
Comment thread web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/ogimage.tsx Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 38b9c77006

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread graphql/og.ts Outdated
dahlia added 3 commits May 1, 2026 11:05
Use grapheme-aware truncation for OG image text so emoji sequences and
combining marks are not split before the ellipsis is appended.

Add a focused test that covers emoji ZWJ sequences and combining marks.

hackers-pub#269 (comment)
hackers-pub#269 (comment)

Assisted-by: Codex:gpt-5.5
Convert external avatar URLs to data URIs before handing them to Satori so
profile and article previews render avatars consistently.

Use Deno's standard base64 encoder for both the brand logo and fetched image
bytes, and add coverage for data URI passthrough and remote image embedding.

hackers-pub#269 (comment)
hackers-pub#269 (comment)
hackers-pub#269 (comment)
hackers-pub#269 (comment)

Assisted-by: Codex:gpt-5.5
Treat blank article OG image language parameters as missing so malformed
`?l=` requests fall back to the article's default content language.

hackers-pub#269 (comment)

Assisted-by: Codex:gpt-5.5
@dahlia

dahlia commented May 1, 2026

Copy link
Copy Markdown
Member Author

@codex review

@dahlia

dahlia commented May 1, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements dynamic Open Graph (OG) image generation for user profiles and articles using Satori and Resvg. It introduces new ogImageUrl fields to the GraphQL schema, implements caching logic with complexity limits, and adds API routes for image redirection. Feedback focuses on a potential Denial of Service vulnerability in remote image fetching due to missing timeouts and size limits. Additionally, the reviewer noted architectural concerns regarding tight coupling between packages caused by hardcoded relative paths for font and logo assets.

Comment thread graphql/og.ts Outdated
Comment thread graphql/og.ts
Comment thread graphql/og.ts
Keep avatar URLs out of the OG cache hash and use a stable avatar identity
instead, so signed or otherwise transient image URLs do not force repeated
preview generation for unchanged avatars.

Add profile and article coverage proving that changing only the rendered
avatar URL does not change the cached OG image key.

hackers-pub#269 (comment)

Assisted-by: Codex:gpt-5.5
@dahlia

dahlia commented May 1, 2026

Copy link
Copy Markdown
Member Author

@codex review

@dahlia

dahlia commented May 1, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/ogimage.tsx (1)

28-49: ⚡ Quick win

Add structured handling around the two GraphQL lookups.

If either fetchQuery(...).toPromise() rejects, this route currently falls through as an unstructured 500. Catch those failures, log them with handle, idOrYear, slug, and requestedLanguage, and return a controlled 5xx instead. As per coding guidelines "Use structured logging via LogTape for error handling" and "Include context in error details when logging errors".

Also applies to: 64-85

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-next/src/routes/`(root)/[handle]/[idOrYear]/[slug]/ogimage.tsx around
lines 28 - 49, Wrap both fetchQuery(...).toPromise() GraphQL calls (the one
using ogimageQuery and the second lookup around lines 64-85) in try/catch
blocks; on catch, log the error using the LogTape logger (e.g., LogTape.error or
processLogger equivalent) with structured fields { handle, idOrYear, slug,
requestedLanguage: language } and the caught error, and return a controlled 5xx
Response (e.g., new Response("Internal Server Error", { status: 500 })) instead
of allowing an unstructured throw; keep createEnvironment(), fetchQuery, and the
query names (ogimageQuery / the second query) intact while only adding the
try/catch, logging, and the 5xx return.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@graphql/og.ts`:
- Around line 437-456: Compute the canonical input, digest and resulting key up
front in putOgImage (using canonicalize, OG_VERSION, OG_SIZE,
crypto.subtle.digest, and encodeHex/OG_NAMESPACE) and return early if
existingKey === key so you avoid calling renderPng, asset embedding, or disk.put
on cache hits; apply the same lazy-change to the other similar function/block
around the 459-482 region so both code paths skip asset fetching and Satori
element construction when the derived key matches.
- Around line 124-135: The loadImageDataUri function can hang or OOM when
fetching remote images; update loadImageDataUri to use an AbortController with a
short timeout (e.g., a few seconds) and to enforce a max-size limit by streaming
response.body instead of calling arrayBuffer() directly: create an
AbortController, pass controller.signal to fetch(imageUrl), set a timeout to
call controller.abort(), then read response.body.getReader() and accumulate
chunks into a Uint8Array while tracking total bytes and aborting/returning
FALLBACK_IMAGE_DATA_URI if the total exceeds the configured MAX_IMAGE_BYTES;
keep the existing content-type handling, convert the accumulated bytes to base64
only if under the limit, and ensure any fetch/stream errors or aborts return
FALLBACK_IMAGE_DATA_URI.
- Around line 51-58: The loadFont function currently resolves fonts from the
legacy web tree; vendor the font files into a renderer-owned/shared assets
directory and update loadFont(filename: string) to read from that renderer
assets location (e.g., reference a new RENDERER_ASSETS_DIR or renderer assets
path used by the renderer), replacing the join(import.meta.dirname!, "..",
"web", "fonts", filename) resolution; ensure the new directory contains the same
font files and that loadFont still returns Promise<ArrayBuffer> for the renamed
path so OG generation no longer depends on the legacy web/ tree.

---

Nitpick comments:
In `@web-next/src/routes/`(root)/[handle]/[idOrYear]/[slug]/ogimage.tsx:
- Around line 28-49: Wrap both fetchQuery(...).toPromise() GraphQL calls (the
one using ogimageQuery and the second lookup around lines 64-85) in try/catch
blocks; on catch, log the error using the LogTape logger (e.g., LogTape.error or
processLogger equivalent) with structured fields { handle, idOrYear, slug,
requestedLanguage: language } and the caught error, and return a controlled 5xx
Response (e.g., new Response("Internal Server Error", { status: 500 })) instead
of allowing an unstructured throw; keep createEnvironment(), fetchQuery, and the
query names (ogimageQuery / the second query) intact while only adding the
try/catch, logging, and the 5xx return.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 037cb3c9-e53d-4b1d-8a3f-3c8dc48fda93

📥 Commits

Reviewing files that changed from the base of the PR and between 38b9c77 and ad09a96.

📒 Files selected for processing (3)
  • graphql/og.test.ts
  • graphql/og.ts
  • web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/ogimage.tsx

Comment thread graphql/og.ts
Comment thread graphql/og.ts Outdated
Comment thread graphql/og.ts
Add timeout and size checks when fetching remote images for OG rendering so slow or oversized avatar responses fall back instead of tying up Satori generation.

Cover both Content-Length rejection and fetch timeout fallback.

hackers-pub#269 (comment)

Assisted-by: Codex:gpt-5.5
@dahlia

dahlia commented May 1, 2026

Copy link
Copy Markdown
Member Author

@codex review

@dahlia

dahlia commented May 1, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces dynamic Open Graph (OG) image generation for user profiles and articles, utilizing Satori and Resvg to render images that are then cached on disk. The changes include new GraphQL fields, complexity limits for bulk queries, and frontend integration for social sharing. Key feedback highlights a security risk regarding SSRF and potential memory issues when fetching remote avatars, suggests optimizing the caching layer to prevent unnecessary processing, and recommends preserving newlines during text truncation to maintain intended formatting.

Comment thread graphql/og.ts
Comment thread graphql/og.ts
Comment thread graphql/og.ts
Comment thread graphql/og.ts
Comment thread graphql/og.ts Outdated
Read remote OG images as bounded streams so responses without Content-Length cannot be fully buffered past the image limit.

Defer Satori element construction until after the cache key check so cache hits skip avatar and logo loading.

hackers-pub#269 (comment)

hackers-pub#269 (comment)

Assisted-by: Codex:gpt-5.5
@dahlia

dahlia commented May 1, 2026

Copy link
Copy Markdown
Member Author

@codex review

@dahlia

dahlia commented May 1, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Open Graph (OG) image generation for user profiles and article content, utilizing Satori and Resvg to render dynamic images. It adds new GraphQL fields for retrieving these images and implements a caching mechanism on the disk to store and reuse generated images. The review feedback highlighted a security improvement for the image fetching process and raised concerns regarding the use of side effects within GraphQL query resolvers.

Comment thread graphql/og.ts
Comment thread graphql/account.ts
Comment thread graphql/post.ts
Restrict remote OG image embedding to HTTP(S) URLs and require image content types before reading the response body.

Cover non-image responses and unsupported URL schemes in the OG image tests.

hackers-pub#269 (comment)

Assisted-by: Codex:gpt-5.5
@dahlia

dahlia commented May 1, 2026

Copy link
Copy Markdown
Member Author

@codex review

@dahlia

dahlia commented May 1, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements dynamic Open Graph (OG) image generation for user profiles and articles using Satori and Resvg. It introduces a new og.ts utility for rendering images, adds ogImageUrl fields to the GraphQL schema for Account and ArticleContent, and updates the frontend to include the necessary meta tags. Feedback focuses on adhering to the repository's import style guide, improving the robustness of remote image fetching, and optimizing Relay environment initialization in server-side routes.

Comment thread graphql/account.ts
Comment thread graphql/og.ts
Comment thread graphql/og.ts
Comment thread graphql/post.ts Outdated
Comment thread web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/ogimage.tsx Outdated

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements dynamic Open Graph (OG) image generation for user profiles and articles using Satori and Resvg. It introduces a new ogImageUrl field to the GraphQL schema for both Account and ArticleContent types, along with a caching mechanism that stores generated PNGs on disk. The feedback highlights potential race conditions due to database updates and disk deletions occurring within GraphQL query resolvers, suggesting that cache invalidation or generation should be moved to mutations or background tasks. Additionally, there is a suggestion to improve font selection for CJK characters in profile bios by explicitly setting the lang attribute.

Comment thread graphql/og.ts
Comment thread graphql/account.ts
Comment thread graphql/post.ts
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

dahlia added 4 commits May 1, 2026 11:35
Treat image responses with a missing or empty body as failed remote avatar loads so OG rendering uses the fallback image instead of producing an invalid empty data URI.

hackers-pub#269 (comment)

hackers-pub#269 (comment)

Assisted-by: Codex:gpt-5.5
Move external GraphQL imports before internal model imports and keep the internal model imports in alphabetical order.

hackers-pub#269 (comment)

hackers-pub#269 (comment)

Assisted-by: Codex:gpt-5.5
Create the Relay environment once per OG image request and pass it into the default-language lookup instead of initializing a second environment.

hackers-pub#269 (comment)

Assisted-by: Codex:gpt-5.5
Stop deleting previously stored OG images from query resolvers after a cache key update. This avoids races where concurrent requests can still be using the old object while another resolver refreshes the cache.

hackers-pub#269 (comment)

hackers-pub#269 (comment)

Assisted-by: Codex:gpt-5.5
@dahlia

dahlia commented May 1, 2026

Copy link
Copy Markdown
Member Author

@codex review

@dahlia

dahlia commented May 1, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements dynamic Open Graph (OG) image generation for user profiles and articles. It introduces a new og.ts utility that uses Satori and Resvg to render PNG images, which are then cached on disk. The GraphQL schema is updated to include ogImageUrl fields for Account and ArticleContent types, with complexity limits to prevent abuse. Corresponding frontend changes in the SolidStart stack add the necessary meta tags and API routes to serve these images. Extensive tests were added to verify image generation, caching behavior, and complexity constraints. I have no feedback to provide.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@dahlia dahlia merged commit 30aa6d1 into hackers-pub:main May 1, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

web-next: Migrate and redesign Open Graph image generation

1 participant