Skip to content

docs: import old Python docs as Astro tutorials + executed notebooks - #79

Merged
Roger-luo merged 7 commits into
mainfrom
docs/import-python-tutorials
May 18, 2026
Merged

Roger-luo merged 7 commits into
mainfrom
docs/import-python-tutorials

Conversation

@Roger-luo

Copy link
Copy Markdown
Collaborator

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.py runs before npx astro build:

  1. Reads every Jupytext .py file under docs/notebooks/.
  2. Executes each via nbclient.NotebookClient so stdout, return values, and matplotlib figures get captured as embedded outputs.
  3. Renders an HTML fragment via nbconvert's basic template (stripping the <body> so JupyterLab's own stylesheet isn't pulled in — the site's CSS themes the .jp-* classes instead).
  4. Writes docs/src/generated/notebooks/<slug>.{html,json} and an index.json listing.

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 via set:html.

Adding another notebook later is just dropping another .py under docs/notebooks/. Two ship in this PR:

  • trotter.py — XZZ Ising Trotter evolution + a noisy variant with loss
  • msd.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.astro under src/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.astro indexes them with one-sentence summaries.

Site wiring

  • Base.astro gains a Tutorials and Examples nav item.
  • KaTeX loaded via CDN in Base.astro — the notebooks' LaTeX ($$…$$) and the loss-channel tutorial's matrix definitions actually render now.
  • New CSS sections in 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-list for the long-form pages.
  • .github/workflows/docs.yml runs build-notebooks.py before npx astro build; 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).

Test plan

  • CI's Build Astro site passes: notebook build runs, then npx astro build emits 13 pages including examples/index, examples/trotter, examples/msd, tutorials/index, tutorials/pauli-propagation, tutorials/loss-channel, tutorials/generalized-tableau.
  • PR preview shows the executed notebooks with code, text outputs, and embedded plots (matplotlib figures).
  • KaTeX renders the math in tutorials/loss-channel (the five-symbol basis, Kraus operators).
  • Light + dark themes both look right on the notebook pages and on the tutorial pages.

What this PR does not do

  • Doesn't delete ppvm-python/docs/ or ppvm-python/mkdocs.yml. A follow-up can do the cleanup once any external redirects are sorted.
  • Doesn't drop the [dependency-groups].doc block in ppvm-python/pyproject.toml. Same reason.

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings May 18, 2026 04:30
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 &lt;Z&gt; = 1 instead of -1.
print(f"&lt;Z&gt;: {`{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)])

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a018a60 by the same refactor — generalized-tableau.astro now stores the Python snippets as frontmatter strings.

Comment on lines +2 to +12
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[];

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a018a60. Removed the unused notebooks constant.

Comment thread docs/src/pages/examples/[slug].astro Outdated
Comment on lines +25 to +29
return {
params: { slug: nb.slug },
props: {
meta: nb,
html: key ? htmls[key] : "",

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a018a60. Missing HTML fragments now throw with an actionable message pointing the developer at npm run extract:notebooks.

Comment thread docs/scripts/build-notebooks.py Outdated
Comment on lines +43 to +45
# notebook before execution; it never appears in the rendered output
# because the cell is tagged ``remove-input`` (and we filter outputs
# from it below).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread docs/src/styles/global.css Outdated
markdown cells, plots embedded as their natural images. */
.notebook { max-width: 50rem; }
.notebook-body { margin-top: 2rem; }
.notebook-body > main { display: contents; }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +2 to +5
import Base from "../../layouts/Base.astro";
import index from "../../generated/notebooks/index.json";

const base = import.meta.env.BASE_URL.replace(/\/$/, "");

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions

github-actions Bot commented May 18, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-05-18 16:53 UTC

Roger-luo and others added 2 commits May 18, 2026 00:37
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>
Copilot AI review requested due to automatic review settings May 18, 2026 04:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.

Comment thread docs/src/styles/global.css Outdated
Comment thread docs/scripts/build-notebooks.py Outdated
Comment thread docs/src/pages/examples/[slug].astro
Comment thread .github/workflows/docs.yml
Roger-luo and others added 2 commits May 18, 2026 00:56
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>
Copilot AI review requested due to automatic review settings May 18, 2026 05:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Comment thread docs/scripts/build-notebooks.py Outdated
Comment thread docs/scripts/build-notebooks.py Outdated
Comment thread docs/notebooks/msd.py
- 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>
@Roger-luo
Roger-luo merged commit 4726418 into main May 18, 2026
8 checks passed
@Roger-luo
Roger-luo deleted the docs/import-python-tutorials branch May 18, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants