{"id":41857018,"url":"https://github.com/pathsim/pathview","last_synced_at":"2026-05-22T07:15:07.492Z","repository":{"id":302128441,"uuid":"1006697880","full_name":"pathsim/pathview","owner":"pathsim","description":"A Python first graphical user interface for PathSim.","archived":false,"fork":false,"pushed_at":"2026-02-14T21:18:25.000Z","size":10117,"stargazers_count":49,"open_issues_count":23,"forks_count":7,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-15T05:39:47.948Z","etag":null,"topics":["blockdiagram","modeling","simulation","ui"],"latest_commit_sha":null,"homepage":"http://view.pathsim.org/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pathsim.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-22T20:20:26.000Z","updated_at":"2026-02-14T21:15:27.000Z","dependencies_parsed_at":"2026-01-25T11:01:05.299Z","dependency_job_id":null,"html_url":"https://github.com/pathsim/pathview","commit_stats":null,"previous_names":["tasnimxvi/fuel-cycle-sim","festim-dev/pathview","pathsim/pathview"],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/pathsim/pathview","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathsim%2Fpathview","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathsim%2Fpathview/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathsim%2Fpathview/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathsim%2Fpathview/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pathsim","download_url":"https://codeload.github.com/pathsim/pathview/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathsim%2Fpathview/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29549224,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-17T14:33:00.708Z","status":"ssl_error","status_checked_at":"2026-02-17T14:32:58.657Z","response_time":100,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blockdiagram","modeling","simulation","ui"],"created_at":"2026-01-25T11:00:33.062Z","updated_at":"2026-05-22T07:15:07.456Z","avatar_url":"https://github.com/pathsim.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/pathsim/pathview/main/static/pathview_logo.png\" width=\"300\" alt=\"PathView Logo\" /\u003e\n\u003c/p\u003e\n\n------------\n\n\n# PathView - System Modeling in the Browser\n\nA 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.\n\n## Tech Stack\n\n- [SvelteKit 5](https://kit.svelte.dev/) with Svelte 5 runes\n- [SvelteFlow](https://svelteflow.dev/) for the node editor\n- [Pyodide](https://pyodide.org/) for in-browser Python/NumPy/SciPy\n- [Plotly.js](https://plotly.com/javascript/) for interactive plots\n- [CodeMirror 6](https://codemirror.net/) for code editing\n\n## Installation\n\n### pip install (recommended for users)\n\n```bash\npip install pathview\npathview serve\n```\n\nThis starts the PathView server with a local Python backend and opens your browser. No Node.js required.\n\n**Options:**\n- `--port PORT` — server port (default: 5000)\n- `--host HOST` — bind address (default: 127.0.0.1)\n- `--no-browser` — don't auto-open the browser\n- `--debug` — debug mode with auto-reload\n\n### Convert `.pvm` to Python\n\nConvert PathView model files to standalone PathSim scripts:\n\n```bash\npathview convert model.pvm                # outputs model.py\npathview convert model.pvm -o output.py   # custom output path\npathview convert model.pvm --stdout       # print to stdout\n```\n\nOr use the Python API directly:\n\n```python\nfrom pathview import convert\n\npython_code = convert(\"model.pvm\")\n```\n\n### Development setup\n\n```bash\nnpm install\nnpm run dev\n```\n\nTo use the Flask backend during development:\n\n```bash\npip install flask flask-cors\nnpm run server   # Start Flask backend on port 5000\nnpm run dev      # Start Vite dev server (separate terminal)\n# Open http://localhost:5173/?backend=flask\n```\n\n## Project Structure\n\n```\nsrc/\n├── lib/\n│   ├── actions/           # Svelte actions (paramInput)\n│   ├── animation/         # Graph loading animations\n│   ├── components/        # UI components\n│   │   ├── canvas/        # Flow editor utilities (connection, transforms)\n│   │   ├── dialogs/       # Modal dialogs\n│   │   │   └── shared/    # Shared dialog components (ColorPicker, etc.)\n│   │   ├── edges/         # SvelteFlow edge components (ArrowEdge)\n│   │   ├── icons/         # Icon component (Icon.svelte)\n│   │   ├── nodes/         # Node components (BaseNode, EventNode, AnnotationNode, PlotPreview)\n│   │   └── panels/        # Side panels (Simulation, NodeLibrary, CodeEditor, Plot, Console, Events)\n│   ├── constants/         # Centralized constants (nodeTypes, layout, handles)\n│   ├── events/            # Event system\n│   │   └── generated/     # Auto-generated from PathSim\n│   ├── export/            # Export utilities\n│   │   └── svg/           # SVG graph export (renderer, types)\n│   ├── nodes/             # Node type system\n│   │   ├── generated/     # Auto-generated from PathSim\n│   │   └── shapes/        # Node shape definitions\n│   ├── plotting/          # Plot system\n│   │   ├── core/          # Constants, types, utilities\n│   │   ├── processing/    # Data processing, render queue\n│   │   └── renderers/     # Plotly and SVG renderers\n│   ├── routing/           # Orthogonal wire routing (A* pathfinding)\n│   ├── pyodide/           # Python runtime (backend, bridge)\n│   │   └── backend/       # Modular backend system (registry, state, types)\n│   │       ├── pyodide/   # Pyodide Web Worker implementation\n│   │       └── flask/     # Flask HTTP/SSE backend implementation\n│   ├── schema/            # File I/O (save/load, component export)\n│   ├── simulation/        # Simulation metadata\n│   │   └── generated/     # Auto-generated defaults\n│   ├── stores/            # Svelte stores (state management)\n│   │   └── graph/         # Graph state with subsystem navigation\n│   ├── toolbox/           # Runtime toolbox install/registry (catalog, installer, dependencies)\n│   ├── tours/             # In-app onboarding tours (builder, anchors, scripts)\n│   ├── types/             # TypeScript type definitions\n│   └── utils/             # Utilities (colors, download, csvExport, codemirror)\n├── routes/                # SvelteKit pages\n└── app.css                # Global styles with CSS variables\n\npathview/                  # Python package (pip install pathview)\n├── app.py                 # Flask server (subprocess management, HTTP routes)\n├── worker.py              # REPL worker subprocess (Python execution)\n├── cli.py                 # CLI entry point (pathview serve)\n├── config.py              # Server configuration (host, port, packages)\n├── converter.py           # PVM to Python converter (public API)\n├── data/                  # Bundled data files\n│   └── registry.json      # Block/event registry for converter\n└── static/                # Bundled frontend (generated at build time)\n\n\nscripts/\n├── config/                # Configuration files for extraction\n│   ├── schemas/           # JSON schemas for validation\n│   ├── pathsim/           # Core PathSim blocks, events, simulation config\n│   ├── pathsim-chem/      # Chemical toolbox blocks\n│   ├── pyodide.json       # Pyodide version and preload packages\n│   ├── requirements-pyodide.txt   # Runtime Python packages\n│   └── requirements-build.txt     # Build-time Python packages\n├── generated/             # Generated files (from extract.py)\n│   └── registry.json      # Block/event registry with import paths\n├── extract.py             # Unified extraction script\n├── pathview_introspect.py # Shared block/event introspection helpers\n├── pvm2py.py              # Standalone .pvm to Python converter\n├── build_package.py       # Build pip wheel + bundled frontend\n└── capture-screenshots.js # Snapshot block icons for build (Playwright)\n```\n\n---\n\n## Architecture Overview\n\n### Data Flow\n\n```\n┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐\n│  Graph Store    │────\u003e│ pathsimRunner   │────\u003e│ Python Code     │\n│  (nodes, edges) │     │ (code gen)      │     │ (string)        │\n└─────────────────┘     └─────────────────┘     └─────────────────┘\n                                                        │\n                                                        v\n┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐\n│  Plot/Console   │\u003c────│ bridge.ts       │\u003c────│ Backend         │\n│  (results)      │     │ (queue + rAF)   │     │ (Pyodide/Flask) │\n└─────────────────┘     └─────────────────┘     └─────────────────┘\n```\n\n### Streaming Architecture\n\nSimulations run in streaming mode for real-time visualization. The worker runs autonomously and pushes results without waiting for the UI:\n\n```\nWorker (10 Hz)              Main Thread                 UI (10 Hz)\n┌──────────────┐           ┌──────────────┐           ┌──────────────┐\n│ Python loop  │ ────────\u003e │ Result Queue │ ────────\u003e │ Plotly       │\n│ (autonomous) │  stream-  │ (accumulate) │    rAF    │ extendTraces │\n│              │   data    │              │  batched  │              │\n└──────────────┘           └──────────────┘           └──────────────┘\n```\n\n- **Decoupled rates**: Python generates data at 10 Hz, UI renders at 10 Hz max\n- **Queue-based**: Results accumulate in queue, merged on each UI frame\n- **Non-blocking**: Simulation never waits for plot rendering\n- **extendTraces**: Scope plots append data incrementally instead of full re-render\n\n### Wire Routing\n\nPathView uses Simulink-style orthogonal wire routing with A* pathfinding:\n\n- **Automatic routing**: Wires route around nodes with 90° bends only\n- **User waypoints**: Press `\\` on selected edge to add manual waypoints\n- **Draggable waypoints**: Drag waypoint markers to reposition, double-click to delete\n- **Segment dragging**: Drag segment midpoints to create new waypoints\n- **Incremental updates**: Spatial indexing (O(1) node updates) for smooth dragging\n- **Hybrid routing**: Routes through user waypoints: Source → A* → W1 → A* → Target\n\nKey files: `src/lib/routing/` (pathfinder, grid builder, route calculator)\n\n### Key Abstractions\n\n| Layer | Purpose | Key Files |\n|-------|---------|-----------|\n| **Main App** | Orchestrates panels, shortcuts, file ops | `routes/+page.svelte` |\n| **Flow Canvas** | SvelteFlow wrapper, node/edge sync | `components/FlowCanvas.svelte` |\n| **Flow Updater** | View control, animation triggers | `components/FlowUpdater.svelte` |\n| **Context Menus** | Right-click menus for nodes/canvas/plots | `components/ContextMenu.svelte`, `contextMenuBuilders.ts` |\n| **Graph Store** | Node/edge state, subsystem navigation | `stores/graph/` |\n| **View Actions** | Fit view, zoom, pan controls | `stores/viewActions.ts`, `stores/viewTriggers.ts` |\n| **Clipboard** | Copy/paste/duplicate operations | `stores/clipboard.ts` |\n| **Plot Settings** | Per-trace and per-block plot options | `stores/plotSettings.ts` |\n| **Node Registry** | Block type definitions, parameters | `nodes/registry.ts` |\n| **Code Generation** | Graph → Python code | `pyodide/pathsimRunner.ts` |\n| **Backend** | Modular Python execution interface | `pyodide/backend/` |\n| **Backend Registry** | Factory for swappable backends | `pyodide/backend/registry.ts` |\n| **PyodideBackend** | Web Worker Pyodide implementation | `pyodide/backend/pyodide/` |\n| **FlaskBackend** | HTTP/SSE Flask server implementation | `pyodide/backend/flask/` |\n| **Simulation Bridge** | High-level simulation API | `pyodide/bridge.ts` |\n| **Schema** | File/component save/load operations | `schema/fileOps.ts`, `schema/componentOps.ts` |\n| **Export Utils** | SVG/CSV/Python file downloads | `utils/download.ts`, `export/svg/`, `utils/csvExport.ts` |\n\n### Centralized Constants\n\nUse these imports instead of magic strings:\n\n```typescript\nimport { NODE_TYPES } from '$lib/constants/nodeTypes';\n// NODE_TYPES.SUBSYSTEM, NODE_TYPES.INTERFACE\n\nimport { PORT_COLORS, DIALOG_COLOR_PALETTE } from '$lib/utils/colors';\n// PORT_COLORS.default, etc.\n```\n\n---\n\n## Adding New Blocks\n\nBlocks are extracted automatically from PathSim using the `Block.info()` classmethod. The extraction is config-driven for easy maintenance.\n\n### 1. Ensure the block exists in PathSim\n\nThe block must be importable from `pathsim.blocks` (or toolbox module):\n\n```python\nfrom pathsim.blocks import YourNewBlock\n```\n\n### 2. Add to block configuration\n\nEdit `scripts/config/pathsim/blocks.json` and add the block class name to the appropriate category:\n\n```json\n{\n  \"categories\": {\n    \"Algebraic\": [\n      \"Adder\",\n      \"Multiplier\",\n      \"YourNewBlock\"\n    ]\n  }\n}\n```\n\nPort configurations are automatically extracted from `Block.info()`:\n- `None` → Variable/unlimited ports (UI allows add/remove)\n- `{}` → No ports of this type\n- `{\"name\": index}` → Fixed labeled ports (locked count)\n\n### 3. Run extraction\n\n```bash\nnpm run extract\n```\n\nThis generates TypeScript files in `src/lib/*/generated/` with:\n- Block metadata (parameters, descriptions, docstrings)\n- Port configurations from `Block.info()`\n- Pyodide runtime config\n\n### 4. Verify\n\nStart the dev server and check that your block appears in the Block Library panel.\n\n### Port Synchronization\n\nSome blocks process inputs as parallel paths where each input has a corresponding output (e.g., Integrator, Amplifier, Sin). For these blocks, the UI only shows input port controls and outputs auto-sync.\n\nConfigure in `src/lib/nodes/uiConfig.ts`:\n\n```typescript\nexport const syncPortBlocks = new Set([\n  // Dynamic blocks\n  'Integrator', 'Differentiator', 'Delay',\n  // Algebraic blocks (element-wise)\n  'Amplifier', 'Sin', 'Cos', 'Tan', 'Tanh',\n  'Abs', 'Sqrt', 'Exp', 'Log', 'Log10',\n  'Mod', 'Clip', 'Pow', 'Polynomial',\n  'Rescale', 'Alias',\n  // Logic blocks\n  'LogicNot',\n  // Discrete blocks\n  'SampleHold', 'FirstOrderHold',\n  'DiscreteIntegrator', 'DiscreteDerivative'\n]);\n```\n\n### Port Labels from Parameters\n\nSome blocks derive port names from a parameter (e.g., Scope and Spectrum use `labels` to name input traces). When the parameter changes, port names update automatically.\n\nConfigure in `src/lib/nodes/uiConfig.ts`:\n\n```typescript\nexport const portLabelParams: Record\u003cstring, PortLabelConfig | PortLabelConfig[]\u003e = {\n  Scope: { param: 'labels', direction: 'input' },\n  Spectrum: { param: 'labels', direction: 'input' },\n  // Multiple directions supported:\n  // SomeBlock: [\n  //   { param: 'input_labels', direction: 'input' },\n  //   { param: 'output_labels', direction: 'output' }\n  // ]\n};\n```\n\n---\n\n## Toolboxes\n\nPathView supports two complementary ways to extend the block library: **runtime toolboxes** that the user installs from the UI at any time, and **build-time toolboxes** that are baked into the deployed bundle.\n\n### Runtime Toolboxes (default)\n\nUsers install toolboxes from the **Toolbox Manager** dialog without rebuilding the app. Three install sources are supported:\n\n- **PyPI** — install a published package via `micropip` (Pyodide) or `pip` (Flask backend)\n- **URL** — load a wheel or sdist hosted anywhere\n- **Inline** — paste / upload a `.py` module to register ad-hoc blocks for one session\n\nOnce installed, PathView introspects the module's `Block` (and optional `Event`) subclasses, registers them as new node types, and persists the selection to `localStorage` so it replays on reload. The user can disable individual blocks, override their category, name, shape, or `syncPorts` flag, and uninstall the toolbox at any time. Saving a `.pvm` file embeds the list of toolbox dependencies; opening one elsewhere prompts to install missing pieces.\n\nImplementation lives in `src/lib/toolbox/` (catalog, installer, register, persistence). The curated catalog of one-click installable toolboxes is in `catalog.ts`:\n\n```typescript\nexport const TOOLBOX_CATALOG: CatalogEntry[] = [\n  {\n    id: 'pathsim-chem',\n    displayName: 'pathsim-chem',\n    source: { type: 'pypi', pkg: 'pathsim-chem' },\n    importPath: 'pathsim_chem',\n    defaultCategory: 'Chemical',\n    preloaded: true   // seeded into store on first launch\n  }\n];\n```\n\nTo add an entry to the catalog: append a `CatalogEntry` to `TOOLBOX_CATALOG` and ship — no extraction step, no rebuild required for the user. Toolboxes outside the catalog still work via the manager's PyPI/URL/file inputs.\n\nFor the toolbox-package contract (which Python conventions a toolbox must follow to be introspectable), see [**docs/toolbox-spec.md**](docs/toolbox-spec.md).\n\n### Build-time Toolboxes (bundled into the deploy)\n\nFor toolboxes that should be available without an install step (e.g. the core `pathsim` toolbox itself), block metadata is extracted at build time:\n\n1. Add the package to `scripts/config/requirements-pyodide.txt` so Pyodide can install it.\n2. Create `scripts/config/\u003ctoolbox-id\u003e/blocks.json` (and optionally `events.json`) listing the categories and block class names.\n3. Run `npm run extract` to regenerate the TypeScript registry under `src/lib/*/generated/`.\n\nThe build-time path is appropriate when a toolbox is part of the core deployment; for everything else prefer the runtime path, which avoids a redeploy.\n\n---\n\n## Python Backend System\n\nThe Python runtime uses a modular backend architecture, allowing different execution environments (Pyodide, local Python, remote server) to be swapped without changing application code.\n\n### Architecture\n\n```\n┌─────────────────────────────────────────────────────────────────────┐\n│                          Backend Interface                          │\n│  init(), exec(), evaluate(), startStreaming(), stopStreaming()...   │\n└─────────────────────────────────────────────────────────────────────┘\n                                    │\n                     ┌──────────────┼──────────────┐\n                     ▼              ▼              ▼\n              ┌───────────┐  ┌───────────┐  ┌───────────┐\n              │ Pyodide   │  │ Flask     │  │ Remote    │\n              │ Backend   │  │ Backend   │  │ Backend   │\n              │ (default) │  │ (HTTP)    │  │ (future)  │\n              └───────────┘  └───────────┘  └───────────┘\n                   │              │\n                   ▼              ▼\n            ┌───────────┐  ┌───────────┐\n            │ Web Worker│  │ Flask     │──\u003e Python subprocess\n            │ (Pyodide) │  │ Server   │    (one per session)\n            └───────────┘  └───────────┘\n```\n\n### Backend Registry\n\n```typescript\nimport { getBackend, switchBackend, setFlaskHost } from '$lib/pyodide/backend';\n\n// Get current backend (defaults to Pyodide)\nconst backend = getBackend();\n\n// Switch to Flask backend\nsetFlaskHost('http://localhost:5000');\nswitchBackend('flask');\n```\n\nBackend selection can also be controlled via URL parameters:\n\n```\nhttp://localhost:5173/?backend=flask                          # Flask on default port\nhttp://localhost:5173/?backend=flask\u0026host=http://myserver:5000  # Custom host\n```\n\n### REPL Protocol\n\n**Requests** (Main → Worker):\n\n```typescript\ntype REPLRequest =\n  | { type: 'init' }\n  | { type: 'exec'; id: string; code: string }      // Execute code (no return)\n  | { type: 'eval'; id: string; expr: string }      // Evaluate expression (returns JSON)\n  | { type: 'stream-start'; id: string; expr: string }  // Start streaming loop\n  | { type: 'stream-stop' }                         // Stop streaming loop\n  | { type: 'stream-exec'; code: string }           // Execute code during streaming\n```\n\n**Responses** (Worker → Main):\n\n```typescript\ntype REPLResponse =\n  | { type: 'ready' }\n  | { type: 'ok'; id: string }                   // exec succeeded\n  | { type: 'value'; id: string; value: string } // eval result (JSON)\n  | { type: 'error'; id: string; error: string; traceback?: string }\n  | { type: 'stdout'; value: string }\n  | { type: 'stderr'; value: string }\n  | { type: 'progress'; value: string }\n  | { type: 'stream-data'; id: string; value: string }  // Streaming result\n  | { type: 'stream-done'; id: string }                 // Streaming completed\n```\n\n### Usage Example\n\n```typescript\nimport { init, exec, evaluate } from '$lib/pyodide/backend';\n\n// Initialize backend (Pyodide by default)\nawait init();\n\n// Execute Python code\nawait exec(`\nimport numpy as np\nx = np.linspace(0, 10, 100)\n`);\n\n// Evaluate and get result\nconst result = await evaluate\u003cnumber[]\u003e('x.tolist()');\n```\n\n### High-Level API (bridge.ts)\n\nFor simulation, use the higher-level API in `bridge.ts`:\n\n```typescript\nimport {\n  runStreamingSimulation,\n  continueStreamingSimulation,\n  stopSimulation,\n  execDuringStreaming\n} from '$lib/pyodide/bridge';\n\n// Run streaming simulation\nconst result = await runStreamingSimulation(pythonCode, duration, (partialResult) =\u003e {\n  console.log('Progress:', partialResult.scopeData);\n});\n// result.scopeData, result.spectrumData, result.nodeNames\n\n// Continue simulation from where it stopped\nconst moreResult = await continueStreamingSimulation('5.0');\n\n// Stop simulation gracefully\nawait stopSimulation();\n\n// Execute code during active simulation (queued between steps)\nexecDuringStreaming('source.amplitude = 2.0');\n```\n\n### Flask Backend\n\nThe 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.\n\n```\nBrowser Tab                     Flask Server                  Worker Subprocess\n┌──────────────┐               ┌──────────────────┐          ┌──────────────────┐\n│ FlaskBackend │  HTTP/SSE     │ app.py           │  stdin   │ worker.py        │\n│  exec()      │──POST────────→│  route → session │──JSON───→│  exec(code, ns)  │\n│  eval()      │──POST────────→│  subprocess mgr  │──JSON───→│  eval(expr, ns)  │\n│  stream()    │──POST (SSE)──→│  pipe SSE relay  │←─JSON────│  streaming loop  │\n│  inject()    │──POST────────→│  → code queue    │──JSON───→│  queue drain     │\n│  stop()      │──POST────────→│  → stop flag     │──JSON───→│  stop check      │\n└──────────────┘               └──────────────────┘          └──────────────────┘\n```\n\n**Standalone (pip package):**\n\n```bash\npip install pathview\npathview serve\n```\n\n**Development (separate servers):**\n\n```bash\npip install flask flask-cors\nnpm run server   # Starts Flask API on port 5000\nnpm run dev      # Starts Vite dev server (separate terminal)\n# Open http://localhost:5173/?backend=flask\n```\n\n**Key properties:**\n- **Process isolation** — each session gets its own Python subprocess\n- **Host environment** — workers run with the same Python used to install pathview, so all packages in the user's environment are available in the code editor\n- **Namespace persistence** — variables persist across exec/eval calls within a session\n- **Dynamic packages** — packages from `PYTHON_PACKAGES` (the same config used by Pyodide) are pip-installed on first init if not already present\n- **Session TTL** — stale sessions cleaned up after 1 hour of inactivity\n- **Streaming** — simulations stream via SSE, with the same code injection support as Pyodide\n\nFor 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).\n\n**API routes:**\n\n| Route | Method | Action |\n|-------|--------|--------|\n| `/api/health` | GET | Health check |\n| `/api/init` | POST | Initialize worker with packages |\n| `/api/exec` | POST | Execute Python code |\n| `/api/eval` | POST | Evaluate expression, return JSON |\n| `/api/stream` | POST | Start streaming simulation (SSE) |\n| `/api/stream/exec` | POST | Inject code during streaming |\n| `/api/stream/stop` | POST | Stop streaming |\n| `/api/session` | DELETE | Kill session subprocess |\n\n---\n\n## State Management\n\n### SvelteFlow vs Graph Store\n\nSvelteFlow manages its own UI state (selection, viewport, node positions). The graph store manages application data:\n\n| State Type | Managed By | Examples |\n|------------|------------|----------|\n| **UI State** | SvelteFlow | Selection, viewport, dragging |\n| **App Data** | Graph Store | Node parameters, connections, subsystems |\n\nDo not duplicate SvelteFlow state in custom stores. Use SvelteFlow's APIs (`useSvelteFlow`, event handlers) to interact with canvas state.\n\n### Store Pattern\n\nStores use Svelte's writable with custom wrapper objects:\n\n```typescript\nconst internal = writable\u003cT\u003e(initialValue);\n\nexport const myStore = {\n    subscribe: internal.subscribe,\n\n    // Custom methods\n    doSomething() {\n        internal.update(state =\u003e ({ ...state, ... }));\n    }\n};\n```\n\n**Important**: Do NOT wrap `.subscribe()` in `$effect()` - this causes infinite loops.\n\n```svelte\n\u003cscript\u003e\n// Correct\nmyStore.subscribe(value =\u003e { localState = value; });\n\n// Wrong - causes infinite loop\n$effect(() =\u003e {\n    myStore.subscribe(value =\u003e { localState = value; });\n});\n\u003c/script\u003e\n```\n\n### Subsystem Navigation\n\nSubsystems are nested graphs with path-based navigation:\n\n```typescript\ngraphStore.drillDown(subsystemId);  // Drill into subsystem\ngraphStore.drillUp();               // Go up one level\ngraphStore.navigateTo(level);       // Navigate to breadcrumb level\ngraphStore.currentPath              // Current navigation path\n```\n\nThe Interface node inside a subsystem mirrors its parent Subsystem's ports (with inverted direction).\n\n---\n\n## Keyboard Shortcuts\n\nPress `?` to see all shortcuts in the app. Key shortcuts:\n\n| Category | Shortcut | Action |\n|----------|----------|--------|\n| **File** | `Ctrl+O` | Open |\n| | `Ctrl+S` | Save |\n| | `Ctrl+E` | Export Python |\n| **Edit** | `Ctrl+Z/Y` | Undo/Redo |\n| | `Ctrl+D` | Duplicate |\n| | `Ctrl+F` | Find |\n| | `Del` | Delete |\n| **Transform** | `R` | Rotate 90° |\n| | `X` / `Y` | Flip H/V |\n| | `Arrows` | Nudge selection |\n| **Wires** | `\\` | Add waypoint to selected edge |\n| **Labels** | `L` | Toggle port labels |\n| **View** | `F` | Fit view |\n| | `H` | Go to root |\n| | `T` | Toggle theme |\n| **Panels** | `B` | Blocks |\n| | `N` | Events |\n| | `S` | Simulation |\n| | `V` | Results |\n| | `C` | Console |\n| **Run** | `Ctrl+Enter` | Simulate |\n| | `Shift+Enter` | Continue |\n\n---\n\n## File Formats\n\nPathView uses JSON-based file formats for saving and sharing:\n\n| Extension | Type | Description |\n|-----------|------|-------------|\n| `.pvm` | Model | Complete simulation model (graph, events, settings, code) |\n| `.blk` | Block | Single block with parameters (for sharing/reuse) |\n| `.sub` | Subsystem | Subsystem with internal graph (for sharing/reuse) |\n\nThe `.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`.\n\n### Specification Documents\n\n| Document | Audience |\n|----------|----------|\n| [**docs/pvm-spec.md**](docs/pvm-spec.md) | Building tools that read/write `.pvm` model files |\n| [**docs/backend-protocol-spec.md**](docs/backend-protocol-spec.md) | Implementing a new execution backend (remote server, cloud worker, etc.) |\n| [**docs/toolbox-spec.md**](docs/toolbox-spec.md) | Creating a third-party toolbox package for PathView |\n\n### Export Options\n\n- **File \u003e Save** - Save complete model as `.pvm`\n- **File \u003e Export Python** - Generate standalone Python script\n- **Right-click node \u003e Export** - Save individual block/subsystem\n- **Right-click canvas \u003e Export SVG** - Export graph as vector image\n- **Right-click plot \u003e Download PNG/SVG** - Export plot as image\n- **Right-click plot \u003e Export CSV** - Export simulation data as CSV\n- **Scope/Spectrum node context menu** - Export simulation data as CSV\n\n---\n\n## Sharing Models via URL\n\nModels can be loaded directly from a URL using query parameters:\n\n```\nhttps://view.pathsim.org/?model=\u003curl\u003e\nhttps://view.pathsim.org/?modelgh=\u003cgithub-shorthand\u003e\n```\n\n### Parameters\n\n| Parameter | Description | Example |\n|-----------|-------------|---------|\n| `model` | Direct URL to a `.pvm` or `.json` file | `?model=https://example.com/mymodel.pvm` |\n| `modelgh` | GitHub shorthand (expands to raw.githubusercontent.com) | `?modelgh=user/repo/path/to/model.pvm` |\n\n### GitHub Shorthand\n\nThe `modelgh` parameter expands to a raw GitHub URL:\n\n```\nmodelgh=user/repo/examples/demo.pvm\n→ https://raw.githubusercontent.com/user/repo/main/examples/demo.pvm\n```\n\n### Examples\n\n```\n# Load from any URL\nhttps://view.pathsim.org/?model=https://mysite.com/models/feedback.pvm\n\n# Load from GitHub repository\nhttps://view.pathsim.org/?modelgh=pathsim/pathview/static/examples/feedback-system.json\n```\n\n---\n\n## Scripts\n\n| Script | Purpose |\n|--------|---------|\n| `npm run dev` | Start Vite development server |\n| `npm run server` | Start Flask backend server (port 5000) |\n| `npm run build` | Production build (GitHub Pages) |\n| `npm run build:package` | Build pip package (frontend + wheel) |\n| `npm run preview` | Preview production build |\n| `npm run check` | TypeScript/Svelte type checking |\n| `npm run check:watch` | Type checking in watch mode |\n| `npm run screenshots` | Capture block-icon screenshots (Playwright) |\n| `npm run lint` | Run ESLint |\n| `npm run format` | Format code with Prettier |\n| `npm run extract` | Regenerate all definitions from PathSim |\n| `npm run extract:blocks` | Blocks only |\n| `npm run extract:events` | Events only |\n| `npm run extract:simulation` | Simulation params only |\n| `npm run extract:deps` | Dependencies only |\n| `npm run extract:validate` | Validate config files |\n| `npm run pvm2py -- \u003cfile\u003e` | Convert `.pvm` file to standalone Python script |\n\n---\n\n## Node Styling\n\nNodes are styled based on their category, with CSS-driven shapes and colors.\n\n### Shapes by Category\n\n| Category | Shape | Border Radius |\n|----------|-------|---------------|\n| Sources | Pill | 20px |\n| Dynamic | Rectangle | 4px |\n| Algebraic | Rectangle | 4px |\n| Logic | Rectangle | 4px |\n| Discrete | Asymmetric | 12px 4px 12px 4px |\n| Recording | Pill | 20px |\n| Subsystem | Rectangle | 4px |\n\nShapes are defined in `src/lib/nodes/shapes/registry.ts` and applied via CSS classes (`.shape-pill`, `.shape-rect`, etc.).\n\n### Colors\n\n- **Default node color**: CSS variable `--accent` (#0070C0 - PathSim blue)\n- **Custom colors**: Right-click node → Properties → Color picker (12 colors available)\n- **Port colors**: `PORT_COLORS.default` (#969696 gray), customizable per-port\n\nColors are CSS-driven - see `src/app.css` for variables and `src/lib/utils/colors.ts` for palettes.\n\n### Port Labels\n\nPort labels show the name of each input/output port alongside the node. Toggle globally with `L` key, or per-node via right-click menu.\n\n- **Global toggle**: Press `L` to show/hide port labels for all nodes\n- **Per-node override**: Right-click node → \"Show Input Labels\" / \"Show Output Labels\"\n- **Truncation**: Labels are truncated to 5 characters for compact display\n- **SVG export**: Port labels are included when exporting the graph as SVG\n\n### Adding Custom Shapes\n\n1. Register the shape in `src/lib/nodes/shapes/registry.ts`:\n   ```typescript\n   registerShape({\n     id: 'hexagon',\n     name: 'Hexagon',\n     cssClass: 'shape-hexagon',\n     borderRadius: '0px'\n   });\n   ```\n\n2. Add CSS in `src/app.css` or component styles:\n   ```css\n   .shape-hexagon {\n     clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);\n   }\n   ```\n\n3. Optionally map categories to the new shape:\n   ```typescript\n   setCategoryShape('MyCategory', 'hexagon');\n   ```\n\n---\n\n## Design Principles\n\n1. **Python is first-class** - All node parameters are Python expressions stored as strings and passed verbatim to PathSim. PathSim handles all type checking and validation at runtime.\n\n2. **Subsystems are nested graphs** - The Interface node inside a subsystem mirrors its parent's ports (inverted direction).\n\n3. **No server required by default** - Everything runs client-side via Pyodide. The optional Flask backend enables server-side execution for packages with native dependencies.\n\n4. **Registry pattern** - Nodes and events are registered centrally for extensibility.\n\n5. **Minimal state** - Derive where possible, avoid duplicating truth. SvelteFlow manages its own UI state.\n\n6. **CSS for styling** - Use CSS variables from `app.css` and component `\u003cstyle\u003e` blocks, not JavaScript theme APIs.\n\n7. **Svelte 5 runes** - Use `$state`, `$derived`, `$effect` exclusively.\n\n---\n\n## Performance Optimizations\n\n### Streaming Simulation\n\n- **Autonomous worker**: Python runs in a Web Worker loop, pushing results without waiting for UI acknowledgment\n- **Queue-based updates**: Results accumulate in a queue, merged in batches via `requestAnimationFrame`\n- **Decoupled rates**: Simulation @ 10 Hz, UI updates @ 10 Hz max - expensive plots don't slow simulation\n\n### Plotly Rendering\n\n- **extendTraces**: During streaming, scope plots append new data instead of full re-render\n- **SVG mode**: Uses `scatter` (SVG) instead of `scattergl` (WebGL) for stability during streaming\n- **Visibility API**: Pauses plot updates when browser tab is hidden\n\n### Node Previews\n\n- **Separate render queue**: Plot previews in nodes use SVG paths (not Plotly)\n- **Min-max decimation**: Large datasets downsampled while preserving peaks/valleys\n- **Deferred rendering**: Shared queue prevents preview updates from blocking main plots\n\n---\n\n## Deployment\n\nPathView has two deployment targets:\n\n### GitHub Pages (web)\n\n| Trigger | What happens | Deployed to |\n|---------|--------------|-------------|\n| Push to `main` | Build with base path `/dev` | [view.pathsim.org/dev/](https://view.pathsim.org/dev/) |\n| Release published | Bump `package.json`, build, deploy | [view.pathsim.org/](https://view.pathsim.org/) |\n| Manual dispatch | Choose `dev` or `release` | Respective path |\n\n### PyPI (pip package)\n\n| Trigger | What happens | Published to |\n|---------|--------------|--------------|\n| Release published | Build frontend + wheel, publish | [pypi.org/project/pathview](https://pypi.org/project/pathview/) |\n| Manual dispatch | Choose `testpypi` or `pypi` | Respective index |\n\n### How it works\n\n1. Both versions deploy to the `deployment` branch using GitHub Actions\n2. Dev builds update only the `/dev` folder, preserving the release at root\n3. Release builds update root, preserving `/dev`\n4. Version in `package.json` is automatically bumped from the release tag (e.g., `v0.4.0` → `0.4.0`)\n\n### Creating a release\n\n1. Create a GitHub release with a version tag (e.g., `v0.4.0`)\n2. The workflow automatically:\n   - Updates `package.json` to match the tag\n   - Commits the version bump to `main`\n   - Builds and deploys to production\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpathsim%2Fpathview","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpathsim%2Fpathview","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpathsim%2Fpathview/lists"}