gabriel / musehub public
feat BREAKING musehub#129 feat/opengraph-repo-cards #1 / 2
aaronrene · 107 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"