fix(shell): resolve og:image through the asset pipeline; image is site content#50
Merged
Conversation
…e content The og:image tag pointed at the raw config path (https://docs-kit.zoolutions.llc/og/og.png), which 404s — Propshaft serves the DIGESTED asset under /assets, not the logical path. A shared link showed a broken image. Root cause: DocsUI::MetaTags base-joined the raw og_image path onto the host instead of resolving it through the Rails asset pipeline. And the gem shipped a default OG image + defaulted og_image to "og/og.png" — but the social-share image is each SITE's own branding, not the gem's, and a stale site had no such asset precompiled at all. Fix: - A relative og_image now resolves via image_url → the digested /assets/og/og-<digest>.png URL Propshaft actually serves. Absolute URLs pass through. image_url uses the Static resolver (needs the asset precompiled), so a misconfigured og_image fails loudly at deploy (assets:precompile) rather than shipping a silent 404 — NOT rescued. - The OG image is SITE content: removed the gem-shipped image + the generator copy + the guard spec. c.seo.og_image now defaults to nil → unset emits NO og:image (a valid card, never a 404). docs_kit:og writes into the SITE's app/assets/images/. - The dogfood /docs site gets its own OG image + c.seo config, so its live card works once deployed (verified: og:image renders as the /assets digest URL). Test coverage: - New booted-app integration test in the dogfood site (docs/test/integration/seo_meta_tags_test.rb): GET /, assert og:image is a /assets URL AND that GETting it returns 200 image/png — the exact regression that would have caught this. Self-precompiles so it's run-order independent. Isolated component specs can't catch a pipeline/precompile break; only a booted app can. - Updated meta_tags_spec: og:image resolves via the (stubbed) pipeline, is omitted when unset, absolute passes through, never emits the raw path. ## Verification - [x] bundle exec rake (714 examples, 0 failures; coverage 94.48%) - [x] bundle exec rubocop — no offenses - [x] docs/ integration test — 4 runs, 22 assertions, 0 failures (from clean state) - [x] gem build --strict — no image shipped; component/task/generator present - [x] live-parity render — og:image = https://…/assets/og/og-<digest>.png (served 200) Refs #48 Claude-Session: https://claude.ai/code/session_01FPQb6z3YwcKRMbvoJhdxnX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
The
og:imageon the live docs site pointed athttps://docs-kit.zoolutions.llc/og/og.png— which 404s. Propshaft serves the digested asset under/assets/…, not the raw logical path, so a shared link showed a broken image.Root cause
Two mistakes in the original SEO feature (#49):
DocsUI::MetaTagsbase-joined the rawog_imagepath onto the host ("#{base}/#{og_image}") instead of resolving it through the Rails asset pipeline. The result was never a served URL.og_imageto"og/og.png". But the social-share image is each site's own branding — not docs-kit's to render. A site that predates the feature (like the deployed dogfood site) had no such asset precompiled at all, so even a correct URL would 404.The fix
og_imagenow goes throughimage_url→ the digested/assets/og/og-<digest>.pngURL Propshaft actually serves. An absolute URL passes through untouched.image_urluses Propshaft'sStaticresolver (needs the asset precompiled), so a misconfiguredog_imagefails loudly at deploy (assets:precompile) rather than shipping a silent 404 — deliberately not rescued.copy_file, and the guard spec.c.seo.og_imagenow defaults to nil → unset emits noog:imagetag (a valid card, never a 404, same shape as favicon/robots/theme-color). Thedocs_kit:ogtask writes into the site'sapp/assets/images/./docssite gets its own OG image +c.seoconfig, so its live card works once deployed.Why the original tests missed it
The gem's isolated component specs can't exercise the asset pipeline — only a booted app has the Propshaft resolver that turns a logical path into a served
/assetsURL. So this adds a real integration test in the dogfood site.Test plan
docs/test/integration/seo_meta_tags_test.rb— boots the real docs app,GET /, and assertsog:imageis a/assetsURL and that GETting it returns200 image/png. That last assertion is the one that reproduces and guards the production 404. Self-precompiles in setup, so it's run-order independent.spec/docs_ui/meta_tags_spec.rb: og:image resolves via the (stubbed) pipeline, is omitted when unset, absolute passes through, and the raw path is never emitted.bundle exec rake→ 714 examples, 0 failures (coverage 94.48%), rubocop clean.docs/integration test → 4 runs, 22 assertions, 0 failures from a clean state.gem build --strict→ no image shipped (gem shrank 171K → 134K); component/task/generator still present./docslanding now emitsog:image = https://docs-kit.zoolutions.llc/assets/og/og-<digest>.png, which serves 200.Fixes the 404 reported on https://docs-kit.zoolutions.llc/og/og.png. Refs #48.
https://claude.ai/code/session_01FPQb6z3YwcKRMbvoJhdxnX