Skip to content

fix(web): assert the deployed homepage og-home card in smoke - #199

Merged
duyet merged 1 commit into
masterfrom
fix/smoke-homepage-og-image-20260926
Sep 25, 2026
Merged

duyet merged 1 commit into
masterfrom
fix/smoke-homepage-og-image-20260926

Conversation

@duyet

@duyet duyet commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to #191. Draft — do not merge yet.

What is broken

pnpm --filter @aidr/web smoke — the post-deploy check that pnpm run cf:deploy:prod runs on every production deploy — fails against https://aidr.today on exactly one assertion:

[FAIL] GET / -> 200 with SSR shell marker — homepage og:image should be /og.jpg
34/35 checks passed

The deployed homepage is correct. This is a stale assertion, not a product defect.

Root cause

Commit 177a633 ("feat(web): refresh OG images with homepage masthead variant") split the share image in two:

constant path used by
default card SITE_OG_IMAGE_PATH /og.jpg every non-homepage page
homepage card SITE_OG_HOME_IMAGE_PATH /og-home.jpg the homepage only

homepageHead() passes imageUrl: SITE_OG_HOME_IMAGE_URL (apps/web/src/lib/seo.ts:172), so the homepage has emitted https://aidr.today/og-home.jpg since that commit. apps/web/scripts/smoke.ts:75 was never updated and kept a hard-coded body.includes("/og.jpg").

"/og-home.jpg" does not contain the substring "/og.jpg", so the check fails deterministically — it has failed on every production deploy since 177a633, and the red smoke was never the reason it got noticed. The commit landed directly on master (no PR, no linked issue) and touched no test that runs the script, because apps/web/vitest.config.ts only includes worker/** and src/** — scripts/ has no unit coverage at all.

Live evidence for the deployed value:

$ curl -s https://aidr.today/ | grep -o '<meta property="og:image"[^>]*>'
<meta property="og:image" content="https://aidr.today/og-home.jpg"/>
$ curl -s https://aidr.today/ | grep -c 'og.jpg'
0

The fix

apps/web/scripts/smoke.ts

  1. Imports SITE_OG_HOME_IMAGE_URL from ../src/lib/site — the same module seo.ts imports it from — so the assertion and the renderer read one constant and a future rename cannot drift them apart.
  2. Replaces the loose substring test with metaContent(body, "property", "og:image") === SITE_OG_HOME_IMAGE_URL, and adds the same check for twitter:image. metaContent scans <meta> tags and matches property= / content= independently of attribute order, so a renderer that reorders attributes cannot silently turn the assertion into a no-op. It also asserts the absolute URL, so a relative or wrong-origin og:image fails.
  3. Adds a separate check, GET homepage og:image -> 200 JPEG, which reads the og:image out of the live homepage HTML and fetches that URL, asserting 200, an image/* content type, > 5 000 bytes, and a FFD8 JPEG magic. The old check only string-matched the body, so a 404-ing or renamed card would have passed the smoke and shown up as a blank share preview in a crawler instead.

apps/web/src/lib/smoke-og-image.test.ts (new, 6 tests)

  • pins SITE_OG_HOME_IMAGE_PATH to /og-home.jpg and SITE_OG_HOME_IMAGE_URL to https://aidr.today/og-home.jpg
  • asserts both advertised paths exist in apps/web/public as real JPEGs over 5 KB (this is the "does the deployed URL actually resolve" guarantee at build time)
  • asserts the homepage/default split holds: homepageHead() → the masthead card, pageHead() → /og.jpg
  • three source-text guards on scripts/smoke.ts (same pattern as src/lib/aidr-guide.test.ts): the script must derive from the constant, must not re-introduce a hard-coded "/og.jpg" or "/og-home.jpg", and must keep the meta-tag and image-fetch checks

The three guard tests fail against master's smoke.ts and pass after this change, verified by restoring git show origin/master:apps/web/scripts/smoke.ts and re-running:

✓ homepage OG image asset (3)
× derives the expected homepage og:image from the shared constant
× asserts the rendered meta tag, not a loose substring
× fetches the advertised og:image URL as its own check
Tests  3 failed | 3 passed (6)

Verification

All on 48ca088, in a clean worktree at origin/master caa4407:

Gate Result
pnpm --filter @aidr/web smoke (live https://aidr.today) 36/36 passed (was 34/35)
pnpm --filter @aidr/web exec vitest run src/lib/smoke-og-image.test.ts 6/6 passed
pnpm run test apps/web 155 files / 1593 tests passed; apps/extension 61 pass / 0 fail
pnpm run lint (all workspaces) passed
pnpm run check-types passed
pnpm run build (client + SSR) passed
git diff --check origin/master...HEAD passed (no output)

The new GET homepage og:image -> 200 JPEG check passes against production:

[PASS] GET / -> 200 with SSR shell marker
[PASS] GET homepage og:image -> 200 JPEG
...
36/36 checks passed

and directly:

$ curl -sSI https://aidr.today/og-home.jpg | grep -iE '^(HTTP|content-type)'
HTTP/2 200
content-type: image/jpeg

Smoke fixture limitations (unchanged, stated explicitly)

The smoke has no local fixture or mock server; it is a live-surface script and was run only against the deployed production base, which is the only supported target. Consequences worth recording:

  • 12 of the 36 checks depend on real production data (feed items with ids, /api/system totals, /__clerk/v1/environment, the aidr.zip release redirect). They cannot be exercised in CI or offline.
  • The /api/subscribe checks send a live POST with the deliberately invalid address not-an-email; it is rejected 4xx and never reaches a mail provider, but it is a real request against production.
  • The new image check fetches the absolute og:image from the metadata, so a --base http://localhost:8787 run still fetches the canonical production card. That is intentional — the metadata is hard-coded to SITE_URL for every environment — and it is the asset a crawler would fetch. It is also why the equal-path guarantee is covered by the build-time test instead.
  • Nothing in this change makes the smoke fixture-able; adding that is a separate piece of work under Quality gates: security, regression, and verification for the roadmap #147.

Not changed on purpose

  • Homepage metadata, OG rendering, and the assets themselves. https://aidr.today/og-home.jpg is already what production serves; the bug was only in the assertion.
  • /og.jpg remains the default card for every non-homepage page, so the old literal was not simply "wrong everywhere" — only the homepage assertion was.
  • /og-home.jpg is not in apps/web/public/_headers, so it is served with the Workers default cache-control: public, max-age=0, must-revalidate while /og.jpg gets max-age=86400 from that file. Pre-existing, unrelated to this failure, and deliberately left alone here — it needs its own decision. The new smoke check does not assert cache headers, so it stays green either way.
  • /og-home.jpg is missing from PUBLIC_ASSET_PATHS (apps/web/worker/public-assets.ts, used for mail-embedded assets) and from the /brand asset list. Also pre-existing and out of scope.

Still failing, out of scope (reported for a follow-up)

verify-aidr drive homepage fails on production for a different, also-stale assertion, plus the same OG one:

$ .cursor/skills/verify-aidr/bin/verify-aidr drive homepage
{"ok":false, ... "identity":{... "og:title":true,"/og.jpg":false},"permalinks":false}

Two separate defects in .cursor/skills/verify-aidr/bin/verify-aidr:

  • line 538: "/og.jpg" in the homepage identity list — the same OG staleness fixed here
  • line 29: PERMALINK_RE = /href="\/[0-9a-f]{8}"/, which no longer matches the deployed href="/ed833179?lang=vi" form that f5d525d introduced

Not touched: it is a separate live-verification tool with its own two-part failure, and bundling it would make this PR unfocused. It is tracked under #147 and should be a separate follow-up that also refreshes features/homepage.md.

Linked

Summary by Sourcery

Correct homepage social-image smoke coverage and add regression tests to ensure deployed metadata and assets remain aligned.

Bug Fixes:

  • Update the post-deploy smoke test to validate the homepage’s masthead OG image and Twitter image against the shared canonical URL.
  • Verify that the advertised homepage OG image resolves to a sufficiently sized JPEG.

Enhancements:

  • Add regression coverage for homepage and default OG image configuration, asset validity, and smoke-test assertions.

Tests:

  • Add tests covering OG image paths, metadata selection, public JPEG assets, and protection against stale hard-coded smoke assertions.

`pnpm run smoke` failed against production on a single check: the homepage
assertion still looked for the literal `/og.jpg`, but the homepage has
advertised the masthead variant `/og-home.jpg` since 177a633. The deployed
page was correct; the assertion was stale, and `/og.jpg` is still the
correct default card for every non-homepage page.

- derive the expected URL from `SITE_OG_HOME_IMAGE_URL` instead of a
  hard-coded path, and compare the parsed `og:image` / `twitter:image`
  meta content so an attribute-order change cannot void the check
- add a check that fetches the og:image the deployed homepage advertises
  and asserts a real JPEG, so a renamed or missing card fails in CI
- add `src/lib/smoke-og-image.test.ts` pinning the constant, the shipped
  asset, the homepage/default split, and the smoke script's wiring

No metadata, rendering, or asset change: the homepage keeps its current
`https://aidr.today/og-home.jpg` card.
@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 0d99cbf7-31e6-407a-93c3-834605aa96b2


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

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

@sourcery-ai

sourcery-ai Bot commented Sep 25, 2026

Copy link
Copy Markdown

Reviewer's Guide

Fixes the stale production smoke assertion by deriving the homepage OG image from the shared SEO constant, checking the exact rendered metadata and live JPEG, and adding tests that protect the homepage/default asset split and smoke-script behavior.

Sequence diagram for post-deploy homepage OG image smoke check

sequenceDiagram
    participant Smoke as Smoke script
    participant Homepage as Deployed homepage
    participant Image as Advertised OG image
    Smoke->>Homepage: fetch GET /
    Homepage-->>Smoke: HTML with og:image and twitter:image
    Smoke->>Smoke: metaContent(...)
    Smoke->>Smoke: Compare with SITE_OG_HOME_IMAGE_URL
    Smoke->>Image: fetch advertised og:image URL
    Image-->>Smoke: 200 image/jpeg
    Smoke->>Smoke: Validate size > 5 KB and JPEG FFD8
    Smoke-->>Smoke: Pass homepage OG checks
Loading

File-Level Changes

Change Details Files
Align the post-deploy homepage metadata assertions with the deployed homepage OG-card variant and make them validate the rendered tag precisely.
  • Import the shared homepage OG-image URL constant used by SEO rendering.
  • Parse meta tags independently of attribute order and assert exact absolute URLs for both og:image and twitter:image.
  • Add a live check that fetches the advertised image and validates its status, media type, size, and JPEG signature.
apps/web/scripts/smoke.ts
Add regression coverage for homepage/default OG-image configuration, assets, rendering, and smoke-script invariants.
  • Verify the homepage and default image constants remain distinct and point to real JPEG assets over 5 KB.
  • Verify homepageHead() uses the homepage card while pageHead() uses the default card.
  • Use source-text guards to prevent hard-coded paths, loose substring assertions, or removal of the image-fetch check.
apps/web/src/lib/smoke-og-image.test.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@duyet
duyet marked this pull request as ready for review September 25, 2026 18:06

@sourcery-ai sourcery-ai 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.

Sorry @duyet, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 1 day and 1 hour by commenting @sourcery-ai review. Upgrade to get a review now.

@duyet
duyet merged commit bb5c0cb into master Sep 25, 2026
5 checks passed
@duyet
duyet deleted the fix/smoke-homepage-og-image-20260926 branch October 4, 2026 10:28
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