gabriel / musehub public
feat BREAKING musehub#129 feat/opengraph-repo-cards #1 / 2
aaronrene · 14 days ago · Jul 9, 2026 · Diff

feat(musehub#129): OG repo preview cards — endpoint, cache, meta wiring

Phase 2–3: Playwright HTML→PNG at /{owner}/{repo}/opengraph-image with content-hash BlobBackend cache, singleflight render, stub renderer for tests. Repo pages emit og:image/twitter meta with ?cb= cache bust; base.html renders og_meta. OG_01–OG_06 test suite; Dockerfile Chromium + cairosvg deps.

sha256:6ad6d62107bbd6940c52d63327966b8a65c6391f4a3da6cd9e7548ba4b38bc48 sha
+126 ~51 −17 symbols
16 changed · 1036 in snapshot files
sha256:4ab77b8486cba6601d509c45ee6969a57a32a945a727d71819e7fd428a1c1f01 snapshot
+126
symbols added
~51
symbols modified
−17
symbols removed
16
files changed
1036
files in snapshot
0
dead code introduced
Semantic Changes 194 symbols
+ APIRouter import import APIRouter L12–12
+ AsyncSession import import AsyncSession L15–15
+ Depends import import Depends L12–12
+ OPENGRAPH_LIMIT import import OPENGRAPH_LIMIT L21–21
+ Request import import Request L12–12
+ Response import import Response L14–14
+ SlugParam import import SlugParam L18–18
+ StarletteResponse import import StarletteResponse L16–16
+ TokenClaims import import TokenClaims L19–19
+ annotations import import annotations L8–8
+ collect_repo_card_context import import collect_repo_card_context L22–22
+ get_db import import get_db L20–20
+ get_or_create_repo_card_png import import get_or_create_repo_card_png L22–22
+ http_status import import http_status L13–13
+ limiter import import limiter L21–21
+ logging import import logging L10–10
+ optional_token import import optional_token L19–19
+ logger variable variable logger L24–24
+ repo_opengraph_image function async_function repo_opengraph_image L35–67
+ router variable variable router L26–26
~ musehub/services/opengraph_cards.py .py 34 symbols added
+ LAYOUT_VERSION variable variable LAYOUT_VERSION L31–31
+ OpenGraphCardContext class class OpenGraphCardContext L42–54
+ _STUB_PNG variable variable _STUB_PNG L34–36
+ _rasterize_sigil_b64 function function _rasterize_sigil_b64 L143–152
+ _render_locks variable variable _render_locks L38–38
+ build_card_context_from_repo_page function function build_card_context_from_repo_page L200–226
+ build_opengraph_image_url_from_request function function build_opengraph_image_url_from_request L96–112
+ cache_bust_token function function cache_bust_token L91–93
+ cache_object_id function function cache_object_id L86–88
+ collect_repo_card_context function async_function collect_repo_card_context L155–197
+ compute_content_hash function function compute_content_hash L79–83
+ description_fallback function function description_fallback L57–59
+ get_or_create_repo_card_png function async_function get_or_create_repo_card_png L229–248
+ hash_payload_from_context function function hash_payload_from_context L62–76
+ AsyncSession import import AsyncSession L18–18
+ MSignContext import import MSignContext L20–20
+ MusehubIdentity import import MusehubIdentity L21–21
+ MusehubIssue import import MusehubIssue L22–22
+ annotations import import annotations L8–8
+ asyncio import import asyncio L10–10
+ base64 import import base64 L11–11
+ dataclass class import dataclass L15–15
+ func function import func L17–17
+ generate_sigil import import generate_sigil L25–25
+ get_backend import import get_backend L26–26
+ get_opengraph_renderer import import get_opengraph_renderer L24–24
+ hashlib import import hashlib L12–12
+ json import import json L13–13
+ logging import import logging L14–14
+ musehub_repository import import musehub_repository L23–23
+ sa_select import import sa_select L17–17
+ logger variable variable logger L28–28
+ resolve_owner_avatar function async_function resolve_owner_avatar L115–140
+ shutdown_opengraph_renderer function async_function shutdown_opengraph_renderer L251–255
+ OpenGraphRenderer class class OpenGraphRenderer L37–45
+ render method async_method render L44–45
+ PlaywrightOpenGraphRenderer class class PlaywrightOpenGraphRenderer L57–64
+ render method async_method render L60–64
+ StubOpenGraphRenderer class class StubOpenGraphRenderer L48–54
+ render method async_method render L51–54
+ _STUB_PNG variable variable _STUB_PNG L28–30
+ _TEMPLATE_DIR variable variable _TEMPLATE_DIR L23–25
+ _browser variable variable _browser L33–33
+ _get_sync_browser function function _get_sync_browser L91–101
+ _playwright_instance variable variable _playwright_instance L32–32
+ _render_template function function _render_template L67–88
+ _renderer_instance variable variable _renderer_instance L34–34
+ _screenshot_html_sync function function _screenshot_html_sync L104–111
+ get_opengraph_renderer function function get_opengraph_renderer L125–137
+ ABC import import ABC L12–12
+ Path import import Path L13–13
+ TYPE_CHECKING import import TYPE_CHECKING L14–14
+ abstractmethod method import abstractmethod L12–12
+ annotations import import annotations L7–7
+ asyncio import import asyncio L9–9
+ base64 import import base64 L10–10
+ logging import import logging L11–11
+ settings import import settings L16–16
+ logger variable variable logger L21–21
+ reset_renderer_for_tests function function reset_renderer_for_tests L140–145
+ shutdown_renderer function async_function shutdown_renderer L114–122
+ h1: {{ repo_title }} section h1: {{ repo_title }} L145–145
~ tests/test_opengraph_repo_cards.py .py 26 symbols added
+ _PNG_MAGIC variable variable _PNG_MAGIC L16–16
+ _make_repo function async_function _make_repo L19–43
+ _reset_og_renderer function function _reset_og_renderer L47–48
+ AsyncClient import import AsyncClient L8–8
+ AsyncSession import import AsyncSession L9–9
+ MusehubRepo import import MusehubRepo L12–12
+ StubOpenGraphRenderer import import StubOpenGraphRenderer L14–14
+ annotations import import annotations L3–3
+ compute_content_hash import import compute_content_hash L13–13
+ compute_identity_id import import compute_identity_id L11–11
+ compute_repo_id import import compute_repo_id L11–11
+ datetime import import datetime L5–5
+ pytest import import pytest L7–7
+ reset_renderer_for_tests import import reset_renderer_for_tests L14–14
+ timezone import import timezone L5–5
+ test_content_hash_changes_when_description_changes function function test_content_hash_changes_when_description_changes L191–220
+ test_og01_opengraph_image_returns_png function async_function test_og01_opengraph_image_returns_png L56–64
+ test_og01_private_repo_returns_404 function async_function test_og01_private_repo_returns_404 L67–73
+ test_og02_description_in_renderer_context function async_function test_og02_description_in_renderer_context L81–90
+ test_og02_empty_description_uses_fallback function async_function test_og02_empty_description_uses_fallback L93–101
+ test_og03_avatar_b64_present_for_unregistered_owner function async_function test_og03_avatar_b64_present_for_unregistered_owner L109–117
+ test_og04_stats_in_renderer_context function async_function test_og04_stats_in_renderer_context L125–135
+ test_og05_cache_hit_avoids_second_render function async_function test_og05_cache_hit_avoids_second_render L143–156
+ test_og06_repo_page_emits_og_image_meta function async_function test_og06_repo_page_emits_og_image_meta L164–173
+ test_og06_twitter_large_image_card function async_function test_og06_twitter_large_image_card L176–183
+ test_og_security_invalid_slug_rejected function async_function test_og_security_invalid_slug_rejected L228–230
~ docs/issues/opengraph-repo-cards-design.md .md 16 symbols added, 17 symbols removed, 17 symbols modified
~ code
Implementation sketch section Implementation sketch L40–68
code variable variable code L42–47
twitter:card upgrade (Phase 3 only) section twitter:card upgrade (Phase 3 only) L111–117
Strategy: content-hash key + object-store persistence section Strategy: content-hash key + object-store persistence L119–170
code[json] variable variable code[json] L125–139
table section table L144–149
New endpoint section New endpoint L172–182
html head block (prerequisite for OG_06) section Phase 3 — base.html head block (prerequisite for OG_06) L199–215
code[html] variable variable code[html] L201–210
py wiring section Phase 3 — ui_repo.py wiring L182–199
code[python] variable variable code[python] L184–198
Files to touch (Phase 2–3) section Files to touch (Phase 2–3) L243–261
table section table L245–258
Implementation order (frozen) section Implementation order (frozen) L234–243
Review checklist (Gabriel) section Review checklist (Gabriel) L261–269
Test matrix (Phase 2–3) section Test matrix (Phase 2–3) L215–234
table section table L217–225
+ Avatar embedding section Avatar embedding L53–60
+ Font strategy (amended) section Font strategy (amended) L48–53
+ Runtime lifecycle (amended) section Runtime lifecycle (amended) L40–48
+ Crawler cache busting (amended — Phase 3) section Crawler cache busting (amended — Phase 3) L138–152
+ code variable variable code L144–147
+ Strategy: content-hash key + BlobBackend persistence section Strategy: content-hash key + BlobBackend persistence L105–138
+ code variable variable code L109–113
+ code[json] variable variable code[json] L118–133
+ Endpoint section Endpoint L154–164
+ html meta tags (amended) section Phase 3 — base.html meta tags (amended) L164–180
+ code[html] variable variable code[html] L166–179
+ py section Phase 3 — ui_repo.py L180–188
+ Docker amendments section Docker amendments L201–209
+ Review checklist section Review checklist L209–216
+ Test matrix section Test matrix L188–201
+ table section table L190–198
~ table
~ table
~ table
~ code
~ Goal
~ table
~ musehub/config.py .py 1 symbol modified
~ musehub/rate_limits.py .py 1 symbol added
+ OPENGRAPH_LIMIT variable variable OPENGRAPH_LIMIT L81–81
~ musehub/templates/musehub/base.html .html 1 symbol modified
~ pyproject.toml .toml 2 symbols modified
Files Changed
+5 ~11
1036 in snapshot

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:6ad6d62107bbd6940c52d63327966b8a65c6391f4a3da6cd9e7548ba4b38bc48 --body "your comment"