diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d90acc2f..81b7287026 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,12 @@ aggregate instead: an italic *Catalog* line at the end of the version section an ### Fixed +- **Image structured data carries the licensing fields Google recommends** — Search + Console flagged every implementation page's `ImageObject` for missing `creator`, + `copyrightNotice`, `creditText`, and `acquireLicensePage`. The bot-page JSON-LD now + ships all four (creator/credit: anyplot; MIT copyright notice; `/legal` as the + license page), completing the image-metadata rich result beside the existing + `license` field (#10537). - **Top-rated thumbnails follow the theme** — the stats page picks a preview per theme, but `/insights/dashboard` never shipped one: its `TopImpl` response model carried only the legacy `preview_url` (a synonym for the light render), so the top-rated grid showed diff --git a/api/routers/seo.py b/api/routers/seo.py index 9fc123b4cb..4824dd6d3b 100644 --- a/api/routers/seo.py +++ b/api/routers/seo.py @@ -583,6 +583,13 @@ def _build_source_code_node(spec, impl, lib_name: str, lang_name: str, page_url: "encodingFormat": "image/png", "caption": f"{spec.title} rendered with {lib_name}", "license": "https://opensource.org/licenses/MIT", + # Google's image-metadata rich result recommends these four beside + # `license`; Search Console flags each absence per page. /legal is + # the page that states the MIT terms the renders are offered under. + "acquireLicensePage": "https://anyplot.ai/legal", + "creator": {"@type": "Organization", "name": "anyplot", "url": "https://anyplot.ai"}, + "creditText": "anyplot.ai", + "copyrightNotice": "© anyplot.ai — MIT License", } if impl.updated: node["dateModified"] = impl.updated.date().isoformat() diff --git a/tests/unit/api/test_seo_helpers.py b/tests/unit/api/test_seo_helpers.py index 037116b6d0..3e6d070555 100644 --- a/tests/unit/api/test_seo_helpers.py +++ b/tests/unit/api/test_seo_helpers.py @@ -420,6 +420,16 @@ def test_jsonld_image_is_the_render_not_the_card(self) -> None: assert image["contentUrl"] == "https://gcs/preview.png" assert image["thumbnailUrl"] == "https://gcs/preview_1200.png" + def test_jsonld_image_carries_licensing_metadata(self) -> None: + """Google's image-metadata rich result recommends these beside `license`; + Search Console flags each missing one per page (2026-08 notice).""" + image = _extract_jsonld(self._page())["@graph"][1]["image"] + assert image["license"] == "https://opensource.org/licenses/MIT" + assert image["acquireLicensePage"] == "https://anyplot.ai/legal" + assert image["creator"] == {"@type": "Organization", "name": "anyplot", "url": "https://anyplot.ai"} + assert image["creditText"] == "anyplot.ai" + assert image["copyrightNotice"] == "© anyplot.ai — MIT License" + def test_jsonld_without_render_keeps_card_image(self) -> None: impl = _mock_impl("matplotlib", "python", preview=None) spec = _mock_spec([impl])