Evidence (verified on production 2026-06-12)
curl https://ogsfrompoly.com/ | grep -iE 'og:|canonical|twitter' → empty. src/layouts/Layout.astro emits only charset/viewport/description/favicon/generator/title.
Consequences for a site whose distribution is "get cited and shared":
- No
og:title/og:description/og:type/og:url and no twitter:card → links shared on X/Discord/Telegram (the prediction-market-native audience) unfurl as bare URLs.
- No
<link rel="canonical"> while both /statements/2026-05-31-weekly and /statements/2026-05-31-weekly/ return 200 (trailing-slash duplicate, verified) → split indexing.
astro.config.mjs sets no site, and https://ogsfrompoly.com is hardcoded independently in src/lib/feeds/collect.ts, src/pages/index.astro, src/pages/statements.md.ts, src/pages/dev/preview.astro.
TDD plan (failing tests first)
src/lib/site/meta.ts (pure): buildPageMeta({ site, path, title, description, type }): { canonical: string; og: Record<string,string>; twitter: Record<string,string> }.
Tests FIRST: canonical strips trailing slash (decide policy + set trailingSlash in astro config to match); og:type article for statement pages / website otherwise; twitter summary card; all URLs absolute on the configured site.
- Set
site: "https://ogsfrompoly.com" in astro.config.mjs; replace hardcoded constants with Astro.site (pages) / a single src/lib/site/config.ts consumed by collect.ts (keeps feed generators pure).
- Render the meta in
Layout.astro via a path (or Astro.url) prop; statement page passes type: "article".
- Optional follow-up (separate): og:image — needs a design decision, out of scope here.
Acceptance criteria
| AC |
Test |
buildPageMeta contract incl. canonical normalization |
unit |
| One source of truth for the site origin (grep: the literal appears once outside tests) |
grep -r "ogsfrompoly.com" src/ | wc check in PR |
| Pages emit canonical + OG + twitter tags |
curl after deploy |
Evidence (verified on production 2026-06-12)
curl https://ogsfrompoly.com/ | grep -iE 'og:|canonical|twitter'→ empty.src/layouts/Layout.astroemits only charset/viewport/description/favicon/generator/title.Consequences for a site whose distribution is "get cited and shared":
og:title/og:description/og:type/og:urland notwitter:card→ links shared on X/Discord/Telegram (the prediction-market-native audience) unfurl as bare URLs.<link rel="canonical">while both/statements/2026-05-31-weeklyand/statements/2026-05-31-weekly/return 200 (trailing-slash duplicate, verified) → split indexing.astro.config.mjssets nosite, andhttps://ogsfrompoly.comis hardcoded independently insrc/lib/feeds/collect.ts,src/pages/index.astro,src/pages/statements.md.ts,src/pages/dev/preview.astro.TDD plan (failing tests first)
src/lib/site/meta.ts(pure):buildPageMeta({ site, path, title, description, type }): { canonical: string; og: Record<string,string>; twitter: Record<string,string> }.Tests FIRST: canonical strips trailing slash (decide policy + set
trailingSlashin astro config to match); og:typearticlefor statement pages /websiteotherwise; twittersummarycard; all URLs absolute on the configured site.site: "https://ogsfrompoly.com"inastro.config.mjs; replace hardcoded constants withAstro.site(pages) / a singlesrc/lib/site/config.tsconsumed bycollect.ts(keeps feed generators pure).Layout.astrovia apath(orAstro.url) prop; statement page passestype: "article".Acceptance criteria
buildPageMetacontract incl. canonical normalizationgrep -r "ogsfrompoly.com" src/ | wccheck in PR