# pathview **Repository Path**: mouseout/pathview ## Basic Information - **Project Name**: pathview - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-11 - **Last Updated**: 2026-04-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

PathView Logo

------------ # 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 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 - [SvelteKit 5](https://kit.svelte.dev/) with Svelte 5 runes - [SvelteFlow](https://svelteflow.dev/) for the node editor - [Pyodide](https://pyodide.org/) for in-browser Python/NumPy/SciPy - [Plotly.js](https://plotly.com/javascript/) for interactive plots - [CodeMirror 6](https://codemirror.net/) for code editing ## 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 ### Convert `.pvm` to Python Convert PathView model files to standalone PathSim scripts: ```bash pathview convert model.pvm # outputs model.py pathview convert model.pvm -o output.py # custom output path pathview convert model.pvm --stdout # print to stdout ``` Or use the Python API directly: ```python from pathview import convert python_code = convert("model.pvm") ``` ### Development setup ```bash npm install npm run dev ``` To use the Flask backend during development: ```bash 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 ``` src/ ├── lib/ │ ├── actions/ # Svelte actions (paramInput) │ ├── animation/ # Graph loading animations │ ├── components/ # UI components │ │ ├── canvas/ # Flow editor utilities (connection, transforms) │ │ ├── dialogs/ # Modal dialogs │ │ │ └── shared/ # Shared dialog components (ColorPicker, etc.) │ │ ├── edges/ # SvelteFlow edge components (ArrowEdge) │ │ ├── icons/ # Icon component (Icon.svelte) │ │ ├── nodes/ # Node components (BaseNode, EventNode, AnnotationNode, PlotPreview) │ │ └── panels/ # Side panels (Simulation, NodeLibrary, CodeEditor, Plot, Console, Events) │ ├── constants/ # Centralized constants (nodeTypes, layout, handles) │ ├── events/ # Event system │ │ └── generated/ # Auto-generated from PathSim │ ├── export/ # Export utilities │ │ └── svg/ # SVG graph export (renderer, types) │ ├── nodes/ # Node type system │ │ ├── generated/ # Auto-generated from PathSim │ │ └── shapes/ # Node shape definitions │ ├── plotting/ # Plot system │ │ ├── core/ # Constants, types, utilities │ │ ├── processing/ # Data processing, render queue │ │ └── renderers/ # Plotly and SVG renderers │ ├── routing/ # Orthogonal wire routing (A* pathfinding) │ ├── pyodide/ # Python runtime (backend, bridge) │ │ └── backend/ # Modular backend system (registry, state, types) │ │ ├── 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 │ ├── stores/ # Svelte stores (state management) │ │ └── graph/ # Graph state with subsystem navigation │ ├── types/ # TypeScript type definitions │ └── utils/ # Utilities (colors, download, csvExport, codemirror) ├── routes/ # SvelteKit pages └── app.css # Global styles with CSS variables pathview/ # 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) ├── converter.py # PVM to Python converter (public API) ├── data/ # Bundled data files │ └── registry.json # Block/event registry for converter └── static/ # Bundled frontend (generated at build time) scripts/ ├── config/ # Configuration files for extraction │ ├── schemas/ # JSON schemas for validation │ ├── pathsim/ # Core PathSim blocks, events, simulation config │ ├── pathsim-chem/ # Chemical toolbox blocks │ ├── pyodide.json # Pyodide version and preload packages │ ├── requirements-pyodide.txt # Runtime Python packages │ └── requirements-build.txt # Build-time Python packages ├── generated/ # Generated files (from extract.py) │ └── registry.json # Block/event registry with import paths ├── extract.py # Unified extraction script └── pvm2py.py # Standalone .pvm to Python converter ``` --- ## Architecture Overview ### Data Flow ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Graph Store │────>│ pathsimRunner │────>│ Python Code │ │ (nodes, edges) │ │ (code gen) │ │ (string) │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ v ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Plot/Console │<────│ bridge.ts │<────│ Backend │ │ (results) │ │ (queue + rAF) │ │ (Pyodide/Flask) │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` ### Streaming Architecture Simulations run in streaming mode for real-time visualization. The worker runs autonomously and pushes results without waiting for the UI: ``` Worker (10 Hz) Main Thread UI (10 Hz) ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Python loop │ ────────> │ Result Queue │ ────────> │ Plotly │ │ (autonomous) │ stream- │ (accumulate) │ rAF │ extendTraces │ │ │ data │ │ batched │ │ └──────────────┘ └──────────────┘ └──────────────┘ ``` - **Decoupled rates**: Python generates data at 10 Hz, UI renders at 10 Hz max - **Queue-based**: Results accumulate in queue, merged on each UI frame - **Non-blocking**: Simulation never waits for plot rendering - **extendTraces**: Scope plots append data incrementally instead of full re-render ### Wire Routing PathView uses Simulink-style orthogonal wire routing with A* pathfinding: - **Automatic routing**: Wires route around nodes with 90° bends only - **User waypoints**: Press `\` on selected edge to add manual waypoints - **Draggable waypoints**: Drag waypoint markers to reposition, double-click to delete - **Segment dragging**: Drag segment midpoints to create new waypoints - **Incremental updates**: Spatial indexing (O(1) node updates) for smooth dragging - **Hybrid routing**: Routes through user waypoints: Source → A* → W1 → A* → Target Key files: `src/lib/routing/` (pathfinder, grid builder, route calculator) ### Key Abstractions | Layer | Purpose | Key Files | |-------|---------|-----------| | **Main App** | Orchestrates panels, shortcuts, file ops | `routes/+page.svelte` | | **Flow Canvas** | SvelteFlow wrapper, node/edge sync | `components/FlowCanvas.svelte` | | **Flow Updater** | View control, animation triggers | `components/FlowUpdater.svelte` | | **Context Menus** | Right-click menus for nodes/canvas/plots | `components/ContextMenu.svelte`, `contextMenuBuilders.ts` | | **Graph Store** | Node/edge state, subsystem navigation | `stores/graph/` | | **View Actions** | Fit view, zoom, pan controls | `stores/viewActions.ts`, `stores/viewTriggers.ts` | | **Clipboard** | Copy/paste/duplicate operations | `stores/clipboard.ts` | | **Plot Settings** | Per-trace and per-block plot options | `stores/plotSettings.ts` | | **Node Registry** | Block type definitions, parameters | `nodes/registry.ts` | | **Code Generation** | Graph → Python code | `pyodide/pathsimRunner.ts` | | **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` | ### Centralized Constants Use these imports instead of magic strings: ```typescript import { NODE_TYPES } from '$lib/constants/nodeTypes'; // NODE_TYPES.SUBSYSTEM, NODE_TYPES.INTERFACE import { PORT_COLORS, DIALOG_COLOR_PALETTE } from '$lib/utils/colors'; // PORT_COLORS.default, etc. ``` --- ## Adding New Blocks Blocks are extracted automatically from PathSim using the `Block.info()` classmethod. The extraction is config-driven for easy maintenance. ### 1. Ensure the block exists in PathSim The block must be importable from `pathsim.blocks` (or toolbox module): ```python from pathsim.blocks import YourNewBlock ``` ### 2. Add to block configuration Edit `scripts/config/pathsim/blocks.json` and add the block class name to the appropriate category: ```json { "categories": { "Algebraic": [ "Adder", "Multiplier", "YourNewBlock" ] } } ``` Port configurations are automatically extracted from `Block.info()`: - `None` → Variable/unlimited ports (UI allows add/remove) - `{}` → No ports of this type - `{"name": index}` → Fixed labeled ports (locked count) ### 3. Run extraction ```bash npm run extract ``` This generates TypeScript files in `src/lib/*/generated/` with: - Block metadata (parameters, descriptions, docstrings) - Port configurations from `Block.info()` - Pyodide runtime config ### 4. Verify Start the dev server and check that your block appears in the Block Library panel. ### Port Synchronization Some 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. Configure in `src/lib/nodes/uiConfig.ts`: ```typescript export const syncPortBlocks = new Set([ 'Integrator', 'Differentiator', 'Delay', 'PID', 'PID_Antiwindup', 'Amplifier', 'Sin', 'Cos', 'Tan', 'Tanh', 'Abs', 'Sqrt', 'Exp', 'Log', 'Log10', 'Mod', 'Clip', 'Pow', 'SampleHold' ]); ``` ### Port Labels from Parameters Some 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. Configure in `src/lib/nodes/uiConfig.ts`: ```typescript export const portLabelParams: Record = { Scope: { param: 'labels', direction: 'input' }, Spectrum: { param: 'labels', direction: 'input' }, // Multiple directions supported: // SomeBlock: [ // { param: 'input_labels', direction: 'input' }, // { param: 'output_labels', direction: 'output' } // ] }; ``` --- ## Adding New Toolboxes To add a new PathSim toolbox (like `pathsim-chem`): ### 1. Add to requirements Edit `scripts/config/requirements-pyodide.txt`: ```txt --pre pathsim pathsim-chem>=0.2rc2 # optional pathsim-controls # optional - your new toolbox ``` The `# optional` comment means Pyodide will continue loading if this package fails to install. ### 2. Create toolbox config Create `scripts/config/pathsim-controls/blocks.json`: ```json { "$schema": "../schemas/blocks.schema.json", "toolbox": "pathsim-controls", "importPath": "pathsim_controls.blocks", "categories": { "Controls": [ "PIDController", "StateEstimator" ] } } ``` ### 3. (Optional) Add events Create `scripts/config/pathsim-controls/events.json` if the toolbox has custom events. ### 4. Run extraction and build ```bash npm run extract 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 The Python runtime uses a modular backend architecture, allowing different execution environments (Pyodide, local Python, remote server) to be swapped without changing application code. ### Architecture ``` ┌─────────────────────────────────────────────────────────────────────┐ │ Backend Interface │ │ init(), exec(), evaluate(), startStreaming(), stopStreaming()... │ └─────────────────────────────────────────────────────────────────────┘ │ ┌──────────────┼──────────────┐ ▼ ▼ ▼ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ Pyodide │ │ Flask │ │ Remote │ │ Backend │ │ Backend │ │ Backend │ │ (default) │ │ (HTTP) │ │ (future) │ └───────────┘ └───────────┘ └───────────┘ │ │ ▼ ▼ ┌───────────┐ ┌───────────┐ │ Web Worker│ │ Flask │──> Python subprocess │ (Pyodide) │ │ Server │ (one per session) └───────────┘ └───────────┘ ``` ### Backend Registry ```typescript import { getBackend, switchBackend, setFlaskHost } from '$lib/pyodide/backend'; // Get current backend (defaults to Pyodide) const backend = getBackend(); // 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 **Requests** (Main → Worker): ```typescript type REPLRequest = | { type: 'init' } | { type: 'exec'; id: string; code: string } // Execute code (no return) | { type: 'eval'; id: string; expr: string } // Evaluate expression (returns JSON) | { type: 'stream-start'; id: string; expr: string } // Start streaming loop | { type: 'stream-stop' } // Stop streaming loop | { type: 'stream-exec'; code: string } // Execute code during streaming ``` **Responses** (Worker → Main): ```typescript type REPLResponse = | { type: 'ready' } | { type: 'ok'; id: string } // exec succeeded | { type: 'value'; id: string; value: string } // eval result (JSON) | { type: 'error'; id: string; error: string; traceback?: string } | { type: 'stdout'; value: string } | { type: 'stderr'; value: string } | { type: 'progress'; value: string } | { type: 'stream-data'; id: string; value: string } // Streaming result | { type: 'stream-done'; id: string } // Streaming completed ``` ### Usage Example ```typescript import { init, exec, evaluate } from '$lib/pyodide/backend'; // Initialize backend (Pyodide by default) await init(); // Execute Python code await exec(` import numpy as np x = np.linspace(0, 10, 100) `); // Evaluate and get result const result = await evaluate('x.tolist()'); ``` ### High-Level API (bridge.ts) For simulation, use the higher-level API in `bridge.ts`: ```typescript import { runStreamingSimulation, continueStreamingSimulation, stopSimulation, execDuringStreaming } from '$lib/pyodide/bridge'; // Run streaming simulation const result = await runStreamingSimulation(pythonCode, duration, (partialResult) => { console.log('Progress:', partialResult.scopeData); }); // result.scopeData, result.spectrumData, result.nodeNames // Continue simulation from where it stopped const moreResult = await continueStreamingSimulation('5.0'); // Stop simulation gracefully await stopSimulation(); // Execute code during active simulation (queued between steps) 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 - **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 - **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 if not already present - **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 ### SvelteFlow vs Graph Store SvelteFlow manages its own UI state (selection, viewport, node positions). The graph store manages application data: | State Type | Managed By | Examples | |------------|------------|----------| | **UI State** | SvelteFlow | Selection, viewport, dragging | | **App Data** | Graph Store | Node parameters, connections, subsystems | Do not duplicate SvelteFlow state in custom stores. Use SvelteFlow's APIs (`useSvelteFlow`, event handlers) to interact with canvas state. ### Store Pattern Stores use Svelte's writable with custom wrapper objects: ```typescript const internal = writable(initialValue); export const myStore = { subscribe: internal.subscribe, // Custom methods doSomething() { internal.update(state => ({ ...state, ... })); } }; ``` **Important**: Do NOT wrap `.subscribe()` in `$effect()` - this causes infinite loops. ```svelte ``` ### Subsystem Navigation Subsystems are nested graphs with path-based navigation: ```typescript graphStore.drillDown(subsystemId); // Drill into subsystem graphStore.drillUp(); // Go up one level graphStore.navigateTo(level); // Navigate to breadcrumb level graphStore.currentPath // Current navigation path ``` The Interface node inside a subsystem mirrors its parent Subsystem's ports (with inverted direction). --- ## Keyboard Shortcuts Press `?` to see all shortcuts in the app. Key shortcuts: | Category | Shortcut | Action | |----------|----------|--------| | **File** | `Ctrl+O` | Open | | | `Ctrl+S` | Save | | | `Ctrl+E` | Export Python | | **Edit** | `Ctrl+Z/Y` | Undo/Redo | | | `Ctrl+D` | Duplicate | | | `Ctrl+F` | Find | | | `Del` | Delete | | **Transform** | `R` | Rotate 90° | | | `X` / `Y` | Flip H/V | | | `Arrows` | Nudge selection | | **Wires** | `\` | Add waypoint to selected edge | | **Labels** | `L` | Toggle port labels | | **View** | `F` | Fit view | | | `H` | Go to root | | | `T` | Toggle theme | | **Panels** | `B` | Blocks | | | `N` | Events | | | `S` | Simulation | | | `V` | Results | | | `C` | Console | | **Run** | `Ctrl+Enter` | Simulate | | | `Shift+Enter` | Continue | --- ## File Formats PathView uses JSON-based file formats for saving and sharing: | Extension | Type | Description | |-----------|------|-------------| | `.pvm` | Model | Complete simulation model (graph, events, settings, code) | | `.blk` | Block | Single block with parameters (for sharing/reuse) | | `.sub` | Subsystem | Subsystem with internal graph (for sharing/reuse) | 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` - **File > Export Python** - Generate standalone Python script - **Right-click node > Export** - Save individual block/subsystem - **Right-click canvas > Export SVG** - Export graph as vector image - **Right-click plot > Download PNG/SVG** - Export plot as image - **Right-click plot > Export CSV** - Export simulation data as CSV - **Scope/Spectrum node context menu** - Export simulation data as CSV --- ## Sharing Models via URL Models can be loaded directly from a URL using query parameters: ``` https://view.pathsim.org/?model= https://view.pathsim.org/?modelgh= ``` ### Parameters | Parameter | Description | Example | |-----------|-------------|---------| | `model` | Direct URL to a `.pvm` or `.json` file | `?model=https://example.com/mymodel.pvm` | | `modelgh` | GitHub shorthand (expands to raw.githubusercontent.com) | `?modelgh=user/repo/path/to/model.pvm` | ### GitHub Shorthand The `modelgh` parameter expands to a raw GitHub URL: ``` modelgh=user/repo/examples/demo.pvm → https://raw.githubusercontent.com/user/repo/main/examples/demo.pvm ``` ### Examples ``` # Load from any URL https://view.pathsim.org/?model=https://mysite.com/models/feedback.pvm # Load from GitHub repository https://view.pathsim.org/?modelgh=pathsim/pathview/static/examples/feedback-system.json ``` --- ## Scripts | Script | Purpose | |--------|---------| | `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 | | `npm run format` | Format code with Prettier | | `npm run extract` | Regenerate all definitions from PathSim | | `npm run extract:blocks` | Blocks only | | `npm run extract:events` | Events only | | `npm run extract:simulation` | Simulation params only | | `npm run extract:deps` | Dependencies only | | `npm run extract:validate` | Validate config files | | `npm run pvm2py -- ` | Convert `.pvm` file to standalone Python script | --- ## Node Styling Nodes are styled based on their category, with CSS-driven shapes and colors. ### Shapes by Category | Category | Shape | Border Radius | |----------|-------|---------------| | Sources | Pill | 20px | | Dynamic | Rectangle | 4px | | Algebraic | Rectangle | 4px | | Mixed | Asymmetric | 12px 4px 12px 4px | | Recording | Pill | 20px | | Subsystem | Rectangle | 4px | Shapes are defined in `src/lib/nodes/shapes/registry.ts` and applied via CSS classes (`.shape-pill`, `.shape-rect`, etc.). ### Colors - **Default node color**: CSS variable `--accent` (#0070C0 - PathSim blue) - **Custom colors**: Right-click node → Properties → Color picker (12 colors available) - **Port colors**: `PORT_COLORS.default` (#969696 gray), customizable per-port Colors are CSS-driven - see `src/app.css` for variables and `src/lib/utils/colors.ts` for palettes. ### Port Labels Port labels show the name of each input/output port alongside the node. Toggle globally with `L` key, or per-node via right-click menu. - **Global toggle**: Press `L` to show/hide port labels for all nodes - **Per-node override**: Right-click node → "Show Input Labels" / "Show Output Labels" - **Truncation**: Labels are truncated to 5 characters for compact display - **SVG export**: Port labels are included when exporting the graph as SVG ### Adding Custom Shapes 1. Register the shape in `src/lib/nodes/shapes/registry.ts`: ```typescript registerShape({ id: 'hexagon', name: 'Hexagon', cssClass: 'shape-hexagon', borderRadius: '0px' }); ``` 2. Add CSS in `src/app.css` or component styles: ```css .shape-hexagon { clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%); } ``` 3. Optionally map categories to the new shape: ```typescript setCategoryShape('MyCategory', 'hexagon'); ``` --- ## Design Principles 1. **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. 2. **Subsystems are nested graphs** - The Interface node inside a subsystem mirrors its parent's ports (inverted direction). 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. 5. **Minimal state** - Derive where possible, avoid duplicating truth. SvelteFlow manages its own UI state. 6. **CSS for styling** - Use CSS variables from `app.css` and component `