|
| 1 | +--- |
| 2 | +summary: docs-site og:image shipped — all 7 docs repos serve a self-hosted social card via a Material override; one PR per repo. |
| 3 | +--- |
| 4 | + |
| 5 | +# Design: docs-site og:image rollout |
| 6 | + |
| 7 | +## Summary |
| 8 | + |
| 9 | +Make each of the seven `modern-python` docs sites emit a proper |
| 10 | +`og:image`/`twitter:image` pointing at its own social card, so link unfurls on |
| 11 | +GitHub/Slack/X/etc. show the branded card instead of nothing. Each docs repo |
| 12 | +self-hosts its card (`docs/assets/social-card.png`, copied from this repo's |
| 13 | +`brand/projects/<repo>/social-card.png`) and gains a small MkDocs Material |
| 14 | +template override that emits the Open Graph + Twitter meta. Shipped as **seven |
| 15 | +PRs**, one per docs repo. This planning bundle lives in `.github` (the org/brand |
| 16 | +home where the cards are generated); no code changes land in `.github`. |
| 17 | + |
| 18 | +## Motivation |
| 19 | + |
| 20 | +The cards shipped in `brand/projects/<repo>/social-card.png` (PR #23) but nothing |
| 21 | +consumes them yet — the docs sites have no `og:image`, so shared links are bare. |
| 22 | +Probing confirmed all 7 sites are live MkDocs Material on their subdomain, none |
| 23 | +use Material's `social` plugin (no auto-card to conflict with), and only |
| 24 | +`that-depends` has a `custom_dir` (`docs/overrides`). (At rollout, one repo — |
| 25 | +`faststream-redis-timers` — turned out to have no `site_url` in `mkdocs.yml`; its |
| 26 | +PR added it, since the override needs it.) |
| 27 | + |
| 28 | +The 7 docs repos: `modern-di`, `that-depends`, `lite-bootstrap`, `httpware`, |
| 29 | +`faststream-redis-timers`, `faststream-outbox`, `semvertag`. |
| 30 | + |
| 31 | +## Non-goals |
| 32 | + |
| 33 | +- README logos — a separate, later spec (this is the og:image piece only). |
| 34 | +- Cards for non-docs repos — they have no docs site. |
| 35 | +- Auto-generating cards with Material's `social` plugin — we use the bespoke |
| 36 | + cards already generated in this repo. |
| 37 | +- Changing the card design or this repo's own og:image. |
| 38 | + |
| 39 | +## Design |
| 40 | + |
| 41 | +### 1. Per-repo change set (identical shape across all 7) |
| 42 | + |
| 43 | +Each docs repo PR adds: |
| 44 | + |
| 45 | +1. **`docs/assets/social-card.png`** — the repo's card, copied verbatim from |
| 46 | + `modern-python/.github`'s `brand/projects/<repo>/social-card.png` (current |
| 47 | + `main`). |
| 48 | +2. **`overrides/main.html`** — a Material override (template below). For |
| 49 | + `that-depends`, this goes in its existing `docs/overrides/` instead of a new |
| 50 | + `overrides/`. |
| 51 | +3. **`mkdocs.yml`** — add `theme.custom_dir: overrides` for the six repos that |
| 52 | + lack it. `that-depends` already sets `custom_dir: docs/overrides` — no change. |
| 53 | + |
| 54 | +### 2. The override template |
| 55 | + |
| 56 | +One file, identical for every repo (it reads values from `mkdocs.yml`, so nothing |
| 57 | +is hard-coded per repo): |
| 58 | + |
| 59 | +```jinja |
| 60 | +{% extends "base.html" %} |
| 61 | +{% block extrahead %} |
| 62 | + {{ super() }} |
| 63 | + {% set base = config.site_url if config.site_url.endswith('/') else config.site_url ~ '/' %} |
| 64 | + {% set card = base ~ 'assets/social-card.png' %} |
| 65 | + {% set title = (page.title ~ ' · ' ~ config.site_name) if (page.title and not page.is_homepage) else config.site_name %} |
| 66 | + {% set description = page.meta.description if page.meta and page.meta.description else config.site_description %} |
| 67 | + <meta property="og:type" content="website"> |
| 68 | + <meta property="og:site_name" content="{{ config.site_name }}"> |
| 69 | + <meta property="og:title" content="{{ title }}"> |
| 70 | + <meta property="og:description" content="{{ description }}"> |
| 71 | + <meta property="og:url" content="{{ page.canonical_url }}"> |
| 72 | + <meta property="og:image" content="{{ card }}"> |
| 73 | + <meta property="og:image:type" content="image/png"> |
| 74 | + <meta property="og:image:width" content="1280"> |
| 75 | + <meta property="og:image:height" content="640"> |
| 76 | + <meta name="twitter:card" content="summary_large_image"> |
| 77 | + <meta name="twitter:title" content="{{ title }}"> |
| 78 | + <meta name="twitter:description" content="{{ description }}"> |
| 79 | + <meta name="twitter:image" content="{{ card }}"> |
| 80 | +{% endblock %} |
| 81 | +``` |
| 82 | + |
| 83 | +**Trailing-slash guard is load-bearing:** these repos' `site_url` has no trailing |
| 84 | +slash (e.g. `https://modern-di.modern-python.org`), unlike this repo's |
| 85 | +(`https://modern-python.org/`); the `base` computation normalises it so the card |
| 86 | +URL is `https://<repo>.modern-python.org/assets/social-card.png`, never |
| 87 | +`…orgassets/…`. |
| 88 | + |
| 89 | +It extends `base.html` and only appends to `extrahead` via `{{ super() }}`, so it |
| 90 | +adds meta without overriding anything Material already renders. |
| 91 | + |
| 92 | +### 3. Asset sourcing |
| 93 | + |
| 94 | +The card is copied from this `.github` checkout: |
| 95 | +`brand/projects/<repo>/social-card.png` → `<docs-repo>/docs/assets/social-card.png`. |
| 96 | +It is committed into the docs repo (self-hosted), so the docs site serves it |
| 97 | +same-origin and there is no cross-repo runtime dependency. If a card is later |
| 98 | +regenerated here, re-copy it into the repo (rare; noted as a maintenance step). |
| 99 | + |
| 100 | +### 4. Execution: seven PRs |
| 101 | + |
| 102 | +One PR per repo, done sequentially and verified before the next: |
| 103 | +clone → branch → add card + override (+ `custom_dir` where missing) → verify |
| 104 | +(below) → push → PR → watch CI. Branch name per repo: `docs-ogimage`. |
| 105 | +`that-depends` is the one special case (existing `docs/overrides/`, |
| 106 | +`custom_dir` already set). |
| 107 | + |
| 108 | +## Operations |
| 109 | + |
| 110 | +After each PR merges and the site redeploys, the card is live at |
| 111 | +`https://<repo>.modern-python.org/assets/social-card.png`. Optionally validate the |
| 112 | +unfurl with a debugger (e.g. opengraph.xyz) — manual, out of band. |
| 113 | + |
| 114 | +## Out of scope |
| 115 | + |
| 116 | +- README logos (separate spec). |
| 117 | +- Any change to this `.github` repo's site or to the card design. |
| 118 | + |
| 119 | +## Testing |
| 120 | + |
| 121 | +Per repo, before opening the PR: |
| 122 | + |
| 123 | +- Build the docs the way the repo builds them (e.g. `uv run mkdocs build` or the |
| 124 | + repo's documented command). The build must succeed. |
| 125 | +- Assert the generated `site/index.html` contains |
| 126 | + `<meta property="og:image" content="https://<repo>.modern-python.org/assets/social-card.png">` |
| 127 | + and `site/assets/social-card.png` exists. |
| 128 | +- Spot-check one interior page's built HTML carries the same `og:image`. |
| 129 | + |
| 130 | +## Risk |
| 131 | + |
| 132 | +- **Docs build needs Material + plugins installed.** Cloning a repo and running |
| 133 | + `mkdocs build` may require its docs dependencies. Likelihood medium, impact |
| 134 | + low. *Mitigation:* install via the repo's own dev/docs extra |
| 135 | + (`uv sync`/`uv run`), or `uvx --with mkdocs-material[imaging] mkdocs build`; if |
| 136 | + a repo's plugin set is heavy and unavailable, fall back to verifying the |
| 137 | + override is valid Jinja and the card file is present, and rely on the repo's CI |
| 138 | + to build. |
| 139 | +- **`that-depends` already has a `main.html` in `docs/overrides/`.** Then it must |
| 140 | + be *merged* (append the `extrahead` block), not overwritten. *Mitigation:* the |
| 141 | + plan inspects `docs/overrides/` first and merges if a `main.html` exists. |
| 142 | +- **Per-repo `mkdocs.yml` drift.** A repo might key the theme differently. |
| 143 | + *Mitigation:* the plan reads each `mkdocs.yml` before editing; the override |
| 144 | + template itself is config-driven and unaffected. |
| 145 | +- **Card staleness.** The committed card can drift from a regenerated one in |
| 146 | + `.github`. Likelihood low (cards rarely change), impact low. *Mitigation:* the |
| 147 | + later README spec or a follow-up can document a re-copy step; out of scope here. |
0 commit comments