BUCKYVERSE

WikiJS Math Rendering + Dome Cookbook Markup — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make math render on the live buckyverse Dome Cookbook page by cleaning its transcription into consistent KaTeX markup and enabling WikiJS’s server-side KaTeX renderer.

Architecture: Re-transcribe the 37 math-bearing pages via OpenRouter with a KaTeX-strict prompt (preserving the 11 prose pages and the page-14 ICOSAHEDRON fix), rebuild index.md, re-push; the user enables KaTeX / disables MathJax in the WikiJS Admin UI; re-push to re-render; verify KaTeX renders live.

Tech Stack: OpenRouter qwen2.5-vl-72b (vision), python3, the repo’s dev/wikijs/scripts/push-page-to-prod.exs, curl for live verification.

Note on TDD: Content/ops task — no unit tests. Each task’s verify step is a concrete inspection or a live-HTML check. Commit after each content task.

Working directory: repo root /Volumes/Storage/belmendo/Projects/buckyverse.org, branch pluck/def-1-codexis-dome-cookbook-render-math.

Constants:


Task 1: Re-transcribe the 37 math-bearing pages with KaTeX-strict markup

Files:

cd /Volumes/Storage/belmendo/Projects/buckyverse.org
OCR="/private/tmp/claude-501/-Volumes-Storage-belmendo-Projects-buckyverse-org/af6dd1a6-1d28-431b-83a8-c30aec38ea28/scratchpad/ocr_imgs"
if [ "$(ls "$OCR"/page-*.png 2>/dev/null | wc -l | tr -d ' ')" != "48" ]; then
  mkdir -p "$OCR"
  for p in dome_cookbook_of_geodesic_geometry/DomeCookbookofGeodesicGeometry.images/page-*.png; do
    magick "$p" -resize 1000x "$OCR/$(basename "$p")" 2>/dev/null
  done
fi
ls "$OCR"/page-*.png | wc -l   # expect 48

Write to the scratchpad, dome_transcribe_katex.py. It loads the OpenRouter key from ~/Projects/codexis/.env, transcribes ONLY the pages passed as CLI args, and overwrites their .txt:

import base64, json, os, sys, time, urllib.request

IMG_DIR = "dome_cookbook_of_geodesic_geometry/DomeCookbookofGeodesicGeometry.images"
OCR_DIR = "/private/tmp/claude-501/-Volumes-Storage-belmendo-Projects-buckyverse-org/af6dd1a6-1d28-431b-83a8-c30aec38ea28/scratchpad/ocr_imgs"
MODEL = "qwen/qwen2.5-vl-72b-instruct"
URL = "https://openrouter.ai/api/v1/chat/completions"
PROMPT = (
    "This is one page of a hand-lettered 1975 booklet on geodesic dome geometry, dense with "
    "hand-written mathematics. Transcribe the page as clean Markdown. CRITICAL: wrap EVERY "
    "mathematical expression in KaTeX delimiters — inline math in single dollar signs $...$, and "
    "standalone/displayed equations in double dollar signs $$...$$ on their own line. Use standard "
    "LaTeX inside: \\sin \\cos \\tan \\cot, \\sqrt{}, \\frac{}{}, \\overline{} for overbars, "
    "\\angle for angle, \\therefore, \\Delta, ^\\circ for degrees, subscripts a_{1}, and \\times. "
    "Every equation, angle value, chord factor, and numeric result must be inside math delimiters "
    "(e.g. write $\\sin 60^\\circ = 0.86603$, not sin 60 = .86603). Transcribe headings and prose "
    "normally (no math delimiters on plain words). For a diagram or a large table, write a one- or "
    "two-line italic description prefixed with 'Figure:' or 'Table:' instead of redrawing it. Do not "
    "invent content. Output only the transcription, no preamble."
)


def load_key():
    for line in open(os.path.expanduser("~/Projects/codexis/.env")):
        line = line.strip()
        if line.startswith("OPENROUTER_API_KEY="):
            return line.split("=", 1)[1]
    raise SystemExit("no OPENROUTER_API_KEY")


KEY = load_key()


def transcribe(png_path):
    b64 = base64.b64encode(open(png_path, "rb").read()).decode()
    payload = {"model": MODEL, "messages": [{"role": "user", "content": [
        {"type": "text", "text": PROMPT},
        {"type": "image_url", "image_url": {"url": "data:image/png;base64," + b64}}]}],
        "temperature": 0}
    req = urllib.request.Request(URL, data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json", "Authorization": "Bearer " + KEY})
    with urllib.request.urlopen(req, timeout=180) as r:
        return json.loads(r.read().decode())["choices"][0]["message"]["content"].strip()


def main():
    pages = sys.argv[1:]  # e.g. 05 06 08 ...
    for n in pages:
        png = os.path.join(OCR_DIR, f"page-{n}.png")
        if not os.path.exists(png):
            png = os.path.join(IMG_DIR, f"page-{n}.png")
        out = os.path.join(IMG_DIR, f"page-{n}.txt")
        print(f"transcribing page-{n} ...", flush=True)
        for attempt in (1, 2, 3):
            try:
                t = time.time()
                text = transcribe(png)
                if text:
                    open(out, "w").write(text)
                    print(f"  ok ({len(text)} chars, {time.time()-t:.1f}s)", flush=True)
                    break
            except Exception as e:
                print(f"  attempt {attempt} failed: {e}", flush=True)
                time.sleep(5 * attempt)
    print("done", flush=True)


if __name__ == "__main__":
    main()
cd /Volumes/Storage/belmendo/Projects/buckyverse.org
cp "$IMG_DIR/page-40.txt" /tmp/page-40.txt.bak 2>/dev/null || true
python3 "<scratchpad>/dome_transcribe_katex.py" 40
sed -n '1,30p' dome_cookbook_of_geodesic_geometry/DomeCookbookofGeodesicGeometry.images/page-40.txt

Confirm formulas are wrapped in $…$/$$…$$ with valid LaTeX and the numbers match the page image. If wrong, adjust the prompt and re-run before doing the rest.

cd /Volumes/Storage/belmendo/Projects/buckyverse.org
python3 "<scratchpad>/dome_transcribe_katex.py" \
  05 06 08 09 11 13 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 \
  32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

Runs ~4–5 min (OpenRouter ~6 s/page). Re-run any page that logs a failure.

cd /Volumes/Storage/belmendo/Projects/buckyverse.org
IMG=dome_cookbook_of_geodesic_geometry/DomeCookbookofGeodesicGeometry.images
for n in 05 06 08 09 11 13 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46; do
  [ "$(grep -c '\$' "$IMG/page-$n.txt")" -ge 1 ] || echo "NO MATH MARKUP: page-$n"
done; echo "math check done"
# prose pages must be unchanged vs git
git diff --name-only -- "$IMG"/page-01.txt "$IMG"/page-14.txt | grep . && echo "WARN: prose page changed" || echo "prose pages untouched"

Expected: “math check done” with no NO-MATH lines; “prose pages untouched” (page 14’s ICOSAHEDRON fix intact).

Read page-13.txt, page-30.txt, page-40.txt. Confirm: balanced $/$$, no obviously broken LaTeX (\frac with two args, closed \overline{}, ^\circ for degrees). Hand-fix any malformed expression.

cd /Volumes/Storage/belmendo/Projects/buckyverse.org
git add dome_cookbook_of_geodesic_geometry/DomeCookbookofGeodesicGeometry.images/*.txt
git commit -m "feat: re-transcribe Dome Cookbook math pages with KaTeX markup"

Task 2: Rebuild index.md and re-push (pre-KaTeX)

Files:

Reuse the original assembly (keep the header through ## Pages, re-append 48 page sections with heading demotion, then the footer):

cd /Volumes/Storage/belmendo/Projects/buckyverse.org
IMG=dome_cookbook_of_geodesic_geometry/DomeCookbookofGeodesicGeometry.images
OUT=dome_cookbook_of_geodesic_geometry/index.md
REL=DomeCookbookofGeodesicGeometry.images
# Header ends at the "## Pages" line — find it dynamically
HEADEND=$(grep -n '^## Pages$' "$OUT" | head -1 | cut -d: -f1)
head -"$HEADEND" "$OUT" > "$OUT.tmp"
for i in $(seq -w 1 48); do
  n=$((10#$i))
  {
    printf '\n### Page %s\n\n' "$n"
    printf '![Page %s](%s/page-%s.png)\n\n' "$n" "$REL" "$i"
    sed -E 's/^#{1,3} /#### /' "$IMG/page-$i.txt"
    printf '\n'
  } >> "$OUT.tmp"
done
cat >> "$OUT.tmp" <<'EOF'

---
*Source: David Kruschke, *Dome Cookbook of Geodesic Geometry*, 2nd edition, 1975. Page images rendered with `pdftoppm`; page text transcribed with a vision model (qwen2.5-vl). Diagrams and tables are reproduced as page images.*

File: [DomeCookbookofGeodesicGeometry-Kruschke2ndEdition1975.pdf](DomeCookbookofGeodesicGeometry-Kruschke2ndEdition1975.pdf)
EOF
mv "$OUT.tmp" "$OUT"
grep -c '^### Page ' "$OUT"   # expect 48
grep -c '^!\[Page ' "$OUT"    # expect 48
cd /Volumes/Storage/belmendo/Projects/buckyverse.org
OUT=dome_cookbook_of_geodesic_geometry/index.md
awk 'NR>1 && /^description:/{sub(/^description: /,"");print "desc len:", length($0)}' "$OUT"   # <=255
echo "pages-with-math-in-index: $(grep -c '\$' "$OUT")"   # should be well above the old 12
head -1 "$OUT"   # frontmatter opens with ---
cd /Volumes/Storage/belmendo/Projects/buckyverse.org
git add dome_cookbook_of_geodesic_geometry/index.md
git commit -m "feat: rebuild Dome Cookbook index.md with KaTeX math markup"
cd /Volumes/Storage/belmendo/Projects/buckyverse.org
ASDF_ERLANG_VERSION=28.4.1 ASDF_ELIXIR_VERSION=1.19.5-otp-28 \
  elixir dev/wikijs/scripts/push-page-to-prod.exs --dry-run \
  "dome_cookbook_of_geodesic_geometry/index=dome_cookbook_of_geodesic_geometry"
ASDF_ERLANG_VERSION=28.4.1 ASDF_ELIXIR_VERSION=1.19.5-otp-28 \
  elixir dev/wikijs/scripts/push-page-to-prod.exs \
  "dome_cookbook_of_geodesic_geometry/index=dome_cookbook_of_geodesic_geometry"

Expected: ✓ updated. (Math will still show raw $$ until Task 3 enables KaTeX — that’s expected here.)


Task 3: User enables KaTeX in the WikiJS Admin UI (gate)

Files: none (production WikiJS config, user-applied).

Message the user: In Administration → Rendering → Markdown, enable “KaTeX” and disable “MathJax”, then save. (Reversible; a single renderer toggle.) Wait for their confirmation before continuing.

After the user confirms, verify via the tooling that KaTeX is now enabled (read-only inspection; do not write config):

cd /Volumes/Storage/belmendo/Projects/buckyverse.org
bash dev/wikijs/scripts/inspect-prod.sh 2>/dev/null | grep -iE 'katex|mathjax' || \
  echo "(inspect-prod has no renderer view — rely on the live-page verification in Task 4)"

If the inspection can’t show renderer state, proceed to Task 4 (the live page is the real proof).


Task 4: Re-render, verify KaTeX renders live, spot-check geodesic_math

Files: none (verification + one re-push to trigger re-render).

WikiJS re-renders a page on save. Enabling the renderer does NOT re-render existing pages, so re-push:

cd /Volumes/Storage/belmendo/Projects/buckyverse.org
ASDF_ERLANG_VERSION=28.4.1 ASDF_ELIXIR_VERSION=1.19.5-otp-28 \
  elixir dev/wikijs/scripts/push-page-to-prod.exs \
  "dome_cookbook_of_geodesic_geometry/index=dome_cookbook_of_geodesic_geometry"
cd /Volumes/Storage/belmendo/Projects/buckyverse.org
curl -s "https://www.buckyverse.org/en/dome_cookbook_of_geodesic_geometry/index" > /tmp/dome-live.html
echo "http: $(curl -s -o /dev/null -w '%{http_code}' https://www.buckyverse.org/en/dome_cookbook_of_geodesic_geometry/index)"
echo "katex spans: $(grep -o 'class="katex' /tmp/dome-live.html | wc -l | tr -d ' ')"   # expect > 0
echo "raw \$\$ leftover in body: $(grep -o '\$\$[^$]' /tmp/dome-live.html | wc -l | tr -d ' ')"  # expect ~0

Expected: HTTP 200, katex spans > 0, raw $$ ~0. If KaTeX spans are 0, the renderer toggle didn’t take — return to Task 3.

geodesic_math needs re-rendering too. Re-render by re-pushing its page (it already has valid math markup in its index.md):

cd /Volumes/Storage/belmendo/Projects/buckyverse.org
ASDF_ERLANG_VERSION=28.4.1 ASDF_ELIXIR_VERSION=1.19.5-otp-28 \
  elixir dev/wikijs/scripts/push-page-to-prod.exs \
  "geodesic_math_and_how_to_use_it/index=geodesic_math_and_how_to_use_it"
curl -s "https://www.buckyverse.org/en/geodesic_math_and_how_to_use_it/index" | grep -o 'class="katex' | wc -l   # expect > 0

Report the result. Note that a full wiki-wide re-render (all other math pages) is a follow-up beyond this pluck — only Dome Cookbook and geodesic_math are re-rendered here.


Task 5: Re-file the deferred codexis ebook build

Files: Modify work/DEFERRED.md (via the dev-xp_work deferred: record flow, on the base branch during housekeeping).

Note for the pluck controller: DEF-1 was re-scoped by this pluck to the WikiJS math fix. The original “full codexis LaTeX/ebook build” is still wanted (per the user’s “Both” choice) — record it as a fresh DEF-N so it isn’t lost when DEF-1 is yanked.

Content: “Full codexis LaTeX/ebook build of the Dome Cookbook” — init codexis project, OCR→LaTeX with math environments, handle the hand-drawn diagrams (TikZ or embedded page-image figures), codexis build --all (EPUB/PDF/mobi/kfx where math renders as real typeset math), codexis deploy --target buckyverse. Why: downloadable typeset editions with rendered math, independent of the wiki. Source: split from DEF-1 (the wiki-side math fix shipped separately).


Verification checklist (whole plan)