seo: complete the social share image on home (dogfood d87d715be3d0) - #31
Conversation
The leak audit this site sells flags a homepage whose served HTML cannot tell a social platform what to show when shared. The dogfood audit run 20260808T074205Z-msk2fl3n found exactly that on this site's own home page (finding d87d715be3d0, 'Social share image incomplete on home'): public/index.html served zero og:/twitter: tags even though public/og-image.png exists and is allow-listed in the worker. Add the complete Open Graph + Twitter Card set (og:title, og:description, og:type, og:url, og:image with width/height/alt, twitter:card summary_large_image, twitter:title/description/image) to the heads of all five public pages, mirroring each page's meta description and pointing og:image at the absolute https://tinystudio.io/og-image.png (1200x630, matching the PNG header). Add a source-string CI guard in scripts/check-site.mjs (social share tags section) so the fault cannot return: each page must carry each tag exactly once inside <head>, og:description must equal the page's meta description, og:image must be the absolute og-image.png URL, and the declared dimensions must match the actual PNG bytes.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
📝 WalkthroughWalkthroughFive public pages now include Open Graph and Twitter Card metadata. ChangesSocial metadata
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 69bb89aa5d
ℹ️ 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".
| <meta property="og:description" content="TinyStudio: the free leak audit of high-ticket service homepages. Each fault named in order of what it costs you, with the fix beside it. Six a month."> | ||
| <meta property="og:type" content="website"> | ||
| <meta property="og:url" content="https://tinystudio.io/"> | ||
| <meta property="og:image" content="https://tinystudio.io/og-image.png"> |
There was a problem hiding this comment.
Replace the legacy Agent Desk share image
When any of these pages is shared, this new tag renders the existing public/og-image.png, whose visible copy advertises “TinyStudio Agent Desk” and “Self-serve AI agents.” That directly contradicts the current Website Appraisal offer and public/offer.md, which identifies the Agent Desk as a legacy product, so every newly enabled social card promotes the wrong service; replace the asset with artwork for the current offer before pointing all five pages at it.
AGENTS.md reference: AGENTS.md:L2-L3
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
scripts/check-site.mjs (1)
976-988: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAssert page-specific title values.
The empty expected values for
og:titleandtwitter:titleonly enforce non-empty content. A copied title or a mismatch between the two card formats passes. Add explicit expected titles, or compare both values with the canonical page title after HTML entity decoding.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/check-site.mjs` around lines 976 - 988, Update the expected metadata assertions in the page-check logic around the expected Map so og:title and twitter:title are validated against the canonical page title after HTML entity decoding, ensuring both values are page-specific and consistent rather than merely non-empty.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/check-site.mjs`:
- Around line 961-964: Update shareTagIn to select the required metadata
attribute based on the key prefix: use property for og:* keys and name for
twitter:* keys. Anchor the regex at a valid attribute boundary so data-property
or data-name attributes cannot match, while preserving case-insensitive
matching.
- Around line 973-1005: Validate that the page description parsed in the
socialSharePages loop is non-empty before constructing the expected Map; record
a failure for the page and skip its social-tag checks when missing, so
og:description and twitter:description cannot pass as empty expected values.
---
Nitpick comments:
In `@scripts/check-site.mjs`:
- Around line 976-988: Update the expected metadata assertions in the page-check
logic around the expected Map so og:title and twitter:title are validated
against the canonical page title after HTML entity decoding, ensuring both
values are page-specific and consistent rather than merely non-empty.
🪄 Autofix
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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9a0182ca-52b1-4aa3-bae7-6c2de235a28e
📒 Files selected for processing (6)
public/agents.htmlpublic/audit.htmlpublic/index.htmlpublic/pricing.htmlpublic/specimen.htmlscripts/check-site.mjs
| for (const [pageName, pageHtml, pageUrl] of socialSharePages) { | ||
| const head = pageHtml.match(/<head\b[\s\S]*?<\/head>/i)?.[0] ?? ""; | ||
| const description = pageHtml.match(/<meta\b[^>]*\bname="description"[^>]*>/i)?.[0]?.match(/\bcontent="([^"]*)"/i)?.[1] ?? ""; | ||
| const expected = new Map([ | ||
| ["og:title", ""], // non-empty, per page | ||
| ["og:description", description], | ||
| ["og:type", "website"], | ||
| ["og:url", pageUrl], | ||
| ["og:image", SOCIAL_IMAGE_URL], | ||
| ["og:image:width", "1200"], | ||
| ["og:image:height", "630"], | ||
| ["og:image:alt", ""], // non-empty, per page | ||
| ["twitter:card", "summary_large_image"], | ||
| ["twitter:title", ""], // non-empty, per page | ||
| ["twitter:description", description], | ||
| ["twitter:image", SOCIAL_IMAGE_URL] | ||
| ]); | ||
| for (const [key, expectedContent] of expected) { | ||
| const inDoc = shareTagIn(pageHtml, key); | ||
| if (inDoc.length !== 1) { | ||
| failures.push(`Social share tag ${key} must appear exactly once on ${pageName} (found ${inDoc.length}).`); | ||
| continue; | ||
| } | ||
| const inHead = shareTagIn(head, key); | ||
| if (inHead.length !== 1) { | ||
| failures.push(`Social share tag ${key} on ${pageName} must sit inside <head>.`); | ||
| continue; | ||
| } | ||
| const content = inHead[0].match(/\bcontent="([^"]*)"/i)?.[1] ?? ""; | ||
| if (expectedContent === "" && !content.trim()) { | ||
| failures.push(`Social share tag ${key} on ${pageName} must not be empty.`); | ||
| } else if (expectedContent !== "" && content !== expectedContent) { | ||
| failures.push(`Social share tag ${key} on ${pageName} must be ${JSON.stringify(expectedContent)} (found ${JSON.stringify(content)}).`); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Fail when the page description is missing.
If line 975 cannot parse name="description", description becomes an empty string. The expected values for og:description and twitter:description then become empty. Lines 1002-1003 check only that the social descriptions are non-empty. A page without a parseable meta description can therefore pass without matching page metadata.
Fail on an empty description before building expected.
Proposed fix
const description = pageHtml.match(/<meta\b[^>]*\bname="description"[^>]*>/i)?.[0]?.match(/\bcontent="([^"]*)"/i)?.[1] ?? "";
+ if (!description.trim()) {
+ failures.push(`Page ${pageName} must have a non-empty meta description.`);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (const [pageName, pageHtml, pageUrl] of socialSharePages) { | |
| const head = pageHtml.match(/<head\b[\s\S]*?<\/head>/i)?.[0] ?? ""; | |
| const description = pageHtml.match(/<meta\b[^>]*\bname="description"[^>]*>/i)?.[0]?.match(/\bcontent="([^"]*)"/i)?.[1] ?? ""; | |
| const expected = new Map([ | |
| ["og:title", ""], // non-empty, per page | |
| ["og:description", description], | |
| ["og:type", "website"], | |
| ["og:url", pageUrl], | |
| ["og:image", SOCIAL_IMAGE_URL], | |
| ["og:image:width", "1200"], | |
| ["og:image:height", "630"], | |
| ["og:image:alt", ""], // non-empty, per page | |
| ["twitter:card", "summary_large_image"], | |
| ["twitter:title", ""], // non-empty, per page | |
| ["twitter:description", description], | |
| ["twitter:image", SOCIAL_IMAGE_URL] | |
| ]); | |
| for (const [key, expectedContent] of expected) { | |
| const inDoc = shareTagIn(pageHtml, key); | |
| if (inDoc.length !== 1) { | |
| failures.push(`Social share tag ${key} must appear exactly once on ${pageName} (found ${inDoc.length}).`); | |
| continue; | |
| } | |
| const inHead = shareTagIn(head, key); | |
| if (inHead.length !== 1) { | |
| failures.push(`Social share tag ${key} on ${pageName} must sit inside <head>.`); | |
| continue; | |
| } | |
| const content = inHead[0].match(/\bcontent="([^"]*)"/i)?.[1] ?? ""; | |
| if (expectedContent === "" && !content.trim()) { | |
| failures.push(`Social share tag ${key} on ${pageName} must not be empty.`); | |
| } else if (expectedContent !== "" && content !== expectedContent) { | |
| failures.push(`Social share tag ${key} on ${pageName} must be ${JSON.stringify(expectedContent)} (found ${JSON.stringify(content)}).`); | |
| for (const [pageName, pageHtml, pageUrl] of socialSharePages) { | |
| const head = pageHtml.match(/<head\b[\s\S]*?<\/head>/i)?.[0] ?? ""; | |
| const description = pageHtml.match(/<meta\b[^>]*\bname="description"[^>]*>/i)?.[0]?.match(/\bcontent="([^"]*)"/i)?.[1] ?? ""; | |
| if (!description.trim()) { | |
| failures.push(`Page ${pageName} must have a non-empty meta description.`); | |
| } | |
| const expected = new Map([ | |
| ["og:title", ""], // non-empty, per page | |
| ["og:description", description], | |
| ["og:type", "website"], | |
| ["og:url", pageUrl], | |
| ["og:image", SOCIAL_IMAGE_URL], | |
| ["og:image:width", "1200"], | |
| ["og:image:height", "630"], | |
| ["og:image:alt", ""], // non-empty, per page | |
| ["twitter:card", "summary_large_image"], | |
| ["twitter:title", ""], // non-empty, per page | |
| ["twitter:description", description], | |
| ["twitter:image", SOCIAL_IMAGE_URL] | |
| ]); | |
| for (const [key, expectedContent] of expected) { | |
| const inDoc = shareTagIn(pageHtml, key); | |
| if (inDoc.length !== 1) { | |
| failures.push(`Social share tag ${key} must appear exactly once on ${pageName} (found ${inDoc.length}).`); | |
| continue; | |
| } | |
| const inHead = shareTagIn(head, key); | |
| if (inHead.length !== 1) { | |
| failures.push(`Social share tag ${key} on ${pageName} must sit inside <head>.`); | |
| continue; | |
| } | |
| const content = inHead[0].match(/\bcontent="([^"]*)"/i)?.[1] ?? ""; | |
| if (expectedContent === "" && !content.trim()) { | |
| failures.push(`Social share tag ${key} on ${pageName} must not be empty.`); | |
| } else if (expectedContent !== "" && content !== expectedContent) { | |
| failures.push(`Social share tag ${key} on ${pageName} must be ${JSON.stringify(expectedContent)} (found ${JSON.stringify(content)}).`); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/check-site.mjs` around lines 973 - 1005, Validate that the page
description parsed in the socialSharePages loop is non-empty before constructing
the expected Map; record a failure for the page and skip its social-tag checks
when missing, so og:description and twitter:description cannot pass as empty
expected values.
The original og-image.png (PR #6) was built while the root page was the retired TinyStudio Agent Desk, so its copy advertised the legacy product — sharing the home page after the tag fix would render a card for the wrong offer. Rebuild it with the current site's palette (index.css cream/ink/ brass), the existing favicon.svg mark, and copy verbatim from the current home page headline and description. No new claims. scripts/generate-og-image.mjs renders the card in real Chromium at exactly 1200x630 and refuses to write unless the Google Fonts load, every element sits inside the canvas, and the pixels contain the cream background, ink text, and brass accents. The check-site.mjs social guard keeps all five pages pointed at this file with dimensions matching its PNG header.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 08f2c19436
ℹ️ 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".
| const HEADLINE = "Most of them leave <em>before they ever get in touch.</em>"; | ||
| const DESCRIPTION = | ||
| "TinyStudio: the free leak audit of high-ticket service homepages. Each fault named in order of what it costs you, with the fix beside it. Six a month."; |
There was a problem hiding this comment.
Preserve a separate share card for the Agent Desk
When someone shares the still-public /agent-desk route (allow-listed in src/worker.js), its existing metadata continues to advertise “The Tiny Studio Agent Desk” but points to /og-image.png; regenerating that shared asset with this Website Appraisal-only copy therefore produces a contradictory card. Give the Agent Desk its own matching image or update that page's social metadata as part of replacing the global asset.
Useful? React with 👍 / 👎.
Resolve the drift between fix/apple-touch-icon-home (finding 98a7bf8e08fc) and origin/main after #28 (heading hierarchy) and #31 (social share tags) landed. Both changes touched the same head regions and check-site.mjs; the merged result keeps the apple-touch-icon link and its regression guard alongside main's og:/twitter: share blocks on all five public pages.
…current main and live (#168) Judges the lane-1 item for dogfood finding d87d715be3d0 ("Social share image incomplete on home", audit 20260808T074205Z-msk2fl3n). The finding was fixed by PR #31 (eae1d87), verified live by PR #76, and engine-rerun-confirmed by PR #122; this receipt re-verifies on the current head (5209ec7) and the live deployment that the twelve-tag share set, the CI guard, and the reachable 1200x630 og-image.png all still hold. Nothing further to change; item closes with a reason. Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
…live (#175) Dogfood finding d87d715be3d0 ("Social share image incomplete on home") was fixed by PR #31 and closed on 2026-08-11/12/13. This lane re-verifies the fix on the current origin/main head (afb5d49) and the live deployment: the twelve-tag share set is intact in public/index.html, the CI guard passes, and the live site serves the complete set with a reachable 1200x630 og-image.png byte-identical to the committed file. No code change required. Co-authored-by: nish3451 <nish3451@users.noreply.github.com> Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
…live (#221) (#221) Lane-1 re-verification of dogfood finding d87d715be3d0 (social share image incomplete on home). The fix (PR #31, eae1d87) is present on the current origin/main head 9944fec, the source guard passes, and the live deployment serves the complete twelve-tag share set with a reachable 1200x630 og-image.png byte-identical to the committed file. Co-authored-by: nish3451 <nish3451@users.noreply.github.com> Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
What
Closes dogfood finding d87d715be3d0 — "Social share image incomplete on home" (audit run
20260808T074205Z-msk2fl3n).The leak audit this site sells flags a homepage whose served HTML cannot tell a social platform what to show when the page is shared — the share card comes back with no image, or a scraped guess. The dogfood run found exactly that on our own home page:
public/index.htmlserved zeroog:/twitter:tags even thoughpublic/og-image.png(1200×630) exists and is allow-listed in the worker (src/worker.js).Change
index.htmlhome,audit.html,agents.html,pricing.html,specimen.html) now carry a complete, per-page share set in<head>:og:title,og:description,og:type=website,og:url(each page's own absolute URL),og:image=https://tinystudio.io/og-image.pngwithog:image:width=1200,og:image:height=630,og:image:alttwitter:card=summary_large_image,twitter:title/description/imageog:descriptionmirrors each page's meta description; nothing revives the retired "Agent Desk"/"The Tiny Studio" framing (checked by the existing stale-identity guard).og-image.png(PR Add HSTS, social image, touch icon, and structured data #6) was built while the root page was the retired TinyStudio Agent Desk, so its copy advertised the legacy product. The new card uses the current site's palette (index.css cream/ink/brass), the existingfavicon.svgmark, and copy verbatim from the current home page headline + description.scripts/generate-og-image.mjsrenders it in real Chromium at exactly 1200×630 and refuses to write unless fonts load, elements fit the canvas, and pixels contain the cream background / ink text / brass accents.scripts/check-site.mjs("Social share tags" section, modeled on the meta-descriptions guard from PR SEO: add truthful page meta descriptions #21): each tag exactly once inside<head>;og:tags must useproperty=,twitter:tags must usename=(nodata-propertypass-through);og:descriptionmust equal the page's meta description;og:imagemust be the absoluteog-image.pngURL; declared dimensions must match the actual PNG header bytes.Verification
npm run check— passes; mutation-tested (wrong og:image URL fails; malformedname="og:title"fails; restored fixes pass)npm test— 68/68 pass (site checks + 53 worker + 15 UI)npm run check:render-blocking— all six pages PASS under the production CSPdocument.head,og:imageresolves to the absolute URL with correct 1200×630 dimensionsscripts/generate-og-image.mjsis deterministic (same bytes on rerun)