Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0205c41
Add REPL worker subprocess for Flask backend
milanofthe Feb 10, 2026
fd735ff
Add Flask server and fix worker threading for Windows subprocess pipes
milanofthe Feb 10, 2026
a32539c
Add FlaskBackend class implementing Backend interface via HTTP/SSE
milanofthe Feb 10, 2026
36d8496
Integrate FlaskBackend into registry with URL param switching
milanofthe Feb 10, 2026
4b4df3d
Add dynamic package installation from PYTHON_PACKAGES config
milanofthe Feb 10, 2026
b183c18
Update README with Flask backend documentation
milanofthe Feb 10, 2026
7491986
Add backend protocol and toolbox integration spec docs
milanofthe Feb 10, 2026
29a1ee0
Add spec doc references to README
milanofthe Feb 10, 2026
281510e
Remove dead code from worker.py and FlaskBackend
milanofthe Feb 10, 2026
ceba711
Add pip-installable package with CLI and auto-detection
milanofthe Feb 10, 2026
8c042c0
Fix streaming, logging, and review issues for Flask backend
milanofthe Feb 10, 2026
0cb9ecb
Add exec/eval timeout watchdog (30s worker, 35s server)
milanofthe Feb 10, 2026
4938d11
Use waitress as production WSGI server, keep Flask dev server for --d…
milanofthe Feb 10, 2026
4ee85e2
Replace sleep-based browser open with health check polling
milanofthe Feb 10, 2026
4d18231
Add worker crash recovery with auto-reinit on next request
milanofthe Feb 10, 2026
4bdaa06
Add pytest test suite and CI workflow (22 tests)
milanofthe Feb 10, 2026
67ec646
Use pyproject.toml as single source of truth for version
milanofthe Feb 10, 2026
8c4aa8f
Add network security warning when binding to 0.0.0.0
milanofthe Feb 10, 2026
71e37fd
Share session across tabs via localStorage + BroadcastChannel
milanofthe Feb 10, 2026
24bf65f
Remove shell=True from build script, use explicit npx binary resolution
milanofthe Feb 10, 2026
c2c8162
Fix stream stop→exec crash: wait for reader thread before stdout reads
milanofthe Feb 10, 2026
66061f0
Fix stop→restart hang: actively stop streaming in wait_for_stream_reader
milanofthe Feb 10, 2026
0622777
Keep final stream data in queue so frontend can poll it before restart
milanofthe Feb 10, 2026
412d7de
Add virtualenv isolation for Flask backend workers
milanofthe Feb 11, 2026
5a315c0
Fix CI: ensure venv exists before app tests spawn workers
milanofthe Feb 11, 2026
f4cfa69
Fix CI: handle missing numpy in fresh venv without simulation packages
milanofthe Feb 11, 2026
796a246
Consolidate duplicated exec/eval routes and init logic
milanofthe Feb 11, 2026
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
77 changes: 77 additions & 0 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Publish to PyPI

on:
release:
types: [published]
workflow_dispatch:
inputs:
publish_to:
description: 'Publish target'
required: true
default: 'testpypi'
type: choice
options:
- testpypi
- pypi

permissions:
contents: read

jobs:
build-and-publish:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install Python build tools
run: pip install build twine

- name: Install Python dependencies
run: |
pip install -r scripts/config/requirements-pyodide.txt
pip install -r scripts/config/requirements-build.txt

- name: Install Node dependencies
run: npm ci

- name: Extract blocks from PathSim
run: npm run extract

- name: Build package (frontend + wheel)
run: python scripts/build_package.py

- name: Check distribution
run: twine check dist/*

- name: Publish to Test PyPI
if: github.event_name == 'workflow_dispatch' && inputs.publish_to == 'testpypi'
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_TOKEN }}
run: twine upload --repository testpypi dist/*

- name: Publish to PyPI
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.publish_to == 'pypi')
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: twine upload dist/*

- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Tests

on:
push:
branches: [main, feature/flask-backend]
pull_request:
branches: [main, feature/flask-backend]

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.11", "3.12"]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[test]"

- name: Run tests
run: pytest tests/ -v
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ tmpclaude-*


__pycache__/
*.egg-info/
dist/
pathview_server/static/

# Generated screenshots
static/examples/screenshots/
157 changes: 133 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

# PathView - System Modeling in the Browser

A web-based visual node editor for building and simulating dynamic systems with [PathSim](https://github.com/pathsim/pathsim) as the backend. Runs entirely in the browser via Pyodide - no server required. The UI is hosted at [view.pathsim.org](https://view.pathsim.org), free to use for everyone.
A web-based visual node editor for building and simulating dynamic systems with [PathSim](https://github.com/pathsim/pathsim) as the backend. Runs entirely in the browser via Pyodide by default — no server required. Optionally, a Flask backend enables server-side Python execution with any packages (including those with native dependencies that Pyodide can't run). The UI is hosted at [view.pathsim.org](https://view.pathsim.org), free to use for everyone.

## Tech Stack

Expand All @@ -17,18 +17,37 @@ A web-based visual node editor for building and simulating dynamic systems with
- [Plotly.js](https://plotly.com/javascript/) for interactive plots
- [CodeMirror 6](https://codemirror.net/) for code editing

## Getting Started
## Installation

### pip install (recommended for users)

```bash
pip install pathview
pathview serve
```

This starts the PathView server with a local Python backend and opens your browser. No Node.js required.

**Options:**
- `--port PORT` — server port (default: 5000)
- `--host HOST` — bind address (default: 127.0.0.1)
- `--no-browser` — don't auto-open the browser
- `--debug` — debug mode with auto-reload

### Development setup

```bash
npm install
npm run dev
```

For production:
To use the Flask backend during development:

```bash
npm run build
npm run preview
pip install flask flask-cors
npm run server # Start Flask backend on port 5000
npm run dev # Start Vite dev server (separate terminal)
# Open http://localhost:5173/?backend=flask
```

## Project Structure
Expand Down Expand Up @@ -61,7 +80,8 @@ src/
│ ├── routing/ # Orthogonal wire routing (A* pathfinding)
│ ├── pyodide/ # Python runtime (backend, bridge)
│ │ └── backend/ # Modular backend system (registry, state, types)
│ │ └── pyodide/ # Pyodide Web Worker implementation
│ │ ├── pyodide/ # Pyodide Web Worker implementation
│ │ └── flask/ # Flask HTTP/SSE backend implementation
│ ├── schema/ # File I/O (save/load, component export)
│ ├── simulation/ # Simulation metadata
│ │ └── generated/ # Auto-generated defaults
Expand All @@ -72,6 +92,12 @@ src/
├── routes/ # SvelteKit pages
└── app.css # Global styles with CSS variables

pathview_server/ # Python package (pip install pathview)
├── app.py # Flask server (subprocess management, HTTP routes)
├── worker.py # REPL worker subprocess (Python execution)
├── cli.py # CLI entry point (pathview serve)
└── static/ # Bundled frontend (generated at build time)

scripts/
├── config/ # Configuration files for extraction
│ ├── schemas/ # JSON schemas for validation
Expand Down Expand Up @@ -100,8 +126,8 @@ scripts/
v
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Plot/Console │<────│ bridge.ts │<────│ REPL Worker
│ (results) │ │ (queue + rAF) │ │ (Pyodide)
│ Plot/Console │<────│ bridge.ts │<────│ Backend
│ (results) │ │ (queue + rAF) │ │ (Pyodide/Flask)
└─────────────────┘ └─────────────────┘ └─────────────────┘
```

Expand Down Expand Up @@ -153,6 +179,7 @@ Key files: `src/lib/routing/` (pathfinder, grid builder, route calculator)
| **Backend** | Modular Python execution interface | `pyodide/backend/` |
| **Backend Registry** | Factory for swappable backends | `pyodide/backend/registry.ts` |
| **PyodideBackend** | Web Worker Pyodide implementation | `pyodide/backend/pyodide/` |
| **FlaskBackend** | HTTP/SSE Flask server implementation | `pyodide/backend/flask/` |
| **Simulation Bridge** | High-level simulation API | `pyodide/bridge.ts` |
| **Schema** | File/component save/load operations | `schema/fileOps.ts`, `schema/componentOps.ts` |
| **Export Utils** | SVG/CSV/Python file downloads | `utils/download.ts`, `export/svg/`, `utils/csvExport.ts` |
Expand Down Expand Up @@ -309,6 +336,8 @@ npm run build

No code changes needed - the extraction script automatically discovers toolbox directories.

For the full toolbox integration reference (Python package contract, config schemas, extraction pipeline, generated output), see [**docs/toolbox-spec.md**](docs/toolbox-spec.md).

---

## Python Backend System
Expand All @@ -326,29 +355,36 @@ The Python runtime uses a modular backend architecture, allowing different execu
┌──────────────┼──────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Pyodide │ │ Local │ │ Remote │
│ Pyodide │ │ Flask │ │ Remote │
│ Backend │ │ Backend │ │ Backend │
│ (Worker) │ │ (Flask) │ │ (Server) │
│ (default) │ │ (HTTP) │ │ (future) │
└───────────┘ └───────────┘ └───────────┘
(future) (future)
┌───────────┐
│ Web Worker│
│ (Pyodide) │
└───────────┘
┌───────────┐ ┌───────────┐
│ Web Worker│ │ Flask │──> Python subprocess
│ (Pyodide) │ │ Server │ (one per session)
└───────────┘ └───────────┘
```

### Backend Registry

```typescript
import { getBackend, switchBackend } from '$lib/pyodide/backend';
import { getBackend, switchBackend, setFlaskHost } from '$lib/pyodide/backend';

// Get current backend (defaults to Pyodide)
const backend = getBackend();

// Switch to a different backend type (future)
// switchBackend('local'); // Use local Python via Flask
// switchBackend('remote'); // Use remote server
// Switch to Flask backend
setFlaskHost('http://localhost:5000');
switchBackend('flask');
```

Backend selection can also be controlled via URL parameters:

```
http://localhost:5173/?backend=flask # Flask on default port
http://localhost:5173/?backend=flask&host=http://myserver:5000 # Custom host
```

### REPL Protocol
Expand Down Expand Up @@ -426,6 +462,60 @@ await stopSimulation();
execDuringStreaming('source.amplitude = 2.0');
```

### Flask Backend

The Flask backend enables server-side Python execution for packages that Pyodide can't run (e.g., FESTIM or other packages with native C/Fortran dependencies). It mirrors the Web Worker architecture: one subprocess per session with the same REPL protocol.

```
Browser Tab Flask Server Worker Subprocess
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ FlaskBackend │ HTTP/SSE │ app.py │ stdin │ worker.py │
│ exec() │──POST────────→│ route → session │──JSON───→│ exec(code, ns) │
│ eval() │──POST────────→│ subprocess mgr │──JSON───→│ eval(expr, ns) │
│ stream() │──POST (SSE)──→│ pipe SSE relay │←─JSON────│ streaming loop │
│ inject() │──POST────────→│ → code queue │──JSON───→│ queue drain │
│ stop() │──POST────────→│ → stop flag │──JSON───→│ stop check │
└──────────────┘ └──────────────────┘ └──────────────────┘
```

**Standalone (pip package):**

```bash
pip install pathview
pathview serve
```

**Development (separate servers):**

```bash
pip install flask flask-cors
npm run server # Starts Flask API on port 5000
npm run dev # Starts Vite dev server (separate terminal)
# Open http://localhost:5173/?backend=flask
```

**Key properties:**
- **Process isolation** — each session gets its own Python subprocess
- **Namespace persistence** — variables persist across exec/eval calls within a session
- **Dynamic packages** — packages from `PYTHON_PACKAGES` (the same config used by Pyodide) are pip-installed on first init
- **Session TTL** — stale sessions cleaned up after 1 hour of inactivity
- **Streaming** — simulations stream via SSE, with the same code injection support as Pyodide

For the full protocol reference (message types, HTTP routes, SSE format, streaming semantics, how to implement a new backend), see [**docs/backend-protocol-spec.md**](docs/backend-protocol-spec.md).

**API routes:**

| Route | Method | Action |
|-------|--------|--------|
| `/api/health` | GET | Health check |
| `/api/init` | POST | Initialize worker with packages |
| `/api/exec` | POST | Execute Python code |
| `/api/eval` | POST | Evaluate expression, return JSON |
| `/api/stream` | POST | Start streaming simulation (SSE) |
| `/api/stream/exec` | POST | Inject code during streaming |
| `/api/stream/stop` | POST | Stop streaming |
| `/api/session` | DELETE | Kill session subprocess |

---

## State Management
Expand Down Expand Up @@ -530,6 +620,14 @@ PathView uses JSON-based file formats for saving and sharing:

The `.pvm` format is fully documented in [**docs/pvm-spec.md**](docs/pvm-spec.md). Use this spec if you are building tools that read or write PathView models (e.g., code generators, importers). A reference Python code generator is available at `scripts/pvm2py.py`.

### Specification Documents

| Document | Audience |
|----------|----------|
| [**docs/pvm-spec.md**](docs/pvm-spec.md) | Building tools that read/write `.pvm` model files |
| [**docs/backend-protocol-spec.md**](docs/backend-protocol-spec.md) | Implementing a new execution backend (remote server, cloud worker, etc.) |
| [**docs/toolbox-spec.md**](docs/toolbox-spec.md) | Creating a third-party toolbox package for PathView |

### Export Options

- **File > Save** - Save complete model as `.pvm`
Expand Down Expand Up @@ -583,8 +681,10 @@ https://view.pathsim.org/?modelgh=pathsim/pathview/static/examples/feedback-syst

| Script | Purpose |
|--------|---------|
| `npm run dev` | Start development server |
| `npm run build` | Production build |
| `npm run dev` | Start Vite development server |
| `npm run server` | Start Flask backend server (port 5000) |
| `npm run build` | Production build (GitHub Pages) |
| `npm run build:package` | Build pip package (frontend + wheel) |
| `npm run preview` | Preview production build |
| `npm run check` | TypeScript/Svelte type checking |
| `npm run lint` | Run ESLint |
Expand Down Expand Up @@ -665,7 +765,7 @@ Port labels show the name of each input/output port alongside the node. Toggle g

2. **Subsystems are nested graphs** - The Interface node inside a subsystem mirrors its parent's ports (inverted direction).

3. **No server required** - Everything runs client-side via Pyodide WebAssembly.
3. **No server required by default** - Everything runs client-side via Pyodide. The optional Flask backend enables server-side execution for packages with native dependencies.

4. **Registry pattern** - Nodes and events are registered centrally for extensibility.

Expand Down Expand Up @@ -701,14 +801,23 @@ Port labels show the name of each input/output port alongside the node. Toggle g

## Deployment

PathView uses a dual deployment strategy with automatic versioning:
PathView has two deployment targets:

### GitHub Pages (web)

| Trigger | What happens | Deployed to |
|---------|--------------|-------------|
| Push to `main` | Build with base path `/dev` | [view.pathsim.org/dev/](https://view.pathsim.org/dev/) |
| Release published | Bump `package.json`, build, deploy | [view.pathsim.org/](https://view.pathsim.org/) |
| Manual dispatch | Choose `dev` or `release` | Respective path |

### PyPI (pip package)

| Trigger | What happens | Published to |
|---------|--------------|--------------|
| Release published | Build frontend + wheel, publish | [pypi.org/project/pathview](https://pypi.org/project/pathview/) |
| Manual dispatch | Choose `testpypi` or `pypi` | Respective index |

### How it works

1. Both versions deploy to the `deployment` branch using GitHub Actions
Expand Down
Loading