Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 49 additions & 16 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,23 @@ on:
pull_request:
types: [opened, synchronize, reopened]
paths:
# Site sources. Other paths (crates/, ppvm-python/) don't affect
# the build because the API JSON consumed by /api/ is committed
# under docs/src/data/ and regenerated manually.
# Site sources, the Rust workspace (consumed by the API JSON
# extractor), and the Python package (consumed by the notebook
# build step + the Python API extractor).
- "docs/**"
- "crates/**"
- "ppvm-python/**"
- ".github/workflows/docs.yml"
pull_request_target:
types: [closed]

# No workflow-wide write privileges. Each job declares the narrowest
# token scope it actually needs. The `build` job executes user-controlled
# notebooks via `npm run extract:notebooks`, so its token must not be
# able to push to `gh-pages` (or anything else). Deploys run on
# separately-scoped jobs that don't see the notebook execution context.
permissions:
contents: write
pull-requests: write
contents: read

concurrency:
# Serialise per-PR (and main); a fresh build cancels any in-flight one
Expand All @@ -48,6 +54,11 @@ jobs:
if: github.event.action != 'closed'
name: Build Astro site
runs-on: ubuntu-latest
# Notebook execution runs arbitrary repo-controlled Python here;
# the token in this job stays read-only so a malicious PR can't
# exfiltrate it into a gh-pages write.
permissions:
contents: read
outputs:
base: ${{ steps.compute-base.outputs.base }}
steps:
Expand Down Expand Up @@ -83,24 +94,32 @@ jobs:
echo "base=/" >> "$GITHUB_OUTPUT"
fi

- name: Generate API JSON
# The /api/ page reads docs/src/data/{rust,python}-api.json,
# which are .gitignore'd and produced by docs/scripts/. The
# Rust extractor needs nightly for `--output-format json`;
# the Python extractor uses griffe via `uv run --with griffe`.
working-directory: docs
env:
RUSTFLAGS: "-C target-feature=+aes,+sse2"
- name: Compute git ref for source links
id: compute-ref
# "Source" links on the Examples pages resolve against this ref.
# PR previews use the PR head SHA so new files (that aren't on
# main yet) still link correctly; main deploys use the merge
# commit SHA so the link is stable forever.
run: |
node scripts/extract-rust.mjs
node scripts/extract-python.mjs
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
echo "ref=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT"
else
echo "ref=${{ github.sha }}" >> "$GITHUB_OUTPUT"
fi

- name: Build site
# `npm run build` aggregates the three extraction steps
# (`extract:rust`, `extract:python`, `extract:notebooks`) plus
# `astro build` into a single command. See `docs/README.md`
# for the per-step breakdown and the per-component escape
# hatches a developer would use locally.
working-directory: docs
env:
PPVM_SITE: https://congenial-bassoon-l436wp3.pages.github.io
PPVM_BASE: ${{ steps.compute-base.outputs.base }}
run: npx astro build
PPVM_GIT_REF: ${{ steps.compute-ref.outputs.ref }}
RUSTFLAGS: "-C target-feature=+aes,+sse2"
run: npm run build
Comment thread
Roger-luo marked this conversation as resolved.

- name: Upload built site
uses: actions/upload-artifact@v4
Expand All @@ -115,6 +134,10 @@ jobs:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
# Only this job writes to gh-pages; it doesn't run any
# repo-controlled scripts (the artifact is already built).
permissions:
contents: write
concurrency:
group: deploy-gh-pages
cancel-in-progress: false
Expand All @@ -141,6 +164,11 @@ jobs:
needs: build
if: github.event_name == 'pull_request' && github.event.action != 'closed'
runs-on: ubuntu-latest
# Writes the pre-built artifact to gh-pages/pr-preview/pr-<N>/
# and comments back on the PR.
permissions:
contents: write
pull-requests: write
concurrency:
group: deploy-gh-pages
cancel-in-progress: false
Expand All @@ -163,6 +191,11 @@ jobs:
name: Clean up PR preview
if: github.event_name == 'pull_request_target' && github.event.action == 'closed'
runs-on: ubuntu-latest
# Removes the gh-pages/pr-preview/pr-<N>/ subtree and edits the
# PR's preview comment; nothing else.
permissions:
contents: write
pull-requests: write
concurrency:
group: deploy-gh-pages
cancel-in-progress: false
Expand Down
33 changes: 21 additions & 12 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# AGENTS.md

> **Read the Developer Guide first.** The canonical contributor reference for
> this repository — for both human and AI contributors — lives in the docs
> site at `docs/src/pages/develop.astro` (rendered at `/develop/`). It covers
> project layout, build/test commands, architecture, conventions, the Python
> binding pipeline, and where to look for each subsystem.
> this repository — for both human and AI contributors — is the Astro page
> at [`docs/src/pages/develop.astro`](docs/src/pages/develop.astro) (rendered
> at `/develop/` on the live site). It covers project layout, build/test
> commands for the Rust workspace, the Python package, **and the docs site
> itself** (`§ 2 Build & test`), architecture, conventions, the Python
> binding pipeline, extension recipes, and the file-by-file "where to look
> for X" table.
>
> This file is a short pointer so agents can find that guide quickly. Do not
> add content here that belongs in the Developer Guide — keep the guide as
> the single source of truth.
> This file is a short pointer so agents can find that guide quickly. Do
> not add content here that belongs in the Developer Guide — keep the
> guide as the single source of truth.

## Install the ppvm-usage skill

Expand All @@ -29,13 +32,19 @@ internals.

If you are an AI agent picking up a task in this repository:

1. Open `docs/src/pages/develop.astro` and read the sections relevant to your
task. The "For AI agents" callout at the top tells you which sections are
load-bearing.
1. Open [`docs/src/pages/develop.astro`](docs/src/pages/develop.astro) and
read the sections relevant to your task. The "For AI agents" callout at
the top tells you which sections are load-bearing.
2. Use `uv` for anything Python; never `pip`.
3. Use Conventional Commits: `<type>(<scope>): <description>`.
4. Build & test with the commands documented in the guide
(`cargo test --workspace`, `uv run --project ppvm-python --group dev pytest …`).
4. Build & test the relevant target:
- **Rust workspace**: `cargo test --workspace`
- **Python package**: `uv run --project ppvm-python --group dev pytest …`
- **Docs site**: `cd docs && npm run build` (chains `extract:rust`,
`extract:python`, `extract:notebooks`, then `astro build`).
Per-step commands and the `astro:dev` / `astro:build` escape
hatches are documented under `§ 2 → "This docs site"` in the
Developer Guide and in [`docs/README.md`](docs/README.md).
5. Respect the `Config`-trait generics in `ppvm-runtime`; do not introduce
runtime dispatch where a compile-time bound suffices.
6. Pauli propagation runs **backwards** (Heisenberg picture). Reverse the
Expand Down
4 changes: 4 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ dist/
.astro/
src/data/rust-api.json
src/data/python-api.json
# Outputs from `docs/scripts/build-notebooks.py`. The Jupytext sources
# under `docs/notebooks/` are tracked; the executed HTML fragments,
# their metadata, and the index are regenerated on every build.
src/generated/
74 changes: 74 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# ppvm docs site

Astro site for the ppvm documentation. Deployed to gh-pages root by
`.github/workflows/docs.yml`; PRs that touch this directory get a
per-PR preview deploy.

## Quick reference

From this directory:

```bash
npm install # one-time
npm run dev # extract + astro dev → http://127.0.0.1:4321
npm run build # extract + astro build → ./dist/
```

`npm run dev` and `npm run build` run every prerequisite extraction
step first, so a fresh clone has one command to remember. Each
extraction step is also available on its own when you're iterating on
a specific layer:

| Command | What it does |
|-------------------------------|-----|
| `npm run extract:rust` | Re-run `cargo +nightly rustdoc -- --output-format json` for the three Rust crates and reshape into `src/data/rust-api.json`. Source: `scripts/extract-rust.mjs`. |
| `npm run extract:python` | Re-run `griffe dump ppvm` and reshape into `src/data/python-api.json`. Source: `scripts/extract-python.mjs`. |
| `npm run extract:notebooks` | Execute every Jupytext `.py` under `notebooks/` and emit HTML fragments + metadata to `src/generated/notebooks/`. Source: `scripts/build-notebooks.py`. |
| `npm run extract` | All three of the above, in order. |
| `npm run astro:dev` | `astro dev` without extracting anything — handy when you're editing only the Astro layer and trust the existing inputs. |
| `npm run astro:build` | `astro build` without extracting anything. |

## Workflow patterns

- **First clone / fresh checkout:** `npm install && npm run dev`.
- **Iterating on the homepage / layout:** keep `npm run astro:dev` running; re-run `npm run extract:*` only when you change the underlying source.
- **Changed a Rust public API:** `npm run extract:rust` then refresh the browser. The site picks up the regenerated `src/data/rust-api.json` automatically.
- **Edited a notebook under `notebooks/`:** `npm run extract:notebooks`.
- **Added a *new* notebook:** drop it into `notebooks/<name>.py` as a Jupytext-percent file, then `npm run extract:notebooks`. The Examples landing page picks it up from the regenerated `src/generated/notebooks/index.json`.

## Layout

```
docs/
├── notebooks/ # Jupytext .py — executed at build time
├── scripts/
│ ├── extract-rust.mjs
│ ├── extract-python.mjs
│ └── build-notebooks.py
├── src/
│ ├── data/ # *.json from extract:rust / extract:python (gitignored)
│ ├── generated/ # notebooks/*.html + meta + index (gitignored)
│ ├── components/, layouts/, pages/, styles/
│ └── ...
├── package.json
└── astro.config.mjs
```

`src/data/` and `src/generated/` are `.gitignore`'d; they're rebuilt on
every CI run via `npm run extract`.

## CI

`.github/workflows/docs.yml` builds the site via `npm run build` on
every PR that touches `docs/`, `crates/`, `ppvm-python/`, or itself.
PR previews land at `gh-pages/pr-preview/pr-<N>/`; the main deploy
publishes to `gh-pages` root on every push to `main`.

## Prerequisites

- Node ≥ 20 (Astro 5 requirement).
- `uv` (https://docs.astral.sh/uv/) — used to drive the Python
notebook executor and the griffe-based Python extractor.
- Rust nightly — `extract-rust.mjs` invokes `cargo +nightly rustdoc
--output-format json`. Install with `rustup toolchain install
nightly`.
Loading
Loading