xpsflow

The website and the in-browser build

The site at xpsflow.pages.dev is a static build: a landing page, these docs, and the workbench running entirely in the browser. Nothing on it talks to a server.

How the browser build works

scripts/build_site.py assembles dist/:

path what it is
index.html, site.css, site.js the landing page
docs/*.html and docs/*.md each documentation page, with its Markdown twin linked as rel="alternate"
app/ the workbench front end from src/xpsflow/web/static, re-pointed at a Web Worker
app/worker.js loads Pyodide, installs the wheels and runs xpsflow.web.browser.BrowserApp
app/browser-backend.js patches fetch and window.open so every /api/... call goes to the worker
app/pyodide/ the pinned Pyodide runtime and the wheels it needs, resolved from its lock file
app/wheels/ the pure-Python wheels not in Pyodide (lmfit, asteval, dill) and the xpsflow wheel
_headers, _redirects, robots.txt, sitemap.xml, llms.txt, .well-known/security.txt hosting and discovery files

BrowserApp mirrors the FastAPI endpoints over one Session, so the same JavaScript runs against either backend. The first visit downloads about 25 MB (cached afterwards); fits run at roughly a third of native speed. PDF export and the tool-calling assistant need the local install, because WeasyPrint has no WebAssembly build and the assistant needs a model endpoint.

Building and previewing

python scripts/build_site.py --out dist --no-runtime   # pages only, fast
python scripts/build_site.py --out dist                 # with the runtime
python -m http.server --directory dist 8000

Deployment

.github/workflows/deploy.yml builds the site on every push to main that touches the package, the docs or the site, and publishes it to Cloudflare Pages with wrangler pages deploy. It needs two repository secrets: CLOUDFLARE_API_TOKEN (a token with Cloudflare Pages: Edit) and CLOUDFLARE_ACCOUNT_ID. A manual deploy from a machine with wrangler logged in is npx wrangler pages deploy dist --project-name=xpsflow.

Headers

The _headers file sets a Content Security Policy with 'wasm-unsafe-eval' (required for WebAssembly), restricts workers and connections to the site's own origin, denies framing, and marks the runtime, wheels, fonts and vendor files as immutable so browsers cache them for a year.