|
| 1 | +# docs-site og:image rollout — implementation plan |
| 2 | + |
| 3 | +> **For agentic workers:** REQUIRED SUB-SKILL: Use |
| 4 | +> superpowers:subagent-driven-development (recommended) or |
| 5 | +> superpowers:executing-plans to implement this plan task-by-task. Steps |
| 6 | +> use checkbox (`- [ ]`) syntax for tracking. |
| 7 | +
|
| 8 | +**Goal:** Each of the 7 docs-site repos emits `og:image`/`twitter:image` pointing |
| 9 | +at its self-hosted social card, via a MkDocs Material `custom_dir` + `main.html` |
| 10 | +override — shipped as one PR per repo. |
| 11 | + |
| 12 | +**Spec:** [`design.md`](./design.md) |
| 13 | + |
| 14 | +**Branch (this `.github` bundle):** `docs-ogimage`. **Per docs-repo branch:** `docs-ogimage`. |
| 15 | + |
| 16 | +**Commit strategy:** one PR per docs repo; a final PR lands this planning bundle in `.github`. |
| 17 | + |
| 18 | +## Global constraints |
| 19 | + |
| 20 | +- **The 7 repos and their sites** (all default branch `main`): |
| 21 | + | repo | site_url | custom_dir | |
| 22 | + |------|----------|-----------| |
| 23 | + | `modern-di` | `https://modern-di.modern-python.org` | none (add `overrides`) | |
| 24 | + | `lite-bootstrap` | `https://lite-bootstrap.modern-python.org` | none (add `overrides`) | |
| 25 | + | `httpware` | `https://httpware.modern-python.org` | none (add `overrides`) | |
| 26 | + | `faststream-redis-timers` | `https://faststream-redis-timers.modern-python.org` | none (add `overrides`) | |
| 27 | + | `faststream-outbox` | `https://faststream-outbox.modern-python.org` | none (add `overrides`) | |
| 28 | + | `semvertag` | `https://semvertag.modern-python.org` | none (add `overrides`) | |
| 29 | + | `that-depends` | `https://that-depends.modern-python.org` | **existing** `docs/overrides` | |
| 30 | + |
| 31 | +- **Card source** (this `.github` checkout, already on disk): |
| 32 | + `/Users/kevinsmith/src/pypi/modern-python/brand/projects/<repo>/social-card.png` |
| 33 | +- **Clone work dir:** `/Users/kevinsmith/src/pypi/.ogimage-clones/<repo>` (create the |
| 34 | + parent once; it is outside the `.github` repo). |
| 35 | +- **The override file is byte-identical for all 7 repos** — this exact content |
| 36 | + (it reads everything from `mkdocs.yml`, so nothing is per-repo): |
| 37 | + |
| 38 | + ```jinja |
| 39 | + {% extends "base.html" %} |
| 40 | + {% block extrahead %} |
| 41 | + {{ super() }} |
| 42 | + {% set base = config.site_url if config.site_url.endswith('/') else config.site_url ~ '/' %} |
| 43 | + {% set card = base ~ 'assets/social-card.png' %} |
| 44 | + {% set title = (page.title ~ ' · ' ~ config.site_name) if (page.title and not page.is_homepage) else config.site_name %} |
| 45 | + {% set description = page.meta.description if page.meta and page.meta.description else config.site_description %} |
| 46 | + <meta property="og:type" content="website"> |
| 47 | + <meta property="og:site_name" content="{{ config.site_name }}"> |
| 48 | + <meta property="og:title" content="{{ title }}"> |
| 49 | + <meta property="og:description" content="{{ description }}"> |
| 50 | + <meta property="og:url" content="{{ page.canonical_url }}"> |
| 51 | + <meta property="og:image" content="{{ card }}"> |
| 52 | + <meta property="og:image:type" content="image/png"> |
| 53 | + <meta property="og:image:width" content="1280"> |
| 54 | + <meta property="og:image:height" content="640"> |
| 55 | + <meta name="twitter:card" content="summary_large_image"> |
| 56 | + <meta name="twitter:title" content="{{ title }}"> |
| 57 | + <meta name="twitter:description" content="{{ description }}"> |
| 58 | + <meta name="twitter:image" content="{{ card }}"> |
| 59 | + {% endblock %} |
| 60 | + ``` |
| 61 | + |
| 62 | +- **Per-repo procedure** (Tasks 1–6 follow it exactly with their `<repo>` / |
| 63 | + `<site>` / `<override-dir>` = `overrides`; Task 7 differs — see it): |
| 64 | + |
| 65 | + 1. `mkdir -p /Users/kevinsmith/src/pypi/.ogimage-clones` |
| 66 | + 2. `gh repo clone modern-python/<repo> /Users/kevinsmith/src/pypi/.ogimage-clones/<repo>` (skip if dir exists; then `git -C … pull --ff-only`) |
| 67 | + 3. `cd` into it; `git checkout -b docs-ogimage` |
| 68 | + 4. `mkdir -p docs/assets <override-dir>` |
| 69 | + 5. Copy the card: `cp /Users/kevinsmith/src/pypi/modern-python/brand/projects/<repo>/social-card.png docs/assets/social-card.png` |
| 70 | + 6. Write `<override-dir>/main.html` with the exact override content above. |
| 71 | + 7. Add `custom_dir: <override-dir>` under `theme:` in `mkdocs.yml` **only if absent** (Tasks 1–6 add `custom_dir: overrides`; Task 7 already has it). |
| 72 | + 8. **Verify** (see "Verification" below). |
| 73 | + 9. `git add -A && git commit -m "docs: add social card og:image meta"` |
| 74 | + 10. `git push -u origin docs-ogimage` |
| 75 | + 11. `gh pr create --base main --head docs-ogimage --title "Add social card og:image" --body "<body>"` (body: one line on what + that the card is self-hosted at `<site>/assets/social-card.png`, plus the 🤖 line). |
| 76 | + 12. Watch CI: `gh pr checks <n>` until non-pending. |
| 77 | + |
| 78 | +- **Verification (the task's "test"):** from the repo clone: |
| 79 | + - Build the docs. Try, in order: (a) `uv sync` then `uv run mkdocs build -d /tmp/ogbuild-<repo>`; (b) if that repo isn't uv-managed or deps fail, `uvx --with 'mkdocs-material[imaging]' --with mkdocs mkdocs build -d /tmp/ogbuild-<repo>` (add `--with` for any plugin the build complains is missing). If the docs toolchain genuinely cannot be assembled, STOP and report — do not open the PR on an unverified build. |
| 80 | + - Assert the built homepage carries the card: |
| 81 | + `grep -F '<meta property="og:image" content="<site>/assets/social-card.png">' /tmp/ogbuild-<repo>/index.html` |
| 82 | + must match exactly one line. |
| 83 | + - Assert the asset shipped: `test -f /tmp/ogbuild-<repo>/assets/social-card.png`. |
| 84 | + - Spot-check one interior page's built HTML contains the same `og:image` line. |
| 85 | + |
| 86 | +- Do not touch anything else in the docs repos. No README changes here (separate spec). |
| 87 | + |
| 88 | +--- |
| 89 | + |
| 90 | +### Task 1: modern-di (pilot) |
| 91 | + |
| 92 | +**Repo:** `modern-python/modern-di` · **site:** `https://modern-di.modern-python.org` · **override-dir:** `overrides` (new) · **custom_dir:** add `overrides`. |
| 93 | + |
| 94 | +This is the pilot — execute the per-repo procedure end to end and confirm the |
| 95 | +pattern before the rest. |
| 96 | + |
| 97 | +- [ ] **Step 1: Clone + branch** |
| 98 | + |
| 99 | + ```bash |
| 100 | + mkdir -p /Users/kevinsmith/src/pypi/.ogimage-clones |
| 101 | + gh repo clone modern-python/modern-di /Users/kevinsmith/src/pypi/.ogimage-clones/modern-di |
| 102 | + cd /Users/kevinsmith/src/pypi/.ogimage-clones/modern-di && git checkout -b docs-ogimage |
| 103 | + ``` |
| 104 | + |
| 105 | +- [ ] **Step 2: Add card + override + custom_dir** |
| 106 | + |
| 107 | + ```bash |
| 108 | + mkdir -p docs/assets overrides |
| 109 | + cp /Users/kevinsmith/src/pypi/modern-python/brand/projects/modern-di/social-card.png docs/assets/social-card.png |
| 110 | + ``` |
| 111 | + Write `overrides/main.html` with the exact override content from Global |
| 112 | + Constraints. Then add `custom_dir: overrides` under `theme:` in `mkdocs.yml` |
| 113 | + (it currently has none). Example (match the file's existing indentation): |
| 114 | + |
| 115 | + ```yaml |
| 116 | + theme: |
| 117 | + name: material |
| 118 | + custom_dir: overrides |
| 119 | + # …existing theme keys unchanged… |
| 120 | + ``` |
| 121 | + |
| 122 | +- [ ] **Step 3: Build + verify (the test)** |
| 123 | + |
| 124 | + ```bash |
| 125 | + uv sync && uv run mkdocs build -d /tmp/ogbuild-modern-di |
| 126 | + grep -F '<meta property="og:image" content="https://modern-di.modern-python.org/assets/social-card.png">' /tmp/ogbuild-modern-di/index.html |
| 127 | + test -f /tmp/ogbuild-modern-di/assets/social-card.png && echo CARD_OK |
| 128 | + ``` |
| 129 | + Expected: the grep prints exactly one line; `CARD_OK`. If `uv sync`/`uv run` |
| 130 | + fails, use the `uvx --with …` fallback from Global Constraints. If the build |
| 131 | + cannot be assembled at all, STOP and report BLOCKED. |
| 132 | + |
| 133 | +- [ ] **Step 4: Commit + push + PR** |
| 134 | + |
| 135 | + ```bash |
| 136 | + git add -A && git commit -m "docs: add social card og:image meta" |
| 137 | + git push -u origin docs-ogimage |
| 138 | + gh pr create --base main --head docs-ogimage \ |
| 139 | + --title "Add social card og:image" \ |
| 140 | + --body $'Adds a self-hosted social card and the og:image/twitter:image meta (Material custom_dir override). Card served at https://modern-di.modern-python.org/assets/social-card.png.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)' |
| 141 | + ``` |
| 142 | + |
| 143 | +- [ ] **Step 5: Watch CI** |
| 144 | + |
| 145 | + `gh pr checks <n>` until non-pending; report the result. |
| 146 | + |
| 147 | +--- |
| 148 | + |
| 149 | +### Task 2: lite-bootstrap |
| 150 | + |
| 151 | +**Repo:** `modern-python/lite-bootstrap` · **site:** `https://lite-bootstrap.modern-python.org` · **override-dir:** `overrides` (new) · **custom_dir:** add `overrides`. |
| 152 | + |
| 153 | +Follow the Global Constraints per-repo procedure with these values. Card source: |
| 154 | +`…/brand/projects/lite-bootstrap/social-card.png`. |
| 155 | + |
| 156 | +- [ ] **Step 1: Clone + branch** — `gh repo clone modern-python/lite-bootstrap …/.ogimage-clones/lite-bootstrap`; `git checkout -b docs-ogimage`. |
| 157 | +- [ ] **Step 2: Add card + override + custom_dir** — copy `lite-bootstrap` card to `docs/assets/social-card.png`; write `overrides/main.html` (exact Global-Constraints content); add `custom_dir: overrides` under `theme:` in `mkdocs.yml`. |
| 158 | +- [ ] **Step 3: Build + verify** — build to `/tmp/ogbuild-lite-bootstrap`; assert one match of |
| 159 | + `grep -F '<meta property="og:image" content="https://lite-bootstrap.modern-python.org/assets/social-card.png">' /tmp/ogbuild-lite-bootstrap/index.html` and `test -f /tmp/ogbuild-lite-bootstrap/assets/social-card.png`. |
| 160 | +- [ ] **Step 4: Commit + push + PR** — commit `docs: add social card og:image meta`; PR `--base main` with body naming `https://lite-bootstrap.modern-python.org/assets/social-card.png` + the 🤖 line. |
| 161 | +- [ ] **Step 5: Watch CI** — `gh pr checks <n>` to completion. |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +### Task 3: httpware |
| 166 | + |
| 167 | +**Repo:** `modern-python/httpware` · **site:** `https://httpware.modern-python.org` · **override-dir:** `overrides` (new) · **custom_dir:** add `overrides`. |
| 168 | + |
| 169 | +- [ ] **Step 1: Clone + branch** — clone to `…/.ogimage-clones/httpware`; `git checkout -b docs-ogimage`. |
| 170 | +- [ ] **Step 2: Add card + override + custom_dir** — copy `httpware` card to `docs/assets/social-card.png`; write `overrides/main.html` (exact content); add `custom_dir: overrides` under `theme:`. |
| 171 | +- [ ] **Step 3: Build + verify** — build to `/tmp/ogbuild-httpware`; assert one match of |
| 172 | + `grep -F '<meta property="og:image" content="https://httpware.modern-python.org/assets/social-card.png">' /tmp/ogbuild-httpware/index.html` and the card file exists. |
| 173 | +- [ ] **Step 4: Commit + push + PR** — `docs: add social card og:image meta`; PR body names `https://httpware.modern-python.org/assets/social-card.png` + 🤖 line. |
| 174 | +- [ ] **Step 5: Watch CI.** |
| 175 | + |
| 176 | +--- |
| 177 | + |
| 178 | +### Task 4: faststream-redis-timers |
| 179 | + |
| 180 | +**Repo:** `modern-python/faststream-redis-timers` · **site:** `https://faststream-redis-timers.modern-python.org` · **override-dir:** `overrides` (new) · **custom_dir:** add `overrides`. |
| 181 | + |
| 182 | +- [ ] **Step 1: Clone + branch** — clone to `…/.ogimage-clones/faststream-redis-timers`; `git checkout -b docs-ogimage`. |
| 183 | +- [ ] **Step 2: Add card + override + custom_dir** — copy `faststream-redis-timers` card; write `overrides/main.html`; add `custom_dir: overrides`. |
| 184 | +- [ ] **Step 3: Build + verify** — build to `/tmp/ogbuild-faststream-redis-timers`; assert one match of |
| 185 | + `grep -F '<meta property="og:image" content="https://faststream-redis-timers.modern-python.org/assets/social-card.png">' /tmp/ogbuild-faststream-redis-timers/index.html` and the card exists. |
| 186 | +- [ ] **Step 4: Commit + push + PR** — `docs: add social card og:image meta`; PR body names the redis-timers card URL + 🤖 line. |
| 187 | +- [ ] **Step 5: Watch CI.** |
| 188 | + |
| 189 | +--- |
| 190 | + |
| 191 | +### Task 5: faststream-outbox |
| 192 | + |
| 193 | +**Repo:** `modern-python/faststream-outbox` · **site:** `https://faststream-outbox.modern-python.org` · **override-dir:** `overrides` (new) · **custom_dir:** add `overrides`. |
| 194 | + |
| 195 | +- [ ] **Step 1: Clone + branch** — clone to `…/.ogimage-clones/faststream-outbox`; `git checkout -b docs-ogimage`. |
| 196 | +- [ ] **Step 2: Add card + override + custom_dir** — copy `faststream-outbox` card; write `overrides/main.html`; add `custom_dir: overrides`. |
| 197 | +- [ ] **Step 3: Build + verify** — build to `/tmp/ogbuild-faststream-outbox`; assert one match of |
| 198 | + `grep -F '<meta property="og:image" content="https://faststream-outbox.modern-python.org/assets/social-card.png">' /tmp/ogbuild-faststream-outbox/index.html` and the card exists. |
| 199 | +- [ ] **Step 4: Commit + push + PR** — `docs: add social card og:image meta`; PR body names the outbox card URL + 🤖 line. |
| 200 | +- [ ] **Step 5: Watch CI.** |
| 201 | + |
| 202 | +--- |
| 203 | + |
| 204 | +### Task 6: semvertag |
| 205 | + |
| 206 | +**Repo:** `modern-python/semvertag` · **site:** `https://semvertag.modern-python.org` · **override-dir:** `overrides` (new) · **custom_dir:** add `overrides`. |
| 207 | + |
| 208 | +- [ ] **Step 1: Clone + branch** — clone to `…/.ogimage-clones/semvertag`; `git checkout -b docs-ogimage`. |
| 209 | +- [ ] **Step 2: Add card + override + custom_dir** — copy `semvertag` card; write `overrides/main.html`; add `custom_dir: overrides`. |
| 210 | +- [ ] **Step 3: Build + verify** — build to `/tmp/ogbuild-semvertag`; assert one match of |
| 211 | + `grep -F '<meta property="og:image" content="https://semvertag.modern-python.org/assets/social-card.png">' /tmp/ogbuild-semvertag/index.html` and the card exists. |
| 212 | +- [ ] **Step 4: Commit + push + PR** — `docs: add social card og:image meta`; PR body names the semvertag card URL + 🤖 line. |
| 213 | +- [ ] **Step 5: Watch CI.** |
| 214 | + |
| 215 | +--- |
| 216 | + |
| 217 | +### Task 7: that-depends (special — existing `docs/overrides`) |
| 218 | + |
| 219 | +**Repo:** `modern-python/that-depends` · **site:** `https://that-depends.modern-python.org` · **override-dir:** `docs/overrides` (**exists**; `custom_dir: docs/overrides` already set — do NOT add `custom_dir`). |
| 220 | + |
| 221 | +- [ ] **Step 1: Clone + branch** — clone to `…/.ogimage-clones/that-depends`; `git checkout -b docs-ogimage`. |
| 222 | + |
| 223 | +- [ ] **Step 2: Inspect existing overrides** |
| 224 | + |
| 225 | + ```bash |
| 226 | + ls -la docs/overrides |
| 227 | + cat docs/overrides/main.html 2>/dev/null || echo "no main.html" |
| 228 | + ``` |
| 229 | + - **If `docs/overrides/main.html` does NOT exist:** create it with the exact |
| 230 | + override content from Global Constraints. |
| 231 | + - **If it EXISTS:** merge — keep its `{% extends … %}` and existing blocks, and |
| 232 | + ensure an `extrahead` block contains the OG/Twitter `<meta>` lines from the |
| 233 | + Global Constraints template (with `{{ super() }}` first). If it already has an |
| 234 | + `extrahead` block, append the meta lines inside it; if not, add the |
| 235 | + `{% block extrahead %}{{ super() }} … {% endblock %}` block. Do not remove |
| 236 | + anything it already does. |
| 237 | + |
| 238 | +- [ ] **Step 3: Add card (no mkdocs change)** |
| 239 | + |
| 240 | + ```bash |
| 241 | + mkdir -p docs/assets |
| 242 | + cp /Users/kevinsmith/src/pypi/modern-python/brand/projects/that-depends/social-card.png docs/assets/social-card.png |
| 243 | + ``` |
| 244 | + Do not edit `mkdocs.yml` — `custom_dir: docs/overrides` is already set. |
| 245 | + |
| 246 | +- [ ] **Step 4: Build + verify** |
| 247 | + |
| 248 | + Build to `/tmp/ogbuild-that-depends` (uv first, else the `uvx --with` fallback). |
| 249 | + Assert exactly one match of |
| 250 | + `grep -F '<meta property="og:image" content="https://that-depends.modern-python.org/assets/social-card.png">' /tmp/ogbuild-that-depends/index.html` |
| 251 | + and `test -f /tmp/ogbuild-that-depends/assets/social-card.png`. Spot-check one |
| 252 | + interior page. If a pre-existing override already emitted an `og:image`, confirm |
| 253 | + there is now exactly ONE `og:image` line (grep count) — if two, the merge |
| 254 | + duplicated it; fix so only our line remains. |
| 255 | + |
| 256 | +- [ ] **Step 5: Commit + push + PR** — `docs: add social card og:image meta`; PR `--base main`, body names `https://that-depends.modern-python.org/assets/social-card.png` + 🤖 line. |
| 257 | + |
| 258 | +- [ ] **Step 6: Watch CI.** |
| 259 | + |
| 260 | +--- |
| 261 | + |
| 262 | +### Task 8: finalize the `.github` planning bundle |
| 263 | + |
| 264 | +**Files (in `.github`, branch `docs-ogimage`):** |
| 265 | +- Modify: `planning/changes/2026-06-30.02-docs-ogimage-rollout/design.md` (summary) |
| 266 | + |
| 267 | +- [ ] **Step 1: Finalize the summary** |
| 268 | + |
| 269 | + In `design.md`, set `summary:` to the realized result, e.g.: |
| 270 | + `summary: docs-site og:image shipped — all 7 docs repos serve a self-hosted social card via a Material override; one PR per repo.` |
| 271 | + |
| 272 | +- [ ] **Step 2: Verify planning** |
| 273 | + |
| 274 | + ```bash |
| 275 | + cd /Users/kevinsmith/src/pypi/modern-python && just check-planning |
| 276 | + ``` |
| 277 | + Expected: `planning: OK`. |
| 278 | + |
| 279 | +- [ ] **Step 3: Commit + open the bundle PR** |
| 280 | + |
| 281 | + ```bash |
| 282 | + git add planning/changes/2026-06-30.02-docs-ogimage-rollout/design.md |
| 283 | + git commit -m "docs: finalize og:image rollout bundle summary" |
| 284 | + git push -u origin docs-ogimage |
| 285 | + gh pr create --base main --head docs-ogimage \ |
| 286 | + --title "Plan + record: docs-site og:image rollout" \ |
| 287 | + --body $'Planning bundle (design + plan) for wiring og:image into the 7 docs repos. The implementation shipped as one PR per docs repo (linked).\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)' |
| 288 | + ``` |
| 289 | + |
| 290 | +--- |
| 291 | + |
| 292 | +## Notes for the executor |
| 293 | + |
| 294 | +- Each docs-repo task is a self-contained PR; verify the build (or STOP) before |
| 295 | + opening it. The 7 repo PRs are independent — order does not matter, but do the |
| 296 | + `modern-di` pilot first to shake out the docs-build command. |
| 297 | +- These tasks run in cloned repos under `/Users/kevinsmith/src/pypi/.ogimage-clones/`, |
| 298 | + NOT in the `.github` repo. Only Task 8 touches `.github`. |
| 299 | +- If a repo's docs build needs a plugin the fallback `uvx` line doesn't include, |
| 300 | + add another `--with <plugin>` rather than skipping verification. |
0 commit comments