Repository navigation
feat(seo): let a noindex route opt into a social card - #887
developerz-ai[bot] merged 1 commit into
Conversation
`RouteMeta` gains one optional field, `socialCard?: boolean`.
A document that may not be indexed (`robots: { index: false }`) carried no
Open Graph, article or Twitter tag — declared or derived — so a route that
deliberately declared a link preview lost it. `socialCard: true` on the
route's own `meta` emits the card exactly as an indexable document's is
emitted; without it the suppression stands.
The robots directives are never touched: the route stays `noindex`, and an
indexable route renders the same head tags as before. No new export, no new
error code, no other package changes.
Dz-Task-Id: tsk_e716ee4426347c01cab7a385562d17c9
🟡 Reviewed2 actionable comment(s) · grounded on your code ⏱ 2m 13s wall clock · 🤖 developerz.ai — automated review, running on your model and your box. |
There was a problem hiding this comment.
Review summary — 9 file(s), 2 finding(s).
Critical 0 · Major 0 · Minor 2 · Nit 0
[bot] completed: Adds the noindex social-card opt-in while preserving the existing rendering path and robots directives. The wiki overstates the resulting robots tag. [bot] completed: the social-card tests cover the new guard, but the 404 regression test does not exercise the transition to noindex.
2 non-blocking findings (2 minor; not posted inline)
packages/render/src/route-status.test.ts:125test [bot] The route already declaresindex: false, so this test passes if the status wrapper preservessocialCardonly for already-noindex metadata but drops it when forcing an indexable page to noindex. Add a variant declaringindex: trueand assert the returned metadata hasindex: falseand…wiki/SEO.md:81docs [bot] Withrobots: { index: false, follow: false },renderMeta()emitsnoindex,nofollow, not the promisednoindex,follow. Qualify this output as the default-follow example and state that all declared robots directives remain intact.
🤖 developerz.ai review — automated, running on your model and your box. What is this? · Reviewed by router/gpt-6.1-sol on box hv-dedicated-ci-0 — 201.9k in / 768 out over 3 calls; cost unknown: no price on record for router/gpt-6.1-sol.
What
RouteMetagains one optional field,socialCard?: boolean.A document that may not be indexed (
robots: { index: false }) carried noOpen Graph, article or Twitter tag — declared or derived — so a route that
deliberately declared a link preview lost it.
socialCard: trueon theroute's own
metaemits the card exactly as an indexable document's isemitted; without it the suppression stands.
The robots directives are never touched: the route stays
noindex, and anindexable route renders the same head tags as before. No new export, no new
error code, no other package changes.
Acceptance
packages/seo/src/meta-social.test.tscovers four cases: a noindex route withsocialCard: trueemitsog:*ANDtwitter:*tags; a noindex route without it emits noog:*/article:*/twitter:*tag; the opted-in route's robots tag still readsnoindex,follow; an indexable route's ca [trimmed]bun test packages/seo/src/meta-social.test.ts packages/seo/src/meta.test.tsis green.RouteMetagained exactly one optional property —socialCard?: boolean— andpackages/seo/src/index.tsexports no new name.socialCard, so every existing document in the repo renders the same head tags as before.packages/seo/README.md,packages/seo/CLAUDE.md,wiki/SEO.mdandCHANGELOG.mdall state the opt-in; the README's old "there is nosocialswitch" claim is gone.## [Unreleased]with noBREAKING —line, andbun run changelog-checkis clean.Changes
packages/seo/src/meta.ts(edit) — AddsocialCard?: booleantoRouteMeta(with a doc comment stating the rule) and honour it insocialTags(): today's guardif (meta.robots?.index === false) return [];becomes "no card unless the route opted in". WithsocialCard: truethe card is emitted exactly as it is for an indexable document (declared values win, derived ones come with it); the robots tag is never touched. Rewrite the rule comment abovesocialTagsto state the new contract and why. No new export —packages/seo/src/index.tsstays as it is.packages/seo/src/meta-social.test.ts(create) — The card's own test file (one job: when the card is emitted). Owns every social-card case: (1) a noindex route withsocialCard: trueemits itsog:*ANDtwitter:*tags; (2) that route's robots tag still readsnoindex,followand its canonical/hreflang survive; (3) a noindex route WITHOUT the opt-in emits noog:*/article:*/twitter:*tag; (4) an indexable route keeps its card andsocialCardis inert there. Move the two existing social-card cases out ofmeta.test.tsinto this file rather than duplicating them. Own fixture +findhelper; opens with a 1-4 line header comment.packages/seo/src/meta.test.ts(edit) — Remove the two social-card cases that move tometa-social.test.ts("a document that is not indexed carries no social tags, declared or derived" and "follow: falsealone keeps the card"), leaving the title-template, robots, hreflang, theme-color and JSON-LD cases here. Nothing else changes: no new import, no new fixture —METAandfindstay for the cases that remain.packages/seo/README.md(edit) — Public API: the Modules table'smeta.tsrow says "there is nosocialswitch" — replace that clause with the one opt-in (socialCard: true). Add a short##section after "Type-level enforcement" stating the rule: a document that may not be indexed carries noog:*/article:*/twitter:*tag unless the route declaressocialCard: true; with it the card is emitted as for an indexable page; the robots directives are never changed. No version number, noAs ofstamp.packages/seo/CLAUDE.md(edit) — House rule: rewrite the bullet "A document that is not indexed carries no social card" to the new contract —renderMetaemits the card unlessmeta.robots?.index === false, and a route declaringsocialCard: truegets it anyway; that field is the ONE switch, the robots line is never touched, and a route without it keeps today's suppression. Rewrite the bullet in place: the file is near the 24 KB ceiling, so do not append a section.wiki/SEO.md(edit) — Public reference for app authors: document the opt-in — a page whosemetasaysrobots: { index: false }carries noog:*/article:*/twitter:*tag unless it declaressocialCard: true, in which case the card is emitted as for an indexable page and the robots line is unchanged. Lead with the rule; one short section beside "Head links" plus therenderMeta()row in "The pieces". No version, noAs ofstamp, and never the word "unreleased" beside a version number.CHANGELOG.md(edit) — Replace "Nothing yet." under## [Unreleased]with the release note in the house shape:### Added, aTier 1 — seo.line, then one bolded lead sentence and the details —RouteMeta.socialCard?: boolean, the rule it changes (the card on a document that may not be indexed), what stays (the robots directives, every indexable route, every noindex route without the flag), and that no other package changes. NoBREAKING —line and no version number.packages/admin/README.md(edit) — [docs] packages/admin/README.md:97-98 claims '@ultimat3/seowithdraws them from everynoindexdocument' — false since packages/seo/src/meta.ts:245: a noindex route declaringsocialCard: truekeeps its card. Narrow the claim: seo withdraws the social tags from a noindex document unless the route declaressocialCard: true, and no admin route declares it. The claim about admin documents itself stays true.packages/render/src/route-status.test.ts(edit) — [test] CHANGELOG.md:22-24 and wiki/SEO.md:72-77 promise the opt-in applies to a 4xx/5xx document too —noindexOnError(packages/render/src/route.ts:432-435) forcesrobots.index = falseand keeps the rest of the meta — and nothing pins thatsocialCardrides through. Add a case beside the 404 test at packages/render/src/route-status.test.ts:102: a route withload: () => withStatus(404, { kind: 'missing' })andmeta: () => ({ title: 'Not found', robots: { index: false }, socialCard: true })hands backrobots.index === falseANDsocialCard === true.Verification
bun run verifypassed (exit 0) in 16m 38s.🤖 Opened by developerz.ai for task
tsk_e716ee4426347c01cab7a385562d17c9on developerz-ai/ultimate.Raised automatically by the
raw_task_triagelane.