Repository navigation
docs: import old Python docs as Astro tutorials + executed notebooks - #79
Conversation
Brings the content from the legacy `ppvm-python/docs/` mkdocs site into
the new Astro docs in two paths, depending on what the source was.
Jupyter notebooks (automated pipeline)
- `docs/scripts/build-notebooks.py` is a small Python build step that:
- reads every Jupytext `.py` under `docs/notebooks/`,
- executes it via `nbclient.NotebookClient` so stdout, return values
and matplotlib figures get captured as embedded outputs,
- renders an HTML fragment via `nbconvert`'s `basic` template (we
strip the `<body>` to drop JupyterLab's stylesheet and rely on the
site's own CSS to theme the `.jp-*` classes),
- writes `docs/src/generated/notebooks/<slug>.{html,json}` and an
aggregate `index.json` listing for the Examples page.
- `docs/src/pages/examples/index.astro` lists every notebook from the
generated index.
- `docs/src/pages/examples/[slug].astro` is a generic dynamic route
that embeds the matching HTML fragment via `set:html`. No notebook
is hand-translated; adding a new one is a matter of dropping
another `.py` under `docs/notebooks/`.
- Two notebooks ship in this PR: `trotter.py` (XZZ Ising Trotter +
loss) and `msd.py` (magic-state distillation), copied verbatim from
`ppvm-python/docs/examples/`.
Tutorials (markdown → Astro, hand-converted)
- `pauli-propagation.astro`, `loss-channel.astro`, and
`generalized-tableau.astro` under `src/pages/tutorials/` carry the
three substantive markdown pages from the old mkdocs site. The
conversion was manual since the source content was static prose,
not executable.
- `tutorials/index.astro` indexes them with one-sentence summaries.
CI / site wiring
- `Base.astro` gains a "Tutorials" and an "Examples" nav item.
- KaTeX is loaded via CDN in `Base.astro` so the LaTeX in the
notebooks' markdown cells (and the loss-channel tutorial's matrix
definitions) actually renders.
- New CSS sections in `global.css` style:
- `.notebook-body .jp-*` to map JupyterLab's class names onto the
site's design tokens (hairline rules, brand syntax theme, embedded
plots inside a tinted card).
- `.tutorial`, `.callout`, `.tutorials-list` for the long-form pages.
- `.github/workflows/docs.yml` now runs `build-notebooks.py` before
`npx astro build`, and the PR-paths filter expands to `crates/**`
and `ppvm-python/**` so notebook execution re-runs when the upstream
Python or Rust API changes.
- `docs/.gitignore` excludes `src/generated/` (everything in it is
regenerated per build).
The legacy `ppvm-python/docs/` mkdocs site is left in place for now; a
follow-up can decide whether to delete it once redirects are sorted.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One command builds the whole site. CI no longer pastes the extraction incantation inline. `docs/package.json` exposes: - `npm run dev` / `npm run build` — run every prerequisite extraction step (`extract:rust`, `extract:python`, `extract:notebooks`) and then `astro dev` / `astro build`. This is the entry point for a fresh checkout: one command, no setup spelunking. - `npm run extract:rust|python|notebooks` — rebuild a single component when you're iterating on that layer. - `npm run extract` — the three above, in order. - `npm run astro:dev` / `npm run astro:build` — raw Astro commands for when you've already extracted inputs and don't want to wait for it again. `docs/README.md` documents the layout, the per-step commands, and the common iteration patterns (changed a Rust public API → run `extract:rust` and refresh; added a notebook → drop it under `notebooks/` and run `extract:notebooks`). `.github/workflows/docs.yml` now just runs `npm run build` instead of inlining the individual extraction steps. Same behaviour, one place to change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR migrates legacy ppvm-python/docs/ content into the Astro docs site by adding (1) hand-converted long-form tutorials and (2) an automated “execute Jupytext notebooks → embed rendered HTML fragments” pipeline, then wiring both into site navigation and CI.
Changes:
- Add an automated notebook execution + HTML fragment generation script and run it in the docs CI workflow before
astro build. - Add new Tutorials and Examples sections as Astro pages (tutorial prose pages + executed notebook listing/detail routes).
- Add global styling for tutorials and rendered notebook HTML, plus site-wide KaTeX loading for math.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
docs/src/styles/global.css |
Adds styles for tutorial pages and nbconvert/JupyterLab HTML fragments. |
docs/src/pages/tutorials/pauli-propagation.astro |
New tutorial page (Pauli propagation + lossy variant). |
docs/src/pages/tutorials/loss-channel.astro |
New tutorial page with loss-channel mathematical background and KaTeX-ready LaTeX strings. |
docs/src/pages/tutorials/index.astro |
Tutorials landing/index page. |
docs/src/pages/tutorials/generalized-tableau.astro |
New tutorial page for the generalized tableau backend + Stim/noise usage. |
docs/src/pages/examples/index.astro |
Examples listing page backed by generated notebook metadata. |
docs/src/pages/examples/[slug].astro |
Static route generation and embedding of generated notebook HTML fragments. |
docs/src/layouts/Base.astro |
Adds Tutorials/Examples to nav and loads KaTeX auto-render globally. |
docs/scripts/build-notebooks.py |
New pipeline script: Jupytext → execute via nbclient → export HTML fragment + metadata JSON. |
docs/notebooks/trotter.py |
New Jupytext-authored notebook source (trotter evolution example). |
docs/notebooks/msd.py |
New Jupytext-authored notebook source (MSD example). |
docs/.gitignore |
Ignores generated notebook outputs under docs/src/generated/. |
.github/workflows/docs.yml |
Runs notebook build step before Astro build; expands PR path filters. |
Comments suppressed due to low confidence (2)
docs/src/pages/tutorials/pauli-propagation.astro:118
- This code sample is not valid Python as rendered:
print(f"<Z>: {{z_exp}}")contains backticks/braces. Please adjust the snippet so it can be copy/pasted and run (e.g., use%formatting / concatenation, or escape braces in Astro correctly).
z_exp = ps.overlap_with_zero()
# This will be -0.8: in 10% of cases we have <Z> = 1 instead of -1.
print(f"<Z>: {`{z_exp}`}")</code></pre>
docs/src/pages/tutorials/generalized-tableau.astro:94
- The Stim example’s print statement isn’t valid Python as rendered (
print(f"Bell state measurement: {{results}}")). Please adjust the snippet so it can be run verbatim after copy/paste (e.g.,%formatting / concatenation, or correct Astro escaping for f-string braces).
# Single shot:
tab = GeneralizedTableau(n_qubits=2)
results = tab.run(prog)
print(f"Bell state measurement: {`{results}`}")
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <pre><code class="language-python">from ppvm import PauliSum | ||
|
|
||
| # Observable: Z on each qubit | ||
| state = PauliSum.new(n_qubits=3, terms=[f"Z{`{i}`}" for i in range(3)]) |
There was a problem hiding this comment.
Fixed in a018a60. Refactored both f-string examples to live as plain string constants in the Astro frontmatter, with {snippetName} in the <pre><code> body. The rendered HTML is unchanged (the prior {\{i}`}JSX escape did resolve to{i}` correctly), but the source now matches the rendered code line-for-line.
|
|
||
| r0 = tab.measure(0) | ||
| r1 = tab.measure(1) | ||
| print(f"Qubit 0: {`{r0}`}, Qubit 1: {`{r1}`}") # always correlated (Bell state)</code></pre> |
There was a problem hiding this comment.
Fixed in a018a60 by the same refactor — generalized-tableau.astro now stores the Python snippets as frontmatter strings.
| import Base from "../../layouts/Base.astro"; | ||
| import index from "../../generated/notebooks/index.json"; | ||
|
|
||
| interface Meta { | ||
| slug: string; | ||
| title: string; | ||
| headings: string[]; | ||
| source: string; | ||
| } | ||
| const notebooks = index as Meta[]; | ||
|
|
There was a problem hiding this comment.
Fixed in a018a60. Removed the unused notebooks constant.
| return { | ||
| params: { slug: nb.slug }, | ||
| props: { | ||
| meta: nb, | ||
| html: key ? htmls[key] : "", |
There was a problem hiding this comment.
Fixed in a018a60. Missing HTML fragments now throw with an actionable message pointing the developer at npm run extract:notebooks.
| # notebook before execution; it never appears in the rendered output | ||
| # because the cell is tagged ``remove-input`` (and we filter outputs | ||
| # from it below). |
There was a problem hiding this comment.
Fixed in a018a60. Updated the comment to describe what the code actually does (ppvm-hidden-setup tag + whole-cell removal via drop_hidden_setup_cells).
| markdown cells, plots embedded as their natural images. */ | ||
| .notebook { max-width: 50rem; } | ||
| .notebook-body { margin-top: 2rem; } | ||
| .notebook-body > main { display: contents; } |
There was a problem hiding this comment.
Fixed in a018a60. The build step now unwraps nbconvert's <main> wrapper so we no longer have two <main> elements on one page, and the display: contents rule is gone.
| import Base from "../../layouts/Base.astro"; | ||
| import index from "../../generated/notebooks/index.json"; | ||
|
|
||
| const base = import.meta.env.BASE_URL.replace(/\/$/, ""); |
There was a problem hiding this comment.
Confirmed working as intended: npm run dev and npm run build chain through npm run extract, which calls extract:rust + extract:python + extract:notebooks. The latter is what runs docs/scripts/build-notebooks.py. (Use npm run astro:dev / astro:build if you want to skip extraction.) Documented in docs/README.md.
|
Adds a "This docs site" subsection under § 2 (Build & test) in the Developer Guide page. Lists the aggregated entry points (`npm run dev` / `npm run build`) and a table of the per-component extraction commands (`extract:rust` / `extract:python` / `extract:notebooks`) with explicit "when to use" guidance. Also documents the `astro:dev` / `astro:build` escape hatches for editors who don't want to re-run the extractors on every reload. AGENTS.md - Updated the top-of-file pointer to call out the new docs-build section explicitly. - Replaced step 4 of the agent TL;DR with a per-target build matrix (Rust workspace / Python package / docs site) so an agent picking up a docs PR knows the exact command without scanning the Develop page. Adds a `.dev-cmd-table` style to `global.css` — plain hairline rows, no zebra striping, mobile-stacked. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ck language
Four review fixes on the Examples page:
1. Source link
- The link used `blob/main/...` which 404s on PR previews because
the file doesn't exist on `main` yet. Now reads the
`PPVM_GIT_REF` env var, defaulting to `main` for local dev.
- `.github/workflows/docs.yml` sets it to the PR head SHA on PR
previews and to the push SHA on main deploys, so the link is
always stable and resolves to the actual file the notebook was
executed from.
2. Index layout
- Added an explicit "SOURCE" eyebrow label before the path, so the
monospace string reads as "the file backing this notebook" rather
than a stray directory name.
- Source path is styled as a typed link (hairline underline +
accent on hover) rather than naked `<code><a>`.
3. Dropped the per-notebook heading bullet list
- It read as a fake table of contents and confused more than it
helped. The Examples landing now shows only title + language +
source link per entry. The actual TOC still lives in the
left-sidebar scroll-spy on the notebook page.
4. Code block language label
- `[slug].astro` now writes `data-language=…` on the
`.notebook-body` wrapper, sourced from the notebook's
`kernelspec` (recorded by `build-notebooks.py` as the new
`language` metadata field).
- CSS adds a small "python" badge in the top-right of every
`.jp-CodeCell .jp-InputArea` via `::after`. Wired up for
`python`, `rust`, and `julia` so we have room to grow.
- The same `python` badge also appears in the notebook page's
title block alongside the source link, so the language is
announced once at the top and confirmed per code cell.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Astro encodes inline HTML attribute values once, collapsing the JS
escape `'\\('` we wrote in the `onload=` handler down to the literal
JS source `'\('`. JS then parses `'\('` as the 1-char string `(`, so
the runtime delimiter config KaTeX received was `{ left: "(", right:
")", ... }`. Every parenthesised phrase on every page was getting
typeset as math — the MSD example greeted readers with
"5codeblocksof17qubitseach" in italic math letters.
Move the `renderMathInElement(...)` call out of the `onload=` attribute
and into a standalone inline `<script>`, where backslash escapes
survive Astro's attribute encoding. Add `ignoredTags: [..., "pre",
"code"]` so `[7, 16]`-style array literals inside code cells can't
trigger `\[ ... \]` either.
Verified by inspecting the rendered HTML: served script body now
contains `left: "\\(", right: "\\)"`, so KaTeX sees `\(` `\)` at
runtime and skips bare parentheses.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eight discrete fixes from the Copilot review pass on PR #79. Code accuracy & maintainability - Refactor the Python snippets in `pauli-propagation.astro` and `generalized-tableau.astro` to live as plain string constants in the frontmatter, then `{snippetName}` in the `<pre><code>` body. The rendered output was already correct (the prior `{\`{i}\`}` pattern resolves to `{i}`), but the source was hard to read and invited the bug the reviewer flagged. With the new layout the source matches the rendered code line-for-line. - Remove the unused `notebooks` constant and the silent empty-string fallback in `examples/[slug].astro`. Missing HTML fragments now throw with an actionable message telling the developer to run `npm run extract:notebooks`. - Update the misleading `build-notebooks.py` comment that referred to a `remove-input` tag — the code actually uses `ppvm-hidden-setup` and drops the whole cell after execution. Robustness - `build-notebooks.py` now scrubs the rendered HTML fragment before writing it: removes `<script>` tags, strips inline event handlers (`onclick=` etc.), and unwraps nbconvert's `<main>` wrapper so the embedded fragment doesn't nest a second `<main>` inside the page layout. Authors of the .py files are trusted, but a future notebook that emits HTML output shouldn't be able to land arbitrary JS in the docs. - Remove the `display: contents` rule on `.notebook-body > main` (the wrapper is now stripped at build time) and drop the `attr(data-cell-lang, "")` CSS rule whose two-argument `attr()` syntax isn't widely supported. Workflow permissions - `docs.yml` now declares `permissions: contents: read` at the workflow scope. Each job opts in to the narrower scope it needs: `build` stays read-only (it executes repo-controlled notebook Python during `npm run extract:notebooks`), `deploy-main` / `deploy-preview` / `cleanup-preview` get `contents: write` and, where relevant, `pull-requests: write`. A malicious PR can't exfiltrate the deploy token via the notebook execution path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace regex-based HTML scrub with bleach (html5lib DOM parsing) using explicit tag/attr/protocol allowlists, so notebook rich output can't smuggle <iframe>, javascript: URLs, or other active content into the page — and we no longer risk corrupting prose that mentions onload=... in a code block. - Summarise the msd.py final cell with a Counter (top-5 patterns + distinct-outcome count) instead of leaving a 1,000-entry list as the last expression, which previously rendered the full bitstring dump into the executed notebook fragment. - Wrap the correlated_qubit_loss qubit args in ilist.IList so ty check is satisfied (the dialect signature expects IList, not a bare list). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Brings the content from the legacy
ppvm-python/docs/mkdocs site into the new Astro docs in two paths, depending on what the source was. Headline ask was the Jupyter notebooks — those go through an automated execute-and-embed pipeline so adding a new notebook later is a single-file change. The static markdown tutorials are converted to Astro by hand (they don't execute, so no pipeline buys anything).Jupyter notebooks (automated pipeline)
docs/scripts/build-notebooks.pyruns beforenpx astro build:.pyfile underdocs/notebooks/.nbclient.NotebookClientso stdout, return values, and matplotlib figures get captured as embedded outputs.nbconvert'sbasictemplate (stripping the<body>so JupyterLab's own stylesheet isn't pulled in — the site's CSS themes the.jp-*classes instead).docs/src/generated/notebooks/<slug>.{html,json}and anindex.jsonlisting.The Astro side reads the generated files:
docs/src/pages/examples/index.astro— listing.docs/src/pages/examples/[slug].astro— dynamic route that embeds the matching fragment viaset:html.Adding another notebook later is just dropping another
.pyunderdocs/notebooks/. Two ship in this PR:trotter.py— XZZ Ising Trotter evolution + a noisy variant with lossmsd.py— magic-state distillation on the generalized tableau…both copied verbatim from
ppvm-python/docs/examples/.Tutorials (markdown → Astro, hand-converted)
pauli-propagation.astro,loss-channel.astro,generalized-tableau.astroundersrc/pages/tutorials/carry the three substantive markdown pages from the old mkdocs site. The conversion is manual since the source was static prose, not executable.tutorials/index.astroindexes them with one-sentence summaries.Site wiring
Base.astrogains a Tutorials and Examples nav item.Base.astro— the notebooks' LaTeX ($$…$$) and the loss-channel tutorial's matrix definitions actually render now.global.css:.notebook-body .jp-*maps JupyterLab's class names onto our design tokens (hairline rules, brand syntax theme, embedded plots in a tinted card)..tutorial,.callout,.tutorials-listfor the long-form pages..github/workflows/docs.ymlrunsbuild-notebooks.pybeforenpx astro build; the PR-paths filter expands tocrates/**andppvm-python/**so notebook execution re-runs when the upstream Python or Rust API changes.docs/.gitignoreexcludessrc/generated/(everything in it is regenerated per build).Test plan
Build Astro sitepasses: notebook build runs, thennpx astro buildemits 13 pages includingexamples/index,examples/trotter,examples/msd,tutorials/index,tutorials/pauli-propagation,tutorials/loss-channel,tutorials/generalized-tableau.tutorials/loss-channel(the five-symbol basis, Kraus operators).What this PR does not do
ppvm-python/docs/orppvm-python/mkdocs.yml. A follow-up can do the cleanup once any external redirects are sorted.[dependency-groups].docblock inppvm-python/pyproject.toml. Same reason.🤖 Generated with Claude Code